From 3e4958b41621479b0c7980756b5b2114ab763385 Mon Sep 17 00:00:00 2001 From: "Jim C. Nasby" Date: Thu, 28 Oct 2021 11:37:26 -0500 Subject: [PATCH] Initial commit for Babelfish 1.x development Signed-off-by: Jim C. Nasby --- .github/ISSUE_TEMPLATE/bug.yaml | 61 ++ .github/ISSUE_TEMPLATE/config.yaml | 5 + .github/ISSUE_TEMPLATE/enhancement.yaml | 49 ++ .github/ISSUE_TEMPLATE/question.yaml | 42 ++ .github/pull_request_template.md | 15 + .github/workflows/main.yml | 36 ++ .gitignore | 5 +- CODE_OF_CONDUCT.md | 4 + LICENSE.PostgreSQL | 23 + MAINTAINERS.md | 95 +++ README.md | 22 + SECURITY.md | 3 + doc/src/sgml/config.sgml | 2 +- src/backend/access/common/printtup.c | 31 +- src/backend/access/common/reloptions.c | 9 + src/backend/access/common/tupdesc.c | 4 + src/backend/access/heap/heapam.c | 5 + src/backend/access/index/genam.c | 25 +- src/backend/access/transam/xact.c | 108 +++- src/backend/catalog/catalog.c | 6 + src/backend/catalog/dependency.c | 16 +- src/backend/catalog/heap.c | 62 +- src/backend/catalog/indexing.c | 18 + src/backend/catalog/namespace.c | 112 +++- src/backend/catalog/objectaddress.c | 97 ++- src/backend/catalog/pg_collation.c | 34 + src/backend/catalog/pg_constraint.c | 3 +- src/backend/catalog/pg_type.c | 6 +- src/backend/commands/copy.c | 2 + src/backend/commands/dropcmds.c | 52 +- src/backend/commands/functioncmds.c | 231 ++++++- src/backend/commands/sequence.c | 25 + src/backend/commands/statscmds.c | 6 +- src/backend/commands/tablecmds.c | 76 ++- src/backend/commands/trigger.c | 597 ++++++++++++++++- src/backend/commands/typecmds.c | 25 +- src/backend/executor/nodeModifyTable.c | 218 ++++++- src/backend/executor/spi.c | 59 +- src/backend/libpq/pqcomm.c | 89 ++- src/backend/nodes/copyfuncs.c | 14 + src/backend/nodes/equalfuncs.c | 4 + src/backend/nodes/nodeFuncs.c | 1 + src/backend/nodes/outfuncs.c | 8 +- src/backend/nodes/read.c | 12 + src/backend/nodes/value.c | 15 + src/backend/optimizer/plan/planner.c | 18 +- src/backend/parser/analyze.c | 77 ++- src/backend/parser/check_keywords.pl | 21 +- src/backend/parser/gram.y | 328 ++++++++-- src/backend/parser/parse_clause.c | 11 +- src/backend/parser/parse_coerce.c | 80 ++- src/backend/parser/parse_expr.c | 60 +- src/backend/parser/parse_func.c | 37 +- src/backend/parser/parse_node.c | 164 +++++ src/backend/parser/parse_relation.c | 43 +- src/backend/parser/parse_target.c | 21 +- src/backend/parser/parse_type.c | 13 +- src/backend/parser/parse_utilcmd.c | 15 + src/backend/parser/parser.c | 4 + src/backend/parser/scan.l | 44 +- src/backend/parser/scansup.c | 9 + src/backend/postmaster/pgstat.c | 48 ++ src/backend/postmaster/postmaster.c | 275 +++++++- src/backend/storage/lmgr/lock.c | 63 +- src/backend/storage/lmgr/lwlocknames.txt | 1 + src/backend/tcop/postgres.c | 101 ++- src/backend/tcop/utility.c | 15 +- src/backend/utils/adt/domains.c | 12 +- src/backend/utils/adt/encode.c | 37 ++ src/backend/utils/adt/float.c | 1 - src/backend/utils/adt/like_support.c | 44 +- src/backend/utils/adt/name.c | 6 + src/backend/utils/adt/numeric.c | 8 + src/backend/utils/adt/ri_triggers.c | 56 +- src/backend/utils/adt/varchar.c | 19 +- src/backend/utils/cache/lsyscache.c | 18 +- src/backend/utils/cache/plancache.c | 19 + src/backend/utils/cache/syscache.c | 54 +- src/backend/utils/errcodes.txt | 7 + src/backend/utils/error/elog.c | 10 +- src/backend/utils/fmgr/fmgr.c | 164 ++++- src/backend/utils/init/miscinit.c | 2 +- src/backend/utils/init/postinit.c | 5 +- src/backend/utils/mb/mbutils.c | 14 + src/backend/utils/misc/guc.c | 62 +- src/backend/utils/misc/queryenvironment.c | 606 +++++++++++++++++- src/bin/psql/command.c | 37 ++ src/bin/psql/mainloop.c | 8 +- src/bin/psql/settings.h | 2 +- src/bin/psql/startup.c | 4 +- src/fe_utils/psqlscan.l | 50 +- src/include/access/relscan.h | 3 + src/include/access/xact.h | 9 + src/include/catalog/catalog.h | 3 + src/include/catalog/dependency.h | 1 + src/include/catalog/namespace.h | 7 + src/include/catalog/objectaddress.h | 3 + src/include/catalog/pg_collation.h | 16 + src/include/commands/defrem.h | 5 + src/include/commands/sequence.h | 23 + src/include/commands/tablecmds.h | 10 + src/include/commands/trigger.h | 9 + src/include/commands/typecmds.h | 1 + src/include/executor/spi.h | 3 + src/include/fe_utils/psqlscan.h | 4 + src/include/fe_utils/psqlscan_int.h | 1 + src/include/fmgr.h | 5 + src/include/libpq/libpq-be.h | 32 + src/include/libpq/libpq.h | 1 + src/include/miscadmin.h | 2 + src/include/nodes/execnodes.h | 17 + src/include/nodes/nodes.h | 8 +- src/include/nodes/parsenodes.h | 22 + src/include/nodes/plannodes.h | 1 + src/include/nodes/value.h | 1 + src/include/optimizer/planner.h | 6 + src/include/parser/analyze.h | 22 + src/include/parser/parse_clause.h | 3 + src/include/parser/parse_coerce.h | 14 + src/include/parser/parse_expr.h | 4 + src/include/parser/parse_func.h | 8 + src/include/parser/parse_target.h | 6 + src/include/parser/parse_type.h | 4 + src/include/parser/parse_utilcmd.h | 7 + src/include/parser/parser.h | 53 ++ src/include/parser/scansup.h | 3 + src/include/pgstat.h | 23 + src/include/port/pg_bswap.h | 23 + src/include/postmaster/postmaster.h | 3 + src/include/postmaster/protocol_extension.h | 46 ++ src/include/storage/lock.h | 5 + src/include/tcop/tcopprot.h | 2 + src/include/utils/builtins.h | 9 + src/include/utils/elog.h | 3 + src/include/utils/guc.h | 1 + src/include/utils/guc_tables.h | 11 + src/include/utils/plancache.h | 7 + src/include/utils/queryenvironment.h | 33 +- src/include/utils/reltrigger.h | 3 + src/include/utils/syscache.h | 25 +- src/pl/plpgsql/src/pl_exec.c | 20 + src/test/regress/expected/babel_applock2.out | 147 +++++ src/test/regress/expected/tsql_like.out | 98 +++ src/test/regress/expected/tsql_targetlist.out | 37 ++ src/test/regress/serial_schedule | 3 + src/test/regress/sql/babel_applock2.sql | 126 ++++ src/test/regress/sql/tsql_like.sql | 29 + src/test/regress/sql/tsql_targetlist.sql | 15 + 148 files changed, 5696 insertions(+), 315 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yaml create mode 100644 .github/ISSUE_TEMPLATE/enhancement.yaml create mode 100644 .github/ISSUE_TEMPLATE/question.yaml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/main.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 LICENSE.PostgreSQL create mode 100644 MAINTAINERS.md create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 src/include/postmaster/protocol_extension.h create mode 100644 src/test/regress/expected/babel_applock2.out create mode 100644 src/test/regress/expected/tsql_like.out create mode 100644 src/test/regress/expected/tsql_targetlist.out create mode 100644 src/test/regress/sql/babel_applock2.sql create mode 100644 src/test/regress/sql/tsql_like.sql create mode 100644 src/test/regress/sql/tsql_targetlist.sql diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml new file mode 100644 index 00000000000..42a1e6d2d2b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -0,0 +1,61 @@ +name: Babelfish for PostgreSQL Bug Report +description: File a bug report +title: "[Bug]: " +labels: ["bug", "triage"] +assignees: + - amazon-auto +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: input + id: contact + attributes: + label: Contact Details + description: How can we get in touch with you if we need more info? + placeholder: ex. email@example.com + validations: + required: false + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: dropdown + id: version + attributes: + label: Version + description: What version of our software are you running? + options: + - 13_4 (Default) + validations: + required: true + - type: dropdown + id: os + attributes: + label: What flavour of Linux are you running into the bug? + multiple: true + options: + - Ubuntu (Default) + - Fedora + - Amazon Linux + - Other + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://aws.github.io/code-of-conduct-faq) + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 00000000000..6cb9d501614 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Babelfish for PostgreSQL Website + url: https://github.community/ + about: Please ask and answer questions here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml new file mode 100644 index 00000000000..abf706fe5c1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -0,0 +1,49 @@ +name: Babelfish for PostgreSQL Feature Request Form +description: Propose an enhancement for Babelfish for PostgreSQL +title: "[Enhancement]: " +labels: ["enhancement", "untriaged"] +assignees: + - amazon-auto +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to help us improving Babelfish! + - type: input + id: contact + attributes: + label: Contact Details + description: How can we get in touch with you if we need more info? + placeholder: ex. email@example.com + validations: + required: false + - type: textarea + id: what-intends + attributes: + label: What this feature/enhancement tries to solve? + description: Be explanatory, as detailed as you can. + placeholder: Tell us what you have in mind! + value: "This is my feature request!" + validations: + required: true + - type: textarea + id: brief-desc-implementation + attributes: + label: If want to provide us a more details about how to implement. + description: Share with us if you have more details about the implementation. + validations: + required: false + - type: textarea + id: docs + attributes: + label: Relevant documentation + description: Please attach relevant documentation. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://aws.github.io/code-of-conduct-faq) + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.yaml b/.github/ISSUE_TEMPLATE/question.yaml new file mode 100644 index 00000000000..c17d9cb003b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yaml @@ -0,0 +1,42 @@ +name: Babelfish for PostgreSQL Question +description: Make a question to maintainers +title: "[Question]: " +labels: ["question", "untriaged"] +assignees: + - amazon-auto +body: + - type: markdown + attributes: + value: | + Thanks for using Babelfish! + - type: input + id: contact + attributes: + label: Contact Details + description: How can we get in touch with you if we need more info? + placeholder: ex. email@example.com + validations: + required: false + - type: textarea + id: what-question + attributes: + label: What's the question? + description: Tell us about your doubts. + placeholder: Do your question as detailed as possible. + value: "I have a question!" + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant log output or information + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://aws.github.io/code-of-conduct-faq) + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000000..4f821e66fed --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +### Description + +[Describe what this change achieves] + +### Issues Resolved + +[List any issues this PR will resolve] + +### Check List + +- [ ] Commits are signed per the DCO using --signoff + +By submitting this pull request, I confirm that my contribution is under the terms of the PostgreSQL license, and grant any person obtaining a copy of the contribution permission to relicense all or a portion of my contribution to the PostgreSQL License solely to contribute all or a portion of my contribution to the PostgreSQL open source project. + +For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/babelfish-for-postgresql/postgresql_modified_for_babelfish/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000000..6553eda5cd0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: + - 13_4 + pull_request: + branches: + - 13_4 + +jobs: + build-and-run-tests: + name: Build and run tests + runs-on: ubuntu-latest + steps: + - name: clone-repository + uses: actions/checkout@v2 + - name: build-postgres + run: | + ./configure + make world-bin -j8 + - name: run-tests + run: | + make check -j8 + - name: upload-test-summary + if: failure() + uses: actions/upload-artifact@v2 + with: + name: regression-summary + path: src/test/regress/regression.out + - name: upload-test-differences + if: failure() + uses: actions/upload-artifact@v2 + with: + name: regression-differences + path: src/test/regress/regression.diffs diff --git a/.gitignore b/.gitignore index 794e35b73cb..ba7604e752f 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ win32ver.rc *.exe lib*dll.def lib*.pc +*regression.diffs +*regression.out + # Local excludes in root directory /GNUmakefile @@ -41,4 +44,4 @@ lib*.pc /pgsql.sln.cache /Debug/ /Release/ -/tmp_install/ +/tmp_install/ \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..5b627cfa60b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/LICENSE.PostgreSQL b/LICENSE.PostgreSQL new file mode 100644 index 00000000000..655a3c59d60 --- /dev/null +++ b/LICENSE.PostgreSQL @@ -0,0 +1,23 @@ +PostgreSQL Database Management System +(formerly known as Postgres, then as Postgres95) + +Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + +Portions Copyright (c) 1994, The Regents of the University of California + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this +paragraph and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING +LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 00000000000..f76da55f98a --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,95 @@ +- [Overview](#overview) +- [Current Maintainers](#current-maintainers) +- [Maintainer Responsibilities](#maintainer-responsibilities) + - [Uphold Code of Conduct](#uphold-code-of-conduct) + - [Prioritize Security](#prioritize-security) + - [Review Pull Requests](#review-pull-requests) + - [Triage Open Issues](#triage-open-issues) + - [Be Responsive](#be-responsive) + - [Maintain Overall Health of the Repo](#maintain-overall-health-of-the-repo) + - [Add Continious Integration Checks](#add-continious-integration-checks) + - [Developer Certificate of Origin Workflow](#developer-certificate-of-origin-workflow) + - [Use Semver](#use-semver) + - [Release Frequently](#release-frequently) + - [Promote Other Maintainers](#promote-other-maintainers) + +## Overview + +This document explains who the maintainers are (see below), what they do in this repo, and how they should be doing it. If you're interested in contributing, see [CONTRIBUTING](CONTRIBUTING.md). + +## Current Maintainers + +Initially, GitHub maintainers will be from within AWS: As we progress, we'll add additional external maintainers. Guidelines for adding external maintainers will be published. Current list of maintainers + +| Maintainer | GitHub ID | Affiliation | +| ------------------------ | --------------------------------------- | ----------- | +| Zane Li | [zli236](https://github.com/zli236) | Amazon | +| Huansong Fu | [huansong](https://github.com/huansong) | Amazon | +| Kuntal Ghosh | [slightst](https://github.com/slightst) | Amazon | +| Di Wu | [macwoody](https://github.com/macwoody) | Amazon | +| Korry Douglas | [korryd](https://github.com/korryd) | Amazon | +| Suprio Pal | [suprio-amzn](https://github.com/suprio-amzn) | Amazon | +| Jim Mlodgenski | [jim-mlodgenski](https://github.com/jim-mlodgenski) | Amazon | +| Jim Nasby | [nasbyj](https://github.com/nasbyj) | Amazon | +| Jim Finnerty | [JimFinnerty](https://github.com/JimFinnerty) | Amazon | +| Rob VErschoor | [rcv-aws](https://github.com/rcv-aws) | Amazon | +| Simon Lightstone | [slightst](https://github.com/slightst) | Amazon | +| Richard Waymire | [waymire](https://github.com/waymire) | Amazon | +| Gopinath Pai | [gopinathpai](https://github.com/gopinathpai)| Amazon | + + + +## Maintainer Responsibilities + +Maintainers are active and visible members of the community, and have [maintain-level permissions on a repository](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization). Use those privileges to serve the community and evolve code as follows. + +### Uphold Code of Conduct + +Model the behavior set forward by the [Code of Conduct](CODE_OF_CONDUCT.md) and raise any violations to other maintainers and admins. + +### Prioritize Security + +Security is your number one priority. Maintainer's Github keys must be password protected securely and any reported security vulnerabilities are addressed before features or bugs. + +Note that this repository is monitored and supported 24/7 by Amazon Security, see [Reporting a Vulnerability](SECURITY.md) for details. + +### Review Pull Requests + +Review pull requests regularly, comment, suggest, reject, merge and close. Accept only high quality pull-requests. Provide code reviews and guidance on incomming pull requests. Don't let PRs be stale and do your best to be helpful to contributors. + +### Triage Open Issues + +Manage labels, review issues regularly, and triage by labelling them. + +All repositories in this organization have a standard set of labels, including `bug`, `documentation`, `duplicate`, `enhancement`, `good first issue`, `help wanted`, `blocker`, `invalid`, `question`, `wontfix`, and `untriaged`, along with release labels, such as `v1.0.0`, `v1.1.0`, `v2.0.0`, `patch`, and `backport`. + +Use labels to target an issue or a PR for a given release, add `help wanted` to good issues for new community members, and `blocker` for issues that scare you or need immediate attention. Request for more information from a submitter if an issue is not clear. Create new labels as needed by the project. + +### Be Responsive + +Respond to enhancement requests, and forum posts. Allocate time to reviewing and commenting on issues and conversations as they come in. + +### Maintain Overall Health of the Repo + +Keep the `main` branch at production quality at all times. Backport features as needed. Cut release branches and tags to enable future patches. + +### Add Continious Integration Checks + +Add integration checks that validate pull requests and pushes to ease the burden on Pull Request reviewers. + +#### Developer Certificate of Origin Workflow + +Validates pull requests commits are all signed with the doc, [doc.yml](./workflows/dco.yml). + +### Use Semver + +Use and enforce [semantic versioning](https://semver.org/) and do not let breaking changes be made outside of major releases. + +### Release Frequently + +Make frequent project releases to the community. + +### Promote Other Maintainers + +Assist, add, and remove [MAINTAINERS](MAINTAINERS.md). Exercise good judgement, and propose high quality contributors to become co-maintainers. + diff --git a/README.md b/README.md new file mode 100644 index 00000000000..df92107e928 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +## Babelfish for PostgreSQL + +This repository contains the PostgreSQL code base with a patch applied to +enable some additional functionality provided by the Babelfish extensions. The +Babelfish community strives to minimize the amount of changes to the PostgreSQL +database engine, and will be working with the PostgreSQL development community +to merge these changes into PostgreSQL where possible. + +This repository is available for building patched PostgreSQL binaries. Patches +with the modifications to community PostgreSQL are made available with every +[Babelfish release](https://github.com/babelfish-for-postgresql/babelfish_extensions/releases). + +More information on Babelfish for PostgreSQL can be found at +[babelfishpg.org](https://babelfishpg.org) + +Changes to community PostgreSQL that are unrelated to Babelfish should be made +through the PostgreSQL community. If you'd like to contribute to Babelfish, +please use [this repo](https://github.com/babelfish-for-postgresql/babelfish_extensions). + +Babelfish would not be possible without the work and dedication of the hundreds +of people who have contributed to creation of PostgreSQL itself. Everyone +involved in the development of PostgreSQL has our gratitude. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..0b85ca04ed2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +## Reporting a Vulnerability + +If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. Please do **not** create a public GitHub issue. \ No newline at end of file diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 92ac53f9a5e..8ff054c2d4d 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1825,7 +1825,7 @@ include_dir 'conf.d' reducing the amount of decoded changes written to disk. - + max_stack_depth (integer) diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c index dd1bac0aa9e..c16fca14f8e 100644 --- a/src/backend/access/common/printtup.c +++ b/src/backend/access/common/printtup.c @@ -15,6 +15,7 @@ */ #include "postgres.h" +#include "miscadmin.h" #include "access/printtup.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" @@ -70,6 +71,7 @@ typedef struct MemoryContext tmpcontext; /* Memory context for per-row workspace */ } DR_printtup; + /* ---------------- * Initialize: create a DestReceiver for printtup * ---------------- @@ -79,10 +81,20 @@ printtup_create_DR(CommandDest dest) { DR_printtup *self = (DR_printtup *) palloc0(sizeof(DR_printtup)); - self->pub.receiveSlot = printtup; /* might get changed later */ - self->pub.rStartup = printtup_startup; - self->pub.rShutdown = printtup_shutdown; - self->pub.rDestroy = printtup_destroy; + if (MyProcPort && MyProcPort->protocol_config->fn_printtup) + { + self->pub.receiveSlot = MyProcPort->protocol_config->fn_printtup; + self->pub.rStartup = MyProcPort->protocol_config->fn_printtup_startup; + self->pub.rShutdown = MyProcPort->protocol_config->fn_printtup_shutdown; + self->pub.rDestroy = MyProcPort->protocol_config->fn_printtup_destroy; + } + else + { + self->pub.receiveSlot = printtup; /* might get changed later */ + self->pub.rStartup = printtup_startup; + self->pub.rShutdown = printtup_shutdown; + self->pub.rDestroy = printtup_destroy; + } self->pub.mydest = dest; /* @@ -120,10 +132,13 @@ SetRemoteDestReceiverParams(DestReceiver *self, Portal portal) * for the columns to have different print formats; it's sufficient to * look at the first one. */ - if (portal->formats && portal->formats[0] != 0) - myState->pub.receiveSlot = printtup_internal_20; - else - myState->pub.receiveSlot = printtup_20; + if (MyProcPort == NULL || MyProcPort->protocol_config->fn_printtup == NULL) + { + if (portal->formats && portal->formats[0] != 0) + myState->pub.receiveSlot = printtup_internal_20; + else + myState->pub.receiveSlot = printtup_20; + } } } diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 8ccc228a8cc..586f96730be 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -29,6 +29,7 @@ #include "commands/tablespace.h" #include "commands/view.h" #include "nodes/makefuncs.h" +#include "parser/parser.h" #include "postmaster/postmaster.h" #include "utils/array.h" #include "utils/attoptcache.h" @@ -1433,6 +1434,10 @@ parseRelOptionsInternal(Datum options, bool validate, } } + /* + * We are ignoring unrecognized parameters in TSQL + * dialect without raising error + */ if (j >= numoptions && validate) { char *s; @@ -1442,6 +1447,10 @@ parseRelOptionsInternal(Datum options, bool validate, p = strchr(s, '='); if (p) *p = '\0'; + + if (sql_dialect == SQL_DIALECT_TSQL) + continue; + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized parameter \"%s\"", s))); diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 1e743d7d86e..c5473299e72 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -25,6 +25,7 @@ #include "catalog/pg_type.h" #include "common/hashfn.h" #include "miscadmin.h" +#include "parser/parser.h" #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" @@ -829,6 +830,9 @@ BuildDescForRelation(List *schema) attname = entry->colname; typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod); + if (sql_dialect == SQL_DIALECT_TSQL && check_or_set_default_typmod_hook) + (*check_or_set_default_typmod_hook)(entry->typeName, &atttypmod, false); + aclresult = pg_type_aclcheck(atttypid, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, atttypid); diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index b4bc0ce0c50..5665fb97bf4 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -65,6 +65,7 @@ #include "utils/datum.h" #include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/queryenvironment.h" #include "utils/relcache.h" #include "utils/snapmgr.h" #include "utils/spccache.h" @@ -5733,6 +5734,10 @@ heap_inplace_update(Relation relation, HeapTuple tuple) (errcode(ERRCODE_INVALID_TRANSACTION_STATE), errmsg("cannot update tuples during a parallel operation"))); + /* Don't proceed further if the tuple is for an ENR. We just update there.*/ + if (ENRupdateTuple(relation, tuple)) + return; + buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self))); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); page = (Page) BufferGetPage(buffer); diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index dfba5ae39ae..e527f6e36de 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -31,6 +31,7 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/queryenvironment.h" #include "utils/rel.h" #include "utils/rls.h" #include "utils/ruleutils.h" @@ -374,6 +375,9 @@ systable_beginscan(Relation heapRelation, sysscan->heap_rel = heapRelation; sysscan->irel = irel; sysscan->slot = table_slot_create(heapRelation, NULL); + sysscan->enr = false; + sysscan->enr_tuplist = NULL; + sysscan->enr_tuplist_i = 0; if (snapshot == NULL) { @@ -388,7 +392,13 @@ systable_beginscan(Relation heapRelation, sysscan->snapshot = NULL; } - if (irel) + /* Catalog tuples for ENR are not in the on-disk catalogs */ + if (ENRgetSystableScan(heapRelation, indexId, nkeys, key, &sysscan->enr_tuplist, &sysscan->enr_tuplist_i)) + { + sysscan->enr = true; + index_close(sysscan->irel, AccessShareLock); + } + else if (irel) { int i; @@ -449,7 +459,14 @@ systable_getnext(SysScanDesc sysscan) { HeapTuple htup = NULL; - if (sysscan->irel) + if (sysscan->enr) + { + if (sysscan->enr_tuplist && sysscan->enr_tuplist_i < sysscan->enr_tuplist->length) + { + htup = lfirst(list_nth_cell(sysscan->enr_tuplist, sysscan->enr_tuplist_i++)); + } + } + else if (sysscan->irel) { if (index_getnext_slot(sysscan->iscan, ForwardScanDirection, sysscan->slot)) { @@ -534,12 +551,12 @@ systable_endscan(SysScanDesc sysscan) sysscan->slot = NULL; } - if (sysscan->irel) + if (sysscan->irel && !sysscan->enr) { index_endscan(sysscan->iscan); index_close(sysscan->irel, AccessShareLock); } - else + else if (!sysscan->enr) table_endscan(sysscan->scan); if (sysscan->snapshot) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 44b113b35b3..349dee01d64 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -63,6 +63,7 @@ #include "utils/guc.h" #include "utils/inval.h" #include "utils/memutils.h" +#include "utils/queryenvironment.h" #include "utils/relmapper.h" #include "utils/snapmgr.h" #include "utils/timeout.h" @@ -384,7 +385,6 @@ IsAbortedTransactionBlockState(void) return false; } - /* * GetTopTransactionId * @@ -561,6 +561,13 @@ AssignTransactionId(TransactionState s) Assert(!FullTransactionIdIsValid(s->fullTransactionId)); Assert(s->state == TRANS_INPROGRESS); + if (AbortCurTransaction) + { + ereport(ERROR, + (errcode(ERRCODE_TRANSACTION_ROLLBACK), + errmsg("The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction."))); + } + /* * Workers synchronize transaction state at the beginning of each parallel * operation, so we can't account for new XIDs at this point. @@ -6038,3 +6045,102 @@ xact_redo(XLogReaderState *record) else elog(PANIC, "xact_redo: unknown op code %u", info); } + +uint32 NestedTranCount = 0; +bool AbortCurTransaction = false; + +/* + * IsTopTransactionName + * Transaction name is stored in savepoint field. + * Returns true either if given name is NULL or + * it only matches with root transaction name + * + */ +bool +IsTopTransactionName(const char *name) +{ + TransactionState target; + TransactionState s = CurrentTransactionState; + + if (name == NULL) + return true; + + for (target = s; PointerIsValid(target); target = target->parent) + { + if (PointerIsValid(target->name) && strcmp(target->name, name) == 0) + break; + } + + if (PointerIsValid(target) && target->nestingLevel == 1) + return true; + + return false; +} + +/* + * SetTopTransactionName + * For top transaction, set transaction name. + * + */ +void +SetTopTransactionName(const char *name) +{ + TransactionState s = CurrentTransactionState; + Assert(name != NULL); + s->name = MemoryContextStrdup(TopTransactionContext, name); +} + +/* + * IsTrasactionBlockActive + * If a transaction block is already in progress + * + */ +bool +IsTransactionBlockActive(void) +{ + TransactionState s = CurrentTransactionState; + return (s->blockState == TBLOCK_INPROGRESS || + s->blockState == TBLOCK_PARALLEL_INPROGRESS || + s->blockState == TBLOCK_SUBINPROGRESS); +} + +/* + * RollbackAndReleaseSavepoint + * After rollback to savepoint, postgres + * keeps the savepoint in restart state + * to recreate it during commit command. + * TSQL will cleanup the savepoint instead + * to match rollback + release behavior + * + */ +void +RollbackAndReleaseSavepoint(const char *name) +{ + TransactionState target; + TransactionState s = CurrentTransactionState; + + if (name == NULL) + elog(FATAL, "RollbackAndReleaseSavepoint: NULL savepoint name"); + + for (target = s; PointerIsValid(target); target = target->parent) + { + if (PointerIsValid(target->name) && strcmp(target->name, name) == 0) + break; + if (target->blockState != TBLOCK_SUBABORT_PENDING && + target->blockState != TBLOCK_SUBABORT_END) + elog(FATAL, "RollbackAndReleaseSavepoint: unexpected state %s", + BlockStateAsString(target->blockState)); + } + + if (!PointerIsValid(target)) + elog(FATAL, "RollbackAndReleaseSavepoint: savepoint %s not found", + name); + + if (target->blockState == TBLOCK_SUBRESTART) + target->blockState = TBLOCK_SUBABORT_PENDING; + else if (target->blockState == TBLOCK_SUBABORT_RESTART) + target->blockState = TBLOCK_SUBABORT_END; + else + elog(FATAL, "RollbackAndReleaseSavepoint: unexpected state %s", + BlockStateAsString(target->blockState)); +} diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index 7d6acaed92c..84863c0a335 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -49,6 +49,8 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" +IsExtendedCatalogHookType IsExtendedCatalogHook; + /* * IsSystemRelation * True iff the relation is either a system catalog or a toast table. @@ -114,6 +116,10 @@ IsCatalogRelation(Relation relation) bool IsCatalogRelationOid(Oid relid) { + /* Allows extension to add new catalog tables on demand */ + if (IsExtendedCatalogHook && IsExtendedCatalogHook(relid)) + return true; + /* * We consider a relation to be a system catalog if it has an OID that was * manually assigned or assigned by genbki.pl. This includes all the diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 5565e6fc197..96d108d5543 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -1341,6 +1341,10 @@ doDeletion(const ObjectAddress *object, int flags) { char relKind = get_rel_relkind(object->objectId); + if (flags & PERFORM_DELETION_SKIP_ENR && + !SearchSysCacheExists1(RELOID, ObjectIdGetDatum(object->objectId))) + break; + if (relKind == RELKIND_INDEX || relKind == RELKIND_PARTITIONED_INDEX) { @@ -1373,7 +1377,9 @@ doDeletion(const ObjectAddress *object, int flags) break; case OCLASS_TYPE: - RemoveTypeById(object->objectId); + if ((flags & PERFORM_DELETION_SKIP_ENR) == 0 || + SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(object->objectId))) + RemoveTypeById(object->objectId); break; case OCLASS_CAST: @@ -1385,7 +1391,9 @@ doDeletion(const ObjectAddress *object, int flags) break; case OCLASS_CONSTRAINT: - RemoveConstraintById(object->objectId); + if ((flags & PERFORM_DELETION_SKIP_ENR) == 0 || + SearchSysCacheExists1(CONSTROID, ObjectIdGetDatum(object->objectId))) + RemoveConstraintById(object->objectId); break; case OCLASS_CONVERSION: @@ -1441,7 +1449,9 @@ doDeletion(const ObjectAddress *object, int flags) break; case OCLASS_STATISTIC_EXT: - RemoveStatisticsById(object->objectId); + if ((flags & PERFORM_DELETION_SKIP_ENR) == 0 || + SearchSysCacheExists1(STATEXTOID, ObjectIdGetDatum(object->objectId))) + RemoveStatisticsById(object->objectId); break; case OCLASS_TSPARSER: diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index a42070fd925..ef879c5fe15 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -64,9 +64,11 @@ #include "commands/tablecmds.h" #include "commands/typecmds.h" #include "executor/executor.h" +#include "executor/spi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" +#include "parser/parser.h" /* only needed for GUC variables */ #include "parser/parse_coerce.h" #include "parser/parse_collate.h" #include "parser/parse_expr.h" @@ -87,6 +89,7 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" +InvokePreAddConstraintsHook_type InvokePreAddConstraintsHook = NULL; /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_heap_pg_class_oid = InvalidOid; @@ -686,6 +689,16 @@ CheckAttributeType(const char *attname, containing_rowtypes, flags); } + else if (att_typtype == TYPTYPE_RANGE) + { + /* + * If it's a range, recurse to check its subtype. + */ + CheckAttributeType(attname, get_range_subtype(atttypid), + get_range_collation(atttypid), + containing_rowtypes, + flags); + } else if (OidIsValid((att_typelem = get_element_type(atttypid)))) { /* @@ -1137,6 +1150,10 @@ heap_create_with_catalog(const char *relname, Oid new_array_oid = InvalidOid; TransactionId relfrozenxid; MultiXactId relminmxid; + bool is_enr = false; + + if (relpersistence == RELPERSISTENCE_TEMP && sql_dialect == SQL_DIALECT_TSQL) + is_enr = true; pg_class_desc = table_open(RelationRelationId, RowExclusiveLock); @@ -1156,9 +1173,10 @@ heap_create_with_catalog(const char *relname, /* * This would fail later on anyway, if the relation already exists. But * by catching it here we can emit a nicer error message. + * But allow same-name ENR as long as the it's not in the current query env. */ existing_relid = get_relname_relid(relname, relnamespace); - if (existing_relid != InvalidOid) + if (existing_relid != InvalidOid && (!is_enr || get_ENR(currentQueryEnv, relname))) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_TABLE), errmsg("relation \"%s\" already exists", relname))); @@ -1172,7 +1190,7 @@ heap_create_with_catalog(const char *relname, old_type_oid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, CStringGetDatum(relname), ObjectIdGetDatum(relnamespace)); - if (OidIsValid(old_type_oid)) + if (OidIsValid(old_type_oid) && (!is_enr || get_ENR(currentQueryEnv, relname))) { if (!moveArrayTypeName(old_type_oid, relname, relnamespace)) ereport(ERROR, @@ -1272,6 +1290,21 @@ heap_create_with_catalog(const char *relname, &relfrozenxid, &relminmxid); + /* + * If it's temp table, create ENR entry for it when using TSQL dialect. + */ + if (is_enr) + { + MemoryContext oldcontext = MemoryContextSwitchTo(CacheMemoryContext); + EphemeralNamedRelation enr = palloc0(sizeof(EphemeralNamedRelationData)); + enr->md.name = palloc0(strlen(relname) + 1); + strncpy(enr->md.name, relname, strlen(relname) + 1); + enr->md.reliddesc = relid; + enr->md.enrtype = ENR_TSQL_TEMP; + register_ENR(currentQueryEnv, enr); + MemoryContextSwitchTo(oldcontext); + } + Assert(relid == RelationGetRelid(new_rel_desc)); new_rel_desc->rd_rel->relrewrite = relrewrite; @@ -1393,6 +1426,7 @@ heap_create_with_catalog(const char *relname, * * Also, skip this in bootstrap mode, since we don't make dependencies * while bootstrapping. + * Skip this for ENR too, since we will be searching them in query environment. */ if (relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_TOASTVALUE && @@ -1525,7 +1559,8 @@ DeleteRelationTuple(Oid relid) elog(ERROR, "cache lookup failed for relation %u", relid); /* delete the relation tuple from pg_class, and finish up */ - CatalogTupleDelete(pg_class_desc, &tup->t_self); + if (!ENRdropTuple(pg_class_desc, tup)) + CatalogTupleDelete(pg_class_desc, &tup->t_self); ReleaseSysCache(tup); @@ -1562,7 +1597,8 @@ DeleteAttributeTuples(Oid relid) /* Delete all the matching tuples */ while ((atttup = systable_getnext(scan)) != NULL) - CatalogTupleDelete(attrel, &atttup->t_self); + if (!ENRdropTuple(attrel, atttup)) + CatalogTupleDelete(attrel, &atttup->t_self); /* Clean up after the scan */ systable_endscan(scan); @@ -1603,7 +1639,8 @@ DeleteSystemAttributeTuples(Oid relid) /* Delete all the matching tuples */ while ((atttup = systable_getnext(scan)) != NULL) - CatalogTupleDelete(attrel, &atttup->t_self); + if (!ENRdropTuple(attrel, atttup)) + CatalogTupleDelete(attrel, &atttup->t_self); /* Clean up after the scan */ systable_endscan(scan); @@ -1649,7 +1686,8 @@ RemoveAttributeById(Oid relid, AttrNumber attnum) { /* System attribute (probably OID) ... just delete the row */ - CatalogTupleDelete(attr_rel, &tuple->t_self); + if (!ENRdropTuple(attr_rel, tuple)) + CatalogTupleDelete(attr_rel, &tuple->t_self); } else { @@ -1821,7 +1859,8 @@ RemoveAttrDefaultById(Oid attrdefId) myrel = relation_open(myrelid, AccessExclusiveLock); /* Now we can delete the pg_attrdef row */ - CatalogTupleDelete(attrdef_rel, &tuple->t_self); + if (!ENRdropTuple(attrdef_rel, tuple)) + CatalogTupleDelete(attrdef_rel, &tuple->t_self); systable_endscan(scan); table_close(attrdef_rel, RowExclusiveLock); @@ -2006,6 +2045,9 @@ heap_drop_with_catalog(Oid relid) */ DeleteRelationTuple(relid); + /* Try drop ENR entry, will skip internally if it's not an ENR.*/ + ENRDropEntry(relid); + if (OidIsValid(parentOid)) { /* @@ -2581,6 +2623,9 @@ AddRelationNewConstraints(Relation rel, true); addNSItemToQuery(pstate, nsitem, true, true, true); + if (InvokePreAddConstraintsHook) + (*InvokePreAddConstraintsHook) (rel, pstate, newColDefaults); + /* * Process column default expressions. */ @@ -3220,7 +3265,8 @@ RemoveStatistics(Oid relid, AttrNumber attnum) /* we must loop even when attnum != 0, in case of inherited stats */ while (HeapTupleIsValid(tuple = systable_getnext(scan))) - CatalogTupleDelete(pgstatistic, &tuple->t_self); + if (!ENRdropTuple(pgstatistic, tuple)) + CatalogTupleDelete(pgstatistic, &tuple->t_self); systable_endscan(scan); diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c index fe277f3ad37..1dd031e994a 100644 --- a/src/backend/catalog/indexing.c +++ b/src/backend/catalog/indexing.c @@ -20,6 +20,8 @@ #include "access/htup_details.h" #include "catalog/index.h" #include "catalog/indexing.h" +#include "catalog/pg_subscription.h" +#include "catalog/pg_subscription_rel.h" #include "executor/executor.h" #include "utils/rel.h" @@ -223,6 +225,10 @@ CatalogTupleInsert(Relation heapRel, HeapTuple tup) CatalogTupleCheckConstraints(heapRel, tup); + /* ENR carries catalog tuples themselves. Do not insert, and skip index.*/ + if (ENRaddTuple(heapRel, tup)) + return; + indstate = CatalogOpenIndexes(heapRel); simple_heap_insert(heapRel, tup); @@ -245,6 +251,10 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup, { CatalogTupleCheckConstraints(heapRel, tup); + /* ENR carries catalog tuples themselves. Do not insert, and skip index.*/ + if (ENRaddTuple(heapRel, tup)) + return; + simple_heap_insert(heapRel, tup); CatalogIndexInsert(indstate, tup); @@ -268,6 +278,10 @@ CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup) CatalogTupleCheckConstraints(heapRel, tup); + /* ENR carries catalog tuples themselves. Do not update, and skip index.*/ + if (ENRupdateTuple(heapRel, tup)) + return; + indstate = CatalogOpenIndexes(heapRel); simple_heap_update(heapRel, otid, tup); @@ -290,6 +304,10 @@ CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup, { CatalogTupleCheckConstraints(heapRel, tup); + /* ENR carries catalog tuples themselves. Do not update.*/ + if (ENRupdateTuple(heapRel, tup)) + return; + simple_heap_update(heapRel, otid, tup); CatalogIndexInsert(indstate, tup); diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 2ec23016fe5..903b02dc7a1 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -45,6 +45,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_func.h" +#include "parser/parser.h" #include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/sinvaladt.h" @@ -197,6 +198,11 @@ static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId; */ char *namespace_search_path = NULL; +/* Catalog schema that TSQL uses */ +char *SYS_NAMESPACE_NAME = "sys"; + +relname_lookup_hook_type relname_lookup_hook = NULL; + /* Local functions */ static void recomputeNamespacePath(void); @@ -314,7 +320,10 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, errmsg("temporary tables cannot specify a schema name"))); } - relId = get_relname_relid(relation->relname, myTempNamespace); + if (relname_lookup_hook) + relId = (*relname_lookup_hook) (relation->relname, myTempNamespace); + else + relId = get_relname_relid(relation->relname, myTempNamespace); } } else if (relation->schemaname) @@ -326,7 +335,12 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, if (missing_ok && !OidIsValid(namespaceId)) relId = InvalidOid; else - relId = get_relname_relid(relation->relname, namespaceId); + { + if (relname_lookup_hook) + relId = (*relname_lookup_hook) (relation->relname, namespaceId); + else + relId = get_relname_relid(relation->relname, namespaceId); + } } else { @@ -688,7 +702,10 @@ RelnameGetRelid(const char *relname) { Oid namespaceId = lfirst_oid(l); - relid = get_relname_relid(relname, namespaceId); + if (relname_lookup_hook) + relid = (*relname_lookup_hook) (relname, namespaceId); + else + relid = get_relname_relid(relname, namespaceId); if (OidIsValid(relid)) return relid; } @@ -806,6 +823,38 @@ TypenameGetTypidExtended(const char *typname, bool temp_ok) return InvalidOid; } +/* + * typenameGetSchemaOID + * Try to resolve an unqualified datatype name in serach path, + * Returns OID of the schema if type found in search path, else InvalidOid. + * + */ +Oid +typenameGetSchemaOID(const char *typname, bool temp_ok) +{ + Oid typid; + ListCell *l; + + recomputeNamespacePath(); + + foreach(l, activeSearchPath) + { + Oid namespaceId = lfirst_oid(l); + + if (!temp_ok && namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + + typid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, + PointerGetDatum(typname), + ObjectIdGetDatum(namespaceId)); + if (OidIsValid(typid)) + return namespaceId; + } + + /* Not found in path */ + return InvalidOid; +} + /* * TypeIsVisible * Determine whether a type (identified by OID) is visible in the @@ -1984,8 +2033,29 @@ lookup_collation(const char *collname, Oid collnamespace, int32 encoding) PointerGetDatum(collname), Int32GetDatum(-1), ObjectIdGetDatum(collnamespace)); + + if (!HeapTupleIsValid(colltup)) - return InvalidOid; + { + if (TranslateCollation_hook) + { + const char *xlatedCollname = (*TranslateCollation_hook) (collname, collnamespace, encoding); + + if (NULL == xlatedCollname) + return InvalidOid; + + colltup = SearchSysCache3(COLLNAMEENCNSP, + PointerGetDatum(xlatedCollname), + Int32GetDatum(-1), + ObjectIdGetDatum(collnamespace)); + + if (!HeapTupleIsValid(colltup)) + return InvalidOid; + } + else + return InvalidOid; + } + collform = (Form_pg_collation) GETSTRUCT(colltup); if (collform->collprovider == COLLPROVIDER_ICU) { @@ -3375,7 +3445,9 @@ GetOverrideSearchPath(MemoryContext context) result->addTemp = true; else { - Assert(linitial_oid(schemas) == PG_CATALOG_NAMESPACE); + /* sys comes before pg_catalog when sql_dialect is tsql */ + if (sql_dialect != SQL_DIALECT_TSQL) + Assert(linitial_oid(schemas) == PG_CATALOG_NAMESPACE); result->addCatalog = true; } schemas = list_delete_first(schemas); @@ -3441,6 +3513,15 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path) /* If path->addCatalog, next item should be pg_catalog. */ if (path->addCatalog) { + /* If tsql dialect, next item should be sys */ + if (sql_dialect == SQL_DIALECT_TSQL) + { + if (lc && lfirst_oid(lc) == get_namespace_oid(SYS_NAMESPACE_NAME, true)) + lc = lnext(activeSearchPath, lc); + else + return false; + } + if (lc && lfirst_oid(lc) == PG_CATALOG_NAMESPACE) lc = lnext(activeSearchPath, lc); else @@ -3847,6 +3928,17 @@ recomputeNamespacePath(void) if (!list_member_oid(oidlist, PG_CATALOG_NAMESPACE)) oidlist = lcons_oid(PG_CATALOG_NAMESPACE, oidlist); + /* + * When sql_dialect is tsql, schema sys is used for catalog instead of + * pg_catalog. So, add it to search_path ahead of pg_catalog. + */ + if (sql_dialect == SQL_DIALECT_TSQL) + { + Oid sys_oid = get_namespace_oid(SYS_NAMESPACE_NAME, true); + if (!list_member_oid(oidlist, sys_oid)) + oidlist = lcons_oid(sys_oid, oidlist); + } + if (OidIsValid(myTempNamespace) && !list_member_oid(oidlist, myTempNamespace)) oidlist = lcons_oid(myTempNamespace, oidlist); @@ -4233,7 +4325,8 @@ RemoveTempRelations(Oid tempNamespaceId) PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY | PERFORM_DELETION_SKIP_ORIGINAL | - PERFORM_DELETION_SKIP_EXTENSIONS); + PERFORM_DELETION_SKIP_EXTENSIONS | + PERFORM_DELETION_SKIP_ENR); } /* @@ -4315,6 +4408,13 @@ assign_search_path(const char *newval, void *extra) baseSearchPathValid = false; } +/* assign_hook: do extra actions as needed */ +void +assign_sql_dialect(int newval, void *extra) +{ + baseSearchPathValid = false; +} + /* * InitializeSearchPath: initialize module during InitPostgres. * diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 64ceccbf004..25794bc228c 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -77,6 +77,7 @@ #include "parser/parse_func.h" #include "parser/parse_oper.h" #include "parser/parse_type.h" +#include "parser/parser.h" #include "rewrite/rewriteSupport.h" #include "storage/large_object.h" #include "storage/lmgr.h" @@ -878,7 +879,17 @@ get_object_address(ObjectType objtype, Node *object, missing_ok); break; case OBJECT_RULE: + address = get_object_address_relobject(objtype, castNode(List, object), + &relation, missing_ok); + break; case OBJECT_TRIGGER: + if(sql_dialect == SQL_DIALECT_TSQL) + address = get_object_address_trigger_tsql(castNode(List, object), + &relation, missing_ok); + else + address = get_object_address_relobject(objtype, castNode(List, object), + &relation, missing_ok); + break; case OBJECT_TABCONSTRAINT: case OBJECT_POLICY: address = get_object_address_relobject(objtype, castNode(List, object), @@ -1334,8 +1345,8 @@ get_object_address_relobject(ObjectType objtype, List *object, if (nnames < 2) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("must specify relation and object name"))); - + errmsg("must specify relation and object name"))); + /* Extract relation name and open relation. */ relname = list_truncate(list_copy(object), nnames - 1); relation = table_openrv_extended(makeRangeVarFromNameList(relname), @@ -1391,6 +1402,88 @@ get_object_address_relobject(ObjectType objtype, List *object, return address; } +/* +* A special case of the get_object_address_relobject() function, specifically +* for the case of triggers in tsql dialect. We add a pg_trigger lookup to search +* for the relation that the trigger is associated with, since the relation name +* is not supplied by the user, and thus not a part of the *object list. +* TODO: we can use a new hook and use this function for triggers or expand it +* for all objects in tsql dialect case. +*/ +ObjectAddress +get_object_address_trigger_tsql(List *object, Relation *relp, + bool missing_ok) +{ + ObjectAddress address; + Relation relation = NULL; + const char *depname; + Oid reloid; + Relation tgrel; + ScanKeyData key; + SysScanDesc tgscan; + HeapTuple tuple; + + /* Extract name of dependent object. */ + depname = strVal(llast(object)); + + /* + * Get the table name of the trigger from pg_trigger. We know that + * trigger names are forced to be unique in the tsql dialect, so we + * can rely on searching for trigger name to find the corresponding + * relation name. + */ + tgrel = table_open(TriggerRelationId, AccessShareLock); + + ScanKeyInit(&key, + Anum_pg_trigger_tgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(depname)); + + tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, false, + NULL, 1, &key); + + while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) + { + Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + + if (namestrcmp(&(pg_trigger->tgname), depname) == 0) + { + reloid = OidIsValid(pg_trigger->tgrelid) ? pg_trigger->tgrelid : + InvalidOid; + relation = RelationIdGetRelation(reloid); + RelationClose(relation); + } + } + systable_endscan(tgscan); + + table_close(tgrel, AccessShareLock); + address.classId = TriggerRelationId; + address.objectId = relation ? + get_trigger_oid(reloid, depname, missing_ok) : InvalidOid; + if (!relation && !missing_ok) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("trigger \"%s\" does not exist", + depname))); + } + address.objectSubId = 0; + + /* Avoid relcache leak when object not found. */ + if (!OidIsValid(address.objectId)) + { + if (relation != NULL) + table_close(relation, AccessShareLock); + + relation = NULL; /* department of accident prevention */ + return address; + } + + /* Done. */ + *relp = relation; + return address; +} + /* * Find the ObjectAddress for an attribute. */ diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c index 8559779a4f3..602f629900f 100644 --- a/src/backend/catalog/pg_collation.c +++ b/src/backend/catalog/pg_collation.c @@ -25,12 +25,21 @@ #include "catalog/pg_collation.h" #include "catalog/pg_namespace.h" #include "mb/pg_wchar.h" +#include "parser/parser.h" /* sql_dialect */ #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/pg_locale.h" #include "utils/rel.h" #include "utils/syscache.h" +/* Hook for plugins to get control in CLUSTER_COLLATION_OID() */ +CLUSTER_COLLATION_OID_hook_type CLUSTER_COLLATION_OID_hook = NULL; + +/* Hook for plugins to get control just prior to creating a collation */ +PreCreateCollation_hook_type PreCreateCollation_hook = NULL; + +/* Hook for plugins to get control if a collation name is not found */ +TranslateCollation_hook_type TranslateCollation_hook = NULL; /* * CollationCreate @@ -148,6 +157,19 @@ CollationCreate(const char *collname, Oid collnamespace, collname))); } + /* A hook may be registered to preprocess the parameters that will + * be used to create the collation + */ + if (PreCreateCollation_hook) + { + (*PreCreateCollation_hook)(collprovider, + collisdeterministic, + collencoding, + &collcollate, + &collctype, + collversion); + } + tupDesc = RelationGetDescr(rel); /* form a tuple */ @@ -239,3 +261,15 @@ RemoveCollationById(Oid collationOid) table_close(rel, RowExclusiveLock); } + +Oid CLUSTER_COLLATION_OID() +{ + /* + * We provide a function hook variable that lets loadable plugins get + * control when CLUSTER_COLLATION_OID is called. + */ + if (CLUSTER_COLLATION_OID_hook) + return (*CLUSTER_COLLATION_OID_hook)(); + else + return DEFAULT_COLLATION_OID; +} diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 90932be8310..f9ba236e91e 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -625,7 +625,8 @@ RemoveConstraintById(Oid conId) elog(ERROR, "constraint %u is not of a known type", conId); /* Fry the constraint itself */ - CatalogTupleDelete(conDesc, &tup->t_self); + if (!ENRdropTuple(conDesc, tup)) + CatalogTupleDelete(conDesc, &tup->t_self); /* Clean up */ ReleaseSysCache(tup); diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index cd567149689..b2d969af091 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -414,7 +414,11 @@ TypeCreate(Oid newTypeOid, tup = SearchSysCacheCopy2(TYPENAMENSP, CStringGetDatum(typeName), ObjectIdGetDatum(typeNamespace)); - if (HeapTupleIsValid(tup)) + /* + * If the tuple is for an ENR that's not in the current query environment, + * we are still allowed to create new type. + */ + if (HeapTupleIsValid(tup) && get_ENR(currentQueryEnv, typeName) == NULL) { Form_pg_type typform = (Form_pg_type) GETSTRUCT(tup); diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 29a1a982e5c..df33fb2865d 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -983,6 +983,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, target = makeNode(ResTarget); target->name = NULL; + target->name_location = -1; target->indirection = NIL; target->val = (Node *) cr; target->location = -1; @@ -1008,6 +1009,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, /* Build the ResTarget and add the ColumnRef to it. */ target = makeNode(ResTarget); target->name = NULL; + target->name_location = -1; target->indirection = NIL; target->val = (Node *) cr; target->location = -1; diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c index 81f03801086..85ec3e366a8 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -25,17 +25,21 @@ #include "commands/defrem.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "parser/parser.h" #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/syscache.h" static void does_not_exist_skipping(ObjectType objtype, - Node *object); + Node *object, Relation relation); static bool owningrel_does_not_exist_skipping(List *object, - const char **msg, char **name); + const char **msg, char **name); +static bool owningrel_does_not_exist_skipping_by_relation(Relation relation, + const char **msg, char **name); static bool schema_does_not_exist_skipping(List *object, const char **msg, char **name); static bool type_in_list_does_not_exist_skipping(List *typenames, @@ -83,7 +87,7 @@ RemoveObjects(DropStmt *stmt) if (!OidIsValid(address.objectId)) { Assert(stmt->missing_ok); - does_not_exist_skipping(stmt->removeType, object); + does_not_exist_skipping(stmt->removeType, object, relation); continue; } @@ -116,8 +120,15 @@ RemoveObjects(DropStmt *stmt) if (OidIsValid(namespaceId) && isTempNamespace(namespaceId)) MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE; - /* Release any relcache reference count, but keep lock until commit. */ - if (relation) + /* + * Release any relcache reference count, but keep lock until commit. + * Usually get_object_address() does not close a relation, but in tsql + * dialect, the get_object_address_trigger_tsql() closes the relation + * for the case of triggers. This is required for drop trigger to + * function smoothly. + */ + if (relation && !(sql_dialect == SQL_DIALECT_TSQL && + stmt->removeType == OBJECT_TRIGGER)) table_close(relation, NoLock); add_exact_object_address(&address, objects); @@ -164,6 +175,25 @@ owningrel_does_not_exist_skipping(List *object, const char **msg, char **name) return false; } +static bool +owningrel_does_not_exist_skipping_by_relation(Relation relation, const char **msg, char **name) +{ + if (!relation) + { + *msg = gettext_noop("relation does not exist, skipping"); + return true; + } + + if (!OidIsValid(RelationGetRelid(relation))) + { + *msg = gettext_noop("relation \"%s\" does not exist, skipping"); + *name = RelationGetRelationName(relation); + return true; + } + + return false; +} + /* * schema_does_not_exist_skipping * Subroutine for RemoveObjects @@ -245,7 +275,7 @@ type_in_list_does_not_exist_skipping(List *typenames, const char **msg, * get_object_address() in RemoveObjects would have thrown an ERROR. */ static void -does_not_exist_skipping(ObjectType objtype, Node *object) +does_not_exist_skipping(ObjectType objtype, Node *object, Relation relation) { const char *msg = NULL; char *name = NULL; @@ -415,7 +445,15 @@ does_not_exist_skipping(ObjectType objtype, Node *object) } break; case OBJECT_TRIGGER: - if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name)) + if (sql_dialect == SQL_DIALECT_TSQL && + !owningrel_does_not_exist_skipping_by_relation(relation, &msg, &name)) + { + msg = gettext_noop("trigger \"%s\" does not exist, skipping"); + name = strVal(llast(castNode(List, object))); + } + else + if (sql_dialect != SQL_DIALECT_TSQL && + !owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("trigger \"%s\" for relation \"%s\" does not exist, skipping"); name = strVal(llast(castNode(List, object))); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 4f1f4fb1ef9..22fe9a9f247 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -51,11 +51,14 @@ #include "commands/defrem.h" #include "commands/extension.h" #include "commands/proclang.h" +#include "commands/trigger.h" +#include "commands/tablecmds.h" #include "executor/execdesc.h" #include "executor/executor.h" #include "funcapi.h" #include "miscadmin.h" #include "optimizer/optimizer.h" +#include "parser/parser.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" #include "parser/parse_expr.h" @@ -73,6 +76,9 @@ #include "utils/syscache.h" #include "utils/typcache.h" +check_lang_as_clause_hook_type check_lang_as_clause_hook = NULL; +write_stored_proc_probin_hook_type write_stored_proc_probin_hook = NULL; + /* * Examine the RETURNS clause of the CREATE FUNCTION statement * and return information about it as *prorettype_p and *returnsSet. @@ -770,6 +776,24 @@ compute_function_attributes(ParseState *pstate, parser_errposition(pstate, defel->location))); windowfunc_item = defel; } + else if (strcmp(defel->defname, "trigStmt") == 0) + { + /* + * trigStmt is an implicit option in tsql dialect, we use this + * mechanism to create tsql style function and trigger in one + * statement. + */ + continue; + } + else if (strcmp(defel->defname, "tbltypStmt") == 0) + { + /* + * tbltypStmt an implicit option in tsql dialect, that is already + * handled in ProcessUtilitySlow(). So, here we just skip it without + * throwing error. + */ + continue; + } else if (compute_common_attribute(pstate, is_procedure, defel, @@ -866,6 +890,11 @@ interpret_AS_clause(Oid languageOid, const char *languageName, { Assert(as != NIL); + /* Allow extension language to process the clause itself. */ + if (check_lang_as_clause_hook && + (*check_lang_as_clause_hook)(languageName, as, prosrc_str_p, probin_str_p)) + return; + if (languageOid == ClanguageId) { /* @@ -955,6 +984,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt) Form_pg_language languageStruct; List *as_clause; char parallel; + ObjectAddress objAddr; /* Convert list of names to a name and namespace */ namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname, @@ -1120,6 +1150,10 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt) interpret_AS_clause(languageOid, language, funcname, as_clause, &prosrc_str, &probin_str); + if (write_stored_proc_probin_hook) + { + (*write_stored_proc_probin_hook)(stmt, languageOid, &probin_str); + } /* * Set default values for COST and ROWS depending on other parameters; * reject ROWS if it's not returnsSet. NB: pg_dump knows these default @@ -1150,7 +1184,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt) * And now that we have all the parameters, and know we're permitted to do * so, go ahead and create the function. */ - return ProcedureCreate(funcname, + objAddr = ProcedureCreate(funcname, namespaceId, stmt->replace, returnsSet, @@ -1176,6 +1210,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt) prosupport, procost, prorows); + + return objAddr; } /* @@ -2088,6 +2124,115 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic) OidFunctionCall1(laninline, PointerGetDatum(codeblock)); } +/* + * ExecuteDoStmtInsertExec + * Execute inline procedural-language code for INSERT EXEC + * + * Similar to ExecuteDoStmt(), but adjusted to work for INSERT EXEC + */ +void +ExecuteDoStmtInsertExec(DoStmt *stmt, bool atomic, DestReceiver *dest) +{ + InlineCodeBlock *codeblock = makeNode(InlineCodeBlock); + ListCell *arg; + DefElem *as_item = NULL; + DefElem *language_item = NULL; + char *language; + Oid laninline; + HeapTuple languageTuple; + Form_pg_language languageStruct; + + /* Process options we got from gram.y */ + foreach(arg, stmt->args) + { + DefElem *defel = (DefElem *) lfirst(arg); + + if (strcmp(defel->defname, "as") == 0) + { + if (as_item) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + as_item = defel; + } + else if (strcmp(defel->defname, "language") == 0) + { + if (language_item) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + language_item = defel; + } + else + elog(ERROR, "option \"%s\" not recognized", + defel->defname); + } + + if (as_item) + codeblock->source_text = strVal(as_item->arg); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("no inline code specified"))); + + /* if LANGUAGE option wasn't specified, use the default (pltsql) */ + if (language_item) + language = strVal(language_item->arg); + else + language = "pltsql"; + + /* Look up the language and validate permissions */ + languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language)); + if (!HeapTupleIsValid(languageTuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("language \"%s\" does not exist", language), + (extension_file_exists(language) ? + errhint("Use CREATE EXTENSION to load the language into the database.") : 0))); + + languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); + codeblock->langOid = languageStruct->oid; + codeblock->langIsTrusted = languageStruct->lanpltrusted; + codeblock->atomic = atomic; + + if (languageStruct->lanpltrusted) + { + /* if trusted language, need USAGE privilege */ + AclResult aclresult; + + aclresult = pg_language_aclcheck(codeblock->langOid, GetUserId(), + ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_LANGUAGE, + NameStr(languageStruct->lanname)); + } + else + { + /* if untrusted language, must be superuser */ + if (!superuser()) + aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_LANGUAGE, + NameStr(languageStruct->lanname)); + } + + /* get the handler function's OID */ + laninline = languageStruct->laninline; + if (!OidIsValid(laninline)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("language \"%s\" does not support inline code execution", + NameStr(languageStruct->lanname)))); + + ReleaseSysCache(languageTuple); + + /* for INSERT EXEC */ + codeblock->relation = stmt->relation; + codeblock->attrnos = stmt->attrnos; + codeblock->dest = (Node *) dest; + + /* execute the inline handler */ + OidFunctionCall1(laninline, PointerGetDatum(codeblock)); +} + /* * Execute CALL statement * @@ -2132,6 +2277,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver HeapTuple tp; PgStat_FunctionCallUsage fcusage; Datum retval; + ReturnSetInfo rsinfo; /* for INSERT ... EXECUTE */ fexpr = stmt->funcexpr; Assert(fexpr); @@ -2228,11 +2374,92 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver i++; } + /* + * If we are here for INSERT ... EXECUTE, prepare a resultinfo node for + * communication before invoking the function, which can accumulate the + * result sets. + */ + if (stmt->relation && stmt->attrnos) + { + /* + * If CallStmt->relation and stmt->attrnos are provided, then we need to + * build a TupleDesc for the ReturnSetInfo. + */ + Oid reltypeid; + TupleDesc reldesc; + TupleDesc retdesc; + int natts = 0; + ListCell *lc; + ListCell *next; + + /* look up the INSERT target relation rowtype's tupdesc */ + reltypeid = get_rel_type_id(stmt->relation); + reldesc = lookup_rowtype_tupdesc(reltypeid, -1); + + /* build a tupdesc that only contains relevant INSERT columns */ + retdesc = CreateTemplateTupleDesc(list_length(stmt->attrnos)); + for (lc = list_head(stmt->attrnos); lc != NULL; lc = next) + { + natts += 1; + TupleDescCopyEntry(retdesc, natts, reldesc, lfirst_int(lc)); + next = lnext(stmt->attrnos, lc); + } + + fcinfo->resultinfo = (Node *) &rsinfo; + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.expectedDesc = retdesc; + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + /* note we do not set SFRM_Materialize_Random or _Preferred */ + rsinfo.returnMode = SFRM_ValuePerCall; + rsinfo.isDone = ExprSingleResult; + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; + } + else if (stmt->retdesc && stmt->dest) + { + /* + * If CallStmt->retdesc is provided, use it for the ReturnSetInfo. + */ + fcinfo->resultinfo = (Node *) &rsinfo; + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.expectedDesc = (TupleDesc) stmt->retdesc; + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + /* note we do not set SFRM_Materialize_Random or _Preferred */ + rsinfo.returnMode = SFRM_ValuePerCall; + rsinfo.isDone = ExprSingleResult; + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; + } + pgstat_init_function_usage(fcinfo, &fcusage); retval = FunctionCallInvoke(fcinfo); pgstat_end_function_usage(&fcusage, true); - if (fexpr->funcresulttype == VOIDOID) + if (((stmt->relation && stmt->attrnos) || (stmt->retdesc && stmt->dest)) && + rsinfo.setDesc && rsinfo.setResult) + { + /* + * If we are here for INSERT ... EXECUTE, send all tuples accumulated in + * resultinfo to the DestReceiver, which will later be consumed by the + * INSERT execution. + */ + TupleTableSlot *slot = MakeSingleTupleTableSlot(rsinfo.expectedDesc, + &TTSOpsMinimalTuple); + for (;;) + { + if (!tuplestore_gettupleslot(rsinfo.setResult, true, false, slot)) + break; + if (stmt->dest) + ((DestReceiver *)stmt->dest)->receiveSlot(slot, (DestReceiver *)stmt->dest); + else + dest->receiveSlot(slot, dest); + ExecClearTuple(slot); + } + ExecDropSingleTupleTableSlot(slot); + } + else if (fexpr->funcresulttype == VOIDOID) { /* do nothing */ } diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 6aab73bfd44..4d66f77ec05 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -86,6 +86,12 @@ typedef struct SeqTableData typedef SeqTableData *SeqTable; +/* Hooks for pltsql plugin */ +pltsql_sequence_validate_increment_hook_type pltsql_sequence_validate_increment_hook = NULL; +pltsql_sequence_datatype_hook_type pltsql_sequence_datatype_hook = NULL; +pltsql_nextval_hook_type pltsql_nextval_hook = NULL; +pltsql_resetcache_hook_type pltsql_resetcache_hook = NULL; + static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ /* @@ -758,6 +764,9 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; + if (pltsql_nextval_hook) + (* pltsql_nextval_hook) (elm->relid, result); + /* * If something needs to be WAL logged, acquire an xid, so this * transaction's commit will trigger a WAL flush and wait for syncrep. @@ -1377,6 +1386,14 @@ init_params(ParseState *pstate, List *options, bool for_identity, { Oid newtypid = typenameTypeId(pstate, defGetTypeName(as_type)); + if (pltsql_sequence_datatype_hook) + (* pltsql_sequence_datatype_hook) (pstate, + &newtypid, + for_identity, + as_type, + &max_value, + &min_value); + if (newtypid != INT2OID && newtypid != INT4OID && newtypid != INT8OID) @@ -1627,6 +1644,11 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcache = 1; } + + if(pltsql_sequence_validate_increment_hook) + (* pltsql_sequence_validate_increment_hook) (seqform->seqincrement, + seqform->seqmax, + seqform->seqmin); } /* @@ -1942,6 +1964,9 @@ ResetSequenceCaches(void) } last_used_seq = NULL; + + if (pltsql_resetcache_hook) + (* pltsql_resetcache_hook) (); } /* diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index 2f46180407a..9b8064059d5 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -553,7 +553,8 @@ RemoveStatisticsById(Oid statsOid) if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for statistics data %u", statsOid); - CatalogTupleDelete(relation, &tup->t_self); + if (!ENRdropTuple(relation, tup)) + CatalogTupleDelete(relation, &tup->t_self); ReleaseSysCache(tup); @@ -575,7 +576,8 @@ RemoveStatisticsById(Oid statsOid) CacheInvalidateRelcacheByRelid(relid); - CatalogTupleDelete(relation, &tup->t_self); + if (!ENRdropTuple(relation, tup)) + CatalogTupleDelete(relation, &tup->t_self); ReleaseSysCache(tup); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 8784bec0e6d..bfa6785ef63 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -65,6 +65,10 @@ #include "nodes/nodeFuncs.h" #include "nodes/parsenodes.h" #include "optimizer/optimizer.h" +#include "nodes/pg_list.h" +#include "optimizer/clauses.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" #include "parser/parse_clause.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" @@ -74,6 +78,7 @@ #include "parser/parse_type.h" #include "parser/parse_utilcmd.h" #include "parser/parser.h" +#include "parser/scansup.h" /* downcase_identifier */ #include "partitioning/partbounds.h" #include "partitioning/partdesc.h" #include "pgstat.h" @@ -121,6 +126,8 @@ typedef struct OnCommitItem static List *on_commits = NIL; +InvokePreDropColumnHook_type InvokePreDropColumnHook = NULL; +check_extended_attoptions_hook_type check_extended_attoptions_hook = NULL; /* * State information for ALTER TABLE @@ -606,6 +613,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, LOCKMODE parentLockmode; const char *accessMethod = NULL; Oid accessMethodId = InvalidOid; + ObjectAddress tsql_tabletype_address; /* * Truncate relname to appropriate length (probably a waste of time, as @@ -880,6 +888,12 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (accessMethod != NULL) accessMethodId = get_table_am_oid(accessMethod, false); + /* + * If we are here for creating TSQL table type, we will need to record the + * pg_type entry's address, so that we can adjust the dependency later on. + */ + if (stmt->tsql_tabletype && typaddress == NULL) + typaddress = &tsql_tabletype_address; /* * Create the relation. Inherited defaults and constraints are passed in * for immediate handling --- since they don't need parsing, they can be @@ -1169,6 +1183,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ObjectAddressSet(address, RelationRelationId, relationId); + /* + * If we are here for creating TSQL table type, we will treat the created + * relation as the template table for the table type, and the implicitly + * created composite type as the desired table type. + * So, remove the composite type's dependency on the relation, and record + * the relation's dependency on the composite type. + */ + if (stmt->tsql_tabletype) + { + deleteDependencyRecordsForClass(typaddress->classId, typaddress->objectId, + RelationRelationId, DEPENDENCY_INTERNAL); + recordDependencyOn(&address, typaddress, DEPENDENCY_INTERNAL); + } + /* * Clean up. We keep lock on new relation (although it shouldn't be * visible to anyone else anyway, until commit). @@ -1393,6 +1421,17 @@ RemoveRelations(DropStmt *drop) obj.objectSubId = 0; add_exact_object_address(&obj, objects); + + /* + * If the relation is a table, we must look for triggers and drop them + * when in the tsql dialect because the user does not create a function for + * the trigger - we create it internally, and so the table cannot be dropped + * if there is a tsql trigger on it because of the dependency of the function. + */ + if (object_access_hook && drop->removeType == OBJECT_TABLE) + { + InvokeObjectDropHook(RelationRelationId,relOid,0); + } } performMultipleDeletions(objects, drop->behavior, flags); @@ -2327,6 +2366,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence, /* We can't process inherited defaults until newattmap is complete. */ inherited_defaults = cols_with_defaults = NIL; + /* We can't process inherited defaults until newattno[] is complete. */ + inherited_defaults = cols_with_defaults = NIL; + for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++) { @@ -2986,6 +3028,31 @@ findAttrByName(const char *attributeName, List *schema) i++; } + + if (sql_dialect == SQL_DIALECT_TSQL + && pltsql_case_insensitive_identifiers) + { + char *attrname = downcase_identifier(attributeName, strlen(attributeName), false, false); + int attrlen = strlen(attrname); + + i = 1; + + foreach(s, schema) + { + ColumnDef *def = lfirst(s); + + if (strlen(def->colname) == attrlen) + { + char *defname = downcase_identifier(def->colname, strlen(def->colname), false, false); + + if (strncmp(attrname, defname, attrlen) == 0) + return i; + } + + i++; + } + } + return 0; } @@ -7567,8 +7634,9 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options, newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, castNode(List, options), NULL, NULL, false, isReset); - /* Validate new options */ - (void) attribute_reloptions(newOptions, true); + /* Validate new options except for those allowed by extension */ + if (!check_extended_attoptions_hook || !check_extended_attoptions_hook(options)) + (void) attribute_reloptions(newOptions, true); /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); @@ -7845,6 +7913,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, ReleaseSysCache(tuple); + if (InvokePreDropColumnHook) + (*InvokePreDropColumnHook) (rel, attnum); + /* * Propagate to children as appropriate. Unlike most other ALTER * routines, we have to do this one level of recursion at a time; we can't @@ -10111,6 +10182,7 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName, Datum val; char *conbin; + /* * If we're recursing, the parent has already done this, so skip * it. Also, if the constraint is a NO INHERIT constraint, we diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 788b92c7b8a..872c8f53e69 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -39,6 +39,7 @@ #include "nodes/bitmapset.h" #include "nodes/makefuncs.h" #include "optimizer/optimizer.h" +#include "parser/parser.h" /* only needed for GUC variables */ #include "parser/parse_clause.h" #include "parser/parse_collate.h" #include "parser/parse_func.h" @@ -94,6 +95,20 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture); static void AfterTriggerEnlargeQueryState(void); static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType); +static IOTState instead_stmt_triggers_fired(Oid relid, CmdType cmdType); + +static IOTState instead_stmt_triggers_fired(Oid relid, CmdType cmdType); +static void set_iot_state(Oid relid, CmdType cmdType, IOTState newState); + +/* +* In babelfish triggers, the procedural code is part of the CREATE TRIGGER +* statement, instead of being in a separate function. Therefore we termed these +* triggers as composite triggers, as they handle the procedural code and trigger +* creation in the same statement. +*/ +static bool IsCompositeTriggerActive(void); +static void AddCompositeTriggerLevelData(void); +static bool IsCompositeTrigger(Oid fn_oid); /* @@ -205,7 +220,9 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, if (rel->rd_rel->relkind == RELKIND_RELATION) { /* Tables can't have INSTEAD OF triggers */ - if (stmt->timing != TRIGGER_TYPE_BEFORE && + /* BABELFISH: Support Instead of triggers, lift restriction for TSQL */ + if (sql_dialect != SQL_DIALECT_TSQL && + stmt->timing != TRIGGER_TYPE_BEFORE && stmt->timing != TRIGGER_TYPE_AFTER) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -216,7 +233,9 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { /* Partitioned tables can't have INSTEAD OF triggers */ - if (stmt->timing != TRIGGER_TYPE_BEFORE && + /* BABELFISH: Support Instead of triggers, lift restriction for TSQL */ + if (sql_dialect != SQL_DIALECT_TSQL && + stmt->timing != TRIGGER_TYPE_BEFORE && stmt->timing != TRIGGER_TYPE_AFTER) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -375,10 +394,14 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, /* INSTEAD triggers must be row-level, and can't have WHEN or columns */ if (TRIGGER_FOR_INSTEAD(tgtype)) { - if (!TRIGGER_FOR_ROW(tgtype)) + if (sql_dialect != SQL_DIALECT_TSQL && !TRIGGER_FOR_ROW(tgtype)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("INSTEAD OF triggers must be FOR EACH ROW"))); + errmsg("INSTEAD OF triggers must be FOR EACH ROW"))); + if (sql_dialect == SQL_DIALECT_TSQL && TRIGGER_FOR_ROW(tgtype)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSTEAD OF triggers not supported for FOR EACH ROW with TSQL dialect"))); if (stmt->whenClause) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -459,7 +482,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, errmsg("ROW triggers with transition tables are not supported on inheritance children"))); } - if (stmt->timing != TRIGGER_TYPE_AFTER) + if (sql_dialect != SQL_DIALECT_TSQL && stmt->timing != TRIGGER_TYPE_AFTER) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("transition table name can only be specified for an AFTER trigger"))); @@ -523,11 +546,18 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, } else { - if (!(TRIGGER_FOR_DELETE(tgtype) || - TRIGGER_FOR_UPDATE(tgtype))) - ereport(ERROR, + if (sql_dialect != SQL_DIALECT_TSQL) { + if (!(TRIGGER_FOR_DELETE(tgtype) || + TRIGGER_FOR_UPDATE(tgtype))) + ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("OLD TABLE can only be specified for a DELETE or UPDATE trigger"))); + } else if (!(TRIGGER_FOR_DELETE(tgtype) || + TRIGGER_FOR_UPDATE(tgtype) || + TRIGGER_FOR_INSERT(tgtype))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("TSQL: OLD TABLE can only be specified for a INSERT or DELETE or UPDATE trigger"))); if (oldtablename != NULL) ereport(ERROR, @@ -772,23 +802,85 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, */ if (!isInternal) { - ScanKeyInit(&key, - Anum_pg_trigger_tgrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(rel))); - tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true, - NULL, 1, &key); - while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) + /* + * If we are in tsql dialect, we need to ensure that trigger names are + * unique throughout the database. In PG, unique triggers are enforced on + * a table, and therefore the table name is required to drop a trigger. + * Tsql allows dropping a trigger without the table name, and we need to + * enforce unique trigger names in order to do the same. + * TODO: This can be done using a new hook. + */ + if(sql_dialect == SQL_DIALECT_TSQL) { - Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + ScanKeyInit(&key, + Anum_pg_trigger_tgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(trigname)); + tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, false, + NULL, 1, &key); + while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) + { + Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); - if (namestrcmp(&(pg_trigger->tgname), trigname) == 0) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("trigger \"%s\" for relation \"%s\" already exists", - trigname, RelationGetRelationName(rel)))); + if (namestrcmp(&(pg_trigger->tgname), trigname) == 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("trigger \"%s\" already exists in the database", + trigname))); + } + systable_endscan(tgscan); + if (TRIGGER_FOR_INSTEAD(tgtype)) { + ScanKeyInit(&key, + Anum_pg_trigger_tgrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true, NULL, 1, &key); + while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) + { + Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + if (TRIGGER_FOR_UPDATE(tgtype) && + TRIGGER_TYPE_MATCHES(pg_trigger->tgtype, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_UPDATE)) { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("cannot create trigger \"%s\" for relation \"%s\" because an INSTEAD OF UPDATE trigger already exists on this object.", + trigname, RelationGetRelationName(rel)))); + } else if (TRIGGER_FOR_INSERT(tgtype) && + TRIGGER_TYPE_MATCHES(pg_trigger->tgtype, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_INSERT)) { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("cannot create trigger \"%s\" for relation \"%s\" because an INSTEAD OF INSERT trigger already exists on this object.", + trigname, RelationGetRelationName(rel)))); + } else if (TRIGGER_FOR_DELETE(tgtype) && + TRIGGER_TYPE_MATCHES(pg_trigger->tgtype, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_DELETE)) { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("cannot create trigger \"%s\" for relation \"%s\" because an INSTEAD OF DELETE trigger already exists on this object.", + trigname, RelationGetRelationName(rel)))); + } + } + systable_endscan(tgscan); + } + } + else + { + ScanKeyInit(&key, + Anum_pg_trigger_tgrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true, + NULL, 1, &key); + while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) + { + Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + + if (namestrcmp(&(pg_trigger->tgname), trigname) == 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("trigger \"%s\" for relation \"%s\" already exists", + trigname, RelationGetRelationName(rel)))); + } + systable_endscan(tgscan); } - systable_endscan(tgscan); } /* @@ -1740,13 +1832,16 @@ SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger) TRIGGER_TYPE_AFTER, TRIGGER_TYPE_INSERT); trigdesc->trig_insert_instead_row |= TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW, - TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_INSERT); + TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_INSERT); trigdesc->trig_insert_before_statement |= TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT); trigdesc->trig_insert_after_statement |= TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_AFTER, TRIGGER_TYPE_INSERT); + trigdesc->trig_insert_instead_statement |= + TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT, + TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_INSERT); trigdesc->trig_update_before_row |= TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_UPDATE); @@ -1762,6 +1857,9 @@ SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger) trigdesc->trig_update_after_statement |= TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_AFTER, TRIGGER_TYPE_UPDATE); + trigdesc->trig_update_instead_statement |= + TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT, + TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_UPDATE); trigdesc->trig_delete_before_row |= TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE); @@ -1777,6 +1875,9 @@ SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger) trigdesc->trig_delete_after_statement |= TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_AFTER, TRIGGER_TYPE_DELETE); + trigdesc->trig_delete_instead_statement |= + TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT, + TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_DELETE); /* there are no row-level truncate triggers */ trigdesc->trig_truncate_before_statement |= TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT, @@ -2113,6 +2214,60 @@ ExecCallTriggerFunc(TriggerData *trigdata, return (HeapTuple) DatumGetPointer(result); } +IOTState +ExecISInsertTriggers(EState *estate, ResultRelInfo *relinfo) +{ + TriggerDesc *trigdesc; + int i; + TriggerData LocTriggerData; + IOTState prevState; + trigdesc = relinfo->ri_TrigDesc; + if (trigdesc == NULL) + return IOT_NOT_REQUIRED; + if (!trigdesc->trig_insert_instead_statement) { + set_iot_state(RelationGetRelid(relinfo->ri_RelationDesc), CMD_INSERT, IOT_NOT_REQUIRED); + return IOT_NOT_REQUIRED; + } + + // if the trigger is already fired or not required + prevState = instead_stmt_triggers_fired(RelationGetRelid(relinfo->ri_RelationDesc), CMD_INSERT); + if (prevState != IOT_NOT_FIRED) + return prevState; + LocTriggerData.type = T_TriggerData; + LocTriggerData.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_INSTEAD; + LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.tg_trigtuple = NULL; + LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; + LocTriggerData.tg_oldtable = NULL; + LocTriggerData.tg_newtable = NULL; + for (i = 0; i < trigdesc->numtriggers; i++) + { + Trigger *trigger = &trigdesc->triggers[i]; + HeapTuple newtuple; + if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, + TRIGGER_TYPE_STATEMENT, + TRIGGER_TYPE_INSTEAD, + TRIGGER_TYPE_INSERT)) + continue; + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, NULL, NULL, NULL)) + continue; + LocTriggerData.tg_trigger = trigger; + newtuple = ExecCallTriggerFunc( + &LocTriggerData, + i, + relinfo->ri_TrigFunctions, + relinfo->ri_TrigInstrument, + GetPerTupleMemoryContext(estate)); + if (newtuple) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("INSTEAD STATEMENT trigger cannot return a value"))); + } + return IOT_FIRED; +} + void ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo) { @@ -2378,6 +2533,68 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo) } } +IOTState +ExecISDeleteTriggers(EState *estate, ResultRelInfo *relinfo) +{ + TriggerDesc *trigdesc; + int i; + TriggerData LocTriggerData; + IOTState prevState; + + trigdesc = relinfo->ri_TrigDesc; + + if (trigdesc == NULL) + return IOT_NOT_REQUIRED; + if (!trigdesc->trig_delete_instead_statement) { + set_iot_state(RelationGetRelid(relinfo->ri_RelationDesc), CMD_DELETE, IOT_NOT_REQUIRED); + return IOT_NOT_REQUIRED; + } + prevState = instead_stmt_triggers_fired(RelationGetRelid(relinfo->ri_RelationDesc), CMD_DELETE); + // if the trigger is already fired or not required + if (prevState != IOT_NOT_FIRED) + return prevState; + + LocTriggerData.type = T_TriggerData; + LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | + TRIGGER_EVENT_INSTEAD; + LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.tg_trigtuple = NULL; + LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_oldtable = NULL; + LocTriggerData.tg_newtable = NULL; + LocTriggerData.tg_trigtuple = InvalidBuffer; + LocTriggerData.tg_newtuple = InvalidBuffer; + for (i = 0; i < trigdesc->numtriggers; i++) + { + Trigger *trigger = &trigdesc->triggers[i]; + HeapTuple newtuple; + + if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, + TRIGGER_TYPE_STATEMENT, + TRIGGER_TYPE_INSTEAD, + TRIGGER_TYPE_DELETE)) + continue; + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + NULL, NULL, NULL)) + continue; + + LocTriggerData.tg_trigger = trigger; + newtuple = ExecCallTriggerFunc(&LocTriggerData, + i, + relinfo->ri_TrigFunctions, + relinfo->ri_TrigInstrument, + GetPerTupleMemoryContext(estate)); + + if (newtuple) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("INSTEAD STATEMENT trigger cannot return a value"))); + } + return IOT_FIRED; +} + + + void ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture) @@ -2558,6 +2775,71 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo, return true; } +IOTState +ExecISUpdateTriggers(EState *estate, ResultRelInfo *relinfo) +{ + TriggerDesc *trigdesc; + int i; + TriggerData LocTriggerData; + Bitmapset *updatedCols; + IOTState prevState; + + trigdesc = relinfo->ri_TrigDesc; + + if (trigdesc == NULL) + return IOT_NOT_REQUIRED; + + if (!trigdesc->trig_update_instead_statement) { + set_iot_state(RelationGetRelid(relinfo->ri_RelationDesc), CMD_UPDATE, IOT_NOT_REQUIRED); + return IOT_NOT_REQUIRED; + } + + prevState = instead_stmt_triggers_fired(RelationGetRelid(relinfo->ri_RelationDesc), CMD_UPDATE); + // if the trigger is already fired or not required + if (prevState != IOT_NOT_FIRED) + return prevState; + + updatedCols = ExecGetAllUpdatedCols(relinfo, estate); + + LocTriggerData.type = T_TriggerData; + LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | + TRIGGER_EVENT_INSTEAD; + LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.tg_trigtuple = NULL; + LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_oldtable = NULL; + LocTriggerData.tg_newtable = NULL; + LocTriggerData.tg_trigtuple = InvalidBuffer; + LocTriggerData.tg_newtuple = InvalidBuffer; + for (i = 0; i < trigdesc->numtriggers; i++) + { + Trigger *trigger = &trigdesc->triggers[i]; + HeapTuple newtuple; + + if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, + TRIGGER_TYPE_STATEMENT, + TRIGGER_TYPE_INSTEAD, + TRIGGER_TYPE_UPDATE)) + continue; + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + updatedCols, NULL, NULL)) + continue; + + LocTriggerData.tg_trigger = trigger; + newtuple = ExecCallTriggerFunc(&LocTriggerData, + i, + relinfo->ri_TrigFunctions, + relinfo->ri_TrigInstrument, + GetPerTupleMemoryContext(estate)); + + if (newtuple) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("INSTEAD OF STATEMENT trigger cannot return a value"))); + } + return IOT_FIRED; +} + void ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) { @@ -3443,6 +3725,30 @@ struct AfterTriggersQueryData List *tables; /* list of AfterTriggersTableData, see below */ }; +/* + * TSQL triggers are kept in different data structures + * to support transaction commands inside triggers. These + * structures are allocated using memory context of + * TSQL executor rather than current transaction context + */ +typedef struct CompositeTriggerData +{ + AfterTriggersQueryData data; + int triggerLevel; + uint32 xactSubxid; + bool cleanupReq; + struct CompositeTriggerData *next; +} CompositeTriggerData; + +typedef struct CompositeTriggers +{ + CompositeTriggerData *triggers; + MemoryContext curCxt; + int triggerLevel; +} CompositeTriggers; + +static CompositeTriggers compositeTriggers = {NULL, NULL, 0}; + struct AfterTriggersTransData { /* these fields are just for resetting at subtrans abort: */ @@ -3460,6 +3766,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool instead_trig_done; /* did we already queue IS triggers? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ Tuplestorestate *old_tuplestore; /* "old" transition table, if any */ Tuplestorestate *new_tuplestore; /* "new" transition table, if any */ @@ -3481,7 +3788,7 @@ static AfterTriggersTableData *GetAfterTriggersTableData(Oid relid, CmdType cmdType); static TupleTableSlot *GetAfterTriggersStoreSlot(AfterTriggersTableData *table, TupleDesc tupdesc); -static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs, bool free_tables); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState state); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4271,13 +4578,23 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType) return table; } - oldcxt = MemoryContextSwitchTo(CurTransactionContext); + if (compositeTriggers.triggerLevel > 0 ) + oldcxt = MemoryContextSwitchTo(compositeTriggers.curCxt); + else + oldcxt = MemoryContextSwitchTo(CurTransactionContext); table = (AfterTriggersTableData *) palloc0(sizeof(AfterTriggersTableData)); table->relid = relid; table->cmdType = cmdType; qs->tables = lappend(qs->tables, table); + /* Keep additional reference to tables inside TSQL trigger data */ + if (compositeTriggers.triggerLevel > 0 ) + { + AddCompositeTriggerLevelData(); + compositeTriggers.triggers->data.tables = lappend(compositeTriggers.triggers->data.tables, table); + } + MemoryContextSwitchTo(oldcxt); return table; @@ -4301,7 +4618,10 @@ GetAfterTriggersStoreSlot(AfterTriggersTableData *table, * it last till end-of-subxact is good enough. It'll be freed by * AfterTriggerFreeQuery(). */ - oldcxt = MemoryContextSwitchTo(CurTransactionContext); + if (compositeTriggers.triggerLevel > 0 ) + oldcxt = MemoryContextSwitchTo(compositeTriggers.curCxt); + else + oldcxt = MemoryContextSwitchTo(CurTransactionContext); table->storeslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual); MemoryContextSwitchTo(oldcxt); } @@ -4392,7 +4712,10 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType) table = GetAfterTriggersTableData(relid, cmdType); /* Now create required tuplestore(s), if we don't have them already. */ - oldcxt = MemoryContextSwitchTo(CurTransactionContext); + if (compositeTriggers.triggerLevel > 0 ) + oldcxt = MemoryContextSwitchTo(compositeTriggers.curCxt); + else + oldcxt = MemoryContextSwitchTo(CurTransactionContext); saveResourceOwner = CurrentResourceOwner; CurrentResourceOwner = CurTransactionResourceOwner; @@ -4553,7 +4876,7 @@ AfterTriggerEndQuery(EState *estate) } /* Release query-level-local storage, including tuplestores if any */ - AfterTriggerFreeQuery(&afterTriggers.query_stack[afterTriggers.query_depth]); + AfterTriggerFreeQuery(&afterTriggers.query_stack[afterTriggers.query_depth], !IsCompositeTriggerActive()); afterTriggers.query_depth--; } @@ -4567,7 +4890,7 @@ AfterTriggerEndQuery(EState *estate) * and then called again for the same query level. */ static void -AfterTriggerFreeQuery(AfterTriggersQueryData *qs) +AfterTriggerFreeQuery(AfterTriggersQueryData *qs, bool free_tables) { Tuplestorestate *ts; List *tables; @@ -4582,6 +4905,13 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) if (ts) tuplestore_end(ts); + /* Babelfish triggers will remove transition tables later */ + if (!free_tables) + { + qs->tables = NIL; + return; + } + /* Release per-table subsidiary storage */ tables = qs->tables; foreach(lc, tables) @@ -4799,6 +5129,17 @@ AfterTriggerEndSubXact(bool isCommit) } else { + /* + * Babelfish triggers will skip relation table cleanup + * if savepoint rollback has already done it + */ + CompositeTriggerData *trigger = compositeTriggers.triggers; + while (trigger != NULL) + { + if (trigger->xactSubxid == GetCurrentSubTransactionId()) + trigger->cleanupReq = false; + trigger = trigger->next; + } /* * Aborting. It is possible subxact start failed before calling * AfterTriggerBeginSubXact, in which case we mustn't risk touching @@ -4816,7 +5157,7 @@ AfterTriggerEndSubXact(bool isCommit) while (afterTriggers.query_depth > afterTriggers.trans_stack[my_level].query_depth) { if (afterTriggers.query_depth < afterTriggers.maxquerydepth) - AfterTriggerFreeQuery(&afterTriggers.query_stack[afterTriggers.query_depth]); + AfterTriggerFreeQuery(&afterTriggers.query_stack[afterTriggers.query_depth], !IsCompositeTriggerActive()); afterTriggers.query_depth--; } Assert(afterTriggers.query_depth == @@ -5678,7 +6019,23 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, new_shared.ats_table = NULL; new_shared.ats_modifiedcols = modifiedCols; - afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth].events, + /* + * In case of Babelfish triggers, add events to babelfihs + * trigger data list + */ + if (compositeTriggers.triggerLevel > 0 && + IsCompositeTrigger(trigger->tgfoid)) + { + MemoryContext oldCxt = MemoryContextSwitchTo(compositeTriggers.curCxt); + MemoryContext oldEventCxt = afterTriggers.event_cxt; + AddCompositeTriggerLevelData(); + afterTriggers.event_cxt = CurrentMemoryContext; + afterTriggerAddEvent(&compositeTriggers.triggers->data.events, &new_event, &new_shared); + afterTriggers.event_cxt = oldEventCxt; + MemoryContextSwitchTo(oldCxt); + } + else + afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth].events, &new_event, &new_shared); } @@ -5727,6 +6084,48 @@ before_stmt_triggers_fired(Oid relid, CmdType cmdType) return result; } +static void +set_iot_state(Oid relid, CmdType cmdType, IOTState newState) +{ + AfterTriggersTableData *table; + + if (afterTriggers.query_depth < 0 || + afterTriggers.query_depth >= afterTriggers.maxquerydepth) + return; + table = GetAfterTriggersTableData(relid, cmdType); + table->instead_trig_done = newState; +} +/* + * Detect whether we already queued INSTEAD STATEMENT triggers for the given + * relation + operation, and set the flag so the next call will report "true". + */ +static IOTState +instead_stmt_triggers_fired(Oid relid, CmdType cmdType) +{ + IOTState result; + AfterTriggersTableData *table; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "instead_stmt_triggers_fired() called outside of query"); + + /* Be sure we have enough space to record events at this query depth. */ + if (afterTriggers.query_depth >= afterTriggers.maxquerydepth) + AfterTriggerEnlargeQueryState(); + + /* + * We keep this state in the AfterTriggersTableData that also holds + * transition tables for the relation + operation. In this way, if we are + * forced to make a new set of transition tables because more tuples get + * entered after we've already fired triggers, we will allow a new set of + * statement triggers to get queued. + */ + table = GetAfterTriggersTableData(relid, cmdType); + result = table->instead_trig_done; + table->instead_trig_done = IOT_FIRED; + return result; +} + /* * If we previously queued a set of AFTER STATEMENT triggers for the given * relation + operation, and they've not been fired yet, cancel them. The @@ -5826,3 +6225,139 @@ pg_trigger_depth(PG_FUNCTION_ARGS) { PG_RETURN_INT32(MyTriggerDepth); } + +/* + * If trigger is active at current nesting level + */ +static +bool IsCompositeTriggerActive(void) +{ + return (compositeTriggers.triggers != NULL && + compositeTriggers.triggers->triggerLevel == compositeTriggers.triggerLevel); +} + +/* + * Create a new entry for trigger data at current + * nesting level if not present already + */ +static +void AddCompositeTriggerLevelData(void) +{ + if (compositeTriggers.triggers == NULL || compositeTriggers.triggers->triggerLevel < compositeTriggers.triggerLevel) + { + CompositeTriggerData *triggers = palloc0(sizeof(CompositeTriggerData)); + triggers->triggerLevel = compositeTriggers.triggerLevel; + triggers->next = compositeTriggers.triggers; + triggers->xactSubxid = GetCurrentSubTransactionId(); + triggers->cleanupReq = true; + compositeTriggers.triggers = triggers; + } +} + +/* + * Open new nesting level for trigger from TSQL executor + */ +void BeginCompositeTriggers(MemoryContext curCxt) +{ + compositeTriggers.triggerLevel++; + compositeTriggers.curCxt = curCxt; +} + +/* + * End current nesting level for trigger from TSQL executor + * If there is trigger data at current level, fire all trigger + * events and do cleanup + */ +void EndCompositeTriggers(bool error) +{ + if (IsCompositeTriggerActive()) + { + CompositeTriggerData *triggers = compositeTriggers.triggers; + /* In case of error, only end the nesting level */ + if (!error) + { + AfterTriggerEvent event; + AfterTriggerEventChunk *chunk; + EState *estate; + int before_lxid = MyProc->lxid; + ResourceOwner oldOwner = CurrentResourceOwner; + + /* Mark all triggers as IN PROGRESS */ + for_each_event_chunk(event, chunk, triggers->data.events) + { + AfterTriggerShared evtshared = GetTriggerSharedData(event); + + evtshared->ats_firing_id = 0; + Assert(!(event->ate_flags & + (AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))); + event->ate_flags |= AFTER_TRIGGER_IN_PROGRESS; + } + /* Fire all triggers events, deferred is not supported */ + estate = CreateExecutorState(); + if (!afterTriggerInvokeEvents(&triggers->data.events, 0, estate, false)) + Assert(false); + CurrentResourceOwner = oldOwner; + + /* In case of transactional event, skip cleanup */ + if (before_lxid == MyProc->lxid && triggers->cleanupReq) + ExecCleanUpTriggerState(estate); + ExecResetTupleTable(estate->es_tupleTable, false); + FreeExecutorState(estate); + } + compositeTriggers.triggers = triggers->next; + AfterTriggerFreeQuery(&triggers->data, true); + pfree(triggers); + } + compositeTriggers.triggerLevel--; +} + +/* + * Find if the trigger is TSQL or PG type + */ +static +bool IsCompositeTrigger(Oid fn_oid) +{ + bool isComposite = false; + Oid pltsql_lang_oid = InvalidOid; + Oid pltsql_validator_oid = InvalidOid; + /* + * FIXME: the following typedef (PLtsql_config) describes a + * structure shared with PLtsql. Since the type is shared, + * we should find a header file that is properly shared and + * move this typedef + * + * This typedef *must* be kept in-synch with the PLtsql_config + * type declaration found in contrib/babelfishpg_tsql/src/pgtsql.h + */ + typedef struct PLtsql_config + { + char *version; /* Extension version info */ + Oid handler_oid; /* Oid of language handler function */ + Oid validator_oid; /* Oid of language validator function */ + } PLtsql_config; + + PLtsql_config **config = (PLtsql_config **) find_rendezvous_variable("PLtsql_config"); + + if (*config) + { + pltsql_lang_oid = (*config)->handler_oid; + pltsql_validator_oid = (*config)->validator_oid; + } + + if (pltsql_lang_oid != InvalidOid) + { + HeapTuple tuple; + Form_pg_proc procedureStruct; + tuple = SearchSysCache1(PROCOID, fn_oid); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for trigger %u", fn_oid); + procedureStruct = (Form_pg_proc) GETSTRUCT(tuple); + + if ((procedureStruct->prolang == pltsql_lang_oid) || (procedureStruct->prolang == pltsql_validator_oid)) + isComposite = true; + + ReleaseSysCache(tuple); + + } + return isComposite; +} diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 05158246fba..c0b2e0a8067 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -59,6 +59,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/optimizer.h" +#include "parser/parser.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" #include "parser/parse_expr.h" @@ -75,6 +76,8 @@ #include "utils/syscache.h" +bool enable_domain_typmod = false; + /* result structure for get_rels_with_domain() */ typedef struct { @@ -626,7 +629,8 @@ RemoveTypeById(Oid typeOid) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for type %u", typeOid); - CatalogTupleDelete(relation, &tup->t_self); + if (!ENRdropTuple(relation, tup)) + CatalogTupleDelete(relation, &tup->t_self); /* * If it is an enum, delete the pg_enum entries too; we don't bother with @@ -667,6 +671,8 @@ DefineDomain(CreateDomainStmt *stmt) Oid receiveProcedure; Oid sendProcedure; Oid analyzeProcedure; + Oid typmodinOid = InvalidOid; + Oid typmodoutOid = InvalidOid; bool byValue; char category; char delimiter; @@ -725,6 +731,8 @@ DefineDomain(CreateDomainStmt *stmt) typeTup = typenameType(NULL, stmt->typeName, &basetypeMod); baseType = (Form_pg_type) GETSTRUCT(typeTup); basetypeoid = baseType->oid; + if (sql_dialect == SQL_DIALECT_TSQL && check_or_set_default_typmod_hook) + (*check_or_set_default_typmod_hook)(stmt->typeName, &basetypeMod, false); /* * Base type must be a plain base type, a composite type, another domain, @@ -797,6 +805,13 @@ DefineDomain(CreateDomainStmt *stmt) /* Domains never accept typmods, so no typmodin/typmodout needed */ + /* Only allow typmods if GUC is set, used by babelfishpg_tsql extension */ + if (enable_domain_typmod) + { + typmodinOid = baseType->typmodin; + typmodoutOid = baseType->typmodout; + } + /* Analysis function */ analyzeProcedure = baseType->typanalyze; @@ -992,8 +1007,8 @@ DefineDomain(CreateDomainStmt *stmt) outputProcedure, /* output procedure */ receiveProcedure, /* receive procedure */ sendProcedure, /* send procedure */ - InvalidOid, /* typmodin procedure - none */ - InvalidOid, /* typmodout procedure - none */ + typmodinOid, /* typmodin procedure */ + typmodoutOid, /* typmodout procedure */ analyzeProcedure, /* analyze procedure */ InvalidOid, /* no array element type */ false, /* this isn't an array */ @@ -1032,8 +1047,8 @@ DefineDomain(CreateDomainStmt *stmt) F_ARRAY_OUT, /* output procedure */ F_ARRAY_RECV, /* receive procedure */ F_ARRAY_SEND, /* send procedure */ - InvalidOid, /* typmodin procedure - none */ - InvalidOid, /* typmodout procedure - none */ + typmodinOid, /* typmodin procedure */ + typmodoutOid, /* typmodout procedure */ F_ARRAY_TYPANALYZE, /* analyze procedure */ address.objectId, /* element type ID */ true, /* yes this is an array type */ diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index c5d1c924322..f58d0265ecb 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -43,12 +43,15 @@ #include "access/xact.h" #include "catalog/catalog.h" #include "commands/trigger.h" +#include "commands/defrem.h" #include "executor/execPartition.h" #include "executor/executor.h" #include "executor/nodeModifyTable.h" +#include "executor/tstoreReceiver.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "parser/parser.h" #include "rewrite/rewriteHandler.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" @@ -428,6 +431,47 @@ ExecInsert(ModifyTableState *mtstate, return NULL; /* "do nothing" */ } + /* Process RETURNING if present */ + if (returningRelInfo->ri_projectReturning && sql_dialect == SQL_DIALECT_TSQL) + { + /* + * In a cross-partition UPDATE with RETURNING, we have to use the + * source partition's RETURNING list, because that matches the output + * of the planSlot, while the destination partition might have + * different resjunk columns. This means we have to map the + * destination tuple back to the source's format so we can apply that + * RETURNING list. This is expensive, but it should be an uncommon + * corner case, so we won't spend much effort on making it fast. + * + * We assume that we can use srcSlot to hold the re-converted tuple. + * Note that in the common case where the child partitions both match + * the root's format, previous optimizations will have resulted in + * slot and srcSlot being identical, cueing us that there's nothing to + * do here. + */ + if (returningRelInfo != resultRelInfo && slot != srcSlot) + { + Relation srcRelationDesc = returningRelInfo->ri_RelationDesc; + AttrMap *map; + + map = build_attrmap_by_name_if_req(RelationGetDescr(resultRelationDesc), + RelationGetDescr(srcRelationDesc)); + if (map) + { + TupleTableSlot *origSlot = slot; + + slot = execute_attr_map_slot(map, slot, srcSlot); + slot->tts_tid = origSlot->tts_tid; + slot->tts_tableOid = origSlot->tts_tableOid; + pfree(map); + } + } + + result = ExecProcessReturning(returningRelInfo->ri_projectReturning, + RelationGetRelid(resultRelationDesc), + slot, planSlot); + } + /* INSTEAD OF ROW INSERT Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_instead_row) @@ -701,7 +745,7 @@ ExecInsert(ModifyTableState *mtstate, ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate); /* Process RETURNING if present */ - if (returningRelInfo->ri_projectReturning) + if (returningRelInfo->ri_projectReturning && sql_dialect != SQL_DIALECT_TSQL) { /* * In a cross-partition UPDATE with RETURNING, we have to use the @@ -725,6 +769,7 @@ ExecInsert(ModifyTableState *mtstate, map = build_attrmap_by_name_if_req(RelationGetDescr(resultRelationDesc), RelationGetDescr(srcRelationDesc)); + if (map) { TupleTableSlot *origSlot = slot; @@ -785,6 +830,7 @@ ExecDelete(ModifyTableState *mtstate, TM_FailureData tmfd; TupleTableSlot *slot = NULL; TransitionCaptureState *ar_delete_trig_tcs; + TupleTableSlot *rslot_output = NULL; if (tupleDeleted) *tupleDeleted = false; @@ -808,6 +854,45 @@ ExecDelete(ModifyTableState *mtstate, return NULL; } + /* Process RETURNING if present and if requested */ + if (processReturning && resultRelInfo->ri_projectReturning && sql_dialect == SQL_DIALECT_TSQL) + { + /* + * We have to put the target tuple into a slot, which means first we + * gotta fetch it. We can use the trigger tuple slot. + */ + if (resultRelInfo->ri_FdwRoutine) + { + /* FDW must have provided a slot containing the deleted row */ + Assert(!TupIsNull(slot)); + } + else + { + slot = ExecGetReturningSlot(estate, resultRelInfo); + if (oldtuple != NULL) + { + ExecForceStoreHeapTuple(oldtuple, slot, false); + } + else + { + if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, + SnapshotAny, slot)) + elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); + } + } + rslot_output = ExecProcessReturning(resultRelInfo->ri_projectReturning, + RelationGetRelid(resultRelationDesc), + slot, planSlot); + + /* + * Before releasing the target tuple again, make sure rslot has a + * local copy of any pass-by-reference values. + */ + ExecMaterializeSlot(rslot_output); + + ExecClearTuple(slot); + } + /* INSTEAD OF ROW DELETE Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_delete_instead_row) @@ -1060,7 +1145,7 @@ ldelete:; ar_delete_trig_tcs); /* Process RETURNING if present and if requested */ - if (processReturning && resultRelInfo->ri_projectReturning) + if (processReturning && resultRelInfo->ri_projectReturning && sql_dialect != SQL_DIALECT_TSQL) { /* * We have to put the target tuple into a slot, which means first we @@ -1103,6 +1188,9 @@ ldelete:; return rslot; } + if (processReturning && resultRelInfo->ri_projectReturning && rslot_output) + return rslot_output; + return NULL; } @@ -1144,6 +1232,7 @@ ExecUpdate(ModifyTableState *mtstate, TM_FailureData tmfd; List *recheckIndexes = NIL; TupleConversionMap *saved_tcs_map = NULL; + TupleTableSlot *rslot = NULL; /* * abort the operation if not running transactions @@ -1168,6 +1257,12 @@ ExecUpdate(ModifyTableState *mtstate, return NULL; /* "do nothing" */ } + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning && sql_dialect == SQL_DIALECT_TSQL) + rslot = ExecProcessReturning(resultRelInfo->ri_projectReturning, + RelationGetRelid(resultRelationDesc), + slot, planSlot); + /* INSTEAD OF ROW UPDATE Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_instead_row) @@ -1575,11 +1670,14 @@ lreplace:; ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate); /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - return ExecProcessReturning(resultRelInfo->ri_projectReturning, + if (resultRelInfo->ri_projectReturning && sql_dialect != SQL_DIALECT_TSQL) + rslot = ExecProcessReturning(resultRelInfo->ri_projectReturning, RelationGetRelid(resultRelationDesc), slot, planSlot); + if (resultRelInfo->ri_projectReturning) + return rslot; + return NULL; } @@ -1841,6 +1939,46 @@ fireBSTriggers(ModifyTableState *node) } } +/* + * Process INSTEAD OF EACH STATEMENT triggers + */ +static IOTState +fireISTriggers(ModifyTableState *node) +{ + ModifyTable *plan = (ModifyTable *) node->ps.plan; + ResultRelInfo *resultRelInfo = node->resultRelInfo; + IOTState ret =IOT_UNKNOWN; + + /* + * If the node modifies a partitioned table, we must fire its triggers. + * Note that in that case, node->resultRelInfo points to the first leaf + * partition, not the root table. + */ + if (node->rootResultRelInfo != NULL) + resultRelInfo = node->rootResultRelInfo; + + switch (node->operation) + { + case CMD_INSERT: + ret = ExecISInsertTriggers(node->ps.state, resultRelInfo); + if (plan->onConflictAction == ONCONFLICT_UPDATE) + ret = ExecISUpdateTriggers(node->ps.state, + resultRelInfo); + break; + case CMD_UPDATE: + ret = ExecISUpdateTriggers(node->ps.state, resultRelInfo); + break; + case CMD_DELETE: + ret = ExecISDeleteTriggers(node->ps.state, resultRelInfo); + break; + default: + elog(ERROR, "unknown operation"); + break; + } + return ret; +} + + /* * Return the target rel ResultRelInfo. * @@ -2102,6 +2240,11 @@ ExecModifyTable(PlanState *pstate) ItemPointerData tuple_ctid; HeapTupleData oldtupdata; HeapTuple oldtuple; + /* for INSERT ... EXECUTE */ + bool tsql_insert_exec = node->callStmt != NULL; + Tuplestorestate *tss; + TupleDesc tupdesc; + DestReceiver *dest = NULL; CHECK_FOR_INTERRUPTS(); @@ -2126,6 +2269,23 @@ ExecModifyTable(PlanState *pstate) if (node->mt_done) return NULL; + /* Try to see if IOT exists on the action. fireISTriggers() should return + * IOT_NOT_REQUIRED if there does not exist on the relation and action. + * Otherwise, this should fire the IOT or recognize the IOT has already been + * fired. + */ + if (node->fireISTriggers == IOT_NOT_FIRED) + { + node->fireISTriggers = fireISTriggers(node); + } + + /* If IOT is already fired, bail out */ + if (node->fireISTriggers == IOT_FIRED) + { + node->mt_done = true; + return NULL; + } + /* * On first call, fire BEFORE STATEMENT triggers before proceeding. */ @@ -2151,6 +2311,36 @@ ExecModifyTable(PlanState *pstate) estate->es_result_relation_info = resultRelInfo; + /* + * If we are here for INSERT ... EXECUTE, create a TuplestoreDestReceiver + * and pass it to the procedure execution. The procedure execution will send + * its result sets to the tuplestore via the receiver function. + */ + if (tsql_insert_exec) + { + tss = tuplestore_begin_heap(false, false, work_mem); + tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); + dest = CreateTuplestoreDestReceiver(); + SetTuplestoreDestReceiverParams(dest, tss, CurrentMemoryContext, false); + dest->rStartup(dest, -1, tupdesc); + + switch (nodeTag(node->callStmt)) + { + case T_CallStmt: + ExecuteCallStmt((CallStmt *)node->callStmt, + pstate->state->es_param_list_info, false, dest); + break; + case T_DoStmt: + ExecuteDoStmtInsertExec((DoStmt *)node->callStmt, false, dest); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("Unrecognized stmt in INSERT EXEC"))); + break; + } + } + /* * Fetch rows from subplan(s), and execute the required table modification * for each row. @@ -2173,7 +2363,13 @@ ExecModifyTable(PlanState *pstate) if (pstate->ps_ExprContext) ResetExprContext(pstate->ps_ExprContext); - planSlot = ExecProcNode(subplanstate); + if (tsql_insert_exec) + { + planSlot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsMinimalTuple); + tuplestore_gettupleslot(tss, true, false, planSlot); + } + else + planSlot = ExecProcNode(subplanstate); if (TupIsNull(planSlot)) { @@ -2340,6 +2536,9 @@ ExecModifyTable(PlanState *pstate) break; } + if(tsql_insert_exec) + ExecDropSingleTupleTableSlot(planSlot); + /* * If we got a RETURNING result, return it to caller. We'll continue * the work on next call. @@ -2351,6 +2550,12 @@ ExecModifyTable(PlanState *pstate) } } + if (tsql_insert_exec && dest) + { + dest->rShutdown(dest); + dest->rDestroy(dest); + } + /* Restore es_result_relation_info before exiting */ estate->es_result_relation_info = saved_resultRelInfo; @@ -2401,6 +2606,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex; mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); + mtstate->callStmt = node->callStmt; + /* If modifying a partitioned table, initialize the root table info */ if (node->rootResultRelIndex >= 0) mtstate->rootResultRelInfo = estate->es_root_result_relations + @@ -2412,6 +2619,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* set up epqstate with dummy subplan data for the moment */ EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam); mtstate->fireBSTriggers = true; + mtstate->fireISTriggers = IOT_NOT_FIRED; /* * call ExecInitNode on each of the plans to be executed and save the diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 97b1a635273..802a8206810 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -24,6 +24,7 @@ #include "executor/executor.h" #include "executor/spi_priv.h" #include "miscadmin.h" +#include "parser/parser.h" #include "tcop/pquery.h" #include "tcop/utility.h" #include "utils/builtins.h" @@ -361,6 +362,17 @@ SPICleanup(void) void AtEOXact_SPI(bool isCommit) { + if (!isCommit) + { + while (_SPI_current && !_SPI_current->internal_xact) + { + _SPI_connected--; + if (_SPI_connected < 0) + _SPI_current = NULL; + else + _SPI_current = &(_SPI_stack[_SPI_connected]); + } + } /* Do nothing if the transaction end was initiated by SPI. */ if (_SPI_current && _SPI_current->internal_xact) return; @@ -1579,6 +1591,21 @@ SPI_scroll_cursor_move(Portal portal, FetchDirection direction, long count) } +/* + * SPI_scroll_cursor_fetch_dest() + * + * Fetch rows in a scrollable cursor + */ +void +SPI_scroll_cursor_fetch_dest(Portal portal, FetchDirection direction, long count, + DestReceiver *receiver) +{ + _SPI_cursor_operation(portal, + direction, count, receiver); + /* we know that the DestSPI receiver doesn't need a destroy call */ +} + + /* * SPI_cursor_close() * @@ -2269,8 +2296,14 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, goto fail; } } - else if (IsA(stmt->utilityStmt, TransactionStmt)) + else if (IsA(stmt->utilityStmt, TransactionStmt) && + sql_dialect != SQL_DIALECT_TSQL) { + /* + * Allow transaction only for TSQL mode + * This code path is used by do block + * and procedure execution + */ my_res = SPI_ERROR_TRANSACTION; goto fail; } @@ -2296,8 +2329,13 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, dest = CreateDestReceiver(canSetTag ? DestSPI : DestNone); - if (stmt->utilityStmt == NULL) + if (stmt->utilityStmt == NULL || stmt->commandType == CMD_INSERT) { + /* + * INSERT ... EXECUTE stmt can also have a utilityStmt attached, + * and here we should treat it like an INSERT stmt, and let it + * handle the EXECUTE during its execution. + */ QueryDesc *qdesc; Snapshot snap; @@ -2896,8 +2934,12 @@ SPI_register_relation(EphemeralNamedRelation enr) res = SPI_ERROR_REL_DUPLICATE; else { - if (_SPI_current->queryEnv == NULL) - _SPI_current->queryEnv = create_queryEnv(); + if (_SPI_current->queryEnv == NULL) { + if (sql_dialect == SQL_DIALECT_TSQL) + _SPI_current->queryEnv = currentQueryEnv; + else + _SPI_current->queryEnv = create_queryEnv(); + } register_ENR(_SPI_current->queryEnv, enr); res = SPI_OK_REL_REGISTER; @@ -2987,3 +3029,12 @@ SPI_register_trigger_data(TriggerData *tdata) return SPI_OK_TD_REGISTER; } + +/* + * Enable internal transaction mode for whole connection duration + */ +void +SPI_setCurrentInternalTxnMode(bool mode) +{ + _SPI_current->internal_xact = mode; +} diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index ee2cd86866d..b320254b2d3 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -90,6 +90,7 @@ #include "libpq/libpq.h" #include "miscadmin.h" #include "port/pg_bswap.h" +#include "postmaster/protocol_extension.h" #include "storage/ipc.h" #include "utils/guc.h" #include "utils/memutils.h" @@ -122,6 +123,12 @@ char *Unix_socket_group; /* Where the Unix socket files are (list of palloc'd strings) */ static List *sock_paths = NIL; +/* + * IsNonLibpqClient allows loadable protocol extension to use + * pq_putbytes() directly. + */ +bool IsNonLibpqClient = false; + /* * Buffers for low-level I/O. * @@ -339,7 +346,6 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber, struct addrinfo *addrs = NULL, *addr; struct addrinfo hint; - int listen_index = 0; int added = 0; #ifdef HAVE_UNIX_SOCKETS @@ -410,18 +416,8 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber, } /* See if there is still room to add 1 more socket. */ - for (; listen_index < MaxListen; listen_index++) - { - if (ListenSocket[listen_index] == PGINVALID_SOCKET) - break; - } - if (listen_index >= MaxListen) - { - ereport(LOG, - (errmsg("could not bind to all requested addresses: MAXLISTEN (%d) exceeded", - MaxListen))); + if (!listen_have_free_slot()) break; - } /* set up address family name for log messages */ switch (addr->ai_family) @@ -587,7 +583,7 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber, (errmsg("listening on %s address \"%s\", port %d", familyDesc, addrDesc, (int) portNumber))); - ListenSocket[listen_index] = fd; + listen_add_socket(fd, NULL); added++; } @@ -983,7 +979,7 @@ pq_recvbuf(void) int pq_getbyte(void) { - Assert(PqCommReadingMsg); + Assert(PqCommReadingMsg || IsNonLibpqClient); while (PqRecvPointer >= PqRecvLength) { @@ -1002,7 +998,7 @@ pq_getbyte(void) int pq_peekbyte(void) { - Assert(PqCommReadingMsg); + Assert(PqCommReadingMsg || IsNonLibpqClient); while (PqRecvPointer >= PqRecvLength) { @@ -1025,7 +1021,7 @@ pq_getbyte_if_available(unsigned char *c) { int r; - Assert(PqCommReadingMsg); + Assert(PqCommReadingMsg || IsNonLibpqClient); if (PqRecvPointer < PqRecvLength) { @@ -1079,7 +1075,7 @@ pq_getbytes(char *s, size_t len) { size_t amount; - Assert(PqCommReadingMsg); + Assert(PqCommReadingMsg || IsNonLibpqClient); while (len > 0) { @@ -1113,7 +1109,7 @@ pq_discardbytes(size_t len) { size_t amount; - Assert(PqCommReadingMsg); + Assert(PqCommReadingMsg || IsNonLibpqClient); while (len > 0) { @@ -1151,7 +1147,7 @@ pq_getstring(StringInfo s) { int i; - Assert(PqCommReadingMsg); + Assert(PqCommReadingMsg || IsNonLibpqClient); resetStringInfo(s); @@ -1184,6 +1180,53 @@ pq_getstring(StringInfo s) } +/* -------------------------------- + * pq_getline - get a newline terminated string from connection + * + * The return value is placed in an expansible StringInfo, which has + * already been initialized by the caller. + * + * returns 0 if OK, EOF if trouble + * -------------------------------- + */ +int +pq_getline(StringInfo s) +{ + int i; + + Assert(PqCommReadingMsg || IsNonLibpqClient); + + resetStringInfo(s); + + /* Read until we get the terminating '\0' */ + for (;;) + { + while (PqRecvPointer >= PqRecvLength) + { + if (pq_recvbuf()) /* If nothing in buffer, then recv some */ + return EOF; /* Failed to recv data */ + } + + for (i = PqRecvPointer; i < PqRecvLength; i++) + { + if (PqRecvBuffer[i] == '\n') + { + /* include the '\n' in the copy */ + appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer, + i - PqRecvPointer + 1); + PqRecvPointer = i + 1; /* advance past \n */ + return 0; + } + } + + /* If we're here we haven't got the \0 in the buffer yet. */ + appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer, + PqRecvLength - PqRecvPointer); + PqRecvPointer = PqRecvLength; + } +} + + /* -------------------------------- * pq_startmsgread - begin reading a message from the client. * @@ -1217,7 +1260,7 @@ pq_startmsgread(void) void pq_endmsgread(void) { - Assert(PqCommReadingMsg); + Assert(PqCommReadingMsg || IsNonLibpqClient); PqCommReadingMsg = false; } @@ -1257,7 +1300,7 @@ pq_getmessage(StringInfo s, int maxlen) { int32 len; - Assert(PqCommReadingMsg); + Assert(PqCommReadingMsg || IsNonLibpqClient); resetStringInfo(s); @@ -1338,8 +1381,8 @@ pq_putbytes(const char *s, size_t len) { int res; - /* Should only be called by old-style COPY OUT */ - Assert(DoingCopyOut); + /* Should only be called by old-style COPY OUT or non-libpq wire protocol */ + Assert(DoingCopyOut || IsNonLibpqClient); /* No-op if reentrant call */ if (PqCommBusy) return 0; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 256ab540038..671063668a5 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -222,6 +222,7 @@ _copyModifyTable(const ModifyTable *from) COPY_NODE_FIELD(onConflictWhere); COPY_SCALAR_FIELD(exclRelRTI); COPY_NODE_FIELD(exclRelTlist); + COPY_NODE_FIELD(callStmt); return newnode; } @@ -2659,6 +2660,7 @@ _copyAConst(const A_Const *from) case T_Float: case T_String: case T_BitString: + case T_TSQL_HexString: COPY_STRING_FIELD(val.val.str); break; case T_Null: @@ -2745,6 +2747,7 @@ _copyResTarget(const ResTarget *from) COPY_NODE_FIELD(indirection); COPY_NODE_FIELD(val); COPY_LOCATION_FIELD(location); + COPY_LOCATION_FIELD(name_location); return newnode; } @@ -3119,6 +3122,7 @@ _copyInsertStmt(const InsertStmt *from) COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); COPY_SCALAR_FIELD(override); + COPY_NODE_FIELD(execStmt); return newnode; } @@ -3346,6 +3350,10 @@ _copyCallStmt(const CallStmt *from) COPY_NODE_FIELD(funccall); COPY_NODE_FIELD(funcexpr); + COPY_SCALAR_FIELD(relation); + COPY_NODE_FIELD(attrnos); + COPY_SCALAR_FIELD(retdesc); + COPY_SCALAR_FIELD(dest); return newnode; } @@ -3400,6 +3408,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode) COPY_STRING_FIELD(tablespacename); COPY_STRING_FIELD(accessMethod); COPY_SCALAR_FIELD(if_not_exists); + COPY_SCALAR_FIELD(tsql_tabletype); } static CreateStmt * @@ -3609,6 +3618,8 @@ _copyDoStmt(const DoStmt *from) DoStmt *newnode = makeNode(DoStmt); COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(relation); + COPY_NODE_FIELD(attrnos); return newnode; } @@ -4465,6 +4476,7 @@ _copyCreateSchemaStmt(const CreateSchemaStmt *from) COPY_NODE_FIELD(authrole); COPY_NODE_FIELD(schemaElts); COPY_SCALAR_FIELD(if_not_exists); + COPY_LOCATION_FIELD(location); return newnode; } @@ -4782,6 +4794,7 @@ _copyValue(const Value *from) case T_Float: case T_String: case T_BitString: + case T_TSQL_HexString: COPY_STRING_FIELD(val.str); break; case T_Null: @@ -5176,6 +5189,7 @@ copyObjectImpl(const void *from) case T_Float: case T_String: case T_BitString: + case T_TSQL_HexString: case T_Null: retval = _copyValue(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 8dc50aea811..38c28033851 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2148,6 +2148,7 @@ _equalCreateSchemaStmt(const CreateSchemaStmt *a, const CreateSchemaStmt *b) COMPARE_NODE_FIELD(authrole); COMPARE_NODE_FIELD(schemaElts); COMPARE_SCALAR_FIELD(if_not_exists); + COMPARE_LOCATION_FIELD(location); return true; } @@ -2430,6 +2431,7 @@ _equalResTarget(const ResTarget *a, const ResTarget *b) COMPARE_NODE_FIELD(indirection); COMPARE_NODE_FIELD(val); COMPARE_LOCATION_FIELD(location); + COMPARE_LOCATION_FIELD(name_location); return true; } @@ -3013,6 +3015,7 @@ _equalValue(const Value *a, const Value *b) case T_Float: case T_String: case T_BitString: + case T_TSQL_HexString: COMPARE_STRING_FIELD(val.str); break; case T_Null: @@ -3244,6 +3247,7 @@ equal(const void *a, const void *b) case T_Float: case T_String: case T_BitString: + case T_TSQL_HexString: case T_Null: retval = _equalValue(a, b); break; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 1dc873ed255..4d839976a3c 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3488,6 +3488,7 @@ raw_expression_tree_walker(Node *node, case T_Float: case T_String: case T_BitString: + case T_TSQL_HexString: case T_Null: case T_ParamRef: case T_A_Const: diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 1af3bf47879..8f2a98faa3f 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -3311,6 +3311,10 @@ _outValue(StringInfo str, const Value *value) /* internal representation already has leading 'b' */ appendStringInfoString(str, value->val.str); break; + case T_TSQL_HexString: + /* internal representation already has leading '0x' */ + appendStringInfoString(str, value->val.str); + break; case T_Null: /* this is seen only within A_Const, not in transformed trees */ appendStringInfoString(str, "NULL"); @@ -3406,6 +3410,7 @@ _outResTarget(StringInfo str, const ResTarget *node) WRITE_NODE_FIELD(indirection); WRITE_NODE_FIELD(val); WRITE_LOCATION_FIELD(location); + WRITE_LOCATION_FIELD(name_location); } static void @@ -3705,7 +3710,8 @@ outNode(StringInfo str, const void *obj) else if (IsA(obj, Integer) || IsA(obj, Float) || IsA(obj, String) || - IsA(obj, BitString)) + IsA(obj, BitString) || + IsA(obj, TSQL_HexString)) { /* nodeRead does not want to see { } around these! */ _outValue(str, obj); diff --git a/src/backend/nodes/read.c b/src/backend/nodes/read.c index 8c1e39044c8..f4b8b510b48 100644 --- a/src/backend/nodes/read.c +++ b/src/backend/nodes/read.c @@ -287,6 +287,8 @@ nodeTokenType(const char *token, int length) retval = T_String; else if (*token == 'b') retval = T_BitString; + else if (*token == '0' && (token[1] == 'x' || token[1] == 'X')) + retval = T_TSQL_HexString; else retval = OTHER_TOKEN; return retval; @@ -452,6 +454,16 @@ nodeRead(const char *token, int tok_len) result = (Node *) makeBitString(val); break; } + case T_TSQL_HexString: + { + char *val = palloc(tok_len); + + /* skip leading '0x' */ + memcpy(val, token + 2, tok_len - 2); + val[tok_len - 2] = '\0'; + result = (Node *) makeTSQLHexString(val); + break; + } default: elog(ERROR, "unrecognized node type: %d", (int) type); result = NULL; /* keep compiler happy */ diff --git a/src/backend/nodes/value.c b/src/backend/nodes/value.c index 45b9b8473e0..a489208fadf 100644 --- a/src/backend/nodes/value.c +++ b/src/backend/nodes/value.c @@ -73,3 +73,18 @@ makeBitString(char *str) v->val.str = str; return v; } + +/* + * makeTSQLHexString + * + * Caller is responsible for passing a palloc'd string. + */ +Value * +makeTSQLHexString(char *str) +{ + Value *v = makeNode(Value); + + v->type = T_TSQL_HexString; + v->val.str = str; + return v; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index a3556a44d6f..5960a6d86e3 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -72,7 +72,8 @@ bool parallel_leader_participation = true; /* Hook for plugins to get control in planner() */ planner_hook_type planner_hook = NULL; - +/* Hook for plugins to transform qual nodes in planner */ +planner_node_transformer_hook_type planner_node_transformer_hook = NULL; /* Hook for plugins to get control when grouping_planner() plans upper rels */ create_upper_paths_hook_type create_upper_paths_hook = NULL; @@ -410,6 +411,12 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, best_path = get_cheapest_fractional_path(final_rel, tuple_fraction); top_plan = create_plan(root, best_path); + /* + * For INSERT ... EXECUTE, add the utilityStmt (if any) from the Query to + * the plan node. + */ + if (nodeTag(top_plan) == T_ModifyTable) + ((ModifyTable *)top_plan)->callStmt = parse->utilityStmt; /* * If creating a plan for a scrollable cursor, make sure it can run @@ -794,6 +801,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse, preprocess_qual_conditions(root, (Node *) parse->jointree); + if(planner_node_transformer_hook) + parse->havingQual = planner_node_transformer_hook(root, parse->havingQual, EXPRKIND_QUAL); + parse->havingQual = preprocess_expression(root, parse->havingQual, EXPRKIND_QUAL); @@ -1072,6 +1082,9 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) kind == EXPRKIND_TABLEFUNC)) expr = flatten_join_alias_vars(root->parse, expr); + if(EXPRKIND_TARGET == kind && planner_node_transformer_hook) + expr = planner_node_transformer_hook(root, expr, kind); + /* * Simplify constant expressions. For function RTEs, this was already * done by preprocess_function_rtes ... but we have to do it again if the @@ -1153,6 +1166,9 @@ preprocess_qual_conditions(PlannerInfo *root, Node *jtnode) foreach(l, f->fromlist) preprocess_qual_conditions(root, lfirst(l)); + if(planner_node_transformer_hook) + f->quals = planner_node_transformer_hook(root, f->quals, EXPRKIND_QUAL); + f->quals = preprocess_expression(root, f->quals, EXPRKIND_QUAL); } else if (IsA(jtnode, JoinExpr)) diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index d09e8ccde4e..863bd762cf9 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -50,6 +50,21 @@ /* Hook for plugins to get control at end of parse analysis */ post_parse_analyze_hook_type post_parse_analyze_hook = NULL; +/* Hook for plugins to get control with raw parse tree */ +pre_parse_analyze_hook_type pre_parse_analyze_hook = NULL; + +/* Hook to handle qualifiers in returning list for output clause */ +pre_transform_returning_hook_type pre_transform_returning_hook = NULL; + +/* Hook to read a global variable with info on output clause */ +get_output_clause_status_hook_type get_output_clause_status_hook = NULL; + +/* Hook to perform self-join transformation on UpdateStmt in output clause */ +pre_output_clause_transformation_hook_type pre_output_clause_transformation_hook = NULL; + +/* Hook for plugins to get control after an insert row transform */ +post_transform_insert_row_hook_type post_transform_insert_row_hook = NULL; + static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt); @@ -113,6 +128,9 @@ parse_analyze(RawStmt *parseTree, const char *sourceText, pstate->p_queryEnv = queryEnv; + if (pre_parse_analyze_hook) + (*pre_parse_analyze_hook) (pstate, parseTree); + query = transformTopLevelStmt(pstate, parseTree); if (post_parse_analyze_hook) @@ -143,6 +161,9 @@ parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, parse_variable_parameters(pstate, paramTypes, numParams); + if (pre_parse_analyze_hook) + (*pre_parse_analyze_hook) (pstate, parseTree); + query = transformTopLevelStmt(pstate, parseTree); /* make sure all is well with parameter types */ @@ -188,7 +209,7 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState, * from the RawStmt into the finished Query. */ Query * -transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree) +transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree) /// { Query *result; @@ -439,6 +460,9 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); + if (pre_transform_returning_hook) + (*pre_transform_returning_hook) (qry->commandType, stmt->returningList, pstate); + qry->returningList = transformReturningList(pstate, stmt->returningList); /* done building the range table and jointree */ @@ -555,6 +579,39 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos); Assert(list_length(icolumns) == list_length(attrnos)); + /* + * For INSERT ... EXECUTE, transform the CallStmt/DoStmt, and attach it to + * the Query's utilityStmt. + */ + if (stmt->execStmt) + { + switch (nodeTag(stmt->execStmt)) + { + case T_CallStmt: + { + Query *callStmtQry = transformCallStmt(pstate, (CallStmt *)stmt->execStmt); + ((CallStmt *) stmt->execStmt)->relation = pstate->p_target_relation->rd_id; + ((CallStmt *) stmt->execStmt)->attrnos = attrnos; + ((CallStmt *) stmt->execStmt)->retdesc = NULL; + ((CallStmt *) stmt->execStmt)->dest = NULL; + qry->utilityStmt = callStmtQry->utilityStmt; + } + break; + case T_DoStmt: + ((DoStmt *) stmt->execStmt)->relation = pstate->p_target_relation->rd_id; + ((DoStmt *) stmt->execStmt)->attrnos = attrnos; + qry->utilityStmt = stmt->execStmt; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("Unrecognized stmt in INSERT EXEC"), + parser_errposition(pstate, + exprLocation((Node *) stmt->execStmt)))); + break; + } + } + /* * Determine which variant of INSERT we have. */ @@ -815,6 +872,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) false); } + if (post_transform_insert_row_hook) + (*post_transform_insert_row_hook) (icolumns, exprList); + /* * Generate query's target list using the computed list of expressions. * Also, mark all the target columns as needing insert permissions. @@ -855,6 +915,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) pstate->p_namespace = NIL; addNSItemToQuery(pstate, pstate->p_target_nsitem, false, true, true); + + if (pre_transform_returning_hook) + (*pre_transform_returning_hook) (qry->commandType, stmt->returningList, pstate); + qry->returningList = transformReturningList(pstate, stmt->returningList); } @@ -1187,7 +1251,7 @@ count_rowexpr_columns(ParseState *pstate, Node *expr) * see below for the other cases. */ static Query * -transformSelectStmt(ParseState *pstate, SelectStmt *stmt) +transformSelectStmt(ParseState *pstate, SelectStmt *stmt) /// { Query *qry = makeNode(Query); Node *qual; @@ -1218,7 +1282,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) pstate->p_windowdefs = stmt->windowClause; /* process the FROM clause */ - transformFromClause(pstate, stmt->fromClause); + transformFromClause(pstate, stmt->fromClause); /// /* transform targetlist */ qry->targetList = transformTargetList(pstate, stmt->targetList, @@ -2263,11 +2327,14 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) nsitem->p_lateral_only = false; nsitem->p_lateral_ok = true; - qual = transformWhereClause(pstate, stmt->whereClause, + /* Check if self-join transformation is needed for update satatement with output clause */ + if (pre_output_clause_transformation_hook) + qual = (*pre_output_clause_transformation_hook) (pstate, stmt, qry->commandType); + else + qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); qry->returningList = transformReturningList(pstate, stmt->returningList); - /* * Now we are done with SELECT-like processing, and can get on with * transforming the target list to match the UPDATE target columns. diff --git a/src/backend/parser/check_keywords.pl b/src/backend/parser/check_keywords.pl index 702c97bba2a..89b3a04a3b1 100644 --- a/src/backend/parser/check_keywords.pl +++ b/src/backend/parser/check_keywords.pl @@ -174,27 +174,18 @@ sub error } $prevkwstring = $kwstring; - # Check that the keyword string is valid: all lower-case ASCII chars - if ($kwstring !~ /^[a-z_]+$/) + # Check that the keyword string is valid: all lower-case ASCII chars followed by digits + if ($kwstring !~ /^[a-z_]+[0-9]*$/) { error - "'$kwstring' is not a valid keyword string, must be all lower-case ASCII chars"; + "'$kwstring' is not a valid keyword string, must be all lower-case ASCII chars followed by digits"; } - # Check that the keyword name is valid: all upper-case ASCII chars - if ($kwname !~ /^[A-Z_]+$/) + # Check that the keyword name is valid: all upper-case ASCII chars followed by digits + if ($kwname !~ /^[A-Z_]+[0-9]*$/) { error - "'$kwname' is not a valid keyword name, must be all upper-case ASCII chars"; - } - - # Check that the keyword string matches keyword name - $bare_kwname = $kwname; - $bare_kwname =~ s/_P$//; - if ($bare_kwname ne uc($kwstring)) - { - error - "keyword name '$kwname' doesn't match keyword string '$kwstring'"; + "'$kwname' is not a valid keyword name, must be all upper-case ASCII chars followed by digits"; } # Check that the keyword is present in the grammar diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3adc087e3ff..5563b70274d 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -60,6 +60,7 @@ #include "parser/gramparse.h" #include "parser/parser.h" #include "parser/parse_expr.h" +#include "parser/scansup.h" /* downcase_identifier */ #include "storage/lmgr.h" #include "utils/date.h" #include "utils/datetime.h" @@ -149,6 +150,7 @@ typedef struct SelectLimit static void base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner, const char *msg); +static char *downcaseIfTsqlAndCaseInsensitive(char* str); static RawStmt *makeRawStmt(Node *stmt, int stmt_location); static void updateRawStmtEnd(RawStmt *rs, int end_location); static Node *makeColumnRef(char *colname, List *indirection, @@ -197,6 +199,15 @@ static void processCASbits(int cas_bits, int location, const char *constrType, bool *no_inherit, core_yyscan_t yyscanner); static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "gram-tsql-prologue.y.h"*/ + +#include "libpq/libpq-be.h" /* needed for MyProcPort->is_tds_client */ +#include "miscadmin.h" /* needed for MyProcPort->is_tds_client */ + +rewrite_typmod_expr_hook_type rewrite_typmod_expr_hook = NULL; +validate_numeric_typmods_hook_type validate_numeric_typmods_hook = NULL; +check_recursive_cte_hook_type check_recursive_cte_hook = NULL; %} %pure-parser @@ -542,7 +553,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type Sconst comment_text notify_payload %type RoleId opt_boolean_or_string %type var_list -%type ColId ColLabel var_name type_function_name param_name +%type ColId ColIdDef ColLabel AS_ColLabel var_name type_function_name param_name %type NonReservedWord NonReservedWord_or_Sconst %type createdb_opt_name %type var_value zone_value @@ -618,6 +629,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER %token LESS_EQUALS GREATER_EQUALS NOT_EQUALS +/* Non-keyword TSQL tokens. They need to be appear after the common ones above. */ +/* TODO: these tokens are also used in pg-tsql pl_gram.y. We may remove it once ALNTR parser is introduced */ +%token DIALECT_TSQL +%token TSQL_XCONST TSQL_LABEL + /* * If you want to make any keyword changes, update the keyword table in * src/include/parser/kwlist.h and add new keywords to the appropriate one @@ -773,6 +789,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */ %nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "gram-tsql-nonassoc-ident-tokens"*/ %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' @@ -796,6 +814,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */ %right PRESERVE STRIP_P +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "gram-tsql-decl.y"*/ + %% /* @@ -1067,36 +1088,37 @@ AlterOptRoleElem: } | IDENT { + char *ident = downcaseIfTsqlAndCaseInsensitive($1); /* * We handle identifiers that aren't parser keywords with * the following special-case codes, to avoid bloating the * size of the main parser. */ - if (strcmp($1, "superuser") == 0) + if (strcmp(ident, "superuser") == 0) $$ = makeDefElem("superuser", (Node *)makeInteger(true), @1); - else if (strcmp($1, "nosuperuser") == 0) + else if (strcmp(ident, "nosuperuser") == 0) $$ = makeDefElem("superuser", (Node *)makeInteger(false), @1); - else if (strcmp($1, "createrole") == 0) + else if (strcmp(ident, "createrole") == 0) $$ = makeDefElem("createrole", (Node *)makeInteger(true), @1); - else if (strcmp($1, "nocreaterole") == 0) + else if (strcmp(ident, "nocreaterole") == 0) $$ = makeDefElem("createrole", (Node *)makeInteger(false), @1); - else if (strcmp($1, "replication") == 0) + else if (strcmp(ident, "replication") == 0) $$ = makeDefElem("isreplication", (Node *)makeInteger(true), @1); - else if (strcmp($1, "noreplication") == 0) + else if (strcmp(ident, "noreplication") == 0) $$ = makeDefElem("isreplication", (Node *)makeInteger(false), @1); - else if (strcmp($1, "createdb") == 0) + else if (strcmp(ident, "createdb") == 0) $$ = makeDefElem("createdb", (Node *)makeInteger(true), @1); - else if (strcmp($1, "nocreatedb") == 0) + else if (strcmp(ident, "nocreatedb") == 0) $$ = makeDefElem("createdb", (Node *)makeInteger(false), @1); - else if (strcmp($1, "login") == 0) + else if (strcmp(ident, "login") == 0) $$ = makeDefElem("canlogin", (Node *)makeInteger(true), @1); - else if (strcmp($1, "nologin") == 0) + else if (strcmp(ident, "nologin") == 0) $$ = makeDefElem("canlogin", (Node *)makeInteger(false), @1); - else if (strcmp($1, "bypassrls") == 0) + else if (strcmp(ident, "bypassrls") == 0) $$ = makeDefElem("bypassrls", (Node *)makeInteger(true), @1); - else if (strcmp($1, "nobypassrls") == 0) + else if (strcmp(ident, "nobypassrls") == 0) $$ = makeDefElem("bypassrls", (Node *)makeInteger(false), @1); - else if (strcmp($1, "noinherit") == 0) + else if (strcmp(ident, "noinherit") == 0) { /* * Note that INHERIT is a keyword, so it's handled by main parser, but @@ -1333,6 +1355,7 @@ CreateSchemaStmt: n->authrole = $5; n->schemaElts = $6; n->if_not_exists = false; + n->location = @3; $$ = (Node *)n; } | CREATE SCHEMA ColId OptSchemaEltList @@ -1343,6 +1366,7 @@ CreateSchemaStmt: n->authrole = NULL; n->schemaElts = $4; n->if_not_exists = false; + n->location = @3; $$ = (Node *)n; } | CREATE SCHEMA IF_P NOT EXISTS OptSchemaName AUTHORIZATION RoleSpec OptSchemaEltList @@ -1358,6 +1382,7 @@ CreateSchemaStmt: parser_errposition(@9))); n->schemaElts = $9; n->if_not_exists = true; + n->location = @6; $$ = (Node *)n; } | CREATE SCHEMA IF_P NOT EXISTS ColId OptSchemaEltList @@ -1373,6 +1398,7 @@ CreateSchemaStmt: parser_errposition(@7))); n->schemaElts = $7; n->if_not_exists = true; + n->location = @6; $$ = (Node *)n; } ; @@ -1628,7 +1654,7 @@ zone_value: } | IDENT { - $$ = makeStringConst($1, @1); + $$ = makeStringConst(downcaseIfTsqlAndCaseInsensitive($1), @1); } | ConstInterval Sconst opt_interval { @@ -3169,7 +3195,8 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); - $4->relpersistence = $2; + if (sql_dialect != SQL_DIALECT_TSQL || $4->relpersistence != RELPERSISTENCE_TEMP) + $4->relpersistence = $2; n->relation = $4; n->tableElts = $6; n->inhRelations = $8; @@ -3188,7 +3215,8 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); - $7->relpersistence = $2; + if (sql_dialect != SQL_DIALECT_TSQL || $7->relpersistence != RELPERSISTENCE_TEMP) + $7->relpersistence = $2; n->relation = $7; n->tableElts = $9; n->inhRelations = $11; @@ -3207,7 +3235,8 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); - $4->relpersistence = $2; + if (sql_dialect != SQL_DIALECT_TSQL || $4->relpersistence != RELPERSISTENCE_TEMP) + $4->relpersistence = $2; n->relation = $4; n->tableElts = $7; n->inhRelations = NIL; @@ -3227,7 +3256,8 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); - $7->relpersistence = $2; + if (sql_dialect != SQL_DIALECT_TSQL || $7->relpersistence != RELPERSISTENCE_TEMP) + $7->relpersistence = $2; n->relation = $7; n->tableElts = $10; n->inhRelations = NIL; @@ -3247,7 +3277,8 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' table_access_method_clause OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); - $4->relpersistence = $2; + if (sql_dialect != SQL_DIALECT_TSQL || $4->relpersistence != RELPERSISTENCE_TEMP) + $4->relpersistence = $2; n->relation = $4; n->tableElts = $8; n->inhRelations = list_make1($7); @@ -3267,7 +3298,8 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' table_access_method_clause OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); - $7->relpersistence = $2; + if (sql_dialect != SQL_DIALECT_TSQL || $7->relpersistence != RELPERSISTENCE_TEMP) + $7->relpersistence = $2; n->relation = $7; n->tableElts = $11; n->inhRelations = list_make1($10); @@ -3360,7 +3392,7 @@ TypedTableElement: | TableConstraint { $$ = $1; } ; -columnDef: ColId Typename create_generic_options ColQualList +columnDef: ColIdDef Typename create_generic_options ColQualList { ColumnDef *n = makeNode(ColumnDef); n->colname = $1; @@ -3381,7 +3413,7 @@ columnDef: ColId Typename create_generic_options ColQualList } ; -columnOptions: ColId ColQualList +columnOptions: ColIdDef ColQualList { ColumnDef *n = makeNode(ColumnDef); n->colname = $1; @@ -3399,7 +3431,7 @@ columnOptions: ColId ColQualList n->location = @1; $$ = (Node *)n; } - | ColId WITH OPTIONS ColQualList + | ColIdDef WITH OPTIONS ColQualList { ColumnDef *n = makeNode(ColumnDef); n->colname = $1; @@ -3540,6 +3572,12 @@ ColConstraintElem: n->cooked_expr = NULL; n->location = @1; + if (sql_dialect == SQL_DIALECT_TSQL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("This syntax is only valid when babelfishpg_tsql.sql_dialect is postgres"), + parser_errposition(@2))); + /* * Can't do this in the grammar because of shift/reduce * conflicts. (IDENTITY allows both ALWAYS and BY @@ -5329,9 +5367,11 @@ RowSecurityOptionalToRole: RowSecurityDefaultPermissive: AS IDENT { - if (strcmp($2, "permissive") == 0) + char *ident = downcaseIfTsqlAndCaseInsensitive($2); + + if (strcmp(ident, "permissive") == 0) $$ = true; - else if (strcmp($2, "restrictive") == 0) + else if (strcmp(ident, "restrictive") == 0) $$ = false; else ereport(ERROR, @@ -5902,7 +5942,7 @@ old_aggr_list: old_aggr_elem { $$ = list_make1($1); } */ old_aggr_elem: IDENT '=' def_arg { - $$ = makeDefElem($1, (Node *)$3, @1); + $$ = makeDefElem(downcaseIfTsqlAndCaseInsensitive($1), (Node *)$3, @1); } ; @@ -6272,6 +6312,16 @@ DropStmt: DROP drop_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->removeType = $2; n->objects = list_make1(lappend($5, makeString($3))); n->behavior = $6; + + if($2 == OBJECT_TRIGGER && sql_dialect == SQL_DIALECT_TSQL) + { + /* + * Force DROP_CASCADE in TSQL dialect so that the + * implicit trigger function also gets dropped + */ + n->behavior = DROP_CASCADE; + } + n->missing_ok = false; n->concurrent = false; $$ = (Node *) n; @@ -6282,6 +6332,16 @@ DropStmt: DROP drop_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->removeType = $2; n->objects = list_make1(lappend($7, makeString($5))); n->behavior = $8; + + if($2 == OBJECT_TRIGGER && sql_dialect == SQL_DIALECT_TSQL) + { + /* + * Force DROP_CASCADE in TSQL dialect so that the + * implicit trigger function also gets dropped + */ + n->behavior = DROP_CASCADE; + } + n->missing_ok = true; n->concurrent = false; $$ = (Node *) n; @@ -10251,7 +10311,7 @@ createdb_opt_item: * exercising every such option, at least at the syntax level. */ createdb_opt_name: - IDENT { $$ = $1; } + IDENT { $$ = downcaseIfTsqlAndCaseInsensitive($1); } | CONNECTION LIMIT { $$ = pstrdup("connection_limit"); } | ENCODING { $$ = pstrdup($1); } | LOCATION { $$ = pstrdup($1); } @@ -11085,6 +11145,7 @@ insert_column_item: { $$ = makeNode(ResTarget); $$->name = $1; + $$->name_location = @1; $$->indirection = check_indirection($2, yyscanner); $$->val = NULL; $$->location = @1; @@ -11279,6 +11340,7 @@ set_target: { $$ = makeNode(ResTarget); $$->name = $1; + $$->name_location = @1; $$->indirection = check_indirection($2, yyscanner); $$->val = NULL; /* upper production sets this */ $$->location = @1; @@ -11517,6 +11579,7 @@ simple_select: cr->location = -1; rt->name = NULL; + rt->name_location = -1; rt->indirection = NIL; rt->val = (Node *)cr; rt->location = -1; @@ -11555,6 +11618,8 @@ with_clause: $$ = makeNode(WithClause); $$->ctes = $2; $$->recursive = false; + if (sql_dialect == SQL_DIALECT_TSQL && check_recursive_cte_hook != NULL) + $$->recursive = (*check_recursive_cte_hook)($$); $$->location = @1; } | WITH_LA cte_list @@ -11562,6 +11627,8 @@ with_clause: $$ = makeNode(WithClause); $$->ctes = $2; $$->recursive = false; + if (sql_dialect == SQL_DIALECT_TSQL && check_recursive_cte_hook != NULL) + $$->recursive = (*check_recursive_cte_hook)($$); $$->location = @1; } | WITH RECURSIVE cte_list @@ -11666,12 +11733,14 @@ OptTempTableName: | TABLE qualified_name { $$ = $2; - $$->relpersistence = RELPERSISTENCE_PERMANENT; + if (sql_dialect != SQL_DIALECT_TSQL || $$->relpersistence != RELPERSISTENCE_TEMP) + $$->relpersistence = RELPERSISTENCE_PERMANENT; } | qualified_name { $$ = $1; - $$->relpersistence = RELPERSISTENCE_PERMANENT; + if (sql_dialect != SQL_DIALECT_TSQL || $$->relpersistence != RELPERSISTENCE_TEMP) + $$->relpersistence = RELPERSISTENCE_PERMANENT; } ; @@ -12645,7 +12714,7 @@ xmltable_column_option_list: xmltable_column_option_el: IDENT b_expr - { $$ = makeDefElem($1, $2, @1); } + { $$ = makeDefElem(downcaseIfTsqlAndCaseInsensitive($1), $2, @1); } | DEFAULT b_expr { $$ = makeDefElem("default", $2, @1); } | NOT NULL_P @@ -12666,6 +12735,7 @@ xml_namespace_el: { $$ = makeNode(ResTarget); $$->name = $3; + $$->name_location = @3; $$->indirection = NIL; $$->val = $1; $$->location = @1; @@ -12674,6 +12744,7 @@ xml_namespace_el: { $$ = makeNode(ResTarget); $$->name = NULL; + $$->name_location = -1; $$->indirection = NIL; $$->val = $2; $$->location = @1; @@ -12794,7 +12865,12 @@ GenericType: } ; -opt_type_modifiers: '(' expr_list ')' { $$ = $2; } +opt_type_modifiers: '(' expr_list ')' + { + $$ = $2; + if (sql_dialect == SQL_DIALECT_TSQL && rewrite_typmod_expr_hook != NULL) + $$ = (*rewrite_typmod_expr_hook)($2); + } | /* EMPTY */ { $$ = NIL; } ; @@ -12838,21 +12914,50 @@ Numeric: INT_P } | DECIMAL_P opt_type_modifiers { - $$ = SystemTypeName("numeric"); - $$->typmods = $2; - $$->location = @1; + if (sql_dialect == SQL_DIALECT_TSQL) + { + $$ = makeTypeName("decimal"); + $$->typmods = $2; + $$->location = @1; + if (validate_numeric_typmods_hook) + { + (*validate_numeric_typmods_hook)(&($$->typmods), false, yyscanner); + } + } + else + { + $$ = SystemTypeName("numeric"); + $$->typmods = $2; + $$->location = @1; + } } | DEC opt_type_modifiers { - $$ = SystemTypeName("numeric"); - $$->typmods = $2; - $$->location = @1; + if (sql_dialect == SQL_DIALECT_TSQL) + { + $$ = makeTypeName("decimal"); + $$->typmods = $2; + $$->location = @1; + if (validate_numeric_typmods_hook) + { + (*validate_numeric_typmods_hook)(&($$->typmods), false, yyscanner); + } + } + else + { + $$ = SystemTypeName("numeric"); + $$->typmods = $2; + $$->location = @1; + } } | NUMERIC opt_type_modifiers { $$ = SystemTypeName("numeric"); $$->typmods = $2; $$->location = @1; + if ((sql_dialect == SQL_DIALECT_TSQL) && + validate_numeric_typmods_hook != NULL) + (*validate_numeric_typmods_hook)(&($$->typmods), true, yyscanner); } | BOOLEAN_P { @@ -13971,7 +14076,10 @@ func_expr_common_subexpr: } | CURRENT_TIMESTAMP { - $$ = makeSQLValueFunction(SVFOP_CURRENT_TIMESTAMP, -1, @1); + if (sql_dialect != SQL_DIALECT_TSQL) + $$ = makeSQLValueFunction(SVFOP_CURRENT_TIMESTAMP, -1, @1); + else + $$ = (Node *)makeFuncCall(list_make2(makeString("sys"), makeString("getdate")), NULL, -1); /* generate "sys"."getdate" for SQL_DIALECT_TSQL */ } | CURRENT_TIMESTAMP '(' Iconst ')' { @@ -13999,7 +14107,10 @@ func_expr_common_subexpr: } | CURRENT_USER { - $$ = makeSQLValueFunction(SVFOP_CURRENT_USER, -1, @1); + if (sql_dialect == SQL_DIALECT_TSQL) + $$ = (Node *) makeFuncCall(list_make2(makeString("sys"), makeString("user_name_sysname")), NIL, @1); + else + $$ = makeSQLValueFunction(SVFOP_CURRENT_USER, -1, @1); } | SESSION_USER { @@ -14007,7 +14118,10 @@ func_expr_common_subexpr: } | USER { - $$ = makeSQLValueFunction(SVFOP_USER, -1, @1); + if (sql_dialect == SQL_DIALECT_TSQL) + $$ = (Node *) makeFuncCall(list_make2(makeString("sys"), makeString("user_name")), NIL, @1); + else + $$ = makeSQLValueFunction(SVFOP_USER, -1, @1); } | CURRENT_CATALOG { @@ -14206,6 +14320,7 @@ xml_attribute_el: a_expr AS ColLabel { $$ = makeNode(ResTarget); $$->name = $3; + $$->name_location = @3; $$->indirection = NIL; $$->val = (Node *) $1; $$->location = @1; @@ -14214,6 +14329,7 @@ xml_attribute_el: a_expr AS ColLabel { $$ = makeNode(ResTarget); $$->name = NULL; + $$->name_location = -1; $$->indirection = NIL; $$->val = (Node *) $1; $$->location = @1; @@ -14656,7 +14772,7 @@ extract_list: * - thomas 2001-04-12 */ extract_arg: - IDENT { $$ = $1; } + IDENT { $$ = downcaseIfTsqlAndCaseInsensitive($1); } | YEAR_P { $$ = "year"; } | MONTH_P { $$ = "month"; } | DAY_P { $$ = "day"; } @@ -14890,10 +15006,11 @@ target_list: | target_list ',' target_el { $$ = lappend($1, $3); } ; -target_el: a_expr AS ColLabel +target_el: a_expr AS AS_ColLabel { $$ = makeNode(ResTarget); $$->name = $3; + $$->name_location = @3; $$->indirection = NIL; $$->val = (Node *)$1; $$->location = @1; @@ -14909,7 +15026,13 @@ target_el: a_expr AS ColLabel | a_expr IDENT { $$ = makeNode(ResTarget); + + /* In TSQL we need to preserve the case of the AS clause in the outermost + * query block, at least. Target list references must be resolved case- + * insensitively when the database collation is case-insensitive. + */ $$->name = $2; + $$->name_location = @2; $$->indirection = NIL; $$->val = (Node *)$1; $$->location = @1; @@ -14918,6 +15041,7 @@ target_el: a_expr AS ColLabel { $$ = makeNode(ResTarget); $$->name = NULL; + $$->name_location = -1; $$->indirection = NIL; $$->val = (Node *)$1; $$->location = @1; @@ -14930,6 +15054,7 @@ target_el: a_expr AS ColLabel $$ = makeNode(ResTarget); $$->name = NULL; + $$->name_location = -1; $$->indirection = NIL; $$->val = (Node *)n; $$->location = @1; @@ -14959,6 +15084,9 @@ qualified_name: ColId { $$ = makeRangeVar(NULL, $1, @1); + /* TSQL temp table names */ + if (strncmp($1, "#", 1) == 0 || strncmp($1, "##", 2) == 0) + $$->relpersistence = RELPERSISTENCE_TEMP; } | ColId indirection { @@ -14968,13 +15096,25 @@ qualified_name: { case 1: $$->catalogname = NULL; - $$->schemaname = $1; - $$->relname = strVal(linitial($2)); + $$->schemaname = downcaseIfTsqlAndCaseInsensitive($1); + /* TSQL temp table names. Schema name is allowed but ignored for temp tables.*/ + if (strncmp(strVal(linitial($2)), "#", 1) == 0 || strncmp(strVal(linitial($2)), "##", 2) == 0) + { + $$->relpersistence = RELPERSISTENCE_TEMP; + $$->schemaname = NULL; + } + $$->relname = downcaseIfTsqlAndCaseInsensitive(strVal(linitial($2))); break; case 2: - $$->catalogname = $1; - $$->schemaname = strVal(linitial($2)); - $$->relname = strVal(lsecond($2)); + $$->catalogname = downcaseIfTsqlAndCaseInsensitive($1); + $$->schemaname = downcaseIfTsqlAndCaseInsensitive(strVal(linitial($2))); + /* TSQL temp table names. Schema name is allowed but ignored for temp tables.*/ + if (strncmp(strVal(lsecond($2)), "#", 1) == 0 || strncmp(strVal(lsecond($2)), "##", 2) == 0) + { + $$->relpersistence = RELPERSISTENCE_TEMP; + $$->schemaname = NULL; + } + $$->relname = downcaseIfTsqlAndCaseInsensitive(strVal(lsecond($2))); break; default: ereport(ERROR, @@ -15221,31 +15361,84 @@ role_list: RoleSpec */ /* Column identifier --- names that can be column, table, etc names. + * ColId downcases in TSQL so that identifiers are case-insensitive */ -ColId: IDENT { $$ = $1; } - | unreserved_keyword { $$ = pstrdup($1); } - | col_name_keyword { $$ = pstrdup($1); } +ColId: IDENT { $$ = downcaseIfTsqlAndCaseInsensitive($1); } + | unreserved_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + | col_name_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + ; + +/* ColIdDef passes through the case produced by the lexer, which for TSQL retains regular identifers + * in their original case. In the PostgreSQL dialect there is no difference between ColId and ColIdDef. + * + * ColIdDef is used in CREATE TABLE to preserve the original case of a column when in the TSQL dialect. + * it is also used in the COLLATE clause to preserve the case of the collation name so that COLLATE C + * and other collation names created in the Postgres dialect that use delimited identifiers can be found + * case-sensitively in TSQL, but in internally when in the TSQL dialect it also downcases the collation + * name so that it finds collation names case-insensitively in the TSQL dialect. It is not supported + * to create a collation name in Postgres that is the the same as another collation name after case + * folding. + */ +ColIdDef: IDENT + { + /* If in the TSQL dialect, the colname is still in its original case + * even for regular identifiers. Save it to the Babelfish metadata + * so that it can be used as the column alias for SELECT * + */ + /* TBD */ + + /* Now downcase the colname if in the TSQL dialect and the default + * collation is case-insensitive. + */ + $$ = downcaseIfTsqlAndCaseInsensitive($1); + } + | unreserved_keyword + { + /* If in the TSQL dialect, save the original column in the Babelfish metadata */ + /* TBD */ + + /* Now downcase the colname in the TSQL dialect if the database collation + * is case-sensitive. + */ + $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); + } + | col_name_keyword + { + /* If in the TSQL dialect, save the original column in the Babelfish metadata */ + /* TBD */ + + /* Now downcase the colname in the TSQL dialect if the database collation + * is case-sensitive. + */ + $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); + } ; /* Type/function identifier --- names that can be type or function names. */ -type_function_name: IDENT { $$ = $1; } - | unreserved_keyword { $$ = pstrdup($1); } - | type_func_name_keyword { $$ = pstrdup($1); } +type_function_name: IDENT { $$ = downcaseIfTsqlAndCaseInsensitive($1); } + | unreserved_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + | type_func_name_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } ; /* Any not-fully-reserved word --- these names can be, eg, role names. */ -NonReservedWord: IDENT { $$ = $1; } - | unreserved_keyword { $$ = pstrdup($1); } - | col_name_keyword { $$ = pstrdup($1); } - | type_func_name_keyword { $$ = pstrdup($1); } +NonReservedWord: IDENT { $$ = downcaseIfTsqlAndCaseInsensitive($1); } + | unreserved_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + | col_name_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + | type_func_name_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } ; /* Column label --- allowed labels in "AS" clauses. * This presently includes *all* Postgres keywords. */ -ColLabel: IDENT { $$ = $1; } +ColLabel: IDENT { $$ = downcaseIfTsqlAndCaseInsensitive($1); } + | unreserved_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + | col_name_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + | type_func_name_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + | reserved_keyword { $$ = downcaseIfTsqlAndCaseInsensitive(pstrdup($1)); } + ; +AS_ColLabel: IDENT { $$ = $1; } | unreserved_keyword { $$ = pstrdup($1); } | col_name_keyword { $$ = pstrdup($1); } | type_func_name_keyword { $$ = pstrdup($1); } @@ -15756,6 +15949,9 @@ reserved_keyword: | WITH ; +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "gram-tsql-rule.y"*/ + %% /* @@ -15769,6 +15965,16 @@ base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner, const char *msg) parser_yyerror(msg); } +static char * +downcaseIfTsqlAndCaseInsensitive(char* str) +{ + if (sql_dialect == SQL_DIALECT_TSQL + && pltsql_case_insensitive_identifiers) + return downcase_identifier(str, strlen(str), false, false); + else + return str; +} + static RawStmt * makeRawStmt(Node *stmt, int stmt_location) { @@ -16598,6 +16804,7 @@ makeRecursiveViewSelect(char *relname, List *aliases, Node *query) ResTarget *rt = makeNode(ResTarget); rt->name = NULL; + rt->name_location = -1; rt->indirection = NIL; rt->val = makeColumnRef(strVal(lfirst(lc)), NIL, -1, 0); rt->location = -1; @@ -16622,3 +16829,6 @@ parser_init(base_yy_extra_type *yyext) { yyext->parsetree = NIL; /* in case grammar forgets to set it */ } + +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "gram-tsql-epilogue.y.c"*/ diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index edcaf276c0a..655d7f26e7e 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -48,9 +48,11 @@ #include "utils/catcache.h" #include "utils/guc.h" #include "utils/lsyscache.h" +#include "utils/queryenvironment.h" #include "utils/rel.h" #include "utils/syscache.h" +tle_name_comparison_hook_type tle_name_comparison_hook = NULL; static int extractRemainingColumns(ParseNamespaceColumn *src_nscolumns, List *src_colnames, @@ -179,13 +181,15 @@ setTargetTable(ParseState *pstate, RangeVar *relation, bool inh, bool alsoSource, AclMode requiredPerms) { ParseNamespaceItem *nsitem; + EphemeralNamedRelationMetadata enrmd; /* * ENRs hide tables of the same name, so we need to check for them first. * In contrast, CTEs don't hide tables (for this purpose). */ if (relation->schemaname == NULL && - scanNameSpaceForENR(pstate, relation->relname)) + (enrmd = get_visible_ENR_metadata(pstate->p_queryEnv, relation->relname)) && + enrmd->enrtype != ENR_TSQL_TEMP) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("relation \"%s\" cannot be the target of a modifying statement", @@ -1950,7 +1954,10 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist, TargetEntry *tle = (TargetEntry *) lfirst(tl); if (!tle->resjunk && - strcmp(tle->resname, name) == 0) + ((!tle_name_comparison_hook && + strcmp(tle->resname, name) == 0) || + (tle_name_comparison_hook && + (*tle_name_comparison_hook)(tle->resname, name)))) { if (target_result != NULL) { diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 7e70cc8fbdc..e48173259bf 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -21,6 +21,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "parser/parser.h" /* needed for sql_dialect */ #include "parser/parse_coerce.h" #include "parser/parse_relation.h" #include "parser/parse_type.h" @@ -30,6 +31,8 @@ #include "utils/syscache.h" #include "utils/typcache.h" +find_coercion_pathway_hook_type find_coercion_pathway_hook = NULL; +determine_datatype_precedence_hook_type determine_datatype_precedence_hook = NULL; static Node *coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, @@ -442,6 +445,16 @@ coerce_type(ParseState *pstate, Node *node, } else { + int32 baseTypeMod; + + /* + * targetTypeMod is allowed for a domain only if enable_domain_typmod + * is enabled when the domain is created. Use the targetTypeMod if + * it is specified for a domain. Otherwise use -1, in which case + * the baseTypeMod of the domain will be used in coerce_to_domain(). + */ + baseTypeMod = targetTypeMod > 0 ? targetTypeMod : -1; + /* * We don't need to do a physical conversion, but we do need to * attach a RelabelType node so that the expression will be seen @@ -451,7 +464,7 @@ coerce_type(ParseState *pstate, Node *node, * that must be accounted for. If the destination is a domain * then we won't need a RelabelType node. */ - result = coerce_to_domain(node, InvalidOid, -1, targetTypeId, + result = coerce_to_domain(node, InvalidOid, baseTypeMod, targetTypeId, ccontext, cformat, location, false); if (result == node) @@ -718,6 +731,21 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, result->coercionformat = cformat; result->location = location; + /* + * tdsprotocol.c requires the correct typmod (instead of -1) for + * sys domains to be passed down. See PrepareRowDescription for deails. + * These sys domains are allowed to have typmod: + * sys.varchar, sys.nvarchar, sys.nchar, sys.datetime2, sys.smalldatetime, + * sys.varbinary, sys.binary + * + * Ideally we should be checking typeId matches one of the above types + * but there is no quick way to do it atm (typcache doesn't have the typename). + * However I don't think this change will cause PG regression since we + * only do it in tsql dialect. + */ + if (sql_dialect == SQL_DIALECT_TSQL) + result->resulttypmod = baseTypeMod; + return (Node *) result; } @@ -1346,7 +1374,8 @@ select_common_type(ParseState *pstate, List *exprs, const char *context, pcategory = ncategory; pispreferred = nispreferred; } - else if (ncategory != pcategory) + else if (ncategory != pcategory + && sql_dialect != SQL_DIALECT_TSQL) /* T-SQL allows to select common datatype between different categories */ { /* * both types in different categories? then not much hope... @@ -1376,6 +1405,22 @@ select_common_type(ParseState *pstate, List *exprs, const char *context, pcategory = ncategory; pispreferred = nispreferred; } + else if (sql_dialect == SQL_DIALECT_TSQL && + determine_datatype_precedence_hook != NULL && + !pispreferred && + can_coerce_type(1, &ptype, &ntype, COERCION_IMPLICIT) && + can_coerce_type(1, &ntype, &ptype, COERCION_IMPLICIT) && + determine_datatype_precedence_hook(ntype, ptype)) + { + /* + * T-SQL allows implicit casting on both-side. + * common datatype should be decided by datatype precedence rule. + */ + pexpr = nexpr; + ptype = ntype; + pcategory = ncategory; + pispreferred = nispreferred; + } } } @@ -1390,6 +1435,23 @@ select_common_type(ParseState *pstate, List *exprs, const char *context, * literals while they are still literals, so a decision has to be made * now. */ + if (ptype == UNKNOWNOID && sql_dialect == SQL_DIALECT_TSQL) + { + bool all_nullconst = true; + foreach(lc, exprs) + { + Node* expr = (Node *) lfirst(lc); + if (exprType(expr) != UNKNOWNOID || !IsA(expr, Const) || !((Const *) expr)->constisnull) + { + all_nullconst = false; + break; + } + } + + if (all_nullconst) + ptype = INT4OID; /* in T-SQL, it is should be decided to INT4 */ + } + if (ptype == UNKNOWNOID) ptype = TEXTOID; @@ -2664,6 +2726,20 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, *funcid = InvalidOid; + /* + * T-SQL allows more implicit casting in general. + * check if rules (defiend with "assignment" property) is supported in T-SQL + */ + if (sql_dialect == SQL_DIALECT_TSQL && + find_coercion_pathway_hook != NULL) + { + result = find_coercion_pathway_hook(sourceTypeId, targetTypeId, ccontext, funcid); + + /* Found overwritten cast rule in T-SQL. use it. */ + if (result != COERCION_PATH_NONE) + return result; + } + /* Perhaps the types are domains; if so, look at their base types */ if (OidIsValid(sourceTypeId)) sourceTypeId = getBaseType(sourceTypeId); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f69976cc8c9..618af40dd53 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -32,9 +32,11 @@ #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "parser/parse_type.h" +#include "parser/parser.h" #include "utils/builtins.h" #include "utils/date.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/timestamp.h" #include "utils/xml.h" @@ -134,7 +136,7 @@ static void emit_precedence_warnings(ParseState *pstate, Node *lchild, Node *rchild, int location); - +lookup_param_hook_type lookup_param_hook = NULL; /* * transformExpr - * Analyze and transform expressions. Type checking and type casting is @@ -513,6 +515,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) char *colname = NULL; ParseNamespaceItem *nsitem; int levels_up; + bool transformCols = false; enum { CRERR_NO_COLUMN, @@ -595,6 +598,9 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) errmsg_internal("%s", err), parser_errposition(pstate, cref->location))); + if (lookup_param_hook && (node = (*lookup_param_hook)(pstate, cref)) != NULL) + return node; + /* * Give the PreParseColumnRefHook, if any, first shot. If it returns * non-null then that's all, folks. @@ -606,6 +612,43 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) return node; } + /* + * Qualify column names with table name. This block of code will only run + * when unqialified column refs of WHERE clause in UPDATE statement are being + * transformed when OUTPUT clause is present. + */ + if (get_output_clause_status_hook && list_length(cref->fields) == 1 + && sql_dialect == SQL_DIALECT_TSQL) + transformCols = (*get_output_clause_status_hook) (); + + if (transformCols && pstate->p_target_relation) + { + Value *table_qualifier; + Node *node_col; + Node *field1 = (Node *) linitial(cref->fields); + + table_qualifier = makeString(RelationGetRelationName(pstate->p_target_relation)); + + Assert(IsA(field1, String)); + colname = strVal(field1); + + /* + * Try to identify as an unqualified column from the nsitem of the + * target table, levels_up = 0. + */ + node_col = scanNSItemForColumn(pstate, pstate->p_target_nsitem, 0, colname, + cref->location); + + /* + * Only qualify the column with target table if it belongs to the target + * table. We can avoid mis-qualifying columns of non-target tables this way. + * Since ambiguous columns are checked for already, we do not care about + * columns belonging to other tables at this point. + */ + if (node_col) + cref->fields = lcons(table_qualifier, cref->fields); + } + /*---------- * The allowed syntaxes are: * @@ -959,13 +1002,21 @@ transformAExprOp(ParseState *pstate, A_Expr *a) */ if (Transform_null_equals && list_length(a->name) == 1 && - strcmp(strVal(linitial(a->name)), "=") == 0 && + (strcmp(strVal(linitial(a->name)), "=") == 0 || + /* If we're in tsql dialect, Extend Transform_null_equals to transform + * "foo <> NULL" and "NULL <> foo" to "IS NOT NULL " */ + (sql_dialect == SQL_DIALECT_TSQL && + (strcmp(strVal(linitial(a->name)), "<>") == 0 || + strcmp(strVal(linitial(a->name)), "!=") == 0))) && (exprIsNullConstant(lexpr) || exprIsNullConstant(rexpr)) && (!IsA(lexpr, CaseTestExpr) && !IsA(rexpr, CaseTestExpr))) { NullTest *n = makeNode(NullTest); - n->nulltesttype = IS_NULL; + if(strcmp(strVal(linitial(a->name)), "=") == 0) + n->nulltesttype = IS_NULL; + else + n->nulltesttype = IS_NOT_NULL; n->location = a->location; if (exprIsNullConstant(lexpr)) @@ -2750,6 +2801,9 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) /* Look up the type name first */ typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod); + if (sql_dialect == SQL_DIALECT_TSQL && check_or_set_default_typmod_hook) + (*check_or_set_default_typmod_hook)(tc->typeName, &targetTypmod, true); + /* * Look through any AEXPR_PAREN nodes that may have been inserted thanks * to operator_precedence_warning. Otherwise, ARRAY[]::foo[] behaves diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 9c3b6ad9166..b25404b6d87 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -30,11 +30,13 @@ #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "parser/parse_type.h" +#include "parser/parser.h" /* SQL_DIALECT_TSQL */ #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" - +func_select_candidate_hook_type func_select_candidate_hook = NULL; +make_fn_arguments_from_stored_proc_probin_hook_type make_fn_arguments_from_stored_proc_probin_hook = NULL; /* Possible error codes from LookupFuncNameInternal */ typedef enum { @@ -664,7 +666,10 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, false); /* perform the necessary typecasting of arguments */ - make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types); + if(sql_dialect == SQL_DIALECT_TSQL && make_fn_arguments_from_stored_proc_probin_hook != NULL) + (*make_fn_arguments_from_stored_proc_probin_hook)(pstate, fargs, actual_arg_types, declared_arg_types, funcid); + else + make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types); /* * If the function isn't actually variadic, forget any VARIADIC decoration @@ -1141,6 +1146,21 @@ func_select_candidate(int nargs, if (ncandidates == 1) return candidates; + /* + * T-SQL allows bidirectional implcit castings (implicit downcasting with precision loss) + * This behavior may cause to find too many multiple candidates. + * If we resolve all the unknwon types but still too many candidates, + * let's try to choose the best candidate by T-SQL precedence rule. + */ + if (nunknowns == 0 && + sql_dialect == SQL_DIALECT_TSQL && + func_select_candidate_hook != NULL) + { + last_candidate = func_select_candidate_hook(nargs, input_base_typeids, candidates, false); + if (last_candidate) + return last_candidate; /* last_candiate->next should be already NULL */ + } + /* * Still too many candidates? Try assigning types for the unknown inputs. * @@ -1226,6 +1246,19 @@ func_select_candidate(int nargs, } } + /* + * unknowns are resolved now. + * let's try to choose the best candidate by T-SQL precedence rule with setting resolved_unknowns. + */ + if (resolved_unknowns && + sql_dialect == SQL_DIALECT_TSQL && + func_select_candidate_hook != NULL) + { + last_candidate = func_select_candidate_hook(nargs, input_base_typeids, candidates, true); + if (last_candidate) + return last_candidate; /* last_candiate->next should be already NULL */ + } + if (resolved_unknowns) { /* Strip non-matching candidates */ diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index 6e98fe55fc4..8abc4d32a02 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -17,9 +17,11 @@ #include "access/htup_details.h" #include "access/table.h" #include "catalog/pg_type.h" +#include "executor/spi.h" #include "mb/pg_wchar.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "parser/parser.h" // Needed for TSQLHexConstTypmod #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" @@ -27,11 +29,16 @@ #include "utils/builtins.h" #include "utils/int8.h" #include "utils/lsyscache.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/varbit.h" static void pcb_error_callback(void *arg); +static Oid lookup_varbinary_oid(); +static Oid lookup_varbinaryin_funcoid(); +static Oid varbinary_oid = InvalidOid; +static PGFunction varbinaryin_funcaddr = NULL; /* * make_parsestate @@ -179,6 +186,131 @@ pcb_error_callback(void *arg) (void) parser_errposition(pcbstate->pstate, pcbstate->location); } +/* + * Get Oid of sys.varbinary from pg_catalog.pg_type. + * Return InvalidOid if it doesn't exist. + */ +static Oid +lookup_varbinary_oid() +{ + int rc; + bool snapshot_set; + char *query; + TupleDesc tupdesc; + HeapTuple row; + bool isnull; + Oid varbinary_oid; + + /* + * Some statement type (i.e. CallStmt) does not captrue the active snapshot. + * (please see (analyze_requires_snapshot().) It may cause a crash while + * excuting a varbinary oid lookup query internally via SPI_execute(). + * If there is no active snapshot, captrue it. + */ + snapshot_set = false; + if (!ActiveSnapshotSet()) + { + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; + } + + /* Connect to the SPI manager */ + if ((rc = SPI_connect()) < 0) + elog(ERROR, "SPI_connect() failed in Parse Analyzer " + "with return code %d", rc); + + query = "SELECT T.oid FROM pg_catalog.pg_type T " + "JOIN pg_catalog.pg_namespace N ON N.oid = T.typnamespace " + "WHERE N.nspname = 'sys' AND T.typname = 'varbinary'"; + rc = SPI_execute(query, true, 0); + if (rc != SPI_OK_SELECT) + elog(ERROR, "SPI_execute() failed in Parse Analyzer " + "with return code %d", rc); + + Assert(SPI_processed <= 1); + if (SPI_processed == 1) + { + tupdesc = SPI_tuptable->tupdesc; + row = SPI_tuptable->vals[0]; + varbinary_oid = DatumGetObjectId(SPI_getbinval(row, tupdesc, 1, &isnull)); + } + else + /* sys.varbinary does not exist in pg_type catalog */ + varbinary_oid = InvalidOid; + + /* Cleanup and done */ + SPI_finish(); + + if (snapshot_set) + { + PopActiveSnapshot(); + } + + return varbinary_oid; +} + +/* + * Get Oid of sys.varbinary from pg_catalog.pg_type. + * Return InvalidOid if it doesn't exist. + */ +static Oid +lookup_varbinaryin_funcoid() +{ + int rc; + bool snapshot_set; + char *query; + TupleDesc tupdesc; + HeapTuple row; + bool isnull; + Oid func_oid; + + /* + * Some statement type (i.e. CallStmt) does not captrue the active snapshot. + * (please see (analyze_requires_snapshot().) It may cause a crash while + * excuting a varbinary oid lookup query internally via SPI_execute(). + * If there is no active snapshot, captrue it. + */ + snapshot_set = false; + if (!ActiveSnapshotSet()) + { + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; + } + + /* Connect to the SPI manager */ + if ((rc = SPI_connect()) < 0) + elog(ERROR, "SPI_connect() failed in Parse Analyzer " + "with return code %d", rc); + + query = "SELECT P.oid FROM pg_catalog.pg_proc P " + "JOIN pg_catalog.pg_namespace N ON N.oid = P.pronamespace " + "WHERE N.nspname = 'sys' AND P.proname = 'varbinaryin'"; + rc = SPI_execute(query, true, 0); + if (rc != SPI_OK_SELECT) + elog(ERROR, "SPI_execute() failed in Parse Analyzer " + "with return code %d", rc); + + Assert(SPI_processed <= 1); + if (SPI_processed == 1) + { + tupdesc = SPI_tuptable->tupdesc; + row = SPI_tuptable->vals[0]; + func_oid = DatumGetObjectId(SPI_getbinval(row, tupdesc, 1, &isnull)); + } + else + /* sys.varbinary does not exist in pg_type catalog */ + func_oid = InvalidOid; + + /* Cleanup and done */ + SPI_finish(); + + if (snapshot_set) + { + PopActiveSnapshot(); + } + + return func_oid; +} /* * transformContainerType() @@ -541,6 +673,38 @@ make_const(ParseState *pstate, Value *value, int location) typebyval = false; break; + /* Unquoted hex input such as 0x1F, process it as type sys.VARBINARY */ + case T_TSQL_HexString: + /* Lookup Oid of type sys.varbinary and the input function sys.varbinaryin */ + if (varbinary_oid == InvalidOid) + { + if ((varbinary_oid = lookup_varbinary_oid()) != InvalidOid) + { + Oid varbinaryin_funcoid = lookup_varbinaryin_funcoid(); + varbinaryin_funcaddr = + lookup_C_func_by_oid(varbinaryin_funcoid, "babelfishpg_common", "varbinaryin"); + } + } + + if (varbinary_oid == InvalidOid || varbinaryin_funcaddr == NULL) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("Type VARBINARY is not supported for input %s.", strVal(value)), + errhint("Install babelfishpg_tsql extension to support Type VARBINARY")); + + /* arrange to report location if the input function fails */ + setup_parser_errposition_callback(&pcbstate, pstate, location); + val = DirectFunctionCall3(varbinaryin_funcaddr, + CStringGetDatum(strVal(value)), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(TSQLHexConstTypmod) + ); + cancel_parser_errposition_callback(&pcbstate); + typeid = varbinary_oid; + typelen = -1; + typebyval = false; + break; + case T_Null: /* return a null const */ con = makeConst(UNKNOWNOID, diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index af43a62cb25..b7cc338164e 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -30,6 +30,8 @@ #include "parser/parse_relation.h" #include "parser/parse_type.h" #include "parser/parsetree.h" +#include "parser/parser.h" /* SQL_DIALECT_TSQL */ +#include "parser/scansup.h" /* downcase_identifier */ #include "storage/lmgr.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -772,7 +774,16 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, int result = InvalidAttrNumber; int attnum = 0; ListCell *c; + char *refname = NULL; + int reflen = 0; + if (sql_dialect == SQL_DIALECT_TSQL + && pltsql_case_insensitive_identifiers) + { + refname = downcase_identifier(colname, strlen(colname), false, false); + reflen = strlen(refname); + } + /* * Scan the user column names (or aliases) for a match. Complain if * multiple matches. @@ -786,12 +797,28 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, * Callers interested in finding match with shortest distance need to * defend against this directly, though. */ + foreach(c, rte->eref->colnames) { const char *attcolname = strVal(lfirst(c)); + bool matches; attnum++; - if (strcmp(attcolname, colname) == 0) + matches = false; + + if (sql_dialect == SQL_DIALECT_TSQL + && pltsql_case_insensitive_identifiers) + { + if (strlen(attcolname) != reflen) + continue; + + if (strcmp(downcase_identifier(attcolname, reflen, false, false), refname) == 0) + matches = true; + } + else if (strcmp(attcolname, colname) == 0) + matches = true; + + if (matches) { if (result) ereport(ERROR, @@ -2326,6 +2353,18 @@ addRangeTableEntryForENR(ParseState *pstate, { case ENR_NAMED_TUPLESTORE: rte->rtekind = RTE_NAMEDTUPLESTORE; + rte->requiredPerms = 0; + rte->inh = false; + break; + + case ENR_TSQL_TEMP: + rte->inh = rv->inh; + rte->rtekind = RTE_RELATION; + rte->rellockmode = isLockedRefname(pstate, refname) ? RowShareLock : AccessShareLock; + rte->requiredPerms = ACL_SELECT; + rte->insertedCols = NULL; + rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; break; default: @@ -2383,10 +2422,8 @@ addRangeTableEntryForENR(ParseState *pstate, * ENRs are never checked for access rights. */ rte->lateral = false; - rte->inh = false; /* never true for ENRs */ rte->inFromCl = inFromCl; - rte->requiredPerms = 0; rte->checkAsUser = InvalidOid; rte->selectedCols = NULL; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 5503f8d1c92..35547a4752f 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -20,6 +20,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "parser/parser.h" /* needed for sql_dialect */ #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" @@ -32,6 +33,12 @@ #include "utils/rel.h" #include "utils/typcache.h" +pre_transform_target_entry_hook_type pre_transform_target_entry_hook = NULL; +resolve_target_list_unknowns_hook_type resolve_target_list_unknowns_hook = NULL; + +/* These parameters are set by GUC */ +bool ansi_qualified_update_set_target; + static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle, Var *var, int levelsup); static Node *transformAssignmentIndirection(ParseState *pstate, @@ -185,6 +192,9 @@ transformTargetList(ParseState *pstate, List *targetlist, } } + if (pre_transform_target_entry_hook) + (*pre_transform_target_entry_hook)(res, pstate, exprKind); + /* * Not "something.*", or we want to treat that as a plain whole-row * variable, so transform as a single expression @@ -214,7 +224,6 @@ transformTargetList(ParseState *pstate, List *targetlist, return p_target; } - /* * transformExpressionList() * @@ -302,6 +311,9 @@ resolveTargetListUnknowns(ParseState *pstate, List *targetlist) { ListCell *l; + if (resolve_target_list_unknowns_hook) + (*resolve_target_list_unknowns_hook)(pstate, targetlist); + foreach(l, targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(l); @@ -1004,8 +1016,15 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) if (attr->attisdropped) continue; + if (sql_dialect == SQL_DIALECT_TSQL) + { + if (attr->attgenerated || attr->attidentity == ATTRIBUTE_IDENTITY_ALWAYS) + continue; + } + col = makeNode(ResTarget); col->name = pstrdup(NameStr(attr->attname)); + col->name_location = -1; col->indirection = NIL; col->val = NULL; col->location = -1; diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 2709f6f9c79..9d6fef774c4 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -16,6 +16,7 @@ #include "access/htup_details.h" #include "catalog/namespace.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "lib/stringinfo.h" #include "nodes/makefuncs.h" @@ -29,6 +30,7 @@ static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName, Type typ); +check_or_set_default_typmod_hook_type check_or_set_default_typmod_hook = NULL; /* * LookupTypeName @@ -555,8 +557,15 @@ GetColumnDefCollation(ParseState *pstate, ColumnDef *coldef, Oid typeOid) } else { - /* Use the type's default collation if any */ - result = typcollation; + if(sql_dialect == SQL_DIALECT_TSQL && OidIsValid(typcollation)) + { + result = CLUSTER_COLLATION_OID(); + } + else + { + /* Use the type's default collation if any */ + result = typcollation; + } } /* Complain if COLLATE is applied to an uncollatable type */ diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 49a898b6461..85fadfcd12d 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -71,6 +71,9 @@ #include "utils/syscache.h" #include "utils/typcache.h" +/* Hook for pltsql plugin */ +pltsql_identity_datatype_hook_type pltsql_identity_datatype_hook = NULL; +post_transform_column_definition_hook_type post_transform_column_definition_hook = NULL; /* State shared by transformCreateStmt and its subroutines */ typedef struct @@ -714,6 +717,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) true, false, NULL, NULL); + if (pltsql_identity_datatype_hook) + (* pltsql_identity_datatype_hook) (cxt->pstate, column); + column->identity = constraint->generated_when; saw_identity = true; @@ -861,6 +867,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) cxt->alist = lappend(cxt->alist, stmt); } + + if (sql_dialect == SQL_DIALECT_TSQL && post_transform_column_definition_hook) + (* post_transform_column_definition_hook) (cxt->pstate, cxt->relation, column, &cxt->alist); } /* @@ -2507,6 +2516,12 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; index->indexParams = lappend(index->indexParams, iparam); + if (nodeTag(lfirst(lc)) == T_IndexElem) { + // Babelfish support for ordered constraints + // Update index parameter from ordering information only provided from T-SQL parser + IndexElem * i = (IndexElem *) lfirst(lc); + iparam->ordering = i->ordering; + } /* * For a primary-key column, also create an item for ALTER TABLE * SET NOT NULL if we couldn't ensure it via is_not_null above. diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index be86eb37fef..44517007f7a 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -30,6 +30,7 @@ static bool check_uescapechar(unsigned char escape); static char *str_udeescape(const char *str, char escape, int position, core_yyscan_t yyscanner); +raw_parser_hook_type raw_parser_hook = NULL; /* * raw_parser @@ -45,6 +46,9 @@ raw_parser(const char *str) base_yy_extra_type yyextra; int yyresult; + if (raw_parser_hook && sql_dialect == SQL_DIALECT_TSQL) /* TODO: dialect condition should be improved */ + return (*raw_parser_hook) (str); + /* initialize the flex scanner */ yyscanner = scanner_init(str, &yyextra.core_yy_extra, &ScanKeywords, ScanKeywordTokens); diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index b1ea0cb5384..116ce142f7d 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -30,6 +30,10 @@ * *------------------------------------------------------------------------- */ + +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "scan-tsql-prologue-top.l.h"*/ + #include "postgres.h" #include @@ -142,6 +146,12 @@ static void check_escape_warning(core_yyscan_t yyscanner); extern int core_yyget_column(yyscan_t yyscanner); extern void core_yyset_column(int column_no, yyscan_t yyscanner); +/* T-SQL specific variables widely used in backend code */ +int sql_dialect = SQL_DIALECT_PG; +bool pltsql_case_insensitive_identifiers = true; + +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "scan-tsql-prologue.l.h"*/ %} %option reentrant @@ -413,8 +423,14 @@ other . * Also whitespace (comment) must appear before operator. */ +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "scan-tsql-decl.l"*/ + %% + /* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ + /*$$include "scan-tsql-rule.l"*/ + {whitespace} { /* ignore */ } @@ -887,10 +903,14 @@ other . * The idea is to lex '=-' as two operators, but not * to forbid operator names like '?-' that could not be * sequences of SQL operators. + * Also lexing '=@' as two operators to support tsql + * identifiers like @foo. */ if (nchars > 1 && (yytext[nchars - 1] == '+' || - yytext[nchars - 1] == '-')) + yytext[nchars - 1] == '-' || + (sql_dialect == SQL_DIALECT_TSQL && + yytext[nchars - 1] == '@'))) { int ic; @@ -913,7 +933,9 @@ other . nchars--; } while (nchars > 1 && (yytext[nchars - 1] == '+' || - yytext[nchars - 1] == '-')); + yytext[nchars - 1] == '-' || + (sql_dialect == SQL_DIALECT_TSQL && + yytext[nchars - 1] == '@'))); } } @@ -966,7 +988,7 @@ other . return Op; } -{param} { +{param} { SET_YYLLOC(); yylval->ival = atol(yytext + 1); return PARAM; @@ -1022,6 +1044,20 @@ other . { yylval->keyword = GetScanKeyword(kwnum, yyextra->keywordlist); + + /* test of BABEL-219 needs special handling of VARCHAR w/o installing pgtsql */ + if (yyextra->keyword_tokens[kwnum] == VARCHAR || + yyextra->keyword_tokens[kwnum] == NCHAR) + { + /* Only a keyword in postgres */ + if (sql_dialect == SQL_DIALECT_TSQL) + { + ident = downcase_truncate_identifier(yytext, yyleng, true); + yylval->str = ident; + return IDENT; + } + } + return yyextra->keyword_tokens[kwnum]; } @@ -1063,6 +1099,8 @@ other . #undef yyleng #define yyleng (((struct yyguts_t *) yyscanner)->yyleng_r) +/* Please note that the following line will be replaced with the contents of given file name even if with starting with a comment */ +/*$$include "scan-tsql-epilogue.l.c"*/ /* * scanner_errposition diff --git a/src/backend/parser/scansup.c b/src/backend/parser/scansup.c index 18169ec4f4c..edf59322637 100644 --- a/src/backend/parser/scansup.c +++ b/src/backend/parser/scansup.c @@ -19,6 +19,9 @@ #include "mb/pg_wchar.h" #include "parser/scansup.h" +#include "parser/parser.h" /* SQL_DIALECT_TSQL */ + +truncate_identifier_hook_type truncate_identifier_hook = NULL; /* ---------------- * scanstr @@ -187,6 +190,12 @@ truncate_identifier(char *ident, int len, bool warn) { if (len >= NAMEDATALEN) { + if (truncate_identifier_hook) + { + if ((*truncate_identifier_hook) (ident, len, warn)) + return; + } + len = pg_mbcliplen(ident, len, NAMEDATALEN - 1); if (warn) { diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 24e54d6766f..f5f2b9be7df 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -62,11 +62,13 @@ #include "storage/procsignal.h" #include "storage/sinvaladt.h" #include "utils/ascii.h" +#include "utils/fmgrtab.h" #include "utils/guc.h" #include "utils/memutils.h" #include "utils/ps_status.h" #include "utils/rel.h" #include "utils/snapmgr.h" +#include "utils/syscache.h" #include "utils/timestamp.h" /* ---------- @@ -361,6 +363,8 @@ static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len); static void pgstat_recv_checksum_failure(PgStat_MsgChecksumFailure *msg, int len); static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len); +pre_function_call_hook_type pre_function_call_hook = NULL; + /* ------------------------------------------------------------ * Public functions called from postmaster follow * ------------------------------------------------------------ @@ -1678,6 +1682,18 @@ pgstat_init_function_usage(FunctionCallInfo fcinfo, PgStat_BackendFunctionEntry *htabent; bool found; + if (pre_function_call_hook) + { + HeapTuple proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid)); + if (HeapTupleIsValid(proctup)) + { + Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup); + (*pre_function_call_hook)(NameStr(proc->proname)); + } + + ReleaseSysCache(proctup); + } + if (pgstat_track_functions <= fcinfo->flinfo->fn_stats) { /* stats not wanted */ @@ -2406,6 +2422,38 @@ PostPrepare_PgStat(void) pgstat_clear_snapshot(); } +/* + * Cleanup_xact_PgStat + * Clean up transaction stat. + * + * Cleanup transaction state only. Used in cases where transaction states are + * changed but we cannot wait until committing to reset those states. + */ +void +Cleanup_xact_PgStat() +{ + PgStat_SubXactStatus *xact_state; + + /* + * We don't bother to free any of the transactional state, since it's all + * in TopTransactionContext and will go away anyway. + */ + xact_state = pgStatXactStack; + if (xact_state != NULL) + { + PgStat_TableXactStatus *trans; + + for (trans = xact_state->first; trans != NULL; trans = trans->next) + { + PgStat_TableStatus *tabstat; + + tabstat = trans->parent; + tabstat->trans = NULL; + } + } + pgStatXactStack = NULL; +} + /* * 2PC processing routine for COMMIT PREPARED case. * diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 5775fc0c091..a4007b4fd3b 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -115,6 +115,7 @@ #include "postmaster/interrupt.h" #include "postmaster/pgarch.h" #include "postmaster/postmaster.h" +#include "postmaster/protocol_extension.h" #include "postmaster/syslogger.h" #include "replication/logicallauncher.h" #include "replication/walsender.h" @@ -212,10 +213,34 @@ char *ListenAddresses; */ int ReservedBackends; +/* The hook for protocol extension init functions */ +listen_init_hook_type listen_init_hook = NULL; + /* The socket(s) we're listening to. */ #define MAXLISTEN 64 static pgsocket ListenSocket[MAXLISTEN]; +/* The wire protocol callbacks to use for those server sockets. */ +static ProtocolExtensionConfig *ListenConfig[MAXLISTEN]; + +static ProtocolExtensionConfig default_protocol_config = { + libpq_accept, + libpq_close, + libpq_init, + libpq_start, + libpq_authenticate, + libpq_mainfunc, + libpq_send_message, + libpq_send_cancel_key, + libpq_comm_reset, + libpq_is_reading_msg, + libpq_send_ready_for_query, + libpq_read_command, + libpq_end_command, + NULL, NULL, NULL, NULL, /* use libpq defaults for printtup*() */ + NULL +}; + /* * Set by the -o option */ @@ -395,7 +420,7 @@ static void CloseServerPorts(int status, Datum arg); static void unlink_external_pid_file(int status, Datum arg); static void getInstallationPaths(const char *argv0); static void checkControlFile(void); -static Port *ConnCreate(int serverFd); +static Port *ConnCreate(int serverFd, ProtocolExtensionConfig *protocol_config); static void ConnFree(Port *port); static void reset_shared(void); static void SIGHUP_handler(SIGNAL_ARGS); @@ -1284,6 +1309,13 @@ PostmasterMain(int argc, char *argv[]) } #endif + /* + * call loadable protocol extension's init functions so they + * may register additional server sockets. + */ + if (listen_init_hook != NULL) + listen_init_hook(); + /* * check that we have some socket to listen on */ @@ -1419,6 +1451,91 @@ PostmasterMain(int argc, char *argv[]) abort(); /* not reached */ } +int +libpq_accept(pgsocket server_fd, Port *port) +{ + return StreamConnection(server_fd, port); +} + +void +libpq_close(pgsocket server_fd) +{ + StreamClose(server_fd); +} + +void +libpq_init(void) +{ + pq_init(); +} + +int +libpq_start(Port *port) +{ + return ProcessStartupPacket(port, false, false); +} + +void +libpq_authenticate(Port *port, const char **username) +{ + PerformAuthentication(port); +} + +void +libpq_mainfunc(Port *port, int argc, char *argv[]) +{ + PostgresMain(argc, argv, port->database_name, port->user_name); +} + +void +libpq_send_message(ErrorData *edata) +{ + send_message_to_frontend(edata); +} + +void +libpq_send_cancel_key(int pid, int32 key) +{ + StringInfoData buf; + + pq_beginmessage(&buf, 'K'); + pq_sendint32(&buf, (int32) pid); + pq_sendint32(&buf, (int32) key); + pq_endmessage(&buf); + /* Need not flush since ReadyForQuery will do it. */ + +} + +void +libpq_comm_reset(void) +{ + pq_comm_reset(); +} + +bool +libpq_is_reading_msg(void) +{ + return pq_is_reading_msg(); +} + +void +libpq_send_ready_for_query(CommandDest dest) +{ + ReadyForQuery(dest); +} + +int +libpq_read_command(StringInfo inBuf) +{ + return SocketBackendReadCommand(inBuf); +} + +void +libpq_end_command(QueryCompletion *qc, CommandDest dest) +{ + EndCommand(qc, dest, false); +} + /* * on_proc_exit callback to close server's listen sockets @@ -1438,8 +1555,9 @@ CloseServerPorts(int status, Datum arg) { if (ListenSocket[i] != PGINVALID_SOCKET) { - StreamClose(ListenSocket[i]); + (ListenConfig[i]->fn_close)(ListenSocket[i]); ListenSocket[i] = PGINVALID_SOCKET; + ListenConfig[i] = NULL; } } @@ -1518,6 +1636,47 @@ getInstallationPaths(const char *argv0) */ } +int +listen_have_free_slot(void) +{ + int listen_index = 0; + + /* See if there is still room to add 1 more socket. */ + for (; listen_index < MAXLISTEN; listen_index++) + { + if (ListenSocket[listen_index] == PGINVALID_SOCKET) + return true; + } + + ereport(LOG, + (errmsg("could not bind to all requested addresses: MAXLISTEN (%d) exceeded", + MAXLISTEN))); + + return false; +} + +void +listen_add_socket(pgsocket fd, ProtocolExtensionConfig *protocol_config) +{ + int listen_index = 0; + + /* Lookup the next free slot */ + for (; listen_index < MAXLISTEN; listen_index++) + { + if (ListenSocket[listen_index] == PGINVALID_SOCKET) + break; + } + + /* Caller must have checked with listen_have_free_slot() before */ + Assert(listen_index < MAXLISTEN); + + if (protocol_config == NULL) + protocol_config = &default_protocol_config; + + ListenSocket[listen_index] = fd; + ListenConfig[listen_index] = protocol_config; +} + /* * Check that pg_control exists in the correct location in the data directory. * @@ -1733,7 +1892,7 @@ ServerLoop(void) { Port *port; - port = ConnCreate(ListenSocket[i]); + port = ConnCreate(ListenSocket[i], ListenConfig[i]); if (port) { BackendStartup(port); @@ -2508,7 +2667,7 @@ canAcceptConnections(int backend_type) * Returns NULL on failure, other than out-of-memory which is fatal. */ static Port * -ConnCreate(int serverFd) +ConnCreate(int serverFd, ProtocolExtensionConfig *protocol_config) { Port *port; @@ -2520,10 +2679,11 @@ ConnCreate(int serverFd) ExitPostmaster(1); } - if (StreamConnection(serverFd, port) != STATUS_OK) - { + port->protocol_config = protocol_config; + + if ((protocol_config->fn_accept)(serverFd, port) != STATUS_OK) { if (port->sock != PGINVALID_SOCKET) - StreamClose(port->sock); + (protocol_config->fn_close)(port->sock); ConnFree(port); return NULL; } @@ -2582,10 +2742,11 @@ ClosePostmasterPorts(bool am_syslogger) */ for (i = 0; i < MAXLISTEN; i++) { - if (ListenSocket[i] != PGINVALID_SOCKET) + if (ListenSocket[i] != PGINVALID_SOCKET && ListenConfig[i] != NULL) { - StreamClose(ListenSocket[i]); + (ListenConfig[i]->fn_close)(ListenSocket[i]); ListenSocket[i] = PGINVALID_SOCKET; + ListenConfig[i] = NULL; } } @@ -4321,10 +4482,11 @@ BackendInitialize(Port *port) port->remote_port = ""; /* - * Initialize libpq and enable reporting of ereport errors to the client. + * Initialize protocol and enable reporting of ereport errors to the client. * Must do this now because authentication uses libpq to send messages. */ - pq_init(); /* initialize libpq to talk to client */ + (port->protocol_config->fn_init)(); + whereToSendOutput = DestRemote; /* now safe to ereport to client */ /* @@ -4428,7 +4590,7 @@ BackendInitialize(Port *port) * Receive the startup packet (which might turn out to be a cancel request * packet). */ - status = ProcessStartupPacket(port, false, false); + status = (port->protocol_config->fn_start)(port); /* * Disable the timeout, and prevent SIGTERM/SIGQUIT again. @@ -4523,7 +4685,7 @@ BackendRun(Port *port) */ MemoryContextSwitchTo(TopMemoryContext); - PostgresMain(ac, av, port->database_name, port->user_name); + (port->protocol_config->fn_mainfunc)(port, ac, av); } @@ -6509,6 +6671,93 @@ ShmemBackendArrayAdd(Backend *bn) ShmemBackendArray[i] = *bn; } +// int +// libpq_accept(pgsocket server_fd, Port *port) +// { +// return StreamConnection(server_fd, port); +// } + +// void +// libpq_close(pgsocket server_fd) +// { +// StreamClose(server_fd); +// } + +// void +// libpq_init(void) +// { +// pq_init(); +// } + +// int +// libpq_start(Port *port) +// { +// return ProcessStartupPacket(port, false, false); +// } + +// void +// libpq_authenticate(Port *port, const char **username) +// { +// PerformAuthentication(port); +// } + +// void +// libpq_mainfunc(Port *port, int argc, char *argv[]) +// { +// PostgresMain(argc, argv, port->database_name, +// port->database_oid? atooid(port->database_oid) : InvalidOid, +// port->user_name); +// } + +// void +// libpq_send_message(ErrorData *edata) +// { +// send_message_to_frontend(edata); +// } + +// void +// libpq_send_cancel_key(int pid, int32 key) +// { +// StringInfoData buf; + +// pq_beginmessage(&buf, 'K'); +// pq_sendint32(&buf, (int32) pid); +// pq_sendint32(&buf, (int32) key); +// pq_endmessage(&buf); +// /* Need not flush since ReadyForQuery will do it. */ + +// } + +// void +// libpq_comm_reset(void) +// { +// pq_comm_reset(); +// } + +// bool +// libpq_is_reading_msg(void) +// { +// return pq_is_reading_msg(); +// } + +// void +// libpq_send_ready_for_query(CommandDest dest) +// { +// ReadyForQuery(dest); +// } + +// int +// libpq_read_command(StringInfo inBuf) +// { +// return SocketBackendReadCommand(inBuf); +// } + +// void +// libpq_end_command(QueryCompletion *qc, CommandDest dest) +// { +// EndCommand(qc, dest, false); +// } + static void ShmemBackendArrayRemove(Backend *bn) { diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index f648a91b7f1..0baa19533cf 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -104,6 +104,44 @@ static const LOCKMASK LockConflicts[] = { }; +/* + * LOCKMODE conflict map for TSQL-style application locks. + * NB: we reuse and map some of the existing LOCKMODE in PG to the + * application lock modes so we don't have to add more LOCKMODE bits. + */ +static const LOCKMASK AppLockConflicts[] = { + 0, + + /* AccessShareLock */ + 0, + + /* (PG) RowShareLock -> (TSQL) IntentShared */ + LOCKBIT_ON(ExclusiveLock), + + /* (PG) RowExclusiveLock -> (TSQL) IntentExclusive */ + LOCKBIT_ON(ShareLock) | LOCKBIT_ON(ShareUpdateExclusiveLock) | + LOCKBIT_ON(ExclusiveLock), + + /* (PG) ShareUpdateExclusiveLock -> (TSQL) Update */ + LOCKBIT_ON(ShareUpdateExclusiveLock) | + LOCKBIT_ON(RowExclusiveLock) | LOCKBIT_ON(ExclusiveLock), + + /* ShareLock */ + LOCKBIT_ON(RowExclusiveLock) | LOCKBIT_ON(ExclusiveLock), + + /* ShareRowExclusiveLock */ + 0, + + /* ExclusiveLock */ + LOCKBIT_ON(RowShareLock) | + LOCKBIT_ON(RowExclusiveLock) | LOCKBIT_ON(ShareUpdateExclusiveLock) | + LOCKBIT_ON(ShareLock) | LOCKBIT_ON(ExclusiveLock), + + /* AccessExclusiveLock */ + 0 + +}; + /* Names of lock modes, for debug printouts */ static const char *const lock_mode_names[] = { @@ -144,13 +182,25 @@ static const LockMethodData user_lockmethod = { #endif }; +static const LockMethodData applock_lockmethod = { + AccessExclusiveLock, /* highest valid lock mode number */ + AppLockConflicts, + lock_mode_names, +#ifdef LOCK_DEBUG + &Trace_userlocks +#else + &Dummy_trace +#endif +}; + /* * map from lock method id to the lock table data structures */ static const LockMethod LockMethods[] = { NULL, &default_lockmethod, - &user_lockmethod + &user_lockmethod, + &applock_lockmethod }; @@ -282,6 +332,9 @@ static HTAB *LockMethodProcLockHash; static HTAB *LockMethodLocalHash; +/* TSQL-only handler for releasing application lock. */ +applock_release_func_handler_type applock_release_func_handler = NULL; + /* private state for error cleanup */ static LOCALLOCK *StrongLockInProgress; static LOCALLOCK *awaitedLock; @@ -2209,6 +2262,14 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) numLockModes = lockMethodTable->numLockModes; + /* + * If we are in TSQL, call the handler to remove the hash entries for + * application locks too. applock_release_func_handler is + * initialized when application lock is acquired the first time. + */ + if (lockmethodid == USER_LOCKMETHOD && applock_release_func_handler != NULL) + applock_release_func_handler(allLocks); + /* * First we run through the locallock table and get rid of unwanted * entries, then we scan the process's proclocks and get rid of those. We diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt index 774292fd942..a1d00ea750c 100644 --- a/src/backend/storage/lmgr/lwlocknames.txt +++ b/src/backend/storage/lmgr/lwlocknames.txt @@ -53,3 +53,4 @@ XactTruncationLock 44 # 45 was XactTruncationLock until removal of BackendRandomLock WrapLimitsVacuumLock 46 NotifyQueueTailLock 47 +TsqlApplockSyncLock 49 \ No newline at end of file diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 1342c18c741..c988d80152e 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1,4 +1,4 @@ -/*------------------------------------------------------------------------- +/*------------------------------------------------------------------------ * * postgres.c * POSTGRES C Backend Interface @@ -41,6 +41,7 @@ #include "access/xact.h" #include "catalog/pg_type.h" #include "commands/async.h" +#include "commands/extension.h" #include "commands/prepare.h" #include "executor/spi.h" #include "jit/jit.h" @@ -77,10 +78,13 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/ps_status.h" +#include "utils/queryenvironment.h" #include "utils/snapmgr.h" #include "utils/timeout.h" #include "utils/timestamp.h" +#include "nodes/nodes.h" + /* ---------------- * global variables * ---------------- @@ -101,8 +105,6 @@ int max_stack_depth = 100; /* wait N seconds to allow attach from a debugger */ int PostAuthDelay = 0; - - /* ---------------- * private variables * ---------------- @@ -173,11 +175,9 @@ static StringInfoData row_description_buf; */ static int InteractiveBackend(StringInfo inBuf); static int interactive_getc(void); -static int SocketBackend(StringInfo inBuf); static int ReadCommand(StringInfo inBuf); static void forbidden_in_wal_sender(char firstchar); static List *pg_rewrite_query(Query *query); -static bool check_log_statement(List *stmt_list); static int errdetail_execute(List *raw_parsetree_list); static int errdetail_params(ParamListInfo params); static int errdetail_abort(void); @@ -317,15 +317,15 @@ interactive_getc(void) } /* ---------------- - * SocketBackend() Is called for frontend-backend connections + * SocketBackendReadCommand() Is called for frontend-backend connections * * Returns the message type code, and loads message body data into inBuf. * * EOF is returned if the connection is lost. * ---------------- */ -static int -SocketBackend(StringInfo inBuf) +int +SocketBackendReadCommand(StringInfo inBuf) { int qtype; @@ -507,7 +507,7 @@ ReadCommand(StringInfo inBuf) int result; if (whereToSendOutput == DestRemote) - result = SocketBackend(inBuf); + result = MyProcPort->protocol_config->fn_read_command(inBuf); else result = InteractiveBackend(inBuf); return result; @@ -735,6 +735,9 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree, pstate->p_queryEnv = queryEnv; (*parserSetup) (pstate, parserSetupArg); + if (pre_parse_analyze_hook) + (*pre_parse_analyze_hook) (pstate, parsetree); + query = transformTopLevelStmt(pstate, parsetree); if (post_parse_analyze_hook) @@ -1293,7 +1296,10 @@ exec_simple_query(const char *query_string) * command the client sent, regardless of rewriting. (But a command * aborted by error will not send an EndCommand report at all.) */ - EndCommand(&qc, dest, false); + if (MyProcPort) + MyProcPort->protocol_config->fn_end_command(&qc, dest); + else + EndCommand(&qc, dest, false); /* Now we may drop the per-parsetree context, if one was created. */ if (per_parsetree_context) @@ -2231,7 +2237,10 @@ exec_execute_message(const char *portal_name, long max_rows) } /* Send appropriate CommandComplete to client */ - EndCommand(&qc, dest, false); + if (MyProcPort) + MyProcPort->protocol_config->fn_end_command(&qc, dest); + else + EndCommand(&qc, dest, false); } else { @@ -2279,7 +2288,7 @@ exec_execute_message(const char *portal_name, long max_rows) * stmt_list can be either raw grammar output or a list of planned * statements */ -static bool +bool check_log_statement(List *stmt_list) { ListCell *stmt_item; @@ -3805,6 +3814,8 @@ PostgresMain(int argc, char *argv[], sigjmp_buf local_sigjmp_buf; volatile bool send_ready_for_query = true; bool disable_idle_in_transaction_timeout = false; + const char* pgtsql_library_name = "babelfishpg_tsql"; + const char* pgtsql_common_library_name = "babelfishpg_common"; /* Initialize startup process environment if necessary. */ if (!IsUnderPostmaster) @@ -3929,6 +3940,7 @@ PostgresMain(int argc, char *argv[], /* Early initialization */ BaseInit(); + /* * Create a per-backend PGPROC struct in shared memory, except in the * EXEC_BACKEND case where this was done in SubPostmasterMain. We must do @@ -3995,15 +4007,12 @@ PostgresMain(int argc, char *argv[], /* * Send this backend's cancellation info to the frontend. */ - if (whereToSendOutput == DestRemote) + if (whereToSendOutput == DestRemote && + PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2) { - StringInfoData buf; - - pq_beginmessage(&buf, 'K'); - pq_sendint32(&buf, (int32) MyProcPid); - pq_sendint32(&buf, (int32) MyCancelKey); - pq_endmessage(&buf); - /* Need not flush since ReadyForQuery will do it. */ + if (MyProcPort && MyProcPort->protocol_config->fn_send_cancel_key) + MyProcPort->protocol_config->fn_send_cancel_key(MyProcPid, + MyCancelKey); } /* Welcome banner for standalone case */ @@ -4039,6 +4048,36 @@ PostgresMain(int argc, char *argv[], if (!IsUnderPostmaster) PgStartTime = GetCurrentTimestamp(); + /* + * We want to load babelfishpg_tsql library beforehand and make sure that the + * queries it needs to run as part of its initializatin is properly + * enclosed inside a transaction, with an active snapshot. + * + * This is intended as a temporary fix for BABEL-669. + * + * FIXME: ideally we should remove the SQL queries from the babelfishpg_tsql + * initialization function. Once we fix that, we can remove the below + * code, and just add 'babelfishpg_tsql' to the 'session_preload_libraries' + * or 'local_preload_libraries' guc parameter. Or, we do not preload + * babelfishpg_tsql at all, but just load when needed, as we did before. + */ + start_xact_command(); + PushActiveSnapshot(GetTransactionSnapshot()); + if (get_extension_oid(pgtsql_library_name, true) != InvalidOid) + { + /* + * babelfishpg_tsql extension depends on babelfishpg_common, so + * babelfishpg_common must be loaded first + */ + load_libraries(pgtsql_common_library_name, NULL, false); + load_libraries(pgtsql_library_name, NULL, false); + } + PopActiveSnapshot(); + finish_xact_command(); + + /* create the top level query environment here */ + create_queryEnv2(CacheMemoryContext, true); + /* * POSTGRES main processing loop begins here * @@ -4095,7 +4134,8 @@ PostgresMain(int argc, char *argv[], DoingCommandRead = false; /* Make sure libpq is in a good state */ - pq_comm_reset(); + if (MyProcPort && MyProcPort->protocol_config->fn_comm_reset) + MyProcPort->protocol_config->fn_comm_reset(); /* Report the error to the client and/or server log */ EmitErrorReport(); @@ -4158,7 +4198,8 @@ PostgresMain(int argc, char *argv[], * messages from the client, so there isn't much we can do with the * connection anymore. */ - if (pq_is_reading_msg()) + if (MyProcPort && MyProcPort->protocol_config->fn_is_reading_msg && + MyProcPort->protocol_config->fn_is_reading_msg()) ereport(FATAL, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("terminating connection because protocol synchronization was lost"))); @@ -4261,7 +4302,10 @@ PostgresMain(int argc, char *argv[], pgstat_report_activity(STATE_IDLE, NULL); } - ReadyForQuery(whereToSendOutput); + if (MyProcPort && MyProcPort->protocol_config->fn_send_ready_for_query) + MyProcPort->protocol_config->fn_send_ready_for_query(whereToSendOutput); + else + ReadyForQuery(whereToSendOutput); send_ready_for_query = false; } @@ -4311,6 +4355,17 @@ PostgresMain(int argc, char *argv[], ProcessConfigFile(PGC_SIGHUP); } + /* + * If firstchar is EOF, then we need to disconnect, ortherwise + * call the protocol hook to process the request. + */ + if (firstchar != EOF && MyProcPort && + MyProcPort->protocol_config->fn_process_command) + { + firstchar = MyProcPort->protocol_config->fn_process_command(); + send_ready_for_query = true; + continue; + } /* * (7) process the command. But ignore it if we're skipping till * Sync. diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 230eed559cd..57336c09169 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -58,6 +58,7 @@ #include "commands/vacuum.h" #include "commands/view.h" #include "miscadmin.h" +#include "parser/parser.h" #include "parser/parse_utilcmd.h" #include "postmaster/bgwriter.h" #include "rewrite/rewriteDefine.h" @@ -753,7 +754,12 @@ standard_ProcessUtility(PlannedStmt *pstmt, case T_CreatedbStmt: /* no event triggers for global objects */ - PreventInTransactionBlock(isTopLevel, "CREATE DATABASE"); + /* In case of TSQL mode, we allow create database from + * transaction blocks to turn batch mode ON by default. + */ + if (sql_dialect != SQL_DIALECT_TSQL) { + PreventInTransactionBlock(isTopLevel, "CREATE DATABASE"); + } createdb(pstate, (CreatedbStmt *) parsetree); break; @@ -769,7 +775,12 @@ standard_ProcessUtility(PlannedStmt *pstmt, case T_DropdbStmt: /* no event triggers for global objects */ - PreventInTransactionBlock(isTopLevel, "DROP DATABASE"); + /* In case of TSQL mode, we allow drop database from + * transaction blocks to turn batch mode ON by default. + */ + if (sql_dialect != SQL_DIALECT_TSQL) { + PreventInTransactionBlock(isTopLevel, "DROP DATABASE"); + } DropDatabase(pstate, (DropdbStmt *) parsetree); break; diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c index 41e1a1b610b..d76da7c7f9e 100644 --- a/src/backend/utils/adt/domains.c +++ b/src/backend/utils/adt/domains.c @@ -215,6 +215,7 @@ domain_in(PG_FUNCTION_ARGS) Oid domainType; DomainIOData *my_extra; Datum value; + int32 typmod = -1; /* * Since domain_in is not strict, we have to check for null inputs. The @@ -242,13 +243,22 @@ domain_in(PG_FUNCTION_ARGS) fcinfo->flinfo->fn_extra = (void *) my_extra; } + /* + * typmod should usually be -1 for a domain unless enable_domain_typmod is + * enabled when the domain is created. If so, use typmod from the input + * instead of the default typmod my_extra->typtypmod that is set when the + * domain is created. + */ + if (!PG_ARGISNULL(2)) + typmod = DatumGetInt32(fcinfo->args[2].value); + /* * Invoke the base type's typinput procedure to convert the data. */ value = InputFunctionCall(&my_extra->proc, string, my_extra->typioparam, - my_extra->typtypmod); + typmod == -1 ? my_extra->typtypmod : typmod); /* * Do the necessary checks to ensure it's a valid domain value. diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c index 61d318d93ca..9ce3f62ad6f 100644 --- a/src/backend/utils/adt/encode.c +++ b/src/backend/utils/adt/encode.c @@ -218,6 +218,43 @@ hex_decode(const char *src, size_t len, char *dst) return p - dst; } +/* A variant of the above hex_decode function, but allows odd number of hex digits */ +uint64 +hex_decode_allow_odd_digits(const char *src, unsigned len, char *dst) +{ + const char *s, + *srcend; + char v1, + v2, + *p; + + srcend = src + len; + s = src; + p = dst; + + if (len % 2 == 1) + { + /* If input has odd number of hex digits, add a 0 to the front to make it even */ + v1 = '\0'; + v2 = get_hex(*s++); + *p++ = v1 | v2; + } + /* The rest of the input must have even number of digits*/ + while (s < srcend) + { + if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r') + { + s++; + continue; + } + v1 = get_hex(*s++) << 4; + v2 = get_hex(*s++); + *p++ = v1 | v2; + } + + return p - dst; +} + static uint64 hex_enc_len(const char *src, size_t srclen) { diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 6a717f19bba..c7b8b652f70 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -104,7 +104,6 @@ float_zero_divide_error(void) errmsg("division by zero"))); } - /* * Returns -1 if 'val' represents negative infinity, 1 if 'val' * represents (positive) infinity, and 0 otherwise. On some platforms, diff --git a/src/backend/utils/adt/like_support.c b/src/backend/utils/adt/like_support.c index b9144e0abb2..c7b3dde8bf7 100644 --- a/src/backend/utils/adt/like_support.c +++ b/src/backend/utils/adt/like_support.c @@ -47,6 +47,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" +#include "parser/parser.h" #include "utils/builtins.h" #include "utils/datum.h" #include "utils/lsyscache.h" @@ -108,6 +109,11 @@ static Const *string_to_const(const char *str, Oid datatype); static Const *string_to_bytea_const(const char *str, size_t str_len); +int pattern_fixed_prefix_wrapper(Const *patt, + int ptype, + Oid collation, + Const **prefix, + Selectivity *rest_selec); /* * Planner support functions for LIKE, regex, and related operators */ @@ -1021,6 +1027,32 @@ like_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation, Assert((Pointer) bstr == DatumGetPointer(patt_const->constvalue)); } + /* TSQL uses the [ and ] characters as a kind of pattern matching + * character. This is not supported in the current version. + */ + if (sql_dialect == SQL_DIALECT_TSQL) + { + for (pos = 0; pos < pattlen; pos++) + { + /* [ and ] are special characters in LIKE for TSQL */ + if (patt[pos] == '[' || + patt[pos] == ']') + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("pattern matching operators '[' and ']' are not supported for LIKE"))); + } + + /* Backslash escapes the next character */ + if (patt[pos] == '\\') + { + pos++; + if (pos >= pattlen) + break; + } + } + } + match = palloc(pattlen + 1); match_pos = 0; for (pos = 0; pos < pattlen; pos++) @@ -1039,7 +1071,7 @@ like_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation, } /* Stop if case-varying character (it's sort of a wildcard) */ - if (case_insensitive && + if (case_insensitive && get_collation_isdeterministic(collation) && pattern_char_isalpha(patt[pos], is_multibyte, locale, locale_is_c)) break; @@ -1242,7 +1274,6 @@ prefix_selectivity(PlannerInfo *root, VariableStatData *vardata, collation, greaterstrcon->constvalue, greaterstrcon->consttype); - /* ineq_histogram_selectivity worked before, it shouldn't fail now */ Assert(topsel >= 0.0); @@ -1768,3 +1799,12 @@ string_to_bytea_const(const char *str, size_t str_len) return makeConst(BYTEAOID, -1, InvalidOid, -1, conval, false, false); } +/* This is a helper to make pattern_fixed_prefix extern */ +int pattern_fixed_prefix_wrapper(Const *patt, + int ptype, + Oid collation, + Const **prefix, + Selectivity *rest_selec) +{ + return pattern_fixed_prefix(patt,ptype,collation,prefix,rest_selec); +} diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c index 64877f67e01..e27b3fb4e87 100644 --- a/src/backend/utils/adt/name.c +++ b/src/backend/utils/adt/name.c @@ -31,6 +31,7 @@ #include "utils/lsyscache.h" #include "utils/varlena.h" +cstr_to_name_hook_type cstr_to_name_hook = NULL; /***************************************************************************** * USER I/O ROUTINES (none) * @@ -55,7 +56,12 @@ namein(PG_FUNCTION_ARGS) /* Truncate oversize input */ if (len >= NAMEDATALEN) + { + if (cstr_to_name_hook) /* to apply special truncation logic */ + PG_RETURN_NAME((*cstr_to_name_hook)(s, len)); + len = pg_mbcliplen(s, len, NAMEDATALEN - 1); + } /* We use palloc0 here to ensure result is zero-padded */ result = (Name) palloc0(NAMEDATALEN); diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 9f16f09c110..eaebbdc7b70 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -35,6 +35,7 @@ #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" +#include "parser/parser.h" /* only needed for GUC variable sql_dialect */ #include "utils/array.h" #include "utils/builtins.h" #include "utils/float.h" @@ -6549,6 +6550,13 @@ apply_typmod(NumericVar *var, int32 typmod) scale = typmod & 0xffff; maxdigits = precision - scale; + /* TSQL client can't handle oversized precision */ + if (sql_dialect == SQL_DIALECT_TSQL && + precision > TSQLMaxNumPrecision) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("The size (%d) given to the type 'numeric' exceeds the maximum allowed (38)", precision))); + /* Round to target scale (and set var->dscale) */ round_var(var, scale); diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 47d4965215d..fa29e724722 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -37,6 +37,7 @@ #include "executor/spi.h" #include "lib/ilist.h" #include "miscadmin.h" +#include "parser/parser.h" #include "parser/parse_coerce.h" #include "parser/parse_relation.h" #include "storage/bufmgr.h" @@ -90,6 +91,35 @@ #define RI_TRIGTYPE_UPDATE 2 #define RI_TRIGTYPE_DELETE 3 +/* macro for switching in and out of tsql mode */ +#define RUN_AS_PSQL(qplan) \ +do \ +{ \ + bool reset_dialect = (sql_dialect == SQL_DIALECT_TSQL); \ + if (reset_dialect) \ + set_config_option("babelfishpg_tsql.sql_dialect", "postgres", \ + (superuser() ? PGC_SUSET : PGC_USERSET), \ + PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); \ + \ + PG_TRY(); \ + { \ + qplan; \ + } \ + PG_CATCH(); \ + { \ + if (reset_dialect) \ + set_config_option("babelfishpg_tsql.sql_dialect", "tsql", \ + (superuser() ? PGC_SUSET : PGC_USERSET), \ + PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); \ + PG_RE_THROW(); \ + } \ + PG_END_TRY(); \ + if (reset_dialect) \ + set_config_option("babelfishpg_tsql.sql_dialect", "tsql", \ + (superuser() ? PGC_SUSET : PGC_USERSET), \ + PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); \ +} while (0) + /* * RI_ConstraintInfo @@ -382,8 +412,8 @@ RI_FKey_check(TriggerData *trigdata) appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, - &qkey, fk_rel, pk_rel); + RUN_AS_PSQL(qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel)); } /* @@ -509,8 +539,8 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, - &qkey, fk_rel, pk_rel); + RUN_AS_PSQL(qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel)); } /* @@ -701,8 +731,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, - &qkey, fk_rel, pk_rel); + RUN_AS_PSQL(qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel)); } /* @@ -806,8 +836,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) } /* Prepare and save the plan */ - qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, - &qkey, fk_rel, pk_rel); + RUN_AS_PSQL(qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel)); } /* @@ -928,8 +958,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids, - &qkey, fk_rel, pk_rel); + RUN_AS_PSQL(qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids, + &qkey, fk_rel, pk_rel)); } /* @@ -1107,8 +1137,8 @@ ri_set(TriggerData *trigdata, bool is_set_null) appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, - &qkey, fk_rel, pk_rel); + RUN_AS_PSQL(qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel)); } /* @@ -1475,7 +1505,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) * Generate the plan. We don't need to cache it, and there are no * arguments to the plan. */ - qplan = SPI_prepare(querybuf.data, 0, NULL); + RUN_AS_PSQL(qplan = SPI_prepare(querybuf.data, 0, NULL)); if (qplan == NULL) elog(ERROR, "SPI_prepare returned %s for %s", diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c index b595ab9569c..bc6bb609b3f 100644 --- a/src/backend/utils/adt/varchar.c +++ b/src/backend/utils/adt/varchar.c @@ -22,12 +22,15 @@ #include "mb/pg_wchar.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" +#include "parser/parser.h" /* only needed for GUC variables */ #include "utils/array.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/pg_locale.h" #include "utils/varlena.h" +suppress_string_truncation_error_hook_type suppress_string_truncation_error_hook = NULL; + /* common code for bpchartypmodin and varchartypmodin */ static int32 anychar_typmodin(ArrayType *ta, const char *typename) @@ -38,6 +41,14 @@ anychar_typmodin(ArrayType *ta, const char *typename) tl = ArrayGetIntegerTypmods(ta, &n); + /* + * Allow typmod of VARCHAR/NVARCHAR(MAX) to go through as is. + */ + if (*tl == TSQLMaxTypmod) + { + return *tl; + } + /* * we're not too tense about good error message here because grammar * shouldn't allow wrong number of modifiers for CHAR @@ -81,7 +92,6 @@ anychar_typmodout(int32 typmod) return res; } - /* * CHAR() and VARCHAR() types are part of the SQL standard. CHAR() * is for blank-padded string whose length is specified in CREATE TABLE. @@ -301,7 +311,8 @@ bpchar(PG_FUNCTION_ARGS) maxmblen = pg_mbcharcliplen(s, len, maxlen); - if (!isExplicit) + if (!isExplicit && + !(suppress_string_truncation_error_hook && (*suppress_string_truncation_error_hook)())) { for (i = maxmblen; i < len; i++) if (s[i] != ' ') @@ -321,6 +332,7 @@ bpchar(PG_FUNCTION_ARGS) } else { + /* * At this point, maxlen is the necessary byte length, not the number * of CHARACTERS! @@ -625,7 +637,8 @@ varchar(PG_FUNCTION_ARGS) /* truncate multibyte string preserving multibyte boundary */ maxmblen = pg_mbcharcliplen(s_data, len, maxlen); - if (!isExplicit) + if (!isExplicit && + !(suppress_string_truncation_error_hook && (*suppress_string_truncation_error_hook)())) { for (i = maxmblen; i < len; i++) if (s_data[i] != ' ') diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 3303c31e575..783d97bc5b3 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -37,6 +37,7 @@ #include "catalog/pg_type.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "parser/parser.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/catcache.h" @@ -2471,9 +2472,22 @@ getBaseTypeAndTypmod(Oid typid, int32 *typmod) break; } - Assert(*typmod == -1); typid = typTup->typbasetype; - *typmod = typTup->typtypmod; + + /* + * Usually typmod is not allowed for domain. But + * enable_domain_typmod enables typmod to be used with + * domains. So if typmod is specified for such a domain, we don't + * look up the base typmod in typTup->typtypmod. + * Only lookup typtypmod (the original typmod in create domain, e.g. create + * domain varchar10 as varchar(10), 10 is typtypmod) if typmod is not + * specified. + * Typmod is allowed for domain only when enable_domain_typmod + * is enabled when executing the CREATE DOMAIN Statement, + * see DefineDomain for details. + */ + if (*typmod == -1) + *typmod = typTup->typtypmod; ReleaseSysCache(tup); } diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 75b475c179b..c6b0ae85abb 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -83,6 +83,9 @@ ((plansource)->raw_parse_tree && \ IsA((plansource)->raw_parse_tree->stmt, TransactionStmt)) +plansource_complete_hook_type plansource_complete_hook = NULL; +plansource_revalidate_hook_type plansource_revalidate_hook = NULL; + /* * This is the head of the backend's list of "saved" CachedPlanSources (i.e., * those that are in long-lived storage and are examined for sinval events). @@ -219,6 +222,7 @@ CreateCachedPlan(RawStmt *raw_parse_tree, plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; + plansource->pltsql_plan_info = NIL; MemoryContextSwitchTo(oldcxt); @@ -286,6 +290,7 @@ CreateOneShotCachedPlan(RawStmt *raw_parse_tree, plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; + plansource->pltsql_plan_info = NIL; return plansource; } @@ -427,6 +432,9 @@ CompleteCachedPlan(CachedPlanSource *plansource, plansource->fixed_result = fixed_result; plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list); + if (plansource_complete_hook) + (* plansource_complete_hook) (plansource); + MemoryContextSwitchTo(oldcxt); plansource->is_complete = true; @@ -600,6 +608,15 @@ RevalidateCachedQuery(CachedPlanSource *plansource, plansource->rewriteRowSecurity != row_security)) plansource->is_valid = false; + /* + * Call revalidation plan hook. + */ + if (plansource->is_valid && plansource_revalidate_hook) + { + if (!(* plansource_revalidate_hook) (plansource)) + plansource->is_valid = false; + } + /* * If the query is currently valid, acquire locks on the referenced * objects; then check again. We need to do it this way to cover the race @@ -1576,6 +1593,8 @@ CopyCachedPlan(CachedPlanSource *plansource) newsource->total_custom_cost = plansource->total_custom_cost; newsource->num_custom_plans = plansource->num_custom_plans; + newsource->pltsql_plan_info = copyObject(plansource->pltsql_plan_info); + MemoryContextSwitchTo(oldcxt); return newsource; diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 53d9ddf1590..286f819f706 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -109,18 +109,6 @@ *--------------------------------------------------------------------------- */ -/* - * struct cachedesc: information defining a single syscache - */ -struct cachedesc -{ - Oid reloid; /* OID of the relation being cached */ - Oid indoid; /* OID of index relation for this cache */ - int nkeys; /* # of keys needed for cache lookup */ - int key[4]; /* attribute numbers of key attrs */ - int nbuckets; /* number of hash buckets for this cache */ -}; - static const struct cachedesc cacheinfo[] = { {AggregateRelationId, /* AGGFNOID */ AggregateFnoidIndexId, @@ -1011,14 +999,14 @@ InitCatalogCache(void) { int cacheId; - StaticAssertStmt(SysCacheSize == (int) lengthof(cacheinfo), + StaticAssertStmt(SysCacheNoExtensionSize == (int) lengthof(cacheinfo), "SysCacheSize does not match syscache.c's array"); Assert(!CacheInitialized); SysCacheRelationOidSize = SysCacheSupportingRelOidSize = 0; - for (cacheId = 0; cacheId < SysCacheSize; cacheId++) + for (cacheId = 0; cacheId < SysCacheNoExtensionSize; cacheId++) { SysCache[cacheId] = InitCatCache(cacheId, cacheinfo[cacheId].reloid, @@ -1059,6 +1047,42 @@ InitCatalogCache(void) CacheInitialized = true; } +/* + * InitExtensionCatalogCache - initialize the catcache for extensions + * + * This function should be called ONLY ONCE by the extension module before + * they start to access their extension catcache. The caller should indicate + * the corresponding portion of SysCache that it wants to initialize + * (specified by startid + ext_cachelength). + * XXX: unlike InitCatalogCache, we skip the OID binary search here since the + * size of extension catcache is rather small. But if it grows larger we + * might consider supporting binary search for them as well. + */ +void +InitExtensionCatalogCache(struct cachedesc *ext_cacheinfo, int startid, int ext_cachelength) +{ + int cacheId; + int i; + + SysCacheRelationOidSize = SysCacheSupportingRelOidSize = 0; + + for (i = 0; i < ext_cachelength; i++) + { + cacheId = startid + i; + SysCache[cacheId] = InitCatCache(cacheId, + ext_cacheinfo[i].reloid, + ext_cacheinfo[i].indoid, + ext_cacheinfo[i].nkeys, + ext_cacheinfo[i].key, + ext_cacheinfo[i].nbuckets); + if (!PointerIsValid(SysCache[cacheId])) + elog(ERROR, "could not initialize extension cache %u (%d)", + ext_cacheinfo[i].reloid, cacheId); + /* see comments for RelationInvalidatesSnapshotsOnly */ + Assert(!RelationInvalidatesSnapshotsOnly(ext_cacheinfo[i].reloid)); + } +} + /* * InitCatalogCachePhase2 - finish initializing the caches * @@ -1078,7 +1102,7 @@ InitCatalogCachePhase2(void) Assert(CacheInitialized); - for (cacheId = 0; cacheId < SysCacheSize; cacheId++) + for (cacheId = 0; cacheId < SysCacheNoExtensionSize; cacheId++) InitCatCachePhase2(SysCache[cacheId], true); } diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt index c79312ed039..a508ee1bce7 100644 --- a/src/backend/utils/errcodes.txt +++ b/src/backend/utils/errcodes.txt @@ -493,3 +493,10 @@ Section: Class XX - Internal Error XX000 E ERRCODE_INTERNAL_ERROR internal_error XX001 E ERRCODE_DATA_CORRUPTED data_corrupted XX002 E ERRCODE_INDEX_CORRUPTED index_corrupted + +Section: Class YY - PLtsql Error + +# (PLtsql-specific error class) +YY000 E ERRCODE_PLTSQL_RAISERROR pltsql_raiserror +YY001 E ERRCODE_PLTSQL_THROW pltsql_throw +YY002 E ERRCODE_PLTSQL_ERROR_NOT_MAPPED pltsql_error_not_mapped \ No newline at end of file diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index e4b717c79a9..9ebad2d6d6a 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -181,7 +181,6 @@ static void log_line_prefix(StringInfo buf, ErrorData *edata); static void write_csvlog(ErrorData *edata); static void send_message_to_server_log(ErrorData *edata); static void write_pipe_chunks(char *data, int len, int dest); -static void send_message_to_frontend(ErrorData *edata); static const char *error_severity(int elevel); static void append_with_tabs(StringInfo buf, const char *str); static bool is_log_level_output(int elevel, int log_min_level); @@ -1457,7 +1456,12 @@ EmitErrorReport(void) /* Send to client, if enabled */ if (edata->output_to_client) - send_message_to_frontend(edata); + { + if (MyProcPort) + MyProcPort->protocol_config->fn_send_message(edata); + else + send_message_to_frontend(edata); + } MemoryContextSwitchTo(oldcontext); recursion_depth--; @@ -3161,7 +3165,7 @@ err_sendstring(StringInfo buf, const char *str) /* * Write error report to client */ -static void +void send_message_to_frontend(ErrorData *edata) { StringInfoData msgbuf; diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 2681b7fbc60..253647c31d8 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -16,9 +16,12 @@ #include "postgres.h" #include "access/detoast.h" +#include "catalog/namespace.h" #include "catalog/pg_language.h" +#include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "commands/proclang.h" #include "executor/functions.h" #include "lib/stringinfo.h" #include "miscadmin.h" @@ -37,6 +40,7 @@ */ PGDLLIMPORT needs_fmgr_hook_type needs_fmgr_hook = NULL; PGDLLIMPORT fmgr_hook_type fmgr_hook = NULL; +PGDLLIMPORT non_tsql_proc_entry_hook_type non_tsql_proc_entry_hook = NULL; /* * Hashtable for fast lookup of external C functions @@ -60,7 +64,7 @@ static void fmgr_info_C_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedur static void fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple); static CFuncHashTabEntry *lookup_C_func(HeapTuple procedureTuple); static void record_C_func(HeapTuple procedureTuple, - PGFunction user_fn, const Pg_finfo_record *inforec); + PGFunction user_fn, const Pg_finfo_record *inforec); /* extern so it's callable via JIT */ extern Datum fmgr_security_definer(PG_FUNCTION_ARGS); @@ -204,6 +208,12 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, if (!ignore_security && (procedureStruct->prosecdef || !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) || + /* + * If babelfishpg_tsql extension is installed, set proconfig of functions + * to have sql_dialect be either postgres or tsql according to + * their language. + */ + (*find_rendezvous_variable("PLtsql_config") != NULL) || FmgrHookIsNeeded(functionId))) { finfo->fn_addr = fmgr_security_definer; @@ -549,6 +559,36 @@ lookup_C_func(HeapTuple procedureTuple) return NULL; /* entry is out of date */ } +PGFunction +lookup_C_func_by_oid(Oid fn_oid, char* probinstring, char* prosrcstring) +{ + PGFunction user_fn = NULL; + + /* Lookup hash table CFuncHash If Functions are already cached */ + if (CFuncHash != NULL) + { + CFuncHashTabEntry *entry; + + entry = (CFuncHashTabEntry *) + hash_search(CFuncHash, + &fn_oid, + HASH_FIND, + NULL); + if (entry == NULL) + user_fn = NULL; /* no such entry */ + else + user_fn = entry->user_fn; /* OK */ + } + /* + * If haven't found using CFuncHash hash table, + * load the dynamically linked library to get the function + */ + if (user_fn == NULL) + user_fn = load_external_function(probinstring, prosrcstring, true, NULL); + + return user_fn; +} + /* * record_C_func: enter (or update) info about a C function in the hash table */ @@ -668,6 +708,14 @@ fmgr_security_definer(PG_FUNCTION_ARGS) int save_sec_context; volatile int save_nestlevel; PgStat_FunctionCallUsage fcusage; + Oid pltsql_lang_oid, pltsql_validator_oid; + bool set_sql_dialect; + char *sql_dialect_value; + const char *sql_dialect_value_old; + char *pg_dialect = "postgres"; + char *tsql_dialect = "tsql"; + int sys_func_count = 0; + int non_tsql_proc_count = 0; if (!fcinfo->flinfo->fn_extra) { @@ -710,6 +758,44 @@ fmgr_security_definer(PG_FUNCTION_ARGS) else fcache = fcinfo->flinfo->fn_extra; + /* + * If babelfishpg_tsql extension is installed, set proconfig of sql_dialect + */ + { + /* + * FIXME: the following typedef (PLtsql_config) describes a + * structure shared with PLtsql. Since the type is shared, + * we should find a header file that is properly shared and + * move this typedef + * + * This typedef *must* be kept in-synch with the PLtsql_config + * type declaration found in contrib/babelfishpg_tsql/src/pgtsql.h + */ + typedef struct PLtsql_config + { + char *version; /* Extension version info */ + Oid handler_oid; /* Oid of language handler function */ + Oid validator_oid; /* Oid of language validator function */ + } PLtsql_config; + + PLtsql_config **config = (PLtsql_config **) find_rendezvous_variable("PLtsql_config"); + + if (*config) + { + pltsql_lang_oid = (*config)->handler_oid; + pltsql_validator_oid = (*config)->validator_oid; + } + else + { + pltsql_lang_oid = InvalidOid; + pltsql_validator_oid = InvalidOid; + } + } + + + // get_language_procs("pltsql", &pltsql_lang_oid, &pltsql_validator_oid); + set_sql_dialect = pltsql_lang_oid != InvalidOid; + /* GetUserIdAndSecContext is cheap enough that no harm in a wasted call */ GetUserIdAndSecContext(&save_userid, &save_sec_context); if (fcache->proconfig) /* Need a new GUC nesting level */ @@ -729,6 +815,52 @@ fmgr_security_definer(PG_FUNCTION_ARGS) GUC_ACTION_SAVE); } + if (set_sql_dialect) + { + HeapTuple tuple; + Form_pg_proc procedureStruct; + tuple = SearchSysCache1(PROCOID, + ObjectIdGetDatum(fcinfo->flinfo->fn_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for function %u", + fcinfo->flinfo->fn_oid); + procedureStruct = (Form_pg_proc) GETSTRUCT(tuple); + + if ((procedureStruct->prolang == pltsql_lang_oid) || (procedureStruct->prolang == pltsql_validator_oid)) + sql_dialect_value = tsql_dialect; + else + { + /* Record PG procedure entry */ + Oid sys_nspoid = get_namespace_oid("sys", false); + /* + * For functions and C procs inside sys/pg_catalog, + * handle errors TSQL way. Otherwise, error in simple + * functions like cast/coerce will change TSQL behavior + */ + if ((procedureStruct->pronamespace == sys_nspoid || + procedureStruct->pronamespace == PG_CATALOG_NAMESPACE) && + (procedureStruct->prokind == PROKIND_FUNCTION || + procedureStruct->prolang == ClanguageId || + procedureStruct->prolang == INTERNALlanguageId)) + sys_func_count = 1; + else + non_tsql_proc_count = 1; + + non_tsql_proc_entry_hook(non_tsql_proc_count, sys_func_count); + sql_dialect_value = pg_dialect; + } + + ReleaseSysCache(tuple); + sql_dialect_value_old = GetConfigOption("babelfishpg_tsql.sql_dialect", true, true); + set_config_option("babelfishpg_tsql.sql_dialect", sql_dialect_value, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + GUC_ACTION_SAVE, + true, + 0, + false); + } + /* function manager hook */ if (fmgr_hook) (*fmgr_hook) (FHET_START, &fcache->flinfo, &fcache->arg); @@ -763,13 +895,40 @@ fmgr_security_definer(PG_FUNCTION_ARGS) fcinfo->flinfo = save_flinfo; if (fmgr_hook) (*fmgr_hook) (FHET_ABORT, &fcache->flinfo, &fcache->arg); + + /* + * TODO PG set commands cannot work with transaction commands + * inside procedures. Fix it as part of larger interoperability + * design for PG vs TSQL procedures. + */ + if (set_sql_dialect) + set_config_option("babelfishpg_tsql.sql_dialect", sql_dialect_value_old, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + GUC_ACTION_SAVE, + true, + 0, + false); + PG_RE_THROW(); } PG_END_TRY(); fcinfo->flinfo = save_flinfo; - if (fcache->proconfig) + if (set_sql_dialect) + { + set_config_option("babelfishpg_tsql.sql_dialect", sql_dialect_value_old, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + GUC_ACTION_SAVE, + true, + 0, + false); + if (strncmp(sql_dialect_value, pg_dialect, strlen(pg_dialect)) == 0) + non_tsql_proc_entry_hook(non_tsql_proc_count * -1, sys_func_count * -1); + } + else if (fcache->proconfig) AtEOXact_GUC(true, save_nestlevel); if (OidIsValid(fcache->userid)) SetUserIdAndSecContext(save_userid, save_sec_context); @@ -2092,3 +2251,4 @@ CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid) return true; } + diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index cca9704d2d7..059d9c45e4a 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -1573,7 +1573,7 @@ bool process_shared_preload_libraries_in_progress = false; * 'gucname': name of GUC variable, for error reports * 'restricted': if true, force libraries to be in $libdir/plugins/ */ -static void +void load_libraries(const char *libraries, const char *gucname, bool restricted) { char *rawstring; diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 01463af22f8..77e7d5d9f3c 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -66,7 +66,6 @@ static HeapTuple GetDatabaseTuple(const char *dbname); static HeapTuple GetDatabaseTupleByOid(Oid dboid); -static void PerformAuthentication(Port *port); static void CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connections); static void InitCommunication(void); static void ShutdownPostgres(int code, Datum arg); @@ -181,7 +180,7 @@ GetDatabaseTupleByOid(Oid dboid) * * returns: nothing. Will not return at all if there's any failure. */ -static void +void PerformAuthentication(Port *port) { /* This should be set already, but let's make sure */ @@ -769,7 +768,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username, { /* normal multiuser case */ Assert(MyProcPort != NULL); - PerformAuthentication(MyProcPort); + (MyProcPort->protocol_config->fn_authenticate)(MyProcPort, &username); InitializeSessionUserId(username, useroid); am_superuser = superuser(); } diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c index a8e13cacfde..3b3cfd95174 100644 --- a/src/backend/utils/mb/mbutils.c +++ b/src/backend/utils/mb/mbutils.c @@ -268,7 +268,21 @@ SetClientEncoding(int encoding) } if (found) + { + /* + * We cache the information required to + * invoke perform_default_encoding_conversion() + * but the cached info may incomplete at this + * point. To complete the cache item, we invoke + * both ToServer and ToClient functions by converting + * a simple value (a space character) and just + * ignore the result. + */ + perform_default_encoding_conversion(" ", 1, true); + perform_default_encoding_conversion(" ", 1, false); + return 0; /* success */ + } else return -1; /* it's not cached, so fail */ } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index f7a3297161f..a18c913f34b 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -40,6 +40,7 @@ #include "commands/async.h" #include "commands/prepare.h" #include "commands/trigger.h" +#include "commands/typecmds.h" #include "commands/user.h" #include "commands/vacuum.h" #include "commands/variable.h" @@ -226,6 +227,8 @@ static bool check_default_with_oids(bool *newval, void **extra, GucSource source static ConfigVariable *ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel); +guc_push_old_value_hook_type guc_push_old_value_hook = NULL; +validate_set_config_function_hook_type validate_set_config_function_hook = NULL; /* * Options for enum values defined in this module. @@ -503,6 +506,13 @@ static struct config_enum_entry shared_memory_options[] = { {NULL, 0, false} }; +const struct config_enum_entry sql_dialect_options[] = { + {"postgres", SQL_DIALECT_PG, false}, + {"tsql", SQL_DIALECT_TSQL, false}, + {"pg", SQL_DIALECT_PG, true}, + {NULL, 0, false} +}; + /* * Options for enum values stored in other modules */ @@ -547,7 +557,7 @@ int log_min_duration_statement = -1; int log_parameter_max_length = -1; int log_parameter_max_length_on_error = 0; int log_temp_files = -1; -double log_statement_sample_rate = 1.0; +double log_statement_sample_rate = 1.0; double log_xact_sample_rate = 0; int trace_recovery_messages = LOG; char *backtrace_functions; @@ -566,6 +576,7 @@ char *external_pid_file; char *pgstat_temp_directory; char *application_name; +char *pltsql_database_name; int tcp_keepalives_idle; int tcp_keepalives_interval; @@ -1100,6 +1111,16 @@ static struct config_bool ConfigureNamesBool[] = true, NULL, NULL, NULL }, + { + {"enable_domain_typmod", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("Allow type modifer to be used on a domain."), + NULL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_DISALLOW_IN_AUTO_FILE + }, + &enable_domain_typmod, + false, + NULL, NULL, NULL + }, { {"enable_partition_pruning", PGC_USERSET, QUERY_TUNING_METHOD, gettext_noop("Enables plan-time and run-time partition pruning."), @@ -4766,6 +4787,17 @@ static struct config_enum ConfigureNamesEnum[] = NULL, NULL, NULL }, + { + {"babelfishpg_tsql.sql_dialect", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("Sets the dialect for SQL commands."), + NULL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_DISALLOW_IN_AUTO_FILE + }, + &sql_dialect, + SQL_DIALECT_PG, sql_dialect_options, + NULL, assign_sql_dialect, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL @@ -5824,6 +5856,12 @@ push_old_value(struct config_generic *gconf, GucAction action) { GucStack *stack; + if (guc_push_old_value_hook && strncmp(gconf->name, "babelfishpg_tsql", strlen("babelfishpg_tsql")) == 0) + { + guc_push_old_value_hook(gconf, action); + return; + } + /* If we're not inside a nest level, do nothing */ if (GUCNestLevel == 0) return; @@ -8545,6 +8583,9 @@ set_config_by_name(PG_FUNCTION_ARGS) else is_local = PG_GETARG_BOOL(2); + if (validate_set_config_function_hook) + validate_set_config_function_hook(name, value); + /* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */ (void) set_config_option(name, value, @@ -11955,6 +11996,7 @@ check_recovery_target_time(char **newval, void **extra, GucSource source) } } } + return true; } @@ -12061,4 +12103,22 @@ check_default_with_oids(bool *newval, void **extra, GucSource source) return true; } +void +guc_set_extra_field(struct config_generic *gconf, void **field, void *newval) +{ + set_extra_field(gconf, field, newval); +} + +void +guc_set_string_field(struct config_string *conf, char **field, char *newval) +{ + set_string_field(conf, field, newval); +} + +void +guc_set_stack_value(struct config_generic *gconf, config_var_value *val) +{ + set_stack_value(gconf, val); +} + #include "guc-file.c" diff --git a/src/backend/utils/misc/queryenvironment.c b/src/backend/utils/misc/queryenvironment.c index 31de81f353e..1e43f8bb13c 100644 --- a/src/backend/utils/misc/queryenvironment.c +++ b/src/backend/utils/misc/queryenvironment.c @@ -22,7 +22,20 @@ */ #include "postgres.h" +#include "access/htup.h" #include "access/table.h" +#include "access/tupdesc.h" +#include "access/htup_details.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_type.h" +#include "parser/parser.h" /* only needed for GUC variables */ +#include "utils/inval.h" +#include "utils/syscache.h" #include "utils/queryenvironment.h" #include "utils/rel.h" @@ -32,8 +45,20 @@ struct QueryEnvironment { List *namedRelList; + struct QueryEnvironment *parentEnv; + MemoryContext memctx; }; +struct QueryEnvironment topLevelQueryEnvData; +struct QueryEnvironment *topLevelQueryEnv = &topLevelQueryEnvData; +struct QueryEnvironment *currentQueryEnv = NULL; + +typedef enum ENRTupleOperationType +{ + ENR_OP_ADD, + ENR_OP_UPDATE, + ENR_OP_DROP +} ENRTupleOperationType; QueryEnvironment * create_queryEnv(void) @@ -41,6 +66,51 @@ create_queryEnv(void) return (QueryEnvironment *) palloc0(sizeof(QueryEnvironment)); } +/* + * Same as create_queryEnv but takes 2 additional arguments for the caller to + * indicate the desired memory context to use, and if this is the top level + * query environment it wants to create. + */ +QueryEnvironment * +create_queryEnv2(MemoryContext cxt, bool top_level) +{ + MemoryContext oldcxt; + QueryEnvironment *queryEnv; + + if (top_level) { + queryEnv = topLevelQueryEnv; + queryEnv->namedRelList = NIL; + queryEnv->parentEnv = NULL; + queryEnv->memctx = cxt; + } else { + oldcxt = MemoryContextSwitchTo(cxt); + queryEnv = (QueryEnvironment *) palloc0(sizeof(QueryEnvironment)); + queryEnv->parentEnv = currentQueryEnv; + queryEnv->memctx = cxt; + MemoryContextSwitchTo(oldcxt); + } + + currentQueryEnv = queryEnv; + return queryEnv; +} + +/* Remove the current query environment and make its parent current. */ +void remove_queryEnv() { + MemoryContext oldcxt; + QueryEnvironment *tmp; + + /* We should never "free" top level query env as it's in stack memory. */ + if (!currentQueryEnv || currentQueryEnv == topLevelQueryEnv) + return; + + tmp = currentQueryEnv->parentEnv; + oldcxt = MemoryContextSwitchTo(currentQueryEnv->memctx); + pfree(currentQueryEnv); + MemoryContextSwitchTo(oldcxt); + + currentQueryEnv = tmp; +} + EphemeralNamedRelationMetadata get_visible_ENR_metadata(QueryEnvironment *queryEnv, const char *refname) { @@ -52,7 +122,6 @@ get_visible_ENR_metadata(QueryEnvironment *queryEnv, const char *refname) return NULL; enr = get_ENR(queryEnv, refname); - if (enr) return &(enr->md); @@ -88,6 +157,14 @@ unregister_ENR(QueryEnvironment *queryEnv, const char *name) queryEnv->namedRelList = list_delete(queryEnv->namedRelList, match); } +/* + * Return the list of ENRs registered in the current query environment. + */ +List *get_namedRelList() +{ + return currentQueryEnv->namedRelList; +} + /* * This returns an ENR if there is a name match in the given collection. It * must quietly return NULL if no match is found. @@ -113,6 +190,28 @@ get_ENR(QueryEnvironment *queryEnv, const char *name) return NULL; } +/* + * Same as get_ENR() but just search for relation oid + */ +EphemeralNamedRelation +get_ENR_withoid(QueryEnvironment *queryEnv, Oid id) +{ + ListCell *lc; + + if (queryEnv == NULL) + return NULL; + + foreach(lc, queryEnv->namedRelList) + { + EphemeralNamedRelation enr = (EphemeralNamedRelation) lfirst(lc); + + if (enr->md.reliddesc == id) + return enr; + } + + return NULL; +} + /* * Gets the TupleDesc for a Ephemeral Named Relation, based on which field was * filled. @@ -142,3 +241,508 @@ ENRMetadataGetTupDesc(EphemeralNamedRelationMetadata enrmd) return tupdesc; } + +/* + * Get the starting tuple (or more precisely, a ListCell that contains the tuple) + * for systable scan functions based on the given keys. + * + * Returns true if we have found a qualified tuple and stored in *tuplist and *tuplist_i. + */ +bool ENRgetSystableScan(Relation rel, Oid indexId, int nkeys, ScanKey key, List **tuplist, int *tuplist_i) +{ + QueryEnvironment *queryEnv = currentQueryEnv; + Oid reloid; + Datum v1 = 0, v2 = 0, v3 = 0, v4 = 0; + + if (sql_dialect != SQL_DIALECT_TSQL) + return false; + + reloid = RelationGetRelid(rel); + + if (reloid != RelationRelationId && reloid != TypeRelationId && + reloid != AttributeRelationId && + reloid != ConstraintRelationId && + reloid != StatisticRelationId && + reloid != StatisticExtRelationId) + return NULL; + + switch (nkeys) { + case 4: + v4 = key[3].sk_argument; + v3 = key[2].sk_argument; + v2 = key[1].sk_argument; + v1 = key[0].sk_argument; + break; + case 3: + v3 = key[2].sk_argument; + v2 = key[1].sk_argument; + v1 = key[0].sk_argument; + break; + case 2: + v2 = key[1].sk_argument; + v1 = key[0].sk_argument; + break; + case 1: + v1 = key[0].sk_argument; + break; + default: + break; + } + if (!v1 && !v2 && !v3 && !v4) + return false; + + while (queryEnv) + { + ListCell *lc; + + foreach(lc, queryEnv->namedRelList) + { + EphemeralNamedRelation enr = (EphemeralNamedRelation) lfirst(lc); + if (enr->md.enrtype != ENR_TSQL_TEMP) + continue; + + if (reloid == RelationRelationId) { + if (indexId == ClassOidIndexId) { + if (enr->md.reliddesc == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_CLASS]; + *tuplist_i = 0; + return true; + } + } + else if (indexId == ClassNameNspIndexId) { + if (enr->md.name && strcmp(enr->md.name, (char*)v1) == 0) { + *tuplist = enr->md.cattups[ENR_CATTUP_CLASS]; + *tuplist_i = 0; + return true; + } + } + } + else if (reloid == TypeRelationId) + { + ListCell *type_lc = list_head(enr->md.cattups[ENR_CATTUP_TYPE]); + ListCell *arraytype_lc = list_head(enr->md.cattups[ENR_CATTUP_ARRAYTYPE]); + if (indexId == TypeOidIndexId) { + /* Composite type */ + if (type_lc && ((Form_pg_type) GETSTRUCT((HeapTuple)lfirst(type_lc)))->oid == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_TYPE]; + *tuplist_i = 0; + return true; + } + /* Array type */ + else if (arraytype_lc && ((Form_pg_type) GETSTRUCT((HeapTuple)lfirst(arraytype_lc)))->oid == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_ARRAYTYPE]; + *tuplist_i = 0; + return true; + } + } else if (indexId == TypeNameNspIndexId) { + /* Composite type */ + if (type_lc && strcmp(((Form_pg_type) GETSTRUCT((HeapTuple)lfirst(type_lc)))->typname.data, (char*)v1) == 0) { + *tuplist = enr->md.cattups[ENR_CATTUP_TYPE]; + *tuplist_i = 0; + return true; + } + /* Array type */ + else if (arraytype_lc && strcmp(((Form_pg_type) GETSTRUCT((HeapTuple)lfirst(arraytype_lc)))->typname.data, (char*)v1) == 0) { + *tuplist = enr->md.cattups[ENR_CATTUP_ARRAYTYPE]; + *tuplist_i = 0; + return true; + } + } + } + else if (reloid == AttributeRelationId) + { + ListCell *lc2; + if (enr->md.reliddesc == (Oid)v1) + { + if (nkeys == 1) { + *tuplist = enr->md.cattups[ENR_CATTUP_ATTRIBUTE]; + *tuplist_i = 0; + return true; + } + foreach(lc2, enr->md.cattups[ENR_CATTUP_ATTRIBUTE]) { + Form_pg_attribute tupform = (Form_pg_attribute) GETSTRUCT((HeapTuple) lfirst(lc2)); + if (indexId == AttributeRelidNumIndexId && (int)v2 <= tupform->attnum) { + *tuplist = enr->md.cattups[ENR_CATTUP_ATTRIBUTE]; + *tuplist_i = foreach_current_index(lc2); + return true; + } else if (indexId == AttributeRelidNameIndexId && strcmp((char*)v2, tupform->attname.data) == 0) { + *tuplist = enr->md.cattups[ENR_CATTUP_ATTRIBUTE]; + *tuplist_i = foreach_current_index(lc2); + return true; + } + } + } + } + else if (reloid == ConstraintRelationId) + { + /* + * XXX: There are multiple combinations of search keys for ConstraintRelidTypidNameIndexId + * but we seem to only need one. Ideally we should support all. + */ + if (nkeys == 1 && indexId == ConstraintRelidTypidNameIndexId) + { + if (enr->md.reliddesc == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_CONSTRAINT]; + *tuplist_i = 0; + return true; + } + } + else if (indexId == ConstraintOidIndexId) { + ListCell *lc2; + foreach(lc2, enr->md.cattups[ENR_CATTUP_CONSTRAINT]) { + Form_pg_constraint tupform = (Form_pg_constraint) GETSTRUCT((HeapTuple) lfirst(lc2)); + if (indexId == ConstraintOidIndexId && tupform->oid == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_CONSTRAINT]; + *tuplist_i = foreach_current_index(lc2); + return true; + } else if (indexId == ConstraintTypidIndexId && tupform->contypid == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_CONSTRAINT]; + *tuplist_i = foreach_current_index(lc2); + return true; + } else if (indexId == ConstraintParentIndexId && tupform->conparentid == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_CONSTRAINT]; + *tuplist_i = foreach_current_index(lc2); + return true; + } else if (indexId == ConstraintNameNspIndexId && strcmp(tupform->conname.data, (char*)v1) == 0) { + *tuplist = enr->md.cattups[ENR_CATTUP_CONSTRAINT]; + *tuplist_i = foreach_current_index(lc2); + return true; + } + } + } + } + else if (reloid == StatisticRelationId) + { + /* + * pg_statistic has only one index StatisticRelidAttnumInhIndexId + * which always has the relation id (starelid) as the first key. + */ + if (enr->md.reliddesc != (Oid)v1) + return false; + + if (nkeys == 1) { + *tuplist = enr->md.cattups[ENR_CATTUP_STATISTIC]; + *tuplist_i = 0; + return true; + } else { + ListCell *lc2; + foreach(lc2, enr->md.cattups[ENR_CATTUP_STATISTIC]) { + Form_pg_statistic tupform = (Form_pg_statistic) GETSTRUCT((HeapTuple) lfirst(lc2)); + if (tupform->staattnum == (int16)v2) { + *tuplist = enr->md.cattups[ENR_CATTUP_STATISTIC]; + *tuplist_i = foreach_current_index(lc2); + return true; + } + } + } + } + else if (reloid == StatisticExtRelationId) + { + ListCell *lc2; + if (indexId == StatisticExtRelidIndexId && enr->md.reliddesc == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_STATISTIC_EXT]; + *tuplist_i = 0; + return true; + } + + foreach(lc2, enr->md.cattups[ENR_CATTUP_STATISTIC_EXT]) { + Form_pg_statistic_ext tupform = (Form_pg_statistic_ext) GETSTRUCT((HeapTuple) lfirst(lc2)); + if (indexId == StatisticExtOidIndexId && tupform->oid == (Oid)v1) { + *tuplist = enr->md.cattups[ENR_CATTUP_STATISTIC_EXT]; + *tuplist_i = 0; + return true; + } + else if (indexId == StatisticExtNameIndexId && strcmp(tupform->stxname.data, (char*)v1)) { + *tuplist = enr->md.cattups[ENR_CATTUP_STATISTIC_EXT]; + *tuplist_i = 0; + return true; + } + } + } + } + queryEnv = queryEnv->parentEnv; + } + return false; +} + +/* + * Workhorse for add/update/drop tuples in the ENR. + * + * Return true if the asked operation is done. + * Return false if the asked operation is not possible. + */ +static bool _ENR_tuple_operation(Relation catalog_rel, HeapTuple tup, ENRTupleOperationType op) +{ + EphemeralNamedRelation enr = NULL; + HeapTuple oldtup, newtup; + MemoryContext oldcxt; + Oid catalog_oid, rel_oid; + HeapTuple tmp; + bool ret = false; + List **list_ptr = NULL; + ListCell *lc = NULL; + QueryEnvironment *queryEnv = currentQueryEnv; + int insert_at = 0; + + if (sql_dialect != SQL_DIALECT_TSQL) + return false; + + catalog_oid = RelationGetRelid(catalog_rel); + + while (queryEnv && !ret) + { + switch (catalog_oid) { + case RelationRelationId: + rel_oid = ((Form_pg_class) GETSTRUCT(tup))->oid; + if ((enr = get_ENR_withoid(queryEnv, rel_oid))) { + list_ptr = &enr->md.cattups[ENR_CATTUP_CLASS]; + lc = list_head(enr->md.cattups[ENR_CATTUP_CLASS]); + ret = true; + } + break; + case TypeRelationId: + /* Composite type */ + if (((Form_pg_type) GETSTRUCT(tup))->typelem == InvalidOid) { + rel_oid = ((Form_pg_type) GETSTRUCT(tup))->typrelid; + if ((enr = get_ENR_withoid(queryEnv, rel_oid))) { + list_ptr = &enr->md.cattups[ENR_CATTUP_TYPE]; + lc = list_head(enr->md.cattups[ENR_CATTUP_TYPE]); + ret = true; + } + /* Array type */ + } else { + /* + * Arraytype tuple is a bit special since it doesn't carry the + * relation OID but it carries the relation's composite type OID. + */ + ListCell *curlc; + foreach(curlc, queryEnv->namedRelList) { + EphemeralNamedRelation tmp_enr; + ListCell *type_lc; + + tmp_enr = (EphemeralNamedRelation) lfirst(curlc); + type_lc = list_head(tmp_enr->md.cattups[ENR_CATTUP_TYPE]); + if (type_lc && ((Form_pg_type) GETSTRUCT((HeapTuple)lfirst(type_lc)))->oid + == ((Form_pg_type) GETSTRUCT(tup))->typelem) { + enr = tmp_enr; + break; + } + } + if (enr) { + list_ptr = &enr->md.cattups[ENR_CATTUP_ARRAYTYPE]; + lc = list_head(enr->md.cattups[ENR_CATTUP_ARRAYTYPE]); + ret = true; + } + } + break; + case AttributeRelationId: + rel_oid = ((Form_pg_attribute) GETSTRUCT(tup))->attrelid; + if ((enr = get_ENR_withoid(queryEnv, rel_oid))) { + ListCell *curlc; + Form_pg_attribute tf1, tf2; /* tuple forms*/ + + list_ptr = &enr->md.cattups[ENR_CATTUP_ATTRIBUTE]; + tf1 = (Form_pg_attribute) GETSTRUCT((HeapTuple)tup); + foreach(curlc, enr->md.cattups[ENR_CATTUP_ATTRIBUTE]) { + tf2 = (Form_pg_attribute) GETSTRUCT((HeapTuple)lfirst(curlc)); + /* + * The attributes tuples are sorted increasingly based + * on the attribute number. However, we should keep + * user attributes(attnum>0) in front of system + * attributes just like how they appear in pg_attributes. + */ + if ((tf1->attnum > 0 && (tf2->attnum >= tf1->attnum || tf2->attnum <= 0)) || + (tf1->attnum <= 0 && tf2->attnum <= tf1->attnum)) { + lc = curlc; + insert_at = foreach_current_index(curlc); + break; + } + insert_at = foreach_current_index(curlc) + 1; + } + ret = true; + } + break; + case ConstraintRelationId: + rel_oid = ((Form_pg_constraint) GETSTRUCT(tup))->conrelid; + if ((enr = get_ENR_withoid(queryEnv, rel_oid))) { + Form_pg_constraint tf1, tf2; /* tuple forms*/ + ListCell *curlc; + + list_ptr = &enr->md.cattups[ENR_CATTUP_CONSTRAINT]; + tf1 = (Form_pg_constraint) GETSTRUCT(tup); + foreach(curlc, enr->md.cattups[ENR_CATTUP_CONSTRAINT]) { + tf2 = (Form_pg_constraint) GETSTRUCT((HeapTuple) lfirst(curlc)); + if (tf2->oid >= tf1->oid) { + lc = curlc; + insert_at = foreach_current_index(curlc) + 1; + break; + } + } + ret = true; + } + break; + case StatisticRelationId: + rel_oid = ((Form_pg_statistic) GETSTRUCT(tup))->starelid; + if ((enr = get_ENR_withoid(queryEnv, rel_oid))) { + Form_pg_statistic tf1, tf2; /* tuple forms*/ + ListCell *curlc; + + list_ptr = &enr->md.cattups[ENR_CATTUP_STATISTIC]; + tf1 = (Form_pg_statistic) GETSTRUCT(tup); + foreach(curlc, enr->md.cattups[ENR_CATTUP_STATISTIC]) { + tf2 = (Form_pg_statistic) GETSTRUCT((HeapTuple) lfirst(curlc)); + if (tf2->staattnum >= tf1->staattnum) { + lc = curlc; + insert_at = foreach_current_index(curlc) + 1; + break; + } + } + ret = true; + } + break; + case StatisticExtRelationId: + rel_oid = ((Form_pg_statistic_ext) GETSTRUCT(tup))->stxrelid; + if ((enr = get_ENR_withoid(queryEnv, rel_oid))) { + Form_pg_statistic_ext tf1, tf2; /* tuple forms*/ + ListCell *curlc; + + list_ptr = &enr->md.cattups[ENR_CATTUP_STATISTIC_EXT]; + tf1 = (Form_pg_statistic_ext) GETSTRUCT(tup); + foreach(curlc, enr->md.cattups[ENR_CATTUP_STATISTIC_EXT]) { + tf2 = (Form_pg_statistic_ext) GETSTRUCT((HeapTuple) lfirst(curlc)); + if (tf2->oid >= tf1->oid) { + lc = curlc; + insert_at = foreach_current_index(curlc) + 1; + break; + } + } + ret = true; + } + break; + default: + break; + } + /* add/drop/update is possible. Do it now. */ + if (ret) { + Assert(queryEnv->memctx); + oldcxt = MemoryContextSwitchTo(queryEnv->memctx); + switch (op) { + case ENR_OP_ADD: + newtup = heap_copytuple(tup); + *list_ptr = list_insert_nth(*list_ptr, insert_at, newtup); + CacheInvalidateHeapTuple(catalog_rel, newtup, NULL); + break; + case ENR_OP_UPDATE: + oldtup = lfirst(lc); + lfirst(lc) = heap_copytuple(tup); + CacheInvalidateHeapTuple(catalog_rel, oldtup, tup); + break; + case ENR_OP_DROP: + tmp = lfirst(lc); + *list_ptr = list_delete_ptr(*list_ptr, tmp); + CacheInvalidateHeapTuple(catalog_rel, tup, NULL); + heap_freetuple(tmp); + break; + default: + break; + } + MemoryContextSwitchTo(oldcxt); + } + queryEnv = queryEnv->parentEnv; + } + + return ret; +} + +/* + * Add tuple to an ENR. It assumes that an ENR entry has been created with + * the relation name and relation oid. + */ +bool ENRaddTuple(Relation rel, HeapTuple tup) +{ + return _ENR_tuple_operation(rel, tup, ENR_OP_ADD); +} + +/* + * Drop tuple of an ENR. + * We shouldn't assume the origin of the input tuples (i.e. whether it comes + * from the ENR itself) so we need to search in ENR based on the given tuple. + */ +bool ENRdropTuple(Relation rel, HeapTuple tup) +{ + return _ENR_tuple_operation(rel, tup, ENR_OP_DROP); +} + +/* + * Update tuple of an ENR. + */ +bool ENRupdateTuple(Relation rel, HeapTuple tup) +{ + return _ENR_tuple_operation(rel, tup, ENR_OP_UPDATE); +} + +/* + * Drop an ENR entry and delete it from the registered list. + */ +void ENRDropEntry(Oid id) +{ + EphemeralNamedRelation enr; + MemoryContext oldcxt; + + if (sql_dialect != SQL_DIALECT_TSQL || !currentQueryEnv) + return; + + if ((enr = get_ENR_withoid(currentQueryEnv, id)) == NULL) + return; + + oldcxt = MemoryContextSwitchTo(currentQueryEnv->memctx); + currentQueryEnv->namedRelList = list_delete(currentQueryEnv->namedRelList, enr); + pfree(enr->md.name); + pfree(enr); + MemoryContextSwitchTo(oldcxt); +} + +/* + * Drop all the temp tables registered as ENR in the given query environment. + */ +void +ENRDropTempTables(QueryEnvironment *queryEnv) +{ + ListCell *lc = NULL; + ObjectAddress object; + + if (!queryEnv) + return; + + object.classId = RelationRelationId; + object.objectSubId = 0; + + /* + * Loop through the registered ENRs to drop temp tables. + */ + while (lc || (lc = list_head(queryEnv->namedRelList))) + { + EphemeralNamedRelation enr; + + enr = (EphemeralNamedRelation) lfirst(lc); + + if (enr->md.enrtype != ENR_TSQL_TEMP) { + lc = lnext(queryEnv->namedRelList, lc); + if (!lc) + break; + else + continue; + } + + /* + * performDeletion() will remove the table AND the ENR entry, so no + * need to remove the entry afterwards. + */ + + object.objectId = enr->md.reliddesc; + performDeletion(&object, DROP_CASCADE, PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY); + + /* Reset lc so we can read the head of the list */ + lc = NULL; + } +} diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 3c836f22936..0e1817beef4 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -123,6 +123,7 @@ static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_ static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch); +static backslashResult exec_command_tsql(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch, @@ -391,6 +392,8 @@ exec_command(const char *cmd, status = exec_command_T(scan_state, active_branch); else if (strcmp(cmd, "timing") == 0) status = exec_command_timing(scan_state, active_branch); + else if (strcmp(cmd, "tsql") == 0) + status = exec_command_tsql(scan_state, active_branch); else if (strcmp(cmd, "unset") == 0) status = exec_command_unset(scan_state, active_branch, cmd); else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) @@ -2499,6 +2502,40 @@ exec_command_timing(PsqlScanState scan_state, bool active_branch) return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } +/* + * \tsql - enable/disable TSQL features (currently changes + * end-of-batch indicator from ';' to GO) + */ +static backslashResult +exec_command_tsql(PsqlScanState scan_state, bool active_branch) +{ + bool success = true; + + if (active_branch) + { + char *opt = psql_scan_slash_option(scan_state, + OT_NORMAL, NULL, false); + + if (opt) + success = ParseVariableBool(opt, "\\tsql", &pset.tsql); + else + pset.tsql = !pset.tsql; + + if (!pset.quiet) + { + if (pset.tsql) + puts(_("TSQL features are enabled.")); + else + puts(_("TSQL features are disabled.")); + } + free(opt); + } + else + ignore_slash_options(scan_state); + + return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; +} + /* * \unset -- unset variable */ diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c index 7abe016e403..45c77cb4c8f 100644 --- a/src/bin/psql/mainloop.c +++ b/src/bin/psql/mainloop.c @@ -381,8 +381,8 @@ MainLoop(FILE *source) /* * Parse line, looking for command separators. */ - psql_scan_setup(scan_state, line, strlen(line), - pset.encoding, standard_strings()); + psql_scan_setup_ex(scan_state, line, strlen(line), + pset.encoding, standard_strings(), pset.tsql); success = true; line_saved_in_history = false; @@ -547,8 +547,8 @@ MainLoop(FILE *source) resetPQExpBuffer(query_buf); /* reset parsing state since we are rescanning whole line */ psql_scan_reset(scan_state); - psql_scan_setup(scan_state, line, strlen(line), - pset.encoding, standard_strings()); + psql_scan_setup_ex(scan_state, line, strlen(line), + pset.encoding, standard_strings(), pset.tsql); line_saved_in_history = false; prompt_status = PROMPT_READY; /* we'll want to redisplay after parsing what we have */ diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h index 97941aa10c6..2a76094de24 100644 --- a/src/bin/psql/settings.h +++ b/src/bin/psql/settings.h @@ -112,7 +112,7 @@ typedef struct _psqlSettings uint64 stmt_lineno; /* line number inside the current statement */ bool timing; /* enable timing of all queries */ - + bool tsql; /* enable TSQL features */ FILE *logfile; /* session log file handle */ VariableSpace vars; /* "shell variable" repository */ diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 392b96eb862..946188626cc 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -381,9 +381,9 @@ main(int argc, char *argv[]) puts(cell->val); scan_state = psql_scan_create(&psqlscan_callbacks); - psql_scan_setup(scan_state, + psql_scan_setup_ex(scan_state, cell->val, strlen(cell->val), - pset.encoding, standard_strings()); + pset.encoding, standard_strings(), pset.tsql); cond_stack = conditional_stack_create(); psql_scan_set_passthrough(scan_state, (void *) cond_stack); diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l index 08dffde1ba0..a098ef761e5 100644 --- a/src/fe_utils/psqlscan.l +++ b/src/fe_utils/psqlscan.l @@ -645,14 +645,41 @@ other . ";" { ECHO; - if (cur_state->paren_depth == 0) + + if (cur_state->tsql == false) { + if (cur_state->paren_depth == 0) + { /* Terminate lexing temporarily */ cur_state->start_state = YY_START; return LEXRES_SEMI; + } } } + /* + * [BABEL-2076] The TSQL enhancement GO feature for PSQL client is not handled + * properly with the new exclusive state xqs in the psql scanner and is failing. + * several tests in installcheck babelfish. The Temporary fix + * is to remove ^ from the pattern. This makes GO a terminator even if it + * doesn't start at a newline, it's probably OK since this feature is just + * for testing purpose. + */ +[Gg][Oo] { + if (cur_state->tsql == true) + { + if (cur_state->paren_depth == 0) + { + /* Terminate lexing temporarily */ + cur_state->start_state = YY_START; + return LEXRES_SEMI; + } + } + else + ECHO; + } + + /* * psql-specific rules to handle backslash commands and variable * substitution. We want these before {self}, also. @@ -958,10 +985,28 @@ psql_scan_set_passthrough(PsqlScanState state, void *passthrough) * encoding is the libpq identifier for the character encoding in use, * and std_strings says whether standard_conforming_strings is on. */ + +/* + * NOTE: for BABEL-120, we must add another argument (tsql) to this + * this function. To avoid future merge conflicts, we leave + * the signature alone and just add a wrapper function that + * invokes the original (5-argument) function. This might + * seem unnecessary, but keep in mind that psql_scan_setup() + * is used by pgbench, not just psql. + */ + void psql_scan_setup(PsqlScanState state, const char *line, int line_len, int encoding, bool std_strings) +{ + psql_scan_setup_ex(state, line, line_len, encoding, std_strings, false); +} + +void +psql_scan_setup_ex(PsqlScanState state, + const char *line, int line_len, + int encoding, bool std_strings, bool tsql) { /* Mustn't be scanning already */ Assert(state->scanbufhandle == NULL); @@ -982,6 +1027,9 @@ psql_scan_setup(PsqlScanState state, /* Set lookaside data in case we have to map unsafe encoding */ state->curline = state->scanbuf; state->refline = state->scanline; + + /* Remember whether we are in TSQL mode */ + state->tsql = tsql; } /* diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h index 6f0258831f7..fe77139a78c 100644 --- a/src/include/access/relscan.h +++ b/src/include/access/relscan.h @@ -171,6 +171,9 @@ typedef struct SysScanDescData struct IndexScanDescData *iscan; /* only valid in index-scan case */ struct SnapshotData *snapshot; /* snapshot to unregister at end of scan */ struct TupleTableSlot *slot; + bool enr; + List *enr_tuplist; + int enr_tuplist_i; } SysScanDescData; #endif /* RELSCAN_H */ diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 3c66119d944..d632f1a1744 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -460,4 +460,13 @@ extern void EnterParallelMode(void); extern void ExitParallelMode(void); extern bool IsInParallelMode(void); +extern bool IsTopTransactionName(const char *name); +extern void SetTopTransactionName(const char *name); +extern bool IsTransactionBlockActive(void); +extern void RollbackAndReleaseSavepoint(const char *name); + +/* Nested transaction count */ +extern uint32 NestedTranCount; +extern bool AbortCurTransaction; + #endif /* XACT_H */ diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h index 7824a123c29..bc44d1799ad 100644 --- a/src/include/catalog/catalog.h +++ b/src/include/catalog/catalog.h @@ -39,4 +39,7 @@ extern Oid GetNewOidWithIndex(Relation relation, Oid indexId, extern Oid GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence); +typedef bool (*IsExtendedCatalogHookType) (Oid relationId); +extern IsExtendedCatalogHookType IsExtendedCatalogHook; + #endif /* CATALOG_H */ diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 7fe3fb7e7cf..ccae476ec11 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -145,6 +145,7 @@ typedef enum ObjectClass #define PERFORM_DELETION_SKIP_EXTENSIONS 0x0010 /* keep extensions */ #define PERFORM_DELETION_CONCURRENT_LOCK 0x0020 /* normal drop with * concurrent lock mode */ +#define PERFORM_DELETION_SKIP_ENR 0x0040 /* Skip ENR-supported catalogs*/ /* in dependency.c */ diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 2456c08bf78..25f43024efe 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -75,6 +75,12 @@ typedef enum RVROption typedef void (*RangeVarGetRelidCallback) (const RangeVar *relation, Oid relId, Oid oldRelId, void *callback_arg); +/* + * Hook for additional temporary relation lookup + */ +typedef Oid (*relname_lookup_hook_type) (const char *relname, Oid relnamespace); +extern PGDLLIMPORT relname_lookup_hook_type relname_lookup_hook; + #define RangeVarGetRelid(relation, lockmode, missing_ok) \ RangeVarGetRelidExtended(relation, lockmode, \ (missing_ok) ? RVR_MISSING_OK : 0, NULL, NULL) @@ -93,6 +99,7 @@ extern bool RelationIsVisible(Oid relid); extern Oid TypenameGetTypid(const char *typname); extern Oid TypenameGetTypidExtended(const char *typname, bool temp_ok); +extern Oid typenameGetSchemaOID(const char *typname, bool temp_ok); extern bool TypeIsVisible(Oid typid); extern FuncCandidateList FuncnameGetCandidates(List *names, diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h index 7f15efd58a8..0ebd054a739 100644 --- a/src/include/catalog/objectaddress.h +++ b/src/include/catalog/objectaddress.h @@ -81,4 +81,7 @@ extern struct ArrayType *strlist_to_textarray(List *list); extern ObjectType get_relkind_objtype(char relkind); +extern ObjectAddress get_object_address_trigger_tsql(List *object, + Relation *relp, bool missing_ok); + #endif /* OBJECTADDRESS_H */ diff --git a/src/include/catalog/pg_collation.h b/src/include/catalog/pg_collation.h index 6955bb12739..57c0ad8c893 100644 --- a/src/include/catalog/pg_collation.h +++ b/src/include/catalog/pg_collation.h @@ -69,5 +69,21 @@ extern Oid CollationCreate(const char *collname, Oid collnamespace, bool if_not_exists, bool quiet); extern void RemoveCollationById(Oid collationOid); +extern Oid CLUSTER_COLLATION_OID(void); + +/* Hook for plugins to get control in CLUSTER_COLLATION_OID() */ +typedef Oid (*CLUSTER_COLLATION_OID_hook_type)(void); +extern PGDLLIMPORT CLUSTER_COLLATION_OID_hook_type CLUSTER_COLLATION_OID_hook; + +typedef void (*PreCreateCollation_hook_type) (char collprovider, + bool collisdeterministic, + int32 collencoding, + const char **collcollate, /* The pointer may be modified */ + const char **collctype, /* The pointer may be modified */ + const char *collversion); +extern PGDLLIMPORT PreCreateCollation_hook_type PreCreateCollation_hook; + +typedef const char * (*TranslateCollation_hook_type) (const char *collname, Oid collnamespace, int32 encoding); +extern PGDLLIMPORT TranslateCollation_hook_type TranslateCollation_hook; #endif /* PG_COLLATION_H */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index c77c9a6ed5a..58a7348dfe4 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -62,6 +62,7 @@ extern void DropTransformById(Oid transformOid); extern void IsThereFunctionInNamespace(const char *proname, int pronargs, oidvector *proargtypes, Oid nspOid); extern void ExecuteDoStmt(DoStmt *stmt, bool atomic); +extern void ExecuteDoStmtInsertExec(DoStmt *stmt, bool atomic, DestReceiver *dest); extern void ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver *dest); extern TupleDesc CallStmtResultDesc(CallStmt *stmt); extern Oid get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok); @@ -77,6 +78,10 @@ extern void interpret_function_parameter_list(ParseState *pstate, Oid *variadicArgType, Oid *requiredResultType); +typedef bool (*check_lang_as_clause_hook_type)(const char *lang, List *as, char **prosrc_str_p, char **probin_str_p); +typedef void (*write_stored_proc_probin_hook_type)(CreateFunctionStmt *stmt, Oid languageOid, char** probin_str_p); +extern PGDLLIMPORT check_lang_as_clause_hook_type check_lang_as_clause_hook; +extern PGDLLIMPORT write_stored_proc_probin_hook_type write_stored_proc_probin_hook; /* commands/operatorcmds.c */ extern ObjectAddress DefineOperator(List *names, List *parameters); extern void RemoveOperatorById(Oid operOid); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index e2638abf0ee..4412f0e928a 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -22,6 +22,29 @@ #include "storage/relfilenode.h" +/* Sequence validate increment hook */ +typedef void (*pltsql_sequence_validate_increment_hook_type) (int64 increment_by, + int64 max_value, + int64 min_value); +extern PGDLLIMPORT pltsql_sequence_validate_increment_hook_type pltsql_sequence_validate_increment_hook; + +/* Sequence datatype hook */ +typedef void (*pltsql_sequence_datatype_hook_type) (ParseState *pstate, + Oid *newtypid, + bool for_identity, + DefElem *as_type, + DefElem **max_value, + DefElem **min_value); +extern PGDLLIMPORT pltsql_sequence_datatype_hook_type pltsql_sequence_datatype_hook; + +/* Sequence nextval hook */ +typedef void (*pltsql_nextval_hook_type) (Oid seqid, int64 val); +extern PGDLLIMPORT pltsql_nextval_hook_type pltsql_nextval_hook; + +/* Sequence reset cache hook */ +typedef void (*pltsql_resetcache_hook_type) (); +extern PGDLLIMPORT pltsql_resetcache_hook_type pltsql_resetcache_hook; + typedef struct FormData_pg_sequence_data { int64 last_value; diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index c1581ad178e..f33d4464914 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -20,6 +20,7 @@ #include "nodes/parsenodes.h" #include "storage/lock.h" #include "utils/relcache.h" +#include "parser/parse_node.h" struct AlterTableUtilityContext; /* avoid including tcop/utility.h here */ @@ -94,4 +95,13 @@ extern void RangeVarCallbackOwnsRelation(const RangeVar *relation, extern bool PartConstraintImpliedByRelConstraint(Relation scanrel, List *partConstraint); +typedef void (*InvokePreDropColumnHook_type) (Relation rel, AttrNumber attnum); +extern PGDLLIMPORT InvokePreDropColumnHook_type InvokePreDropColumnHook; + +typedef void (*InvokePreAddConstraintsHook_type) (Relation rel, ParseState *pstate, + List *newColDefaults); +extern PGDLLIMPORT InvokePreAddConstraintsHook_type InvokePreAddConstraintsHook; +typedef bool (*check_extended_attoptions_hook_type) (Node *options); +extern check_extended_attoptions_hook_type check_extended_attoptions_hook; + #endif /* TABLECMDS_H */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 40b81548764..460c0f48b7f 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -192,6 +192,8 @@ extern void ExecBSInsertTriggers(EState *estate, extern void ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture); +extern IOTState ExecISInsertTriggers(EState *estate, + ResultRelInfo *relinfo); extern bool ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot); @@ -208,6 +210,8 @@ extern void ExecBSDeleteTriggers(EState *estate, extern void ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture); +extern IOTState ExecISDeleteTriggers(EState *estate, + ResultRelInfo *relinfo); extern bool ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, @@ -227,6 +231,8 @@ extern void ExecBSUpdateTriggers(EState *estate, extern void ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture); +extern IOTState ExecISUpdateTriggers(EState *estate, + ResultRelInfo *relinfo); extern bool ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, @@ -279,4 +285,7 @@ extern void RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, extern int RI_FKey_trigger_type(Oid tgfoid); +extern void BeginCompositeTriggers(MemoryContext curCxt); +extern void EndCompositeTriggers(bool error); + #endif /* TRIGGER_H */ diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index 23130895af4..f79ea8d8b03 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -56,4 +56,5 @@ extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, extern ObjectAddress AlterType(AlterTypeStmt *stmt); +extern bool enable_domain_typmod; #endif /* TYPECMDS_H */ diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 06de20ada5e..07109da74e6 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -155,6 +155,8 @@ extern void SPI_cursor_fetch(Portal portal, bool forward, long count); extern void SPI_cursor_move(Portal portal, bool forward, long count); extern void SPI_scroll_cursor_fetch(Portal, FetchDirection direction, long count); extern void SPI_scroll_cursor_move(Portal, FetchDirection direction, long count); +extern void SPI_scroll_cursor_fetch_dest(Portal portal, FetchDirection direction, + long count, DestReceiver *receiver); extern void SPI_cursor_close(Portal portal); extern int SPI_register_relation(EphemeralNamedRelation enr); @@ -172,4 +174,5 @@ extern void AtEOXact_SPI(bool isCommit); extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid); extern bool SPI_inside_nonatomic_context(void); +extern void SPI_setCurrentInternalTxnMode(bool mode); #endif /* SPI_H */ diff --git a/src/include/fe_utils/psqlscan.h b/src/include/fe_utils/psqlscan.h index 91d1091bcc5..c06acbd4243 100644 --- a/src/include/fe_utils/psqlscan.h +++ b/src/include/fe_utils/psqlscan.h @@ -87,4 +87,8 @@ extern void psql_scan_reselect_sql_lexer(PsqlScanState state); extern bool psql_scan_in_quote(PsqlScanState state); +extern void psql_scan_setup_ex(PsqlScanState state, + const char *line, int line_len, + int encoding, bool std_strings, bool tsql); + #endif /* PSQLSCAN_H */ diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h index 311f80394a4..816272ee1bf 100644 --- a/src/include/fe_utils/psqlscan_int.h +++ b/src/include/fe_utils/psqlscan_int.h @@ -103,6 +103,7 @@ typedef struct PsqlScanStateData bool std_strings; /* are string literals standard? */ const char *curline; /* actual flex input string for cur buf */ const char *refline; /* original data for cur buffer */ + bool tsql; /* enable TSQL features? */ /* * All this state lives across successive input lines, until explicitly diff --git a/src/include/fmgr.h b/src/include/fmgr.h index d349510b7c7..182cd14d757 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -711,6 +711,8 @@ extern bool has_fn_opclass_options(FmgrInfo *flinfo); extern void set_fn_opclass_options(FmgrInfo *flinfo, bytea *options); extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid); +extern PGFunction lookup_C_func_by_oid(Oid fn_oid, char* probin, char* prosrc); + /* * Routines in dfmgr.c */ @@ -766,8 +768,11 @@ typedef bool (*needs_fmgr_hook_type) (Oid fn_oid); typedef void (*fmgr_hook_type) (FmgrHookEventType event, FmgrInfo *flinfo, Datum *arg); +typedef void (*non_tsql_proc_entry_hook_type) (int, int); + extern PGDLLIMPORT needs_fmgr_hook_type needs_fmgr_hook; extern PGDLLIMPORT fmgr_hook_type fmgr_hook; +extern PGDLLIMPORT non_tsql_proc_entry_hook_type non_tsql_proc_entry_hook; #define FmgrHookIsNeeded(fn_oid) \ (!needs_fmgr_hook ? false : (*needs_fmgr_hook)(fn_oid)) diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index fa778e11921..91656243dc8 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -64,8 +64,10 @@ typedef struct #endif /* ENABLE_SSPI */ #include "datatype/timestamp.h" +#include "lib/stringinfo.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" +#include "tcop/dest.h" typedef enum CAC_state @@ -94,6 +96,34 @@ typedef struct } pg_gssinfo; #endif +/* + * ProtocolExtensionConfig + * + * All the callbacks implementing a specific wire protocol + */ +typedef struct ProtocolExtensionConfig { + int (*fn_accept)(pgsocket server_fd, struct Port *port); + void (*fn_close)(pgsocket server_fd); + void (*fn_init)(void); + int (*fn_start)(struct Port *port); + void (*fn_authenticate)(struct Port *port, const char **username); + void (*fn_mainfunc)(struct Port *port, + int argc, char *argv[]) pg_attribute_noreturn(); + void (*fn_send_message)(ErrorData *edata); + void (*fn_send_cancel_key)(int pid, int32 key); + void (*fn_comm_reset)(void); + bool (*fn_is_reading_msg)(void); + void (*fn_send_ready_for_query)(CommandDest dest); + int (*fn_read_command)(StringInfo inBuf); + void (*fn_end_command)(QueryCompletion *qc, CommandDest dest); + bool (*fn_printtup)(TupleTableSlot *slot, DestReceiver *self); + void (*fn_printtup_startup)(DestReceiver *self, int operation, + TupleDesc typeinfo); + void (*fn_printtup_shutdown)(DestReceiver *self); + void (*fn_printtup_destroy)(DestReceiver *self); + int (*fn_process_command)(void); +} ProtocolExtensionConfig; + /* * This is used by the postmaster in its communication with frontends. It * contains all state information needed during this communication before the @@ -132,6 +162,8 @@ typedef struct Port char *remote_port; /* text rep of remote port */ CAC_state canAcceptConnections; /* postmaster connection status */ + ProtocolExtensionConfig *protocol_config; /* wire protocol functions */ + /* * Information that needs to be saved from the startup packet and passed * into backend execution. "char *" fields are NULL if not set. diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index b1152475ace..aeacae35979 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -65,6 +65,7 @@ extern void RemoveSocketFiles(void); extern void pq_init(void); extern int pq_getbytes(char *s, size_t len); extern int pq_getstring(StringInfo s); +extern int pq_getline(StringInfo s); extern void pq_startmsgread(void); extern void pq_endmsgread(void); extern bool pq_is_reading_msg(void); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 6b2b4343a0d..bc7b8f3efa7 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -458,6 +458,7 @@ extern void InitializeMaxBackends(void); extern void InitPostgres(const char *in_dbname, Oid dboid, const char *username, Oid useroid, char *out_dbname, bool override_allow_connections); extern void BaseInit(void); +extern void PerformAuthentication(struct Port *port); /* in utils/init/miscinit.c */ extern bool IgnoreSystemIndexes; @@ -473,6 +474,7 @@ extern void TouchSocketLockFiles(void); extern void AddToDataDirLockFile(int target_line, const char *str); extern bool RecheckDataDirLockFile(void); extern void ValidatePgVersion(const char *path); +extern void load_libraries(const char *libraries, const char *gucname, bool restricted); extern void process_shared_preload_libraries(void); extern void process_session_preload_libraries(void); extern void pg_bindtextdomain(const char *domain); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 3c6fecd2e1e..757fb62d352 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1166,6 +1166,19 @@ typedef struct ProjectSetState MemoryContext argcontext; /* context for SRF arguments */ } ProjectSetState; +/*------------------ + * Instead of trigger state information + * Contains the TSQL statement level IOT runtime state. + * ----------------- + */ +typedef enum +{ + IOT_NOT_FIRED, /* Instead of triggers not fired so far */ + IOT_NOT_REQUIRED, /* Attempted to fire TSQL IOT, but relation has none */ + IOT_FIRED, /* IOT fired for this statement */ + IOT_UNKNOWN +} IOTState; + /* ---------------- * ModifyTableState information * ---------------- @@ -1187,6 +1200,7 @@ typedef struct ModifyTableState List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */ EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */ bool fireBSTriggers; /* do we need to fire stmt triggers? */ + IOTState fireISTriggers; /* current state for instead stmt trigger */ /* * Slot for storing tuples in the root partitioned table's rowtype during @@ -1205,6 +1219,9 @@ typedef struct ModifyTableState /* Per plan map for tuple conversion from child to root */ TupleConversionMap **mt_per_subplan_tupconv_maps; + + /* CallStmt for INSERT ... EXECUTE */ + Node *callStmt; } ModifyTableState; /* ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 381d84b4e4f..1ed4f2a3391 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -517,7 +517,13 @@ typedef enum NodeTag T_SupportRequestSelectivity, /* in nodes/supportnodes.h */ T_SupportRequestCost, /* in nodes/supportnodes.h */ T_SupportRequestRows, /* in nodes/supportnodes.h */ - T_SupportRequestIndexCondition /* in nodes/supportnodes.h */ + T_SupportRequestIndexCondition, /* in nodes/supportnodes.h */ + + /* + * Additional TAG FOR VALUE NODES (value.h) + */ + T_TSQL_HexString + } NodeTag; /* diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 557074c2cf1..11f05f6fe78 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -442,6 +442,7 @@ typedef struct ResTarget List *indirection; /* subscripts, field names, and '*', or NIL */ Node *val; /* the value expression to compute or assign */ int location; /* token location, or -1 if unknown */ + int name_location; /* name token location, or -1 if unknown */ } ResTarget; /* @@ -1537,6 +1538,7 @@ typedef struct InsertStmt List *returningList; /* list of expressions to return */ WithClause *withClause; /* WITH clause */ OverridingKind override; /* OVERRIDING clause */ + Node *execStmt; /* for INSERT ... EXECUTE */ } InsertStmt; /* ---------------------- @@ -1764,6 +1766,7 @@ typedef struct CreateSchemaStmt RoleSpec *authrole; /* the owner of the created schema */ List *schemaElts; /* schema components (list of parsenodes) */ bool if_not_exists; /* just do nothing if schema already exists? */ + int location; /* token location, or -1 if unknown */ } CreateSchemaStmt; typedef enum DropBehavior @@ -2088,6 +2091,7 @@ typedef struct CreateStmt char *tablespacename; /* table space to use, or NULL */ char *accessMethod; /* table access method */ bool if_not_exists; /* just do nothing if it already exists? */ + bool tsql_tabletype; /* is this creating a tsql table type? */ } CreateStmt; /* ---------- @@ -2885,6 +2889,9 @@ typedef struct DoStmt { NodeTag type; List *args; /* List of DefElem nodes */ + /* for INSERT ... EXECUTE */ + Oid relation; /* INSERT target relation */ + List *attrnos; /* columns list by attnum */ } DoStmt; typedef struct InlineCodeBlock @@ -2894,6 +2901,10 @@ typedef struct InlineCodeBlock Oid langOid; /* OID of selected language */ bool langIsTrusted; /* trusted property of the language */ bool atomic; /* atomic execution context */ + /* for INSERT ... EXECUTE */ + Oid relation; /* INSERT target relation */ + List *attrnos; /* columns list by attnum */ + Node *dest; /* dest receiver */ } InlineCodeBlock; /* ---------------------- @@ -2905,6 +2916,17 @@ typedef struct CallStmt NodeTag type; FuncCall *funccall; /* from the parser */ FuncExpr *funcexpr; /* transformed */ + /* for INSERT ... EXECUTE */ + Oid relation; /* INSERT target relation */ + List *attrnos; /* columns list by attnum */ + /* + * for nested CallStmt under INSERT ... EXECUTE + * for example, in query "INSERT INTO t1 EXEC ('EXEC p1')" + * the CallStmt of the inner "EXEC p1" will have the following two fields + * filled in based on the outer "INSERT INTO t1 EXEC" + */ + void *retdesc; /* expected TupleDesc of the result rows */ + void *dest; /* DestReceiver to send the result rows */ } CallStmt; typedef struct CallContext diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 83e01074ed1..8140c8e6aab 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -239,6 +239,7 @@ typedef struct ModifyTable Node *onConflictWhere; /* WHERE for ON CONFLICT UPDATE */ Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */ List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */ + Node *callStmt; /* for INSERT ... EXECUTE */ } ModifyTable; struct PartitionPruneInfo; /* forward reference to struct below */ diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h index d4911b5d38f..7dc1568c1fd 100644 --- a/src/include/nodes/value.h +++ b/src/include/nodes/value.h @@ -57,5 +57,6 @@ extern Value *makeInteger(int i); extern Value *makeFloat(char *numericStr); extern Value *makeString(char *str); extern Value *makeBitString(char *str); +extern Value *makeTSQLHexString(char *str); #endif /* VALUE_H */ diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h index beb7dbbcbe8..6e9506c6c11 100644 --- a/src/include/optimizer/planner.h +++ b/src/include/optimizer/planner.h @@ -29,6 +29,12 @@ typedef PlannedStmt *(*planner_hook_type) (Query *parse, ParamListInfo boundParams); extern PGDLLIMPORT planner_hook_type planner_hook; +/* Hook for plugins to transform qual nodes in planner */ +typedef Node* (*planner_node_transformer_hook_type) (PlannerInfo *root, + Node *expr, + int kind); +extern PGDLLIMPORT planner_node_transformer_hook_type planner_node_transformer_hook; + /* Hook for plugins to get control when grouping_planner() plans upper rels */ typedef void (*create_upper_paths_hook_type) (PlannerInfo *root, UpperRelationKind stage, diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index d6a467a5728..a9ecf47d08d 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -21,6 +21,28 @@ typedef void (*post_parse_analyze_hook_type) (ParseState *pstate, Query *query); extern PGDLLIMPORT post_parse_analyze_hook_type post_parse_analyze_hook; +/* Hook for plugins to get control with the raw parse tree */ +typedef void (*pre_parse_analyze_hook_type) (ParseState *pstate, RawStmt *parseTree); + +extern PGDLLIMPORT pre_parse_analyze_hook_type pre_parse_analyze_hook; + +/* Hook to handle qualifiers in returning list for output clause */ +typedef void (*pre_transform_returning_hook_type) (CmdType command, + List *returningList, ParseState *pstate); + +extern PGDLLIMPORT pre_transform_returning_hook_type pre_transform_returning_hook; + +/* Hook to perform self-join transformation on UpdateStmt in output clause */ +typedef Node* (*pre_output_clause_transformation_hook_type) (ParseState *pstate, UpdateStmt *stmt, CmdType command); +extern PGDLLIMPORT pre_output_clause_transformation_hook_type pre_output_clause_transformation_hook; + +/* Hook to read a global variable with info on output clause */ +typedef bool (*get_output_clause_status_hook_type) (void); +extern PGDLLIMPORT get_output_clause_status_hook_type get_output_clause_status_hook; + +/* Hook for plugins to get control after an insert row transform */ +typedef void (*post_transform_insert_row_hook_type) (List *icolumns, List *exprList); +extern PGDLLIMPORT post_transform_insert_row_hook_type post_transform_insert_row_hook; extern Query *parse_analyze(RawStmt *parseTree, const char *sourceText, Oid *paramTypes, int numParams, QueryEnvironment *queryEnv); diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index 75a4fcfa598..d7598dc3726 100644 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -51,4 +51,7 @@ extern List *addTargetToSortList(ParseState *pstate, TargetEntry *tle, extern Index assignSortGroupRef(TargetEntry *tle, List *tlist); extern bool targetIsInSortList(TargetEntry *tle, Oid sortop, List *sortList); +typedef bool (*tle_name_comparison_hook_type)(const char *tlename, const char *identifier); +extern PGDLLIMPORT tle_name_comparison_hook_type tle_name_comparison_hook; + #endif /* PARSE_CLAUSE_H */ diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index 8686eaacbc9..28a1d22d917 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -94,4 +94,18 @@ extern CoercionPathType find_coercion_pathway(Oid targetTypeId, extern CoercionPathType find_typmod_coercion_function(Oid typeId, Oid *funcid); +/* + * Hook interface to check TSQL has implicit coercion path from sourceTypeId to targetTypeId + */ +typedef CoercionPathType (*find_coercion_pathway_hook_type) (Oid sourceTypeId, + Oid targetTypeId, + CoercionContext ccontext, + Oid *funcid); + +/* + * Hook interface to check TSQL datatype precedence + * Return true if typeId1 precedes typeId2 + */ +typedef bool (*determine_datatype_precedence_hook_type) (Oid typeId1, Oid typeId2); + #endif /* PARSE_COERCE_H */ diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h index b01d3b7dec5..f06246553a2 100644 --- a/src/include/parser/parse_expr.h +++ b/src/include/parser/parse_expr.h @@ -23,4 +23,8 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin extern const char *ParseExprKindName(ParseExprKind exprKind); +/* Hook for finding parameter definition */ +typedef Node * (*lookup_param_hook_type)(ParseState *pstate, ColumnRef *cref); +extern PGDLLIMPORT lookup_param_hook_type lookup_param_hook; + #endif /* PARSE_EXPR_H */ diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h index dd189f5452e..a4637f537b5 100644 --- a/src/include/parser/parse_func.h +++ b/src/include/parser/parse_func.h @@ -70,4 +70,12 @@ extern Oid LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, extern void check_srf_call_placement(ParseState *pstate, Node *last_srf, int location); +/* + * Hook interface to select a function from candidates + */ +typedef FuncCandidateList (*func_select_candidate_hook_type) (int nargs, Oid *input_typeids, FuncCandidateList candidates, bool unknowns_resolved); +/* Hook interface to process function arguments using probin */ +typedef void (*make_fn_arguments_from_stored_proc_probin_hook_type)(ParseState *pstate,List *fargs,Oid *actual_arg_types,Oid *declared_arg_types,Oid funcid); +extern PGDLLIMPORT make_fn_arguments_from_stored_proc_probin_hook_type make_fn_arguments_from_stored_proc_probin_hook; + #endif /* PARSE_FUNC_H */ diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h index 7039df29cbb..33ee0f430d0 100644 --- a/src/include/parser/parse_target.h +++ b/src/include/parser/parse_target.h @@ -43,4 +43,10 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var, extern char *FigureColname(Node *node); extern char *FigureIndexColname(Node *node); +typedef void (*pre_transform_target_entry_hook_type)(ResTarget *res, ParseState *pstate, ParseExprKind exprKind); +extern PGDLLIMPORT pre_transform_target_entry_hook_type pre_transform_target_entry_hook; + +typedef void (*resolve_target_list_unknowns_hook_type)(ParseState *pstate, List *targetlist); +extern PGDLLIMPORT resolve_target_list_unknowns_hook_type resolve_target_list_unknowns_hook; + #endif /* PARSE_TARGET_H */ diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h index bb83d7294a3..378be795810 100644 --- a/src/include/parser/parse_type.h +++ b/src/include/parser/parse_type.h @@ -57,4 +57,8 @@ extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, boo /* true if typeid is composite, or domain over composite, but not RECORD */ #define ISCOMPLEX(typeid) (typeOrDomainTypeRelid(typeid) != InvalidOid) +/* Hook to check/setup default typmod for sys.(N)(VAR)CHAR datatypes. */ +typedef void (*check_or_set_default_typmod_hook_type)(TypeName * typeName, int32 *typmod, bool is_cast); +extern PGDLLIMPORT check_or_set_default_typmod_hook_type check_or_set_default_typmod_hook; + #endif /* PARSE_TYPE_H */ diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index bc3d66ed881..20cea37139d 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -19,6 +19,13 @@ struct AttrMap; /* avoid including attmap.h here */ +/* IDENTITY datatype hook */ +typedef void (*pltsql_identity_datatype_hook_type) (ParseState *pstate, + ColumnDef *column); +extern PGDLLIMPORT pltsql_identity_datatype_hook_type pltsql_identity_datatype_hook; +typedef void (*post_transform_column_definition_hook_type) (ParseState *pstate, RangeVar* relation, ColumnDef *column, List **alist); +extern PGDLLIMPORT post_transform_column_definition_hook_type post_transform_column_definition_hook; + extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString); extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, const char *queryString, diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h index 3bdeeb8b0b1..2432302239a 100644 --- a/src/include/parser/parser.h +++ b/src/include/parser/parser.h @@ -25,11 +25,46 @@ typedef enum BACKSLASH_QUOTE_SAFE_ENCODING } BackslashQuoteType; +typedef enum +{ + SQL_DIALECT_PG, + SQL_DIALECT_TSQL +} SQLDialect; + +typedef enum +{ + TSQL_FORXML_RAW, + TSQL_FORXML_AUTO, + TSQL_FORXML_PATH, + TSQL_FORXML_EXPLICIT +} TSQLFORXMLMode; + +typedef enum +{ + TSQL_XML_DIRECTIVE_BINARY_BASE64, + TSQL_XML_DIRECTIVE_TYPE +} TSQLXMLDirective; + +typedef enum +{ + DATEFIRST_MONDAY, + DATEFIRST_TUESDAY, + DATEFIRST_WEDNESDAY, + DATEFIRST_THURSDAY, + DATEFIRST_FRIDAY, + DATEFIRST_SATURDAY, + DATEFIRST_SUNDAY +} DATEFIRST; + /* GUC variables in scan.l (every one of these is a bad idea :-() */ extern int backslash_quote; extern bool escape_string_warning; extern PGDLLIMPORT bool standard_conforming_strings; +extern int sql_dialect; +extern bool pltsql_case_insensitive_identifiers; +extern int datefirst; +extern char* pltsql_server_collation_name; /* Primary entry point for the raw parsing functions */ extern List *raw_parser(const char *str); @@ -38,4 +73,22 @@ extern List *raw_parser(const char *str); extern List *SystemFuncName(char *name); extern TypeName *SystemTypeName(char *name); + +/* Hook to extend backend parser */ +typedef List * (*raw_parser_hook_type) (const char *str); +extern PGDLLIMPORT raw_parser_hook_type raw_parser_hook; + +/* Hooks needed in grammar rule in gram.y */ +typedef List * (*rewrite_typmod_expr_hook_type) (List *expr_list); +extern PGDLLIMPORT rewrite_typmod_expr_hook_type rewrite_typmod_expr_hook; + +typedef void (*validate_numeric_typmods_hook_type) (List **typmods, bool isNumeric, void* yyscanner); +extern PGDLLIMPORT validate_numeric_typmods_hook_type validate_numeric_typmods_hook; + +typedef bool (*check_recursive_cte_hook_type) (WithClause *with_clause); +extern PGDLLIMPORT check_recursive_cte_hook_type check_recursive_cte_hook; + +#define TSQLMaxTypmod -8000 +#define TSQLMaxNumPrecision 38 +#define TSQLHexConstTypmod -16 #endif /* PARSER_H */ diff --git a/src/include/parser/scansup.h b/src/include/parser/scansup.h index 7a6ee529ae0..b4f8dd54b93 100644 --- a/src/include/parser/scansup.h +++ b/src/include/parser/scansup.h @@ -27,4 +27,7 @@ extern void truncate_identifier(char *ident, int len, bool warn); extern bool scanner_isspace(char ch); +typedef bool (*truncate_identifier_hook_type)(char *ident, int len, bool warn); +extern PGDLLIMPORT truncate_identifier_hook_type truncate_identifier_hook; + #endif /* SCANSUP_H */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index c55dc1481ca..7ff2c1e4e70 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -574,6 +574,17 @@ typedef struct PgStat_MsgChecksumFailure TimestampTz m_failure_time; } PgStat_MsgChecksumFailure; +/* ---------- + * PgStat_MsgROInconsistency Sent by the backend to tell the collector + * about an inconsistency that occurred. + * ---------- + */ +typedef struct PgStat_MsgROInconsistency +{ + PgStat_MsgHdr m_hdr; + bool m_corrected; + TimestampTz m_last_invalid_tuple; +} PgStat_MsgROInconsistency; /* ---------- * PgStat_Msg Union over all possible messages. @@ -1451,6 +1462,8 @@ extern void AtEOSubXact_PgStat(bool isCommit, int nestDepth); extern void AtPrepare_PgStat(void); extern void PostPrepare_PgStat(void); +extern void Cleanup_xact_PgStat(void); + extern void pgstat_twophase_postcommit(TransactionId xid, uint16 info, void *recdata, uint32 len); extern void pgstat_twophase_postabort(TransactionId xid, uint16 info, @@ -1484,4 +1497,14 @@ extern void pgstat_count_slru_truncate(int slru_idx); extern const char *pgstat_slru_name(int slru_idx); extern int pgstat_slru_index(const char *name); +/* + * Support functions for allowing call hooks for support stats from + * different protocols, and planning. + */ +extern void pg_stat_write_backend_details_NoAlloc(int outfile, PgBackendStatus *); +extern void write_structdef_file(void); + +typedef void (*pre_function_call_hook_type) (const char *funcName); +extern PGDLLIMPORT pre_function_call_hook_type pre_function_call_hook; + #endif /* PGSTAT_H */ diff --git a/src/include/port/pg_bswap.h b/src/include/port/pg_bswap.h index 9fd14d129ab..7015cd64d80 100644 --- a/src/include/port/pg_bswap.h +++ b/src/include/port/pg_bswap.h @@ -100,6 +100,25 @@ pg_bswap64(uint64 x) } #endif /* HAVE__BUILTIN_BSWAP64 */ +union num128as32 +{ + uint128 v; + uint32 d[4]; +}; + +static inline uint128 +pg_bswap128(uint128 num) +{ + union num128as32 u1, u2; + u1.v = num; + + u2.d[3] = pg_bswap32(u1.d[0]); + u2.d[2] = pg_bswap32(u1.d[1]); + u2.d[1] = pg_bswap32(u1.d[2]); + u2.d[0] = pg_bswap32(u1.d[3]); + + return u2.v; +} /* * Portable and fast equivalents for ntohs, ntohl, htons, htonl, @@ -110,20 +129,24 @@ pg_bswap64(uint64 x) #define pg_hton16(x) (x) #define pg_hton32(x) (x) #define pg_hton64(x) (x) +#define pg_hton128(x) (x) #define pg_ntoh16(x) (x) #define pg_ntoh32(x) (x) #define pg_ntoh64(x) (x) +#define pg_ntoh128(x) (x) #else #define pg_hton16(x) pg_bswap16(x) #define pg_hton32(x) pg_bswap32(x) #define pg_hton64(x) pg_bswap64(x) +#define pg_hton128(x) pg_bswap128(x) #define pg_ntoh16(x) pg_bswap16(x) #define pg_ntoh32(x) pg_bswap32(x) #define pg_ntoh64(x) pg_bswap64(x) +#define pg_ntoh128(x) pg_bswap128(x) #endif /* WORDS_BIGENDIAN */ diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index babc87dfc9d..6b3e655bc61 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -30,6 +30,9 @@ extern bool enable_bonjour; extern char *bonjour_name; extern bool restart_after_crash; +/* Global variables in pqcomm.c */ +extern bool IsNonLibpqClient; + #ifdef WIN32 extern HANDLE PostmasterHandle; #else diff --git a/src/include/postmaster/protocol_extension.h b/src/include/postmaster/protocol_extension.h new file mode 100644 index 00000000000..5a1358f4cf3 --- /dev/null +++ b/src/include/postmaster/protocol_extension.h @@ -0,0 +1,46 @@ +/*------------------------------------------------------------------------- + * + * protocol_extension.h + * Exports and definitions for Loadable Protocol Extensions + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/postmaster/protocol_extension.h + * + *------------------------------------------------------------------------- + */ +#ifndef _PROTOCOL_EXTENSION_H +#define _PROTOCOL_EXTENSION_H + +#include "libpq/libpq.h" + +/* + * hook function type for protocol extensions to register initialization + */ +typedef void (*listen_init_hook_type)(void); + +/* Globals in postmaster.c */ +extern listen_init_hook_type listen_init_hook; + +/* Functions in postmaster.c */ +extern int listen_have_free_slot(void); +extern void listen_add_socket(pgsocket fd, + ProtocolExtensionConfig *protocol_config); + +extern int libpq_accept(pgsocket server_fd, Port *port); +extern void libpq_close(pgsocket server_fd); +extern void libpq_init(void); +extern int libpq_start(Port *port); +extern void libpq_authenticate(Port *port, const char **username); +extern void libpq_mainfunc(Port *port, int argc, char *arvg[]) + pg_attribute_noreturn(); +extern void libpq_send_message(ErrorData *edata); +extern void libpq_send_cancel_key(int pid, int32 key); +extern void libpq_comm_reset(void); +extern bool libpq_is_reading_msg(void); +extern void libpq_send_ready_for_query(CommandDest dest); +extern int libpq_read_command(StringInfo inBuf); +extern void libpq_end_command(QueryCompletion *qc, CommandDest dest); + +#endif /* _PROTOCOL_EXTENSION_H */ diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index 2987c5e4589..0515632653b 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -127,6 +127,7 @@ typedef uint16 LOCKMETHODID; /* These identify the known lock methods */ #define DEFAULT_LOCKMETHOD 1 #define USER_LOCKMETHOD 2 +#define APPLOCK_LOCKMETHOD 3 /* * LOCKTAG is the key information needed to look up a LOCK item in the @@ -530,6 +531,10 @@ typedef enum #define LockHashPartitionLockByProc(leader_pgproc) \ LockHashPartitionLock((leader_pgproc)->pgprocno) +/* TSQL-only handler for releasing application lock. */ +typedef void (*applock_release_func_handler_type) (bool release_session); +extern applock_release_func_handler_type applock_release_func_handler; + /* * function prototypes */ diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index bd30607b070..ba66b317e62 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -76,10 +76,12 @@ extern void process_postgres_switches(int argc, char *argv[], extern void PostgresMain(int argc, char *argv[], const char *dbname, const char *username) pg_attribute_noreturn(); +extern int SocketBackendReadCommand(StringInfo inBuf); extern long get_stack_depth_rlimit(void); extern void ResetUsage(void); extern void ShowUsage(const char *title); extern int check_log_duration(char *msec_str, bool was_logged); +extern bool check_log_statement(List *stmt); extern void set_debug_options(int debug_flag, GucContext context, GucSource source); extern bool set_plan_disabling_options(const char *arg, diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index a5c8772e95f..92ca89a7b96 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -34,6 +34,7 @@ extern int errdomainconstraint(Oid datatypeOid, const char *conname); /* encode.c */ extern uint64 hex_encode(const char *src, size_t len, char *dst); extern uint64 hex_decode(const char *src, size_t len, char *dst); +extern uint64 hex_decode_allow_odd_digits(const char *src, unsigned len, char *dst); /* int.c */ extern int2vector *buildint2vector(const int16 *int2s, int n); @@ -124,4 +125,12 @@ extern int32 type_maximum_size(Oid type_oid, int32 typemod); /* quote.c */ extern char *quote_literal_cstr(const char *rawstr); +/* varchar.c */ +typedef bool (*suppress_string_truncation_error_hook_type)(); +extern PGDLLIMPORT suppress_string_truncation_error_hook_type suppress_string_truncation_error_hook; + +/* name.c */ +typedef Name (*cstr_to_name_hook_type)(char *s, int len); +extern PGDLLIMPORT cstr_to_name_hook_type cstr_to_name_hook; + #endif /* BUILTINS_H */ diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 1e09ee05418..1b9c7dc6baa 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -393,6 +393,9 @@ extern void pg_re_throw(void) pg_attribute_noreturn(); extern char *GetErrorContextStack(void); +/* libpq function for sending messages to client */ +extern void send_message_to_frontend(ErrorData *edata); + /* Hook for intercepting messages before they are sent to the server log */ typedef void (*emit_log_hook_type) (ErrorData *edata); extern PGDLLIMPORT emit_log_hook_type emit_log_hook; diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 28192821816..b43ea4199d3 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -435,6 +435,7 @@ extern void assign_temp_tablespaces(const char *newval, void *extra); /* in catalog/namespace.c */ extern bool check_search_path(char **newval, void **extra, GucSource source); extern void assign_search_path(const char *newval, void *extra); +extern void assign_sql_dialect(int newval, void *extra); /* in access/transam/xlog.c */ extern bool check_wal_buffers(int *newval, void **extra, GucSource source); diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 454c2df4878..1741c5cbf9b 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -164,6 +164,7 @@ struct config_generic char *sourcefile; /* file current setting is from (NULL if not * set in config file) */ int sourceline; /* line in source file */ + GucStack *session_stack; /* stacked prior value at session level */ }; /* bit values in status field */ @@ -269,4 +270,14 @@ extern bool config_enum_lookup_by_name(struct config_enum *record, const char *value, int *retval); extern struct config_generic **get_explain_guc_options(int *num); +extern void guc_set_string_field(struct config_string *conf, char **field, char *newval); +extern void guc_set_extra_field(struct config_generic *gconf, void **field, void *newval); +extern void guc_set_stack_value(struct config_generic *gconf, config_var_value *val); + +typedef void (*guc_push_old_value_hook_type) (struct config_generic *gconf, GucAction action); + +extern PGDLLIMPORT guc_push_old_value_hook_type guc_push_old_value_hook; +typedef void(*validate_set_config_function_hook_type) (char *name, char *value); +extern PGDLLIMPORT validate_set_config_function_hook_type validate_set_config_function_hook; + #endif /* GUC_TABLES_H */ diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 522020d7635..120eed0f51e 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -131,6 +131,8 @@ typedef struct CachedPlanSource double generic_cost; /* cost of generic plan, or -1 if not known */ double total_custom_cost; /* total cost of custom plans so far */ int num_custom_plans; /* number of plans included in total */ + + List *pltsql_plan_info; /* list of guc info lists to revalidate */ } CachedPlanSource; /* @@ -183,6 +185,11 @@ typedef struct CachedExpression dlist_node node; /* link in global list of CachedExpressions */ } CachedExpression; +typedef void (*plansource_complete_hook_type) (CachedPlanSource *plansource); +extern PGDLLIMPORT plansource_complete_hook_type plansource_complete_hook; + +typedef bool (*plansource_revalidate_hook_type) (CachedPlanSource *plansource); +extern PGDLLIMPORT plansource_revalidate_hook_type plansource_revalidate_hook; extern void InitPlanCache(void); extern void ResetPlanCache(void); diff --git a/src/include/utils/queryenvironment.h b/src/include/utils/queryenvironment.h index 4b8e19649a0..0356feca657 100644 --- a/src/include/utils/queryenvironment.h +++ b/src/include/utils/queryenvironment.h @@ -15,13 +15,31 @@ #define QUERYENVIRONMENT_H #include "access/tupdesc.h" +#include "access/htup.h" +#include "access/skey.h" +#include "utils/memutils.h" +#include "utils/relcache.h" typedef enum EphemeralNameRelationType { - ENR_NAMED_TUPLESTORE /* named tuplestore relation; e.g., deltas */ + ENR_NAMED_TUPLESTORE, /* named tuplestore relation; e.g., deltas */ + ENR_TSQL_TEMP /* Temp table created in procedure/function */ } EphemeralNameRelationType; +typedef enum ENRCatalogTupleType +{ + ENR_CATTUP_CLASS = 0, + ENR_CATTUP_TYPE, + ENR_CATTUP_ARRAYTYPE, + ENR_CATTUP_ATTRIBUTE, + ENR_CATTUP_CONSTRAINT, + ENR_CATTUP_STATISTIC, + ENR_CATTUP_STATISTIC_EXT, + ENR_CATTUP_STATISTIC_EXT_DATA, + ENR_CATTUP_END +} ENRCatalogTupleType; + /* * Some ephemeral named relations must match some relation (e.g., trigger * transition tables), so to properly handle cached plans and DDL, we should @@ -39,6 +57,7 @@ typedef struct EphemeralNamedRelationMetadataData EphemeralNameRelationType enrtype; /* to identify type of relation */ double enrtuples; /* estimated number of tuples */ + List *cattups[ENR_CATTUP_END]; } EphemeralNamedRelationMetadataData; typedef EphemeralNamedRelationMetadataData *EphemeralNamedRelationMetadata; @@ -63,12 +82,24 @@ typedef EphemeralNamedRelationData *EphemeralNamedRelation; */ typedef struct QueryEnvironment QueryEnvironment; +extern struct QueryEnvironment *currentQueryEnv; +extern struct QueryEnvironment *topLevelQueryEnv; extern QueryEnvironment *create_queryEnv(void); +extern QueryEnvironment *create_queryEnv2(MemoryContext cxt, bool top_level); +extern void remove_queryEnv(void); extern EphemeralNamedRelationMetadata get_visible_ENR_metadata(QueryEnvironment *queryEnv, const char *refname); extern void register_ENR(QueryEnvironment *queryEnv, EphemeralNamedRelation enr); extern void unregister_ENR(QueryEnvironment *queryEnv, const char *name); +extern List *get_namedRelList(void); extern EphemeralNamedRelation get_ENR(QueryEnvironment *queryEnv, const char *name); +extern EphemeralNamedRelation get_ENR_withoid(QueryEnvironment *queryEnv, Oid oid); extern TupleDesc ENRMetadataGetTupDesc(EphemeralNamedRelationMetadata enrmd); +extern bool ENRaddTuple(Relation rel, HeapTuple tup); +extern bool ENRdropTuple(Relation rel, HeapTuple tup); +extern bool ENRupdateTuple(Relation rel, HeapTuple tup); +extern bool ENRgetSystableScan(Relation rel, Oid indexoid, int nkeys, ScanKey key, List **tuplist, int *tuplist_i); +extern void ENRDropTempTables(QueryEnvironment *queryEnv); +extern void ENRDropEntry(Oid id); #endif /* QUERYENVIRONMENT_H */ diff --git a/src/include/utils/reltrigger.h b/src/include/utils/reltrigger.h index b22acb034e9..0d40410c140 100644 --- a/src/include/utils/reltrigger.h +++ b/src/include/utils/reltrigger.h @@ -58,16 +58,19 @@ typedef struct TriggerDesc bool trig_insert_instead_row; bool trig_insert_before_statement; bool trig_insert_after_statement; + bool trig_insert_instead_statement; bool trig_update_before_row; bool trig_update_after_row; bool trig_update_instead_row; bool trig_update_before_statement; bool trig_update_after_statement; + bool trig_update_instead_statement; bool trig_delete_before_row; bool trig_delete_after_row; bool trig_delete_instead_row; bool trig_delete_before_statement; bool trig_delete_after_statement; + bool trig_delete_instead_statement; /* there are no row-level truncate triggers */ bool trig_truncate_before_statement; bool trig_truncate_after_statement; diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index f27b73d76d0..18b49fa24f5 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -108,13 +108,34 @@ enum SysCacheIdentifier TYPENAMENSP, TYPEOID, USERMAPPINGOID, - USERMAPPINGUSERSERVER + USERMAPPINGUSERSERVER, + /* + * Below are cache IDs for extensions. We need to have them defined here + * instead of their respective extension modules because we do not want + * the IDs to conflict. + */ + SYSDATABASEOID, + SYSDATABASENAME + +#define SysCacheNoExtensionSize (USERMAPPINGUSERSERVER+ 1) +#define SysCacheSize (SYSDATABASENAME + 1) +}; -#define SysCacheSize (USERMAPPINGUSERSERVER + 1) +/* + * struct cachedesc: information defining a single syscache + */ +struct cachedesc +{ + Oid reloid; /* OID of the relation being cached */ + Oid indoid; /* OID of index relation for this cache */ + int nkeys; /* # of keys needed for cache lookup */ + int key[4]; /* attribute numbers of key attrs */ + int nbuckets; /* number of hash buckets for this cache */ }; extern void InitCatalogCache(void); extern void InitCatalogCachePhase2(void); +extern void InitExtensionCatalogCache(struct cachedesc *cacheinfo, int startid, int cachelength); extern HeapTuple SearchSysCache(int cacheId, Datum key1, Datum key2, Datum key3, Datum key4); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 74671daf1ba..fe9d290b308 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3160,6 +3160,26 @@ exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt) return PLPGSQL_RC_OK; } + /* + * NOTE: we are copying the PL/pgSQL EXIT functionality in order + * to implement the TSQL BREAK statement. EXIT offers more + * more functionality than BREAK but we don't expose that + * functionality to the user. In particular, an EXIT (or + * CONTINUE statement can specify a label in order to exit + * or continue) a loop other than the nearest enclosing loop + * + * We don't provide the syntax that would allow the user to + * specify a target so we are compatible with TSQL in this + * respect. + * + * However, we are *incompatible* with TSQL in that we don't + * report an error if we execute a BREAK without an enclosing + * WHILE loop. + * + * We may fix this incompatibility after executing the GOTO + * statement. + */ + estate->exitlabel = stmt->label; if (stmt->is_exit) return PLPGSQL_RC_EXIT; diff --git a/src/test/regress/expected/babel_applock2.out b/src/test/regress/expected/babel_applock2.out new file mode 100644 index 00000000000..3ccbee28345 --- /dev/null +++ b/src/test/regress/expected/babel_applock2.out @@ -0,0 +1,147 @@ +-- Applock test session #2 +-- wait for some initialization of the other session +select pg_sleep(2); + pg_sleep +---------- + +(1 row) + +select pg_advisory_lock(1); + pg_advisory_lock +------------------ + +(1 row) + +select pg_advisory_unlock(1); + pg_advisory_unlock +-------------------- + t +(1 row) + +set babelfish_pg_tsql.sql_dialect = 'tsql'; +\tsql on +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 1; +GO +INFO: ------ Barrier 1 +-- Test #1: basic locking-out and timeout +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 2; +GO +INFO: ------ Barrier 2 +-- timed out after waiting for 3 seconds +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +GO +WARNING: Applock request for 'lock1' timed out +INFO: sp_getapplock returns -1 +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 3; +go +INFO: ------ Barrier 3 +-- lock acquired +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +GO +INFO: sp_getapplock returns 0 +exec babel_releaseapplock_print_return @Resource = 'lock1', @LockOwner = 'session'; +GO +INFO: sp_releaseapplock returns 0 +-- Parallel test #2: additional lock modes +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 4; +go +INFO: ------ Barrier 4 +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 5; +go +INFO: ------ Barrier 5 +-- IntentExclusive lock acquired while the other session is holding IntentShared on lock 1 +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'IntentExclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +go +INFO: sp_getapplock returns 0 +exec babel_releaseapplock_print_return @Resource = 'lock1', @LockOwner = 'SESSION'; +go +INFO: sp_releaseapplock returns 0 +-- Exclusive lock failed to acquire while the other session is holding IntentShared on lock 1 +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +go +WARNING: Applock request for 'lock1' timed out +INFO: sp_getapplock returns -1 +exec babel_getapplock_print_return @Resource = 'lock2', @LockMode = 'Update', @LockOwner = 'SESSION', @LockTimeout = 1000; +go +INFO: sp_getapplock returns 0 +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 6; +go +INFO: ------ Barrier 6 +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 7; +go +INFO: ------ Barrier 7 +exec babel_releaseapplock_print_return @Resource = 'lock2', @LockOwner = 'SESSION'; +go +INFO: sp_releaseapplock returns 0 +-- Parallel test #3: deadlock +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 8; +go +INFO: ------ Barrier 8 +exec babel_getapplock_print_return @Resource = 'lock2', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +go +INFO: sp_getapplock returns 0 +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 9; +go +INFO: ------ Barrier 9 +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 60000; +go +INFO: sp_getapplock returns 0 +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 10; +go +INFO: ------ Barrier 10 +exec babel_releaseapplock_print_return @Resource = 'lock1', @LockOwner = 'SESSION'; +go +INFO: sp_releaseapplock returns 0 +exec babel_releaseapplock_print_return @Resource = 'lock2', @LockOwner = 'SESSION'; +go +INFO: sp_releaseapplock returns 0 +-- APPLOCK_TEST +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 11; +GO +INFO: ------ Barrier 11 +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 12; +GO +INFO: ------ Barrier 12 +SELECT APPLOCK_TEST('public', 'lock1', 'Exclusive', 'session'); -- not grantable +go + applock_test +-------------- + 0 +(1 row) + +SELECT APPLOCK_TEST('public', 'lock1', 'Shared', 'session'); -- grantable +go + applock_test +-------------- + 1 +(1 row) + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 13; +GO +INFO: ------ Barrier 13 diff --git a/src/test/regress/expected/tsql_like.out b/src/test/regress/expected/tsql_like.out new file mode 100644 index 00000000000..310a0d6da16 --- /dev/null +++ b/src/test/regress/expected/tsql_like.out @@ -0,0 +1,98 @@ +set babelfishpg_tsql.sql_dialect = 'tsql'; +select relname from pg_class where relname like '['; +ERROR: pattern matching operators '[' and ']' are not supported for LIKE +select relname from pg_class where relname like ']'; +ERROR: pattern matching operators '[' and ']' are not supported for LIKE +select relname from pg_class where relname like '[]'; +ERROR: pattern matching operators '[' and ']' are not supported for LIKE +select relname from pg_class where relname like NULL; + relname +--------- +(0 rows) + +select relname from pg_class where relname like ''; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg[1:9]class'; +ERROR: pattern matching operators '[' and ']' are not supported for LIKE +select relname from pg_class where relname like 'pg\[1:9\]class'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg\[1:9 ]class'; +ERROR: pattern matching operators '[' and ']' are not supported for LIKE +select relname from pg_class where relname like 'pg [1:9\]class'; +ERROR: pattern matching operators '[' and ']' are not supported for LIKE +select relname from pg_class where relname like 'pg*[1:9*]class' escape '*'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg [1:9*]class' escape '*'; +ERROR: pattern matching operators '[' and ']' are not supported for LIKE +select relname from pg_class where relname like 'pg*[1:9 ]class' escape '*'; +ERROR: pattern matching operators '[' and ']' are not supported for LIKE +set babelfishpg_tsql.sql_dialect = 'postgres'; +select relname from pg_class where relname like '['; + relname +--------- +(0 rows) + +select relname from pg_class where relname like ']'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like '[]'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like NULL; + relname +--------- +(0 rows) + +select relname from pg_class where relname like ''; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg[1:9]class'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg\[1:9\]class'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg\[1:9 ]class'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg [1:9\]class'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg*[1:9*]class' escape '*'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg [1:9*]class' escape '*'; + relname +--------- +(0 rows) + +select relname from pg_class where relname like 'pg*[1:9 ]class' escape '*'; + relname +--------- +(0 rows) + diff --git a/src/test/regress/expected/tsql_targetlist.out b/src/test/regress/expected/tsql_targetlist.out new file mode 100644 index 00000000000..9f71e122f2e --- /dev/null +++ b/src/test/regress/expected/tsql_targetlist.out @@ -0,0 +1,37 @@ +-- [BABEL-2370] - Preserve case of unquoted column names and alias names in the output columns of a top-level SELECT +CREATE EXTENSION IF NOT EXISTS babelfishpg_tsql CASCADE; +NOTICE: installing required extension "uuid-ossp" +NOTICE: installing required extension "babelfishpg_common" +SET babelfishpg_tsql.sql_dialect = 'tsql'; +CREATE SCHEMA ts; +CREATE TABLE ts.t1 (ABC text, b varchar(20), c char(4), Xyz int, "Delimited" int, "Special Chars" bigint); +SELECT * from ts.t1; + abc | b | c | xyz | delimited | special chars +-----+---+---+-----+-----------+--------------- +(0 rows) + +SELECT xyz, XYZ, xYz, xyz ColName, xYz AS ColName, "Special Chars", "Delimited", "DeLIMITed" from ts.t1; + xyz | XYZ | xYz | ColName | ColName | Special Chars | Delimited | DeLIMITed +-----+-----+-----+---------+---------+---------------+-----------+----------- +(0 rows) + +SELECT xyz AS "WOW! This is a very very long identifier that will get truncated with a uniquifying suffix by Babelfish" from ts.t1; +NOTICE: identifier "WOW! This is a very very long identifier that will get truncated with a uniquifying suffix by Babelfish" will be truncated to "WOW! This is a very very long ia01190c3b70a2e95cf74dd179f3280f6" + WOW! This is a very very long ia01190c3b70a2e95cf74dd179f3280f6 +----------------------------------------------------------------- +(0 rows) + +SELECT xyz AS "WOW! This is a very very long identifier that will get truncated with a uniquifying suffix by Babelfish - with extra stuff at the end" from ts.t1; +NOTICE: identifier "WOW! This is a very very long identifier that will get truncated with a uniquifying suffix by Babelfish - with extra stuff at the end" will be truncated to "WOW! This is a very very long i0c38f32b6ce858b1d0f8f2c48746d842" + WOW! This is a very very long i0c38f32b6ce858b1d0f8f2c48746d842 +----------------------------------------------------------------- +(0 rows) + +RESET babelfishpg_tsql.sql_dialect; +DROP EXTENSION babelfishpg_tsql CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to column c of table ts.t1 +drop cascades to column b of table ts.t1 +drop cascades to column abc of table ts.t1 +DROP SCHEMA ts CASCADE; +NOTICE: drop cascades to table ts.t1 diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 525bdc804f6..9a4b0339fa3 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -202,3 +202,6 @@ test: explain test: event_trigger test: fast_default test: stats + +# Tests that verify that babelfish does not regress PG in the postgres dialect +test: tsql_like \ No newline at end of file diff --git a/src/test/regress/sql/babel_applock2.sql b/src/test/regress/sql/babel_applock2.sql new file mode 100644 index 00000000000..91a6023a380 --- /dev/null +++ b/src/test/regress/sql/babel_applock2.sql @@ -0,0 +1,126 @@ +-- Applock test session #2 +-- wait for some initialization of the other session +select pg_sleep(2); +select pg_advisory_lock(1); +select pg_advisory_unlock(1); + +set babelfish_pg_tsql.sql_dialect = 'tsql'; +\tsql on + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 1; +GO + +-- Test #1: basic locking-out and timeout + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 2; +GO + +-- timed out after waiting for 3 seconds +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +GO + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 3; +go + +-- lock acquired +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +GO + +exec babel_releaseapplock_print_return @Resource = 'lock1', @LockOwner = 'session'; +GO + +-- Parallel test #2: additional lock modes + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 4; +go + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 5; +go + +-- IntentExclusive lock acquired while the other session is holding IntentShared on lock 1 +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'IntentExclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +go + +exec babel_releaseapplock_print_return @Resource = 'lock1', @LockOwner = 'SESSION'; +go + +-- Exclusive lock failed to acquire while the other session is holding IntentShared on lock 1 +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +go + +exec babel_getapplock_print_return @Resource = 'lock2', @LockMode = 'Update', @LockOwner = 'SESSION', @LockTimeout = 1000; +go + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 6; +go + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 7; +go + +exec babel_releaseapplock_print_return @Resource = 'lock2', @LockOwner = 'SESSION'; +go + +-- Parallel test #3: deadlock + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 8; +go + +exec babel_getapplock_print_return @Resource = 'lock2', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 1000; +go + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 9; +go + +exec babel_getapplock_print_return @Resource = 'lock1', @LockMode = 'Exclusive', @LockOwner = 'SESSION', @LockTimeout = 60000; +go + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 10; +go + +exec babel_releaseapplock_print_return @Resource = 'lock1', @LockOwner = 'SESSION'; +go + +exec babel_releaseapplock_print_return @Resource = 'lock2', @LockOwner = 'SESSION'; +go + +-- APPLOCK_TEST +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 11; +GO + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 12; +GO + +SELECT APPLOCK_TEST('public', 'lock1', 'Exclusive', 'session'); -- not grantable +go + +SELECT APPLOCK_TEST('public', 'lock1', 'Shared', 'session'); -- grantable +go + +insert into babel_applock_test_t1 values (1); +go +exec babel_applock_test_barrier 13; +GO diff --git a/src/test/regress/sql/tsql_like.sql b/src/test/regress/sql/tsql_like.sql new file mode 100644 index 00000000000..61aeead8b20 --- /dev/null +++ b/src/test/regress/sql/tsql_like.sql @@ -0,0 +1,29 @@ +set babelfishpg_tsql.sql_dialect = 'tsql'; +select relname from pg_class where relname like '['; +select relname from pg_class where relname like ']'; +select relname from pg_class where relname like '[]'; +select relname from pg_class where relname like NULL; +select relname from pg_class where relname like ''; +select relname from pg_class where relname like 'pg[1:9]class'; +select relname from pg_class where relname like 'pg\[1:9\]class'; +select relname from pg_class where relname like 'pg\[1:9 ]class'; +select relname from pg_class where relname like 'pg [1:9\]class'; + +select relname from pg_class where relname like 'pg*[1:9*]class' escape '*'; +select relname from pg_class where relname like 'pg [1:9*]class' escape '*'; +select relname from pg_class where relname like 'pg*[1:9 ]class' escape '*'; + +set babelfishpg_tsql.sql_dialect = 'postgres'; +select relname from pg_class where relname like '['; +select relname from pg_class where relname like ']'; +select relname from pg_class where relname like '[]'; +select relname from pg_class where relname like NULL; +select relname from pg_class where relname like ''; +select relname from pg_class where relname like 'pg[1:9]class'; +select relname from pg_class where relname like 'pg\[1:9\]class'; +select relname from pg_class where relname like 'pg\[1:9 ]class'; +select relname from pg_class where relname like 'pg [1:9\]class'; + +select relname from pg_class where relname like 'pg*[1:9*]class' escape '*'; +select relname from pg_class where relname like 'pg [1:9*]class' escape '*'; +select relname from pg_class where relname like 'pg*[1:9 ]class' escape '*'; diff --git a/src/test/regress/sql/tsql_targetlist.sql b/src/test/regress/sql/tsql_targetlist.sql new file mode 100644 index 00000000000..6c986a363c6 --- /dev/null +++ b/src/test/regress/sql/tsql_targetlist.sql @@ -0,0 +1,15 @@ +-- [BABEL-2370] - Preserve case of unquoted column names and alias names in the output columns of a top-level SELECT +CREATE EXTENSION IF NOT EXISTS babelfishpg_tsql CASCADE; +SET babelfishpg_tsql.sql_dialect = 'tsql'; +CREATE SCHEMA ts; +CREATE TABLE ts.t1 (ABC text, b varchar(20), c char(4), Xyz int, "Delimited" int, "Special Chars" bigint); + +SELECT * from ts.t1; +SELECT xyz, XYZ, xYz, xyz ColName, xYz AS ColName, "Special Chars", "Delimited", "DeLIMITed" from ts.t1; + +SELECT xyz AS "WOW! This is a very very long identifier that will get truncated with a uniquifying suffix by Babelfish" from ts.t1; +SELECT xyz AS "WOW! This is a very very long identifier that will get truncated with a uniquifying suffix by Babelfish - with extra stuff at the end" from ts.t1; + +RESET babelfishpg_tsql.sql_dialect; +DROP EXTENSION babelfishpg_tsql CASCADE; +DROP SCHEMA ts CASCADE;