From 399a289a0f7503dcd50e786ca650b0bf8ba4d533 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Wed, 19 Nov 2025 08:38:54 -0800 Subject: [PATCH 01/14] Updated CI, Labeler, Docker, and branch security files for PG18 (#2246) Updated the CI and Docker files for the PG18 branch to point to PG18 and PostgreSQL version 18. Updated the labeler and branch security files for PG18. Some of these only apply to the master branch but are updated for consistency. modified: .asf.yaml modified: .github/labeler.yml modified: .github/workflows/go-driver.yml modified: .github/workflows/installcheck.yaml modified: .github/workflows/jdbc-driver.yaml modified: .github/workflows/nodejs-driver.yaml modified: .github/workflows/python-driver.yaml modified: docker/Dockerfile modified: docker/Dockerfile.dev modified: drivers/docker-compose.yml --- .asf.yaml | 4 +++ .github/labeler.yml | 3 +++ .github/workflows/go-driver.yml | 4 +-- .github/workflows/installcheck.yaml | 37 ++++++++++++++-------------- .github/workflows/jdbc-driver.yaml | 4 +-- .github/workflows/nodejs-driver.yaml | 4 +-- .github/workflows/python-driver.yaml | 4 +-- docker/Dockerfile | 12 ++++----- docker/Dockerfile.dev | 4 +-- drivers/docker-compose.yml | 2 +- 10 files changed, 43 insertions(+), 35 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index 8a0149a04..be18a25c1 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -50,6 +50,10 @@ github: required_pull_request_reviews: required_approving_review_count: 1 + PG18: + required_pull_request_reviews: + required_approving_review_count: 1 + PG17: required_pull_request_reviews: required_approving_review_count: 1 diff --git a/.github/labeler.yml b/.github/labeler.yml index 6baa297c5..f860c2b19 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -19,5 +19,8 @@ PG16: PG17: - base-branch: 'PG17' +PG18: +- base-branch: 'PG18' + master: - base-branch: 'master' diff --git a/.github/workflows/go-driver.yml b/.github/workflows/go-driver.yml index 5b6d15030..1b0379c34 100644 --- a/.github/workflows/go-driver.yml +++ b/.github/workflows/go-driver.yml @@ -2,10 +2,10 @@ name: Go Driver Tests on: push: - branches: [ "master" ] + branches: [ "PG18" ] pull_request: - branches: [ "master" ] + branches: [ "PG18" ] jobs: build: diff --git a/.github/workflows/installcheck.yaml b/.github/workflows/installcheck.yaml index da266f0da..3ee97296c 100644 --- a/.github/workflows/installcheck.yaml +++ b/.github/workflows/installcheck.yaml @@ -2,62 +2,63 @@ name: Build / Regression on: push: - branches: [ "master" ] + branches: [ "PG18" ] + pull_request: - branches: [ "master" ] + branches: [ "PG18" ] jobs: build: runs-on: ubuntu-latest steps: - - name: Get latest commit id of PostgreSQL 17 + - name: Get latest commit id of PostgreSQL 18 run: | - echo "PG_COMMIT_HASH=$(git ls-remote https://git.postgresql.org/git/postgresql.git refs/heads/REL_17_STABLE | awk '{print $1}')" >> $GITHUB_ENV + echo "PG_COMMIT_HASH=$(git ls-remote https://git.postgresql.org/git/postgresql.git refs/heads/REL_18_STABLE | awk '{print $1}')" >> $GITHUB_ENV - - name: Cache PostgreSQL 17 + - name: Cache PostgreSQL 18 uses: actions/cache@v3 - id: pg17cache + id: pg18cache with: - path: ~/pg17 - key: ${{ runner.os }}-v1-pg17-${{ env.PG_COMMIT_HASH }} + path: ~/pg18 + key: ${{ runner.os }}-v1-pg18-${{ env.PG_COMMIT_HASH }} - name: Install necessary dependencies run: | sudo apt-get update sudo apt-get install -y build-essential libreadline-dev zlib1g-dev flex bison - - name: Install PostgreSQL 17 and some extensions - if: steps.pg17cache.outputs.cache-hit != 'true' + - name: Install PostgreSQL 18 and some extensions + if: steps.pg18cache.outputs.cache-hit != 'true' run: | - git clone --depth 1 --branch REL_17_STABLE https://git.postgresql.org/git/postgresql.git ~/pg17source - cd ~/pg17source - ./configure --prefix=$HOME/pg17 CFLAGS="-std=gnu99 -ggdb -O0" --enable-cassert + git clone --depth 1 --branch REL_18_STABLE https://git.postgresql.org/git/postgresql.git ~/pg18source + cd ~/pg18source + ./configure --prefix=$HOME/pg18 CFLAGS="-std=gnu99 -ggdb -O0" --enable-cassert make install -j$(nproc) > /dev/null cd contrib cd fuzzystrmatch - make PG_CONFIG=$HOME/pg17/bin/pg_config install -j$(nproc) > /dev/null + make PG_CONFIG=$HOME/pg18/bin/pg_config install -j$(nproc) > /dev/null cd ../pg_trgm - make PG_CONFIG=$HOME/pg17/bin/pg_config install -j$(nproc) > /dev/null + make PG_CONFIG=$HOME/pg18/bin/pg_config install -j$(nproc) > /dev/null - uses: actions/checkout@v3 - name: Build AGE id: build run: | - make PG_CONFIG=$HOME/pg17/bin/pg_config install -j$(nproc) + make PG_CONFIG=$HOME/pg18/bin/pg_config install -j$(nproc) - name: Pull and build pgvector id: pgvector run: | git clone https://github.com/pgvector/pgvector.git cd pgvector - make PG_CONFIG=$HOME/pg17/bin/pg_config install -j$(nproc) > /dev/null + make PG_CONFIG=$HOME/pg18/bin/pg_config install -j$(nproc) > /dev/null - name: Regression tests id: regression_tests run: | - make PG_CONFIG=$HOME/pg17/bin/pg_config installcheck EXTRA_TESTS="pgvector fuzzystrmatch pg_trgm" + make PG_CONFIG=$HOME/pg18/bin/pg_config installcheck EXTRA_TESTS="pgvector fuzzystrmatch pg_trgm" continue-on-error: true - name: Dump regression test errors diff --git a/.github/workflows/jdbc-driver.yaml b/.github/workflows/jdbc-driver.yaml index 29b368438..293db7b88 100644 --- a/.github/workflows/jdbc-driver.yaml +++ b/.github/workflows/jdbc-driver.yaml @@ -2,10 +2,10 @@ name: JDBC Driver Tests on: push: - branches: [ "master" ] + branches: [ "PG18" ] pull_request: - branches: [ "master" ] + branches: [ "PG18" ] jobs: build: diff --git a/.github/workflows/nodejs-driver.yaml b/.github/workflows/nodejs-driver.yaml index 3d9e07023..c2da7fe60 100644 --- a/.github/workflows/nodejs-driver.yaml +++ b/.github/workflows/nodejs-driver.yaml @@ -2,10 +2,10 @@ name: Nodejs Driver Tests on: push: - branches: [ "master" ] + branches: [ "PG18" ] pull_request: - branches: [ "master" ] + branches: [ "PG18" ] jobs: build: diff --git a/.github/workflows/python-driver.yaml b/.github/workflows/python-driver.yaml index 099b5c871..ef6cf96a1 100644 --- a/.github/workflows/python-driver.yaml +++ b/.github/workflows/python-driver.yaml @@ -2,10 +2,10 @@ name: Python Driver Tests on: push: - branches: [ "master" ] + branches: [ "PG18" ] pull_request: - branches: [ "master" ] + branches: [ "PG18" ] jobs: build: diff --git a/docker/Dockerfile b/docker/Dockerfile index 0436dc8f9..3eb17c798 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,14 +17,14 @@ # # Build stage: Install necessary development tools for compilation and installation -FROM postgres:17 AS build +FROM postgres:18 AS build RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests \ bison \ build-essential \ flex \ - postgresql-server-dev-17 + postgresql-server-dev-18 COPY . /age @@ -34,7 +34,7 @@ RUN make && make install # Final stage: Create a final image by copying the files created in the build stage -FROM postgres:17 +FROM postgres:18 RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests \ @@ -48,9 +48,9 @@ ENV LANG=en_US.UTF-8 ENV LC_COLLATE=en_US.UTF-8 ENV LC_CTYPE=en_US.UTF-8 -COPY --from=build /usr/lib/postgresql/17/lib/age.so /usr/lib/postgresql/17/lib/ -COPY --from=build /usr/share/postgresql/17/extension/age--1.6.0.sql /usr/share/postgresql/17/extension/ -COPY --from=build /usr/share/postgresql/17/extension/age.control /usr/share/postgresql/17/extension/ +COPY --from=build /usr/lib/postgresql/18/lib/age.so /usr/lib/postgresql/18/lib/ +COPY --from=build /usr/share/postgresql/18/extension/age--1.6.0.sql /usr/share/postgresql/18/extension/ +COPY --from=build /usr/share/postgresql/18/extension/age.control /usr/share/postgresql/18/extension/ COPY docker/docker-entrypoint-initdb.d/00-create-extension-age.sql /docker-entrypoint-initdb.d/00-create-extension-age.sql CMD ["postgres", "-c", "shared_preload_libraries=age"] diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 48b2db3ed..e02c21fc4 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -17,14 +17,14 @@ # -FROM postgres:17 +FROM postgres:18 RUN apt-get update RUN apt-get install --assume-yes --no-install-recommends --no-install-suggests \ bison \ build-essential \ flex \ - postgresql-server-dev-17 \ + postgresql-server-dev-18 \ locales ENV LANG=en_US.UTF-8 diff --git a/drivers/docker-compose.yml b/drivers/docker-compose.yml index 9ec072db5..5ae7b79a8 100644 --- a/drivers/docker-compose.yml +++ b/drivers/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.3" services: db: - image: apache/age:dev_snapshot_master + image: apache/age:dev_snapshot_PG18 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=agens From 146bf38035725004ced7f52ba4c484592e25f53a Mon Sep 17 00:00:00 2001 From: "Krishnakumar R (KK)" <65895020+kk-src@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:25:12 -0800 Subject: [PATCH 02/14] PG18 port for AGE (#2251) * [PG18 port][Set1] Fix header dependencies and use TupleDescAttr macro - Include executor/executor.h for PG18 header reorganization and use TupleDescAttr() accessor macro instead of direct attrs[] access. * [PG18 port][Set2] Adapt to expandRTE signature change and pg_noreturn - Add VarReturningType parameter to expandRTE() calls using VAR_RETURNING_DEFAULT. - Replace pg_attribute_noreturn() with pg_noreturn prefix specifier. * [PG18 port][Set3] Fix double ExecOpenIndices call for PG18 compatibility - PG18 enforces stricter assertions in ExecOpenIndices, requiring ri_IndexRelationDescs to be NULL when called. - In update_entity_tuple(), indices may already be opened by the caller (create_entity_result_rel_info), causing assertion failures. - Add a check to only open indices if not already open, and track ownership with a boolean flag to ensure we only close what we opened. - Found when regression tests failed with assertions, which this change resolves. * [PG18 port][Set4] Update regression test expected output for ordering PG18's implementation changes result in different row ordering for queries without explicit ORDER BY clauses. Update expected output files to reflect the new ordering while maintaining identical result content. * [PG18 port][Set5] Address review comments - coding standard fix Note: Assisted by GitHub Copilot Agent mode. --- regress/expected/cypher_match.out | 48 +++++++++++++-------------- regress/expected/expr.out | 24 +++++++------- src/backend/catalog/ag_label.c | 1 + src/backend/executor/cypher_create.c | 2 ++ src/backend/executor/cypher_delete.c | 3 +- src/backend/executor/cypher_merge.c | 2 ++ src/backend/executor/cypher_set.c | 29 ++++++++++++---- src/backend/executor/cypher_utils.c | 1 + src/backend/parser/cypher_clause.c | 4 +-- src/backend/utils/adt/agtype_parser.c | 8 ++--- 10 files changed, 72 insertions(+), 50 deletions(-) diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index e83ba3b93..ad5a07d05 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -88,8 +88,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a ---------------------------------------------------------------------------------- - {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex + {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex (2 rows) SELECT * FROM cypher('cypher_match', $$ @@ -97,8 +97,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a ---------------------------------------------------------------------------------- - {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex + {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex (2 rows) SELECT * FROM cypher('cypher_match', $$ @@ -132,10 +132,10 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------- - {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge - {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge + {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge + {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge (4 rows) SELECT * FROM cypher('cypher_match', $$ @@ -143,10 +143,10 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------- - {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge - {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge + {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge + {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge (4 rows) SELECT * FROM cypher('cypher_match', $$ @@ -165,8 +165,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------- - {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge + {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge (2 rows) SELECT * FROM cypher('cypher_match', $$ @@ -174,8 +174,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a ---------------------------------------------------------------------------------- - {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex + {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex (2 rows) -- Right Path Test @@ -348,10 +348,10 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (i agtype); i ---------------------------------------------------------------------------------- - {"id": 1688849860263939, "label": "v2", "properties": {"id": "end"}}::vertex - {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex + {"id": 1688849860263939, "label": "v2", "properties": {"id": "end"}}::vertex {"id": 1688849860263937, "label": "v2", "properties": {"id": "initial"}}::vertex + {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex (4 rows) SELECT * FROM cypher('cypher_match', $$ @@ -537,18 +537,18 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (i agtype, b agtype, c agtype); i | b | c ---+-----------+----------- - | "end" | "middle" - 0 | "end" | "middle" - 1 | "end" | "middle" | "middle" | "end" 0 | "middle" | "end" 1 | "middle" | "end" - | "middle" | "initial" - 0 | "middle" | "initial" - 1 | "middle" | "initial" + | "end" | "middle" + 0 | "end" | "middle" + 1 | "end" | "middle" | "initial" | "middle" 0 | "initial" | "middle" 1 | "initial" | "middle" + | "middle" | "initial" + 0 | "middle" | "initial" + 1 | "middle" | "initial" (12 rows) SELECT * FROM cypher('cypher_match', $$ @@ -558,18 +558,18 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (i agtype, c agtype); i | c ---+----------- - | "middle" - 0 | "middle" - 1 | "middle" | "end" 0 | "end" 1 | "end" - | "initial" - 0 | "initial" - 1 | "initial" | "middle" 0 | "middle" 1 | "middle" + | "middle" + 0 | "middle" + 1 | "middle" + | "initial" + 0 | "initial" + 1 | "initial" (12 rows) -- @@ -2407,15 +2407,15 @@ SELECT * FROM cypher('cypher_match', $$ MATCH (a {name:a.name}) MATCH (a {age:a. SELECT * FROM cypher('cypher_match', $$ MATCH p=(a)-[u {relationship: u.relationship}]->(b) RETURN p $$) as (a agtype); a ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex, {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path [{"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex, {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path + [{"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex, {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path (2 rows) SELECT * FROM cypher('cypher_match', $$ MATCH p=(a)-[u {relationship: u.relationship, years: u.years}]->(b) RETURN p $$) as (a agtype); a ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex, {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path [{"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex, {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path + [{"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex, {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path (2 rows) SELECT * FROM cypher('cypher_match', $$ MATCH p=(a {name:a.name})-[u {relationship: u.relationship}]->(b {age:b.age}) RETURN p $$) as (a agtype); diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 513ea142f..20c9e95f3 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -2644,10 +2644,10 @@ SELECT * FROM cypher('expr', $$ MATCH (v) RETURN v $$) AS (expression agtype); SELECT * FROM cypher('expr', $$ MATCH ()-[e]-() RETURN e $$) AS (expression agtype); expression --------------------------------------------------------------------------------------------------------------------------- - {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge - {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge + {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge + {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge (4 rows) -- id() @@ -2656,10 +2656,10 @@ SELECT * FROM cypher('expr', $$ $$) AS (id agtype); id ------------------ - 1407374883553281 - 1407374883553281 1407374883553282 + 1407374883553281 1407374883553282 + 1407374883553281 (4 rows) SELECT * FROM cypher('expr', $$ @@ -2698,10 +2698,10 @@ SELECT * FROM cypher('expr', $$ $$) AS (start_id agtype); start_id ------------------ - 1125899906842626 - 1125899906842626 1125899906842625 + 1125899906842626 1125899906842625 + 1125899906842626 (4 rows) -- should return null @@ -2731,10 +2731,10 @@ SELECT * FROM cypher('expr', $$ $$) AS (end_id agtype); end_id ------------------ - 1125899906842627 - 1125899906842627 1125899906842626 + 1125899906842627 1125899906842626 + 1125899906842627 (4 rows) -- should return null @@ -2764,10 +2764,10 @@ SELECT * FROM cypher('expr', $$ $$) AS (id agtype, start_id agtype, startNode agtype); id | start_id | startnode ------------------+------------------+---------------------------------------------------------------------------------- - 1407374883553281 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex - 1407374883553281 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex 1407374883553282 | 1125899906842625 | {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex + 1407374883553281 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex 1407374883553282 | 1125899906842625 | {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex + 1407374883553281 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex (4 rows) -- should return null @@ -2797,10 +2797,10 @@ SELECT * FROM cypher('expr', $$ $$) AS (id agtype, end_id agtype, endNode agtype); id | end_id | endnode ------------------+------------------+--------------------------------------------------------------------------------- - 1407374883553281 | 1125899906842627 | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex - 1407374883553281 | 1125899906842627 | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex 1407374883553282 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex + 1407374883553281 | 1125899906842627 | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex 1407374883553282 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex + 1407374883553281 | 1125899906842627 | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex (4 rows) -- should return null diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index b6dcf77a3..54c31ef36 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -21,6 +21,7 @@ #include "access/genam.h" #include "catalog/indexing.h" +#include "executor/executor.h" #include "nodes/makefuncs.h" #include "utils/builtins.h" #include "utils/lsyscache.h" diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 2091ea29c..2031fe8d8 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -19,6 +19,8 @@ #include "postgres.h" +#include "executor/executor.h" + #include "catalog/ag_label.h" #include "executor/cypher_executor.h" #include "executor/cypher_utils.h" diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 6bb869833..7e10d9afc 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -19,6 +19,7 @@ #include "postgres.h" +#include "executor/executor.h" #include "storage/bufmgr.h" #include "common/hashfn.h" @@ -257,7 +258,7 @@ static agtype_value *extract_entity(CustomScanState *node, tupleDescriptor = scanTupleSlot->tts_tupleDescriptor; /* type checking, make sure the entity is an agtype vertex or edge */ - if (tupleDescriptor->attrs[entity_position -1].atttypid != AGTYPEOID) + if (TupleDescAttr(tupleDescriptor, entity_position -1)->atttypid != AGTYPEOID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DELETE clause can only delete agtype"))); diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 9136825ab..6cfa70d48 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -19,6 +19,8 @@ #include "postgres.h" +#include "executor/executor.h" + #include "catalog/ag_label.h" #include "executor/cypher_executor.h" #include "executor/cypher_utils.h" diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index d1837fb16..9fd599eed 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -19,6 +19,7 @@ #include "postgres.h" +#include "executor/executor.h" #include "storage/bufmgr.h" #include "executor/cypher_executor.h" @@ -102,6 +103,7 @@ static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TM_Result result; CommandId cid = GetCurrentCommandId(true); ResultRelInfo **saved_resultRels = estate->es_result_relations; + bool close_indices = false; estate->es_result_relations = &resultRelInfo; @@ -113,7 +115,16 @@ static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, if (lock_result == TM_Ok) { - ExecOpenIndices(resultRelInfo, false); + /* + * Open indices if not already open. The resultRelInfo may already + * have indices opened by the caller (e.g., create_entity_result_rel_info), + * so only open if needed and track that we did so for cleanup. + */ + if (resultRelInfo->ri_IndexRelationDescs == NULL) + { + ExecOpenIndices(resultRelInfo, false); + close_indices = true; + } ExecStoreVirtualTuple(elemTupleSlot); tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); tuple->t_self = old_tuple->t_self; @@ -141,7 +152,10 @@ static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, errmsg("tuple to be updated was already modified"))); } - ExecCloseIndices(resultRelInfo); + if (close_indices) + { + ExecCloseIndices(resultRelInfo); + } estate->es_result_relations = saved_resultRels; return tuple; @@ -160,7 +174,10 @@ static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, (update_indexes == TU_Summarizing)); } - ExecCloseIndices(resultRelInfo); + if (close_indices) + { + ExecCloseIndices(resultRelInfo); + } } else if (lock_result == TM_SelfModified) { @@ -310,7 +327,7 @@ static void update_all_paths(CustomScanState *node, graphid id, agtype_value *original_entity_value; /* skip nulls */ - if (scanTupleSlot->tts_tupleDescriptor->attrs[i].atttypid != AGTYPEOID) + if (TupleDescAttr(scanTupleSlot->tts_tupleDescriptor, i)->atttypid != AGTYPEOID) { continue; } @@ -414,7 +431,7 @@ static void process_update_list(CustomScanState *node) continue; } - if (scanTupleSlot->tts_tupleDescriptor->attrs[update_item->entity_position -1].atttypid != AGTYPEOID) + if (TupleDescAttr(scanTupleSlot->tts_tupleDescriptor, update_item->entity_position -1)->atttypid != AGTYPEOID) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -588,7 +605,7 @@ static void process_update_list(CustomScanState *node) } estate->es_snapshot->curcid = cid; - /* close relation */ + /* close relation */ ExecCloseIndices(resultRelInfo); table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index c8d568831..d7a55f709 100644 --- a/src/backend/executor/cypher_utils.c +++ b/src/backend/executor/cypher_utils.c @@ -24,6 +24,7 @@ #include "postgres.h" +#include "executor/executor.h" #include "nodes/makefuncs.h" #include "parser/parse_relation.h" diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 468706a87..971f8c3d2 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -2585,9 +2585,9 @@ static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, List *colnames = NIL; List *colvars = NIL; - expandRTE(l_pnsi->p_rte, l_pnsi->p_rtindex, 0, -1, false, + expandRTE(l_pnsi->p_rte, l_pnsi->p_rtindex, 0, VAR_RETURNING_DEFAULT, -1, false, &l_colnames, &l_colvars); - expandRTE(r_pnsi->p_rte, r_pnsi->p_rtindex, 0, -1, false, + expandRTE(r_pnsi->p_rte, r_pnsi->p_rtindex, 0, VAR_RETURNING_DEFAULT, -1, false, &r_colnames, &r_colvars); /* add in all colnames and colvars from the l_rte. */ diff --git a/src/backend/utils/adt/agtype_parser.c b/src/backend/utils/adt/agtype_parser.c index c485cb925..40fc8d8c5 100644 --- a/src/backend/utils/adt/agtype_parser.c +++ b/src/backend/utils/adt/agtype_parser.c @@ -74,11 +74,9 @@ static void parse_object(agtype_lex_context *lex, agtype_sem_action *sem); static void parse_array_element(agtype_lex_context *lex, agtype_sem_action *sem); static void parse_array(agtype_lex_context *lex, agtype_sem_action *sem); -static void report_parse_error(agtype_parse_context ctx, - agtype_lex_context *lex) - pg_attribute_noreturn(); -static void report_invalid_token(agtype_lex_context *lex) - pg_attribute_noreturn(); +static pg_noreturn void report_parse_error(agtype_parse_context ctx, + agtype_lex_context *lex); +static pg_noreturn void report_invalid_token(agtype_lex_context *lex); static int report_agtype_context(agtype_lex_context *lex); static char *extract_mb_char(char *s); From 201399f4da153aad766e60cf97088d7b5ab55435 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Tue, 2 Dec 2025 10:50:16 -0800 Subject: [PATCH 03/14] Update CI for BaseDockerizedTest (#2254) Updated the CI BaseDockerizedTest to point to dev_snapshot_PG18 --- .../src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java b/drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java index 393175c3d..d9caa4bb0 100644 --- a/drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java +++ b/drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java @@ -52,7 +52,7 @@ public void beforeAll() throws Exception { String CORRECT_DB_PASSWORDS = "postgres"; agensGraphContainer = new GenericContainer<>(DockerImageName - .parse("apache/age:dev_snapshot_master")) + .parse("apache/age:dev_snapshot_PG18")) .withEnv("POSTGRES_PASSWORD", CORRECT_DB_PASSWORDS) .withExposedPorts(5432); agensGraphContainer.start(); From f364664e0d2835429d480c90683d193599e249cb Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Tue, 2 Dec 2025 10:51:54 -0800 Subject: [PATCH 04/14] Fix DockerHub build warning messages (#2252) PR fixes build warning messages on DockerHub and on my local build. No regression tests needed. modified: src/include/nodes/ag_nodes.h modified: src/include/optimizer/cypher_createplan.h modified: src/include/optimizer/cypher_pathnode.h modified: tools/gen_keywordlist.pl --- src/include/nodes/ag_nodes.h | 3 +++ src/include/optimizer/cypher_createplan.h | 7 +++++++ src/include/optimizer/cypher_pathnode.h | 7 +++++++ tools/gen_keywordlist.pl | 1 + 4 files changed, 18 insertions(+) diff --git a/src/include/nodes/ag_nodes.h b/src/include/nodes/ag_nodes.h index f0cc22043..121832c01 100644 --- a/src/include/nodes/ag_nodes.h +++ b/src/include/nodes/ag_nodes.h @@ -78,6 +78,9 @@ typedef enum ag_node_tag cypher_merge_information_t } ag_node_tag; +extern const char *node_names[]; +extern const ExtensibleNodeMethods node_methods[]; + void register_ag_nodes(void); ExtensibleNode *_new_ag_node(Size size, ag_node_tag tag); diff --git a/src/include/optimizer/cypher_createplan.h b/src/include/optimizer/cypher_createplan.h index 2d5d2e698..ab01f2b58 100644 --- a/src/include/optimizer/cypher_createplan.h +++ b/src/include/optimizer/cypher_createplan.h @@ -20,6 +20,8 @@ #ifndef AG_CYPHER_CREATEPLAN_H #define AG_CYPHER_CREATEPLAN_H +#include "nodes/extensible.h" + Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans); @@ -36,4 +38,9 @@ Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans); +extern const CustomScanMethods cypher_create_plan_methods; +extern const CustomScanMethods cypher_set_plan_methods; +extern const CustomScanMethods cypher_delete_plan_methods; +extern const CustomScanMethods cypher_merge_plan_methods; + #endif diff --git a/src/include/optimizer/cypher_pathnode.h b/src/include/optimizer/cypher_pathnode.h index 75c2b07de..676832029 100644 --- a/src/include/optimizer/cypher_pathnode.h +++ b/src/include/optimizer/cypher_pathnode.h @@ -20,6 +20,8 @@ #ifndef AG_CYPHER_PATHNODE_H #define AG_CYPHER_PATHNODE_H +#include "nodes/extensible.h" + #define CREATE_PATH_NAME "Cypher Create" #define SET_PATH_NAME "Cypher Set" #define DELETE_PATH_NAME "Cypher Delete" @@ -34,4 +36,9 @@ CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private); +extern const CustomPathMethods cypher_create_path_methods; +extern const CustomPathMethods cypher_set_path_methods; +extern const CustomPathMethods cypher_delete_path_methods; +extern const CustomPathMethods cypher_merge_path_methods; + #endif diff --git a/tools/gen_keywordlist.pl b/tools/gen_keywordlist.pl index 499300433..58e66db8e 100755 --- a/tools/gen_keywordlist.pl +++ b/tools/gen_keywordlist.pl @@ -112,6 +112,7 @@ #define %s_H #include "common/kwlookup.h" +#include "parser/cypher_keywords.h" EOM From c176b57ce3e1437c23dd486a8bc44fcd3802e989 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Wed, 19 Nov 2025 13:45:44 -0800 Subject: [PATCH 05/14] Fix issue 2245 - Creating more than 41 vlabels causes crash in drop_graph (#2248) Fixed issue 2245 - Creating more than 41 vlabels causes drop_grapth to fail with "label (relation) cache corrupted" and crashing out on the following command. This was due to corruption of the label_relation_cache during the HASH_DELETE process. As the issue was with a cache flush routine, it was necessary to fix them all. Here is the list of the flush functions that were fixed - static void flush_graph_name_cache(void) static void flush_graph_namespace_cache(void) static void flush_label_name_graph_cache(void) static void flush_label_graph_oid_cache(void) static void flush_label_relation_cache(void) static void flush_label_seq_name_graph_cache(void) Added regression tests. modified: regress/expected/catalog.out modified: regress/sql/catalog.sql modified: src/backend/utils/cache/ag_cache.c --- regress/expected/catalog.out | 141 ++++++++++++++++++++++- regress/sql/catalog.sql | 42 ++++++- src/backend/utils/cache/ag_cache.c | 174 ++++++++++------------------- 3 files changed, 241 insertions(+), 116 deletions(-) diff --git a/regress/expected/catalog.out b/regress/expected/catalog.out index d06a0ce67..a15fa4698 100644 --- a/regress/expected/catalog.out +++ b/regress/expected/catalog.out @@ -457,7 +457,146 @@ NOTICE: graph does not exist (1 row) DROP FUNCTION raise_notice(TEXT); --- dropping the graph +-- +-- Fix issue 2245 - Creating more than 41 vlabels causes drop_graph to fail with +-- label (relation) cache corrupted +-- +-- this result will change if another graph was created prior to this point. +SELECT count(*) FROM ag_label; + count +------- + 2 +(1 row) + +SELECT * FROM create_graph('issue_2245'); +NOTICE: graph "issue_2245" has been created + create_graph +-------------- + +(1 row) + +SELECT * FROM cypher('issue_2245', $$ + CREATE (a1:Part1 {part_num: '123'}), (a2:Part2 {part_num: '345'}), (a3:Part3 {part_num: '456'}), + (a4:Part4 {part_num: '789'}), (a5:Part5 {part_num: '123'}), (a6:Part6 {part_num: '345'}), + (a7:Part7 {part_num: '456'}), (a8:Part8 {part_num: '789'}), (a9:Part9 {part_num: '123'}), + (a10:Part10 {part_num: '345'}), (a11:Part11 {part_num: '456'}), (a12:Part12 {part_num: '789'}), + (a13:Part13 {part_num: '123'}), (a14:Part14 {part_num: '345'}), (a15:Part15 {part_num: '456'}), + (a16:Part16 {part_num: '789'}), (a17:Part17 {part_num: '123'}), (a18:Part18 {part_num: '345'}), + (a19:Part19 {part_num: '456'}), (a20:Part20 {part_num: '789'}), (a21:Part21 {part_num: '123'}), + (a22:Part22 {part_num: '345'}), (a23:Part23 {part_num: '456'}), (a24:Part24 {part_num: '789'}), + (a25:Part25 {part_num: '123'}), (a26:Part26 {part_num: '345'}), (a27:Part27 {part_num: '456'}), + (a28:Part28 {part_num: '789'}), (a29:Part29 {part_num: '789'}), (a30:Part30 {part_num: '123'}), + (a31:Part31 {part_num: '345'}), (a32:Part32 {part_num: '456'}), (a33:Part33 {part_num: '789'}), + (a34:Part34 {part_num: '123'}), (a35:Part35 {part_num: '345'}), (a36:Part36 {part_num: '456'}), + (a37:Part37 {part_num: '789'}), (a38:Part38 {part_num: '123'}), (a39:Part39 {part_num: '345'}), + (a40:Part40 {part_num: '456'}), (a41:Part41 {part_num: '789'}), (a42:Part42 {part_num: '345'}), + (a43:Part43 {part_num: '456'}), (a44:Part44 {part_num: '789'}), (a45:Part45 {part_num: '456'}), + (a46:Part46 {part_num: '789'}), (a47:Part47 {part_num: '456'}), (a48:Part48 {part_num: '789'}), + (a49:Part49 {part_num: '789'}), (a50:Part50 {part_num: '456'}), (a51:Part51 {part_num: '789'}) + $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT count(*) FROM ag_label; + count +------- + 55 +(1 row) + +SELECT drop_graph('issue_2245', true); +NOTICE: drop cascades to 53 other objects +DETAIL: drop cascades to table issue_2245._ag_label_vertex +drop cascades to table issue_2245._ag_label_edge +drop cascades to table issue_2245."Part1" +drop cascades to table issue_2245."Part2" +drop cascades to table issue_2245."Part3" +drop cascades to table issue_2245."Part4" +drop cascades to table issue_2245."Part5" +drop cascades to table issue_2245."Part6" +drop cascades to table issue_2245."Part7" +drop cascades to table issue_2245."Part8" +drop cascades to table issue_2245."Part9" +drop cascades to table issue_2245."Part10" +drop cascades to table issue_2245."Part11" +drop cascades to table issue_2245."Part12" +drop cascades to table issue_2245."Part13" +drop cascades to table issue_2245."Part14" +drop cascades to table issue_2245."Part15" +drop cascades to table issue_2245."Part16" +drop cascades to table issue_2245."Part17" +drop cascades to table issue_2245."Part18" +drop cascades to table issue_2245."Part19" +drop cascades to table issue_2245."Part20" +drop cascades to table issue_2245."Part21" +drop cascades to table issue_2245."Part22" +drop cascades to table issue_2245."Part23" +drop cascades to table issue_2245."Part24" +drop cascades to table issue_2245."Part25" +drop cascades to table issue_2245."Part26" +drop cascades to table issue_2245."Part27" +drop cascades to table issue_2245."Part28" +drop cascades to table issue_2245."Part29" +drop cascades to table issue_2245."Part30" +drop cascades to table issue_2245."Part31" +drop cascades to table issue_2245."Part32" +drop cascades to table issue_2245."Part33" +drop cascades to table issue_2245."Part34" +drop cascades to table issue_2245."Part35" +drop cascades to table issue_2245."Part36" +drop cascades to table issue_2245."Part37" +drop cascades to table issue_2245."Part38" +drop cascades to table issue_2245."Part39" +drop cascades to table issue_2245."Part40" +drop cascades to table issue_2245."Part41" +drop cascades to table issue_2245."Part42" +drop cascades to table issue_2245."Part43" +drop cascades to table issue_2245."Part44" +drop cascades to table issue_2245."Part45" +drop cascades to table issue_2245."Part46" +drop cascades to table issue_2245."Part47" +drop cascades to table issue_2245."Part48" +drop cascades to table issue_2245."Part49" +drop cascades to table issue_2245."Part50" +drop cascades to table issue_2245."Part51" +NOTICE: graph "issue_2245" has been dropped + drop_graph +------------ + +(1 row) + +-- this result should be the same as the one before the create_graph +SELECT count(*) FROM ag_label; + count +------- + 2 +(1 row) + +-- create the graph again +SELECT * FROM create_graph('issue_2245'); +NOTICE: graph "issue_2245" has been created + create_graph +-------------- + +(1 row) + +SELECT count(*) FROM ag_label; + count +------- + 4 +(1 row) + +-- dropping the graphs +SELECT drop_graph('issue_2245', true); +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table issue_2245._ag_label_vertex +drop cascades to table issue_2245._ag_label_edge +NOTICE: graph "issue_2245" has been dropped + drop_graph +------------ + +(1 row) + SELECT drop_graph('graph', true); NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table graph._ag_label_vertex diff --git a/regress/sql/catalog.sql b/regress/sql/catalog.sql index 85fc4e8ab..bb72c3495 100644 --- a/regress/sql/catalog.sql +++ b/regress/sql/catalog.sql @@ -193,5 +193,45 @@ SELECT raise_notice('graph1'); DROP FUNCTION raise_notice(TEXT); --- dropping the graph +-- +-- Fix issue 2245 - Creating more than 41 vlabels causes drop_graph to fail with +-- label (relation) cache corrupted +-- + +-- this result will change if another graph was created prior to this point. +SELECT count(*) FROM ag_label; + +SELECT * FROM create_graph('issue_2245'); +SELECT * FROM cypher('issue_2245', $$ + CREATE (a1:Part1 {part_num: '123'}), (a2:Part2 {part_num: '345'}), (a3:Part3 {part_num: '456'}), + (a4:Part4 {part_num: '789'}), (a5:Part5 {part_num: '123'}), (a6:Part6 {part_num: '345'}), + (a7:Part7 {part_num: '456'}), (a8:Part8 {part_num: '789'}), (a9:Part9 {part_num: '123'}), + (a10:Part10 {part_num: '345'}), (a11:Part11 {part_num: '456'}), (a12:Part12 {part_num: '789'}), + (a13:Part13 {part_num: '123'}), (a14:Part14 {part_num: '345'}), (a15:Part15 {part_num: '456'}), + (a16:Part16 {part_num: '789'}), (a17:Part17 {part_num: '123'}), (a18:Part18 {part_num: '345'}), + (a19:Part19 {part_num: '456'}), (a20:Part20 {part_num: '789'}), (a21:Part21 {part_num: '123'}), + (a22:Part22 {part_num: '345'}), (a23:Part23 {part_num: '456'}), (a24:Part24 {part_num: '789'}), + (a25:Part25 {part_num: '123'}), (a26:Part26 {part_num: '345'}), (a27:Part27 {part_num: '456'}), + (a28:Part28 {part_num: '789'}), (a29:Part29 {part_num: '789'}), (a30:Part30 {part_num: '123'}), + (a31:Part31 {part_num: '345'}), (a32:Part32 {part_num: '456'}), (a33:Part33 {part_num: '789'}), + (a34:Part34 {part_num: '123'}), (a35:Part35 {part_num: '345'}), (a36:Part36 {part_num: '456'}), + (a37:Part37 {part_num: '789'}), (a38:Part38 {part_num: '123'}), (a39:Part39 {part_num: '345'}), + (a40:Part40 {part_num: '456'}), (a41:Part41 {part_num: '789'}), (a42:Part42 {part_num: '345'}), + (a43:Part43 {part_num: '456'}), (a44:Part44 {part_num: '789'}), (a45:Part45 {part_num: '456'}), + (a46:Part46 {part_num: '789'}), (a47:Part47 {part_num: '456'}), (a48:Part48 {part_num: '789'}), + (a49:Part49 {part_num: '789'}), (a50:Part50 {part_num: '456'}), (a51:Part51 {part_num: '789'}) + $$) AS (result agtype); + +SELECT count(*) FROM ag_label; +SELECT drop_graph('issue_2245', true); + +-- this result should be the same as the one before the create_graph +SELECT count(*) FROM ag_label; + +-- create the graph again +SELECT * FROM create_graph('issue_2245'); +SELECT count(*) FROM ag_label; + +-- dropping the graphs +SELECT drop_graph('issue_2245', true); SELECT drop_graph('graph', true); diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index e3c4d0794..493ffcfa9 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -286,52 +286,34 @@ static void invalidate_graph_caches(Datum arg, int cache_id, uint32 hash_value) static void flush_graph_name_cache(void) { - HASH_SEQ_STATUS hash_seq; - - hash_seq_init(&hash_seq, graph_name_cache_hash); - for (;;) + /* + * If the graph_name_cache exists, destroy it. This will avoid any + * potential corruption issues. + */ + if (graph_name_cache_hash) { - graph_name_cache_entry *entry; - void *removed; - - entry = hash_seq_search(&hash_seq); - if (!entry) - { - break; - } - removed = hash_search(graph_name_cache_hash, &entry->name, HASH_REMOVE, - NULL); - if (!removed) - { - ereport(ERROR, (errmsg_internal("graph (name) cache corrupted"))); - } + hash_destroy(graph_name_cache_hash); + graph_name_cache_hash = NULL; } + + /* recreate the graph_name_cache */ + create_graph_name_cache(); } static void flush_graph_namespace_cache(void) { - HASH_SEQ_STATUS hash_seq; - - hash_seq_init(&hash_seq, graph_namespace_cache_hash); - for (;;) + /* + * If the graph_namespace_cache exists, destroy it. This will avoid any + * potential corruption issues. + */ + if (graph_namespace_cache_hash) { - graph_namespace_cache_entry *entry; - void *removed; - - entry = hash_seq_search(&hash_seq); - if (!entry) - { - break; - } - - removed = hash_search(graph_namespace_cache_hash, &entry->namespace, - HASH_REMOVE, NULL); - if (!removed) - { - ereport(ERROR, - (errmsg_internal("graph (namespace) cache corrupted"))); - } + hash_destroy(graph_namespace_cache_hash); + graph_namespace_cache_hash = NULL; } + + /* recreate the graph_namespace_cache */ + create_graph_namespace_cache(); } graph_cache_data *search_graph_name_cache(const char *name) @@ -664,27 +646,18 @@ static void invalidate_label_name_graph_cache(Oid relid) static void flush_label_name_graph_cache(void) { - HASH_SEQ_STATUS hash_seq; - - hash_seq_init(&hash_seq, label_name_graph_cache_hash); - for (;;) + /* + * If the label_name_graph_cache exists, destroy it. This will avoid any + * potential corruption issues. + */ + if (label_name_graph_cache_hash) { - label_name_graph_cache_entry *entry; - void *removed; - - entry = hash_seq_search(&hash_seq); - if (!entry) - { - break; - } - removed = hash_search(label_name_graph_cache_hash, &entry->key, - HASH_REMOVE, NULL); - if (!removed) - { - ereport(ERROR, - (errmsg_internal("label (name, graph) cache corrupted"))); - } + hash_destroy(label_name_graph_cache_hash); + label_name_graph_cache_hash = NULL; } + + /* recreate the label_name_graph_cache */ + create_label_name_graph_cache(); } static void invalidate_label_graph_oid_cache(Oid relid) @@ -722,27 +695,18 @@ static void invalidate_label_graph_oid_cache(Oid relid) static void flush_label_graph_oid_cache(void) { - HASH_SEQ_STATUS hash_seq; - - hash_seq_init(&hash_seq, label_graph_oid_cache_hash); - for (;;) + /* + * If the label_graph_oid_cache exists, destroy it. This will avoid any + * potential corruption issues. + */ + if (label_graph_oid_cache_hash) { - label_graph_oid_cache_entry *entry; - void *removed; - - entry = hash_seq_search(&hash_seq); - if (!entry) - { - break; - } - removed = hash_search(label_graph_oid_cache_hash, &entry->key, - HASH_REMOVE, NULL); - if (!removed) - { - ereport(ERROR, - (errmsg_internal("label (graph, id) cache corrupted"))); - } + hash_destroy(label_graph_oid_cache_hash); + label_graph_oid_cache_hash = NULL; } + + /* recreate the label_graph_oid_cache */ + create_label_graph_oid_cache(); } static void invalidate_label_relation_cache(Oid relid) @@ -765,27 +729,18 @@ static void invalidate_label_relation_cache(Oid relid) static void flush_label_relation_cache(void) { - HASH_SEQ_STATUS hash_seq; - - hash_seq_init(&hash_seq, label_relation_cache_hash); - for (;;) + /* + * If the label_relation_cache exists, destroy it. This will avoid any + * potential corruption issues. + */ + if (label_relation_cache_hash) { - label_relation_cache_entry *entry; - void *removed; - - entry = hash_seq_search(&hash_seq); - if (!entry) - { - break; - } - removed = hash_search(label_relation_cache_hash, &entry->relation, - HASH_REMOVE, NULL); - if (!removed) - { - ereport(ERROR, - (errmsg_internal("label (relation) cache corrupted"))); - } + hash_destroy(label_relation_cache_hash); + label_relation_cache_hash = NULL; } + + /* recreate the label_relation_cache */ + create_label_relation_cache(); } static void invalidate_label_seq_name_graph_cache(Oid relid) @@ -823,27 +778,18 @@ static void invalidate_label_seq_name_graph_cache(Oid relid) static void flush_label_seq_name_graph_cache(void) { - HASH_SEQ_STATUS hash_seq; - - hash_seq_init(&hash_seq, label_seq_name_graph_cache_hash); - for (;;) + /* + * If the label_seq_name_graph_cache exists, destroy it. This will + * avoid any potential corruption issues by deleting entries. + */ + if (label_seq_name_graph_cache_hash) { - label_seq_name_graph_cache_entry *entry; - void *removed; - - entry = hash_seq_search(&hash_seq); - if (!entry) - { - break; - } - removed = hash_search(label_seq_name_graph_cache_hash, &entry->key, - HASH_REMOVE, NULL); - if (!removed) - { - ereport(ERROR, - (errmsg_internal("label (seq_name, graph) cache corrupted"))); - } + hash_destroy(label_seq_name_graph_cache_hash); + label_seq_name_graph_cache_hash = NULL; } + + /* recreate the label_seq_name_graph_cache */ + create_label_seq_name_graph_cache(); } label_cache_data *search_label_name_graph_cache(const char *name, Oid graph) From 2f36b1c31cc4cd0d18cdf58fc3c5a8e81aab65a8 Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Wed, 3 Dec 2025 22:17:09 +0500 Subject: [PATCH 06/14] Add index on id columns (#2117) - Whenever a label will be created, indices on id columns will be created by default. In case of vertex, a unique index on id column will be created, which will also serve as a unique constraint. In case of edge, a non-unique index on start_id and end_id columns will be created. - This change is expected to improve the performance of queries that involve joins. From some performance tests, it was observed that the performance of queries improved alot. - Loader was updated to insert tuples in indices as well. This has caused to slow the loader down a bit, but it was necessary. - A bug related to command ids in cypher_delete executor was also fixed. Resolved conflicts: regress/expected/cypher_match.out regress/expected/expr.out --- regress/expected/age_load.out | 14 -- regress/expected/cypher_match.out | 56 +++--- regress/expected/cypher_merge.out | 2 +- regress/expected/cypher_subquery.out | 2 +- regress/expected/cypher_vle.out | 24 +-- regress/expected/expr.out | 20 +-- regress/expected/graph_generation.out | 20 +-- regress/expected/index.out | 97 +++++++++-- regress/expected/map_projection.out | 2 +- regress/sql/age_load.sql | 6 - regress/sql/index.sql | 34 ++-- src/backend/commands/label_commands.c | 78 ++++++++- src/backend/executor/cypher_delete.c | 4 + src/backend/utils/load/ag_load_edges.c | 75 +-------- src/backend/utils/load/ag_load_labels.c | 215 +----------------------- src/backend/utils/load/age_load.c | 193 +++++++++++++++++---- src/include/utils/load/ag_load_labels.h | 5 - src/include/utils/load/age_load.h | 16 +- 18 files changed, 410 insertions(+), 453 deletions(-) diff --git a/regress/expected/age_load.out b/regress/expected/age_load.out index b638e636b..5f2bdab78 100644 --- a/regress/expected/age_load.out +++ b/regress/expected/age_load.out @@ -43,13 +43,6 @@ SELECT load_labels_from_file('agload_test_graph', 'Country', (1 row) --- A temporary table should have been created with 54 ids; 1 from CREATE and 53 from file -SELECT COUNT(*)=54 FROM "_agload_test_graph_ag_vertex_ids"; - ?column? ----------- - t -(1 row) - -- Sequence should be equal to max entry id i.e. 248 SELECT currval('agload_test_graph."Country_id_seq"')=248; ?column? @@ -74,13 +67,6 @@ NOTICE: VLabel "City" has been created (1 row) --- Temporary table should have 54+72485 rows now -SELECT COUNT(*)=54+72485 FROM "_agload_test_graph_ag_vertex_ids"; - ?column? ----------- - t -(1 row) - -- Sequence should be equal to max entry id i.e. 146941 SELECT currval('agload_test_graph."City_id_seq"')=146941; ?column? diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index ad5a07d05..a34f59196 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -79,8 +79,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex, {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge, {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex, {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge, {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex]::path [{"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex, {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge, {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex, {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge, {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex]::path + [{"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex, {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge, {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex, {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge, {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex]::path (2 rows) SELECT * FROM cypher('cypher_match', $$ @@ -115,8 +115,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------- - {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge + {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge (2 rows) SELECT * FROM cypher('cypher_match', $$ @@ -133,9 +133,9 @@ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------- {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge - {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge + {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge (4 rows) SELECT * FROM cypher('cypher_match', $$ @@ -154,10 +154,10 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------- - {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge - {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge + {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge + {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge (4 rows) SELECT * FROM cypher('cypher_match', $$ @@ -250,8 +250,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------- - {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge + {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge (2 rows) --Left Path Test @@ -308,8 +308,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------- - {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge + {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge (2 rows) --Divergent Path Tests @@ -412,8 +412,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (i agtype); i --------------------------------------------------------------------------------------------------------------------------- - {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge {"id": 2533274790395905, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685251, "properties": {}}::edge + {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge (2 rows) SELECT * FROM cypher('cypher_match', $$ @@ -712,8 +712,8 @@ $$) AS (r0 agtype); {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge - {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge {"id": 2533274790395905, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685251, "properties": {}}::edge + {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge (6 rows) SELECT * FROM cypher('cypher_match', $$ @@ -775,8 +775,8 @@ $$) AS (r1 agtype); {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge - {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge + {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge (12 rows) @@ -1055,8 +1055,8 @@ SELECT * FROM cypher('cypher_match', {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex | {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex | {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge | {"id": 1688849860263937, "label": "v2", "properties": {"id": "initial"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex | {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge | {"id": 1688849860263939, "label": "v2", "properties": {"id": "end"}}::vertex - {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex | {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex {"id": 2251799813685251, "label": "v3", "properties": {"id": "end"}}::vertex | {"id": 2533274790395905, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685251, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex + {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex | {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex (6 rows) SELECT * FROM cypher('cypher_match', @@ -1068,8 +1068,8 @@ AS (u agtype, e agtype, v agtype); {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex | {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex | {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge | {"id": 1688849860263937, "label": "v2", "properties": {"id": "initial"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex | {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge | {"id": 1688849860263939, "label": "v2", "properties": {"id": "end"}}::vertex - {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex | {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex {"id": 2251799813685251, "label": "v3", "properties": {"id": "end"}}::vertex | {"id": 2533274790395905, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685251, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex + {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex | {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex (6 rows) -- Property Constraint in EXISTS @@ -1123,8 +1123,8 @@ AS (u agtype, e agtype, v agtype); {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex | {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex | {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge | {"id": 1688849860263937, "label": "v2", "properties": {"id": "initial"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex | {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge | {"id": 1688849860263939, "label": "v2", "properties": {"id": "end"}}::vertex - {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex | {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex {"id": 2251799813685251, "label": "v3", "properties": {"id": "end"}}::vertex | {"id": 2533274790395905, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685251, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex + {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex | {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex {"id": 2814749767106561, "label": "loop", "properties": {"id": "initial"}}::vertex | {"id": 3096224743817217, "label": "self", "end_id": 2814749767106561, "start_id": 2814749767106561, "properties": {}}::edge | {"id": 2814749767106561, "label": "loop", "properties": {"id": "initial"}}::vertex (7 rows) @@ -1156,8 +1156,8 @@ AS (u agtype, e agtype, v agtype); {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex | {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex | {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge | {"id": 1688849860263937, "label": "v2", "properties": {"id": "initial"}}::vertex {"id": 1688849860263938, "label": "v2", "properties": {"id": "middle"}}::vertex | {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge | {"id": 1688849860263939, "label": "v2", "properties": {"id": "end"}}::vertex - {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex | {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex {"id": 2251799813685251, "label": "v3", "properties": {"id": "end"}}::vertex | {"id": 2533274790395905, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685251, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex + {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex | {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge | {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex {"id": 2814749767106561, "label": "loop", "properties": {"id": "initial"}}::vertex | {"id": 3096224743817217, "label": "self", "end_id": 2814749767106561, "start_id": 2814749767106561, "properties": {}}::edge | {"id": 2814749767106561, "label": "loop", "properties": {"id": "initial"}}::vertex (7 rows) @@ -2164,8 +2164,8 @@ SELECT * FROM cypher('cypher_match', $$ MATCH p=(u)-[]-()-[]-(u) RETURN p $$)as p ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [{"id": 281474976710667, "label": "", "properties": {"name": "Dave"}}::vertex, {"id": 4785074604081155, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710668, "properties": {}}::edge, {"id": 281474976710668, "label": "", "properties": {"name": "John"}}::vertex, {"id": 4785074604081156, "label": "knows", "end_id": 281474976710668, "start_id": 281474976710667, "properties": {}}::edge, {"id": 281474976710667, "label": "", "properties": {"name": "Dave"}}::vertex]::path - [{"id": 281474976710668, "label": "", "properties": {"name": "John"}}::vertex, {"id": 4785074604081155, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710668, "properties": {}}::edge, {"id": 281474976710667, "label": "", "properties": {"name": "Dave"}}::vertex, {"id": 4785074604081156, "label": "knows", "end_id": 281474976710668, "start_id": 281474976710667, "properties": {}}::edge, {"id": 281474976710668, "label": "", "properties": {"name": "John"}}::vertex]::path [{"id": 281474976710667, "label": "", "properties": {"name": "Dave"}}::vertex, {"id": 4785074604081156, "label": "knows", "end_id": 281474976710668, "start_id": 281474976710667, "properties": {}}::edge, {"id": 281474976710668, "label": "", "properties": {"name": "John"}}::vertex, {"id": 4785074604081155, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710668, "properties": {}}::edge, {"id": 281474976710667, "label": "", "properties": {"name": "Dave"}}::vertex]::path + [{"id": 281474976710668, "label": "", "properties": {"name": "John"}}::vertex, {"id": 4785074604081155, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710668, "properties": {}}::edge, {"id": 281474976710667, "label": "", "properties": {"name": "Dave"}}::vertex, {"id": 4785074604081156, "label": "knows", "end_id": 281474976710668, "start_id": 281474976710667, "properties": {}}::edge, {"id": 281474976710668, "label": "", "properties": {"name": "John"}}::vertex]::path [{"id": 281474976710668, "label": "", "properties": {"name": "John"}}::vertex, {"id": 4785074604081156, "label": "knows", "end_id": 281474976710668, "start_id": 281474976710667, "properties": {}}::edge, {"id": 281474976710667, "label": "", "properties": {"name": "Dave"}}::vertex, {"id": 4785074604081155, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710668, "properties": {}}::edge, {"id": 281474976710668, "label": "", "properties": {"name": "John"}}::vertex]::path (4 rows) @@ -2421,8 +2421,8 @@ SELECT * FROM cypher('cypher_match', $$ MATCH p=(a)-[u {relationship: u.relation SELECT * FROM cypher('cypher_match', $$ MATCH p=(a {name:a.name})-[u {relationship: u.relationship}]->(b {age:b.age}) RETURN p $$) as (a agtype); a ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex, {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path [{"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex, {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path + [{"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex, {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge, {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex]::path (2 rows) SELECT * FROM cypher('cypher_match', $$ CREATE () WITH * MATCH (x{n0:x.n1}) RETURN 0 $$) as (a agtype); @@ -3514,19 +3514,17 @@ SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH p=(x:Customer)-[ (1 row) SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (costs off) MATCH (x:Customer)-[:bought ={store: 'Amazon', addr:{city: 'Vancouver', street: 30}}]->(y:Product) RETURN 0 $$) as (a agtype); - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Hash Join - Hash Cond: (y.id = _age_default_alias_0.end_id) - -> Seq Scan on "Product" y - -> Hash - -> Hash Join - Hash Cond: (x.id = _age_default_alias_0.start_id) - -> Seq Scan on "Customer" x - -> Hash - -> Seq Scan on bought _age_default_alias_0 - Filter: ((agtype_access_operator(VARIADIC ARRAY[properties, '"store"'::agtype]) = '"Amazon"'::agtype) AND (agtype_access_operator(VARIADIC ARRAY[properties, '"addr"'::agtype]) = '{"city": "Vancouver", "street": 30}'::agtype)) -(10 rows) + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop + -> Nested Loop + -> Seq Scan on bought _age_default_alias_0 + Filter: ((agtype_access_operator(VARIADIC ARRAY[properties, '"store"'::agtype]) = '"Amazon"'::agtype) AND (agtype_access_operator(VARIADIC ARRAY[properties, '"addr"'::agtype]) = '{"city": "Vancouver", "street": 30}'::agtype)) + -> Index Only Scan using "Customer_pkey" on "Customer" x + Index Cond: (id = _age_default_alias_0.start_id) + -> Index Only Scan using "Product_pkey" on "Product" y + Index Cond: (id = _age_default_alias_0.end_id) +(8 rows) SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (costs off) MATCH (x:Customer ={school: { name: 'XYZ College',program: { major: 'Psyc', degree: 'BSc'} },phone: [ 123456789, 987654321, 456987123 ]}) RETURN 0 $$) as (a agtype); QUERY PLAN diff --git a/regress/expected/cypher_merge.out b/regress/expected/cypher_merge.out index 238a4c472..56a23f513 100644 --- a/regress/expected/cypher_merge.out +++ b/regress/expected/cypher_merge.out @@ -655,8 +655,8 @@ $$) AS (name agtype, bornIn agtype, city agtype); name | bornin | city -------------------+--------------+----------------------------------------------------------------------------------------- "Rob Reiner" | "New York" | {"id": 1970324836974593, "label": "City", "properties": {"name": "New York"}}::vertex - "Martin Sheen" | "Ohio" | {"id": 1970324836974595, "label": "City", "properties": {"name": "Ohio"}}::vertex "Michael Douglas" | "New Jersey" | {"id": 1970324836974594, "label": "City", "properties": {"name": "New Jersey"}}::vertex + "Martin Sheen" | "Ohio" | {"id": 1970324836974595, "label": "City", "properties": {"name": "Ohio"}}::vertex (3 rows) --validate diff --git a/regress/expected/cypher_subquery.out b/regress/expected/cypher_subquery.out index ff5672bca..934549f82 100644 --- a/regress/expected/cypher_subquery.out +++ b/regress/expected/cypher_subquery.out @@ -668,8 +668,8 @@ SELECT * FROM cypher('subquery', $$ MATCH (a:person)-[]->(b) RETURN a $$) AS (result agtype); result ------------------------------------------------------------------------------------------------- - {"id": 844424930131975, "label": "person", "properties": {"age": 6, "name": "Calvin"}}::vertex {"id": 844424930131977, "label": "person", "properties": {"age": 8, "name": "Charlie"}}::vertex + {"id": 844424930131975, "label": "person", "properties": {"age": 6, "name": "Calvin"}}::vertex {"id": 844424930131977, "label": "person", "properties": {"age": 8, "name": "Charlie"}}::vertex (3 rows) diff --git a/regress/expected/cypher_vle.out b/regress/expected/cypher_vle.out index 9cbb3420c..57f930d98 100644 --- a/regress/expected/cypher_vle.out +++ b/regress/expected/cypher_vle.out @@ -508,37 +508,37 @@ SELECT * FROM cypher('cypher_vle', $$MATCH p=(u)-[e*0..0]->(v) RETURN id(u), p, SELECT * FROM cypher('cypher_vle', $$MATCH p=()-[*0..0]->()-[]->() RETURN p $$) AS (p agtype); p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 2251799813685249, "label": "alternate_edge", "end_id": 1407374883553281, "start_id": 844424930131969, "properties": {"name": "alternate edge", "number": 1, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": {}}::vertex]::path + [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2533274790395906, "label": "bypass_edge", "end_id": 844424930131969, "start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 2, "packages": [1, 3, 5, 7], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 844424930131969, "label": "begin", "properties": {}}::vertex]::path [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 1125899906842628, "label": "edge", "end_id": 1407374883553281, "start_id": 844424930131969, "properties": {"name": "main edge", "number": 1, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": {}}::vertex]::path - [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, {"id": 1125899906842627, "label": "edge", "end_id": 1407374883553282, "start_id": 1407374883553281, "properties": {"name": "main edge", "number": 2, "packages": [2, 4, 6], "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, {"id": 1970324836974593, "label": "self_loop", "end_id": 1407374883553281, "start_id": 1407374883553281, "properties": {"name": "self loop", "number": 1, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": {}}::vertex]::path + [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 2251799813685249, "label": "alternate_edge", "end_id": 1407374883553281, "start_id": 844424930131969, "properties": {"name": "alternate edge", "number": 1, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": {}}::vertex]::path + [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685253, "label": "alternate_edge", "end_id": 1407374883553282, "start_id": 1407374883553283, "properties": {"name": "backup edge", "number": 2, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path + [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, {"id": 1125899906842627, "label": "edge", "end_id": 1407374883553282, "start_id": 1407374883553281, "properties": {"name": "main edge", "number": 2, "packages": [2, 4, 6], "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 1125899906842626, "label": "edge", "end_id": 1407374883553283, "start_id": 1407374883553282, "properties": {"name": "main edge", "number": 3, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553283, "label": "middle", "properties": {}}::vertex]::path [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685250, "label": "alternate_edge", "end_id": 1407374883553283, "start_id": 1407374883553282, "properties": {"name": "alternate edge", "number": 2, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1407374883553283, "label": "middle", "properties": {}}::vertex]::path - [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2533274790395905, "label": "bypass_edge", "end_id": 1688849860263937, "start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path - [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2533274790395906, "label": "bypass_edge", "end_id": 844424930131969, "start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 2, "packages": [1, 3, 5, 7], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 844424930131969, "label": "begin", "properties": {}}::vertex]::path - [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 1125899906842625, "label": "edge", "end_id": 1688849860263937, "start_id": 1407374883553283, "properties": {"name": "main edge", "number": 4, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path - [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685253, "label": "alternate_edge", "end_id": 1407374883553282, "start_id": 1407374883553283, "properties": {"name": "backup edge", "number": 2, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path - [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685251, "label": "alternate_edge", "end_id": 1688849860263937, "start_id": 1407374883553283, "properties": {"name": "alternate edge", "number": 3, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 2251799813685252, "label": "alternate_edge", "end_id": 1407374883553283, "start_id": 1688849860263937, "properties": {"name": "backup edge", "number": 1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553283, "label": "middle", "properties": {}}::vertex]::path + [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685251, "label": "alternate_edge", "end_id": 1688849860263937, "start_id": 1407374883553283, "properties": {"name": "alternate edge", "number": 3, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path + [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2533274790395905, "label": "bypass_edge", "end_id": 1688849860263937, "start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 1970324836974594, "label": "self_loop", "end_id": 1688849860263937, "start_id": 1688849860263937, "properties": {"name": "self loop", "number": 2, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path + [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 1125899906842625, "label": "edge", "end_id": 1688849860263937, "start_id": 1407374883553283, "properties": {"name": "main edge", "number": 4, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path (13 rows) SELECT * FROM cypher('cypher_vle', $$MATCH p=()-[]->()-[*0..0]->() RETURN p $$) AS (p agtype); p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2533274790395906, "label": "bypass_edge", "end_id": 844424930131969, "start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 2, "packages": [1, 3, 5, 7], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 844424930131969, "label": "begin", "properties": {}}::vertex]::path - [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 2251799813685249, "label": "alternate_edge", "end_id": 1407374883553281, "start_id": 844424930131969, "properties": {"name": "alternate edge", "number": 1, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": {}}::vertex]::path [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 1125899906842628, "label": "edge", "end_id": 1407374883553281, "start_id": 844424930131969, "properties": {"name": "main edge", "number": 1, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": {}}::vertex]::path + [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 2251799813685249, "label": "alternate_edge", "end_id": 1407374883553281, "start_id": 844424930131969, "properties": {"name": "alternate edge", "number": 1, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": {}}::vertex]::path [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, {"id": 1970324836974593, "label": "self_loop", "end_id": 1407374883553281, "start_id": 1407374883553281, "properties": {"name": "self loop", "number": 1, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": {}}::vertex]::path - [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685253, "label": "alternate_edge", "end_id": 1407374883553282, "start_id": 1407374883553283, "properties": {"name": "backup edge", "number": 2, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, {"id": 1125899906842627, "label": "edge", "end_id": 1407374883553282, "start_id": 1407374883553281, "properties": {"name": "main edge", "number": 2, "packages": [2, 4, 6], "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path - [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 2251799813685252, "label": "alternate_edge", "end_id": 1407374883553283, "start_id": 1688849860263937, "properties": {"name": "backup edge", "number": 1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553283, "label": "middle", "properties": {}}::vertex]::path [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 1125899906842626, "label": "edge", "end_id": 1407374883553283, "start_id": 1407374883553282, "properties": {"name": "main edge", "number": 3, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553283, "label": "middle", "properties": {}}::vertex]::path [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685250, "label": "alternate_edge", "end_id": 1407374883553283, "start_id": 1407374883553282, "properties": {"name": "alternate edge", "number": 2, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1407374883553283, "label": "middle", "properties": {}}::vertex]::path [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2533274790395905, "label": "bypass_edge", "end_id": 1688849860263937, "start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path - [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 1970324836974594, "label": "self_loop", "end_id": 1688849860263937, "start_id": 1688849860263937, "properties": {"name": "self loop", "number": 2, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path + [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, {"id": 2533274790395906, "label": "bypass_edge", "end_id": 844424930131969, "start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 2, "packages": [1, 3, 5, 7], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 844424930131969, "label": "begin", "properties": {}}::vertex]::path [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 1125899906842625, "label": "edge", "end_id": 1688849860263937, "start_id": 1407374883553283, "properties": {"name": "main edge", "number": 4, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685251, "label": "alternate_edge", "end_id": 1688849860263937, "start_id": 1407374883553283, "properties": {"name": "alternate edge", "number": 3, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path + [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, {"id": 2251799813685253, "label": "alternate_edge", "end_id": 1407374883553282, "start_id": 1407374883553283, "properties": {"name": "backup edge", "number": 2, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path + [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 2251799813685252, "label": "alternate_edge", "end_id": 1407374883553283, "start_id": 1688849860263937, "properties": {"name": "backup edge", "number": 1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553283, "label": "middle", "properties": {}}::vertex]::path + [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 1970324836974594, "label": "self_loop", "end_id": 1688849860263937, "start_id": 1688849860263937, "properties": {"name": "self loop", "number": 2, "dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": {}}::vertex]::path (13 rows) -- diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 20c9e95f3..d0546d8a3 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -2645,9 +2645,9 @@ SELECT * FROM cypher('expr', $$ MATCH ()-[e]-() RETURN e $$) AS (expression agty expression --------------------------------------------------------------------------------------------------------------------------- {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge - {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge + {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge (4 rows) -- id() @@ -2657,9 +2657,9 @@ $$) AS (id agtype); id ------------------ 1407374883553282 - 1407374883553281 1407374883553282 1407374883553281 + 1407374883553281 (4 rows) SELECT * FROM cypher('expr', $$ @@ -2699,9 +2699,9 @@ $$) AS (start_id agtype); start_id ------------------ 1125899906842625 - 1125899906842626 1125899906842625 1125899906842626 + 1125899906842626 (4 rows) -- should return null @@ -2732,9 +2732,9 @@ $$) AS (end_id agtype); end_id ------------------ 1125899906842626 - 1125899906842627 1125899906842626 1125899906842627 + 1125899906842627 (4 rows) -- should return null @@ -2765,9 +2765,9 @@ $$) AS (id agtype, start_id agtype, startNode agtype); id | start_id | startnode ------------------+------------------+---------------------------------------------------------------------------------- 1407374883553282 | 1125899906842625 | {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex - 1407374883553281 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex 1407374883553282 | 1125899906842625 | {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex 1407374883553281 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex + 1407374883553281 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex (4 rows) -- should return null @@ -2798,9 +2798,9 @@ $$) AS (id agtype, end_id agtype, endNode agtype); id | end_id | endnode ------------------+------------------+--------------------------------------------------------------------------------- 1407374883553282 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex - 1407374883553281 | 1125899906842627 | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex 1407374883553282 | 1125899906842626 | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex 1407374883553281 | 1125899906842627 | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex + 1407374883553281 | 1125899906842627 | {"id": 1125899906842627, "label": "v1", "properties": {"id": "end"}}::vertex (4 rows) -- should return null @@ -7496,10 +7496,10 @@ SELECT * FROM cypher('opt_forms', $$MATCH (u) RETURN *$$) AS (result agtype); SELECT * FROM cypher('opt_forms', $$MATCH (u)--(v) RETURN u.i, v.i$$) AS (u agtype, v agtype); u | v ---+--- - 2 | 3 - 3 | 2 1 | 2 2 | 1 + 2 | 3 + 3 | 2 (4 rows) SELECT * FROM cypher('opt_forms', $$MATCH (u)-->(v) RETURN u.i, v.i$$) AS (u agtype, v agtype); @@ -7686,12 +7686,12 @@ SELECT * FROM cypher('keys', $$MATCH (v) RETURN keys(v)$$) AS (vertex_keys agtyp SELECT * FROM cypher('keys', $$MATCH ()-[e]-() RETURN keys(e)$$) AS (edge_keys agtype); edge_keys ----------- - [] - [] ["song"] ["song"] + [] ["song"] ["song"] + [] (6 rows) SELECT * FROM cypher('keys', $$RETURN keys({a:1,b:'two',c:[1,2,3]})$$) AS (keys agtype); diff --git a/regress/expected/graph_generation.out b/regress/expected/graph_generation.out index 235052a08..ca511eafa 100644 --- a/regress/expected/graph_generation.out +++ b/regress/expected/graph_generation.out @@ -43,15 +43,15 @@ SELECT * FROM cypher('gp1', $$MATCH (a)-[e]->(b) RETURN e$$) as (n agtype); n ---------------------------------------------------------------------------------------------------------------------------- {"id": 1125899906842625, "label": "edges", "end_id": 844424930131970, "start_id": 844424930131969, "properties": {}}::edge - {"id": 1125899906842629, "label": "edges", "end_id": 844424930131971, "start_id": 844424930131970, "properties": {}}::edge {"id": 1125899906842626, "label": "edges", "end_id": 844424930131971, "start_id": 844424930131969, "properties": {}}::edge - {"id": 1125899906842630, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131970, "properties": {}}::edge + {"id": 1125899906842629, "label": "edges", "end_id": 844424930131971, "start_id": 844424930131970, "properties": {}}::edge {"id": 1125899906842627, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131969, "properties": {}}::edge + {"id": 1125899906842630, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131970, "properties": {}}::edge {"id": 1125899906842632, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131971, "properties": {}}::edge + {"id": 1125899906842628, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131969, "properties": {}}::edge {"id": 1125899906842631, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131970, "properties": {}}::edge - {"id": 1125899906842634, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131972, "properties": {}}::edge {"id": 1125899906842633, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131971, "properties": {}}::edge - {"id": 1125899906842628, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131969, "properties": {}}::edge + {"id": 1125899906842634, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131972, "properties": {}}::edge (10 rows) SELECT * FROM create_complete_graph('gp1',5,'edges','vertices'); @@ -140,25 +140,25 @@ SELECT * FROM cypher('gp1', $$MATCH (a)-[e]->(b) RETURN e$$) as (n agtype); n ---------------------------------------------------------------------------------------------------------------------------- {"id": 1125899906842625, "label": "edges", "end_id": 844424930131970, "start_id": 844424930131969, "properties": {}}::edge - {"id": 1125899906842629, "label": "edges", "end_id": 844424930131971, "start_id": 844424930131970, "properties": {}}::edge {"id": 1125899906842626, "label": "edges", "end_id": 844424930131971, "start_id": 844424930131969, "properties": {}}::edge + {"id": 1125899906842629, "label": "edges", "end_id": 844424930131971, "start_id": 844424930131970, "properties": {}}::edge {"id": 1125899906842627, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131969, "properties": {}}::edge - {"id": 1125899906842632, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131971, "properties": {}}::edge {"id": 1125899906842630, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131970, "properties": {}}::edge - {"id": 1125899906842634, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131972, "properties": {}}::edge + {"id": 1125899906842632, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131971, "properties": {}}::edge {"id": 1125899906842628, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131969, "properties": {}}::edge {"id": 1125899906842631, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131970, "properties": {}}::edge {"id": 1125899906842633, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131971, "properties": {}}::edge + {"id": 1125899906842634, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131972, "properties": {}}::edge {"id": 1125899906842635, "label": "edges", "end_id": 844424930131975, "start_id": 844424930131974, "properties": {}}::edge - {"id": 1125899906842639, "label": "edges", "end_id": 844424930131976, "start_id": 844424930131975, "properties": {}}::edge {"id": 1125899906842636, "label": "edges", "end_id": 844424930131976, "start_id": 844424930131974, "properties": {}}::edge + {"id": 1125899906842639, "label": "edges", "end_id": 844424930131976, "start_id": 844424930131975, "properties": {}}::edge {"id": 1125899906842637, "label": "edges", "end_id": 844424930131977, "start_id": 844424930131974, "properties": {}}::edge - {"id": 1125899906842642, "label": "edges", "end_id": 844424930131977, "start_id": 844424930131976, "properties": {}}::edge {"id": 1125899906842640, "label": "edges", "end_id": 844424930131977, "start_id": 844424930131975, "properties": {}}::edge - {"id": 1125899906842644, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131977, "properties": {}}::edge + {"id": 1125899906842642, "label": "edges", "end_id": 844424930131977, "start_id": 844424930131976, "properties": {}}::edge {"id": 1125899906842638, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131974, "properties": {}}::edge {"id": 1125899906842641, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131975, "properties": {}}::edge {"id": 1125899906842643, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131976, "properties": {}}::edge + {"id": 1125899906842644, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131977, "properties": {}}::edge {"id": 1125899906842645, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131969, "properties": {}}::edge (21 rows) diff --git a/regress/expected/index.out b/regress/expected/index.out index f911900ab..3ed7b1c33 100644 --- a/regress/expected/index.out +++ b/regress/expected/index.out @@ -264,18 +264,22 @@ $$) as (n agtype); --- (0 rows) -ALTER TABLE cypher_index."Country" ADD PRIMARY KEY (id); -CREATE UNIQUE INDEX CONCURRENTLY cntry_id_idx ON cypher_index."Country" (id); -ALTER TABLE cypher_index."Country" CLUSTER ON cntry_id_idx; -ALTER TABLE cypher_index."City" ADD PRIMARY KEY (id); -CREATE UNIQUE INDEX city_id_idx ON cypher_index."City" (id); -ALTER TABLE cypher_index."City" CLUSTER ON city_id_idx; -ALTER TABLE cypher_index.has_city -ADD CONSTRAINT has_city_end_fk FOREIGN KEY (end_id) -REFERENCES cypher_index."Country"(id) MATCH FULL; -CREATE INDEX load_has_city_eid_idx ON cypher_index.has_city (end_id); -CREATE INDEX load_has_city_sid_idx ON cypher_index.has_city (start_id); -ALTER TABLE cypher_index."has_city" CLUSTER ON load_has_city_eid_idx; +-- Verify that the incices are created on id columns +SELECT indexname, indexdef FROM pg_indexes WHERE schemaname= 'cypher_index'; + indexname | indexdef +-----------------------------+------------------------------------------------------------------------------------------------ + _ag_label_edge_pkey | CREATE UNIQUE INDEX _ag_label_edge_pkey ON cypher_index._ag_label_edge USING btree (id) + _ag_label_edge_start_id_idx | CREATE INDEX _ag_label_edge_start_id_idx ON cypher_index._ag_label_edge USING btree (start_id) + _ag_label_edge_end_id_idx | CREATE INDEX _ag_label_edge_end_id_idx ON cypher_index._ag_label_edge USING btree (end_id) + _ag_label_vertex_pkey | CREATE UNIQUE INDEX _ag_label_vertex_pkey ON cypher_index._ag_label_vertex USING btree (id) + idx_pkey | CREATE UNIQUE INDEX idx_pkey ON cypher_index.idx USING btree (id) + cypher_index_idx_props_uq | CREATE UNIQUE INDEX cypher_index_idx_props_uq ON cypher_index.idx USING btree (properties) + Country_pkey | CREATE UNIQUE INDEX "Country_pkey" ON cypher_index."Country" USING btree (id) + has_city_start_id_idx | CREATE INDEX has_city_start_id_idx ON cypher_index.has_city USING btree (start_id) + has_city_end_id_idx | CREATE INDEX has_city_end_id_idx ON cypher_index.has_city USING btree (end_id) + City_pkey | CREATE UNIQUE INDEX "City_pkey" ON cypher_index."City" USING btree (id) +(10 rows) + SET enable_mergejoin = ON; SET enable_hashjoin = OFF; SET enable_nestloop = OFF; @@ -288,6 +292,29 @@ $$) as (n agtype); 10 (1 row) +SELECT COUNT(*) FROM cypher('cypher_index', $$ + EXPLAIN (costs off) MATCH (a:Country)<-[e:has_city]-() + RETURN e +$$) as (n agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Aggregate + -> Merge Join + Merge Cond: (_age_default_alias_0.id = e.start_id) + -> Merge Append + Sort Key: _age_default_alias_0.id + -> Index Only Scan using _ag_label_vertex_pkey on _ag_label_vertex _age_default_alias_0_1 + -> Index Only Scan using idx_pkey on idx _age_default_alias_0_2 + -> Index Only Scan using "Country_pkey" on "Country" _age_default_alias_0_3 + -> Index Only Scan using "City_pkey" on "City" _age_default_alias_0_4 + -> Sort + Sort Key: e.start_id + -> Merge Join + Merge Cond: (a.id = e.end_id) + -> Index Only Scan using "Country_pkey" on "Country" a + -> Index Scan using has_city_end_id_idx on has_city e +(15 rows) + SET enable_mergejoin = OFF; SET enable_hashjoin = ON; SET enable_nestloop = OFF; @@ -300,17 +327,53 @@ $$) as (n agtype); 10 (1 row) +SELECT COUNT(*) FROM cypher('cypher_index', $$ + EXPLAIN (costs off) MATCH (a:Country)<-[e:has_city]-() + RETURN e +$$) as (n agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Aggregate + -> Hash Join + Hash Cond: (_age_default_alias_0.id = e.start_id) + -> Append + -> Index Only Scan using _ag_label_vertex_pkey on _ag_label_vertex _age_default_alias_0_1 + -> Index Only Scan using idx_pkey on idx _age_default_alias_0_2 + -> Index Only Scan using "Country_pkey" on "Country" _age_default_alias_0_3 + -> Index Only Scan using "City_pkey" on "City" _age_default_alias_0_4 + -> Hash + -> Hash Join + Hash Cond: (e.end_id = a.id) + -> Index Scan using has_city_end_id_idx on has_city e + -> Hash + -> Index Only Scan using "Country_pkey" on "Country" a +(14 rows) + SET enable_mergejoin = OFF; SET enable_hashjoin = OFF; SET enable_nestloop = ON; SELECT COUNT(*) FROM cypher('cypher_index', $$ - MATCH (a:Country)<-[e:has_city]-() + EXPLAIN (costs off) MATCH (a:Country)<-[e:has_city]-() RETURN e $$) as (n agtype); - count -------- - 10 -(1 row) + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Aggregate + -> Nested Loop + -> Nested Loop + -> Index Scan using has_city_start_id_idx on has_city e + -> Index Only Scan using "Country_pkey" on "Country" a + Index Cond: (id = e.end_id) + -> Append + -> Index Only Scan using _ag_label_vertex_pkey on _ag_label_vertex _age_default_alias_0_1 + Index Cond: (id = e.start_id) + -> Index Only Scan using idx_pkey on idx _age_default_alias_0_2 + Index Cond: (id = e.start_id) + -> Index Only Scan using "Country_pkey" on "Country" _age_default_alias_0_3 + Index Cond: (id = e.start_id) + -> Index Only Scan using "City_pkey" on "City" _age_default_alias_0_4 + Index Cond: (id = e.start_id) +(15 rows) SET enable_mergejoin = ON; SET enable_hashjoin = ON; diff --git a/regress/expected/map_projection.out b/regress/expected/map_projection.out index dcb7f0e76..f0c45c557 100644 --- a/regress/expected/map_projection.out +++ b/regress/expected/map_projection.out @@ -152,7 +152,7 @@ $$ $$) as (a agtype); a -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"name": "Christian Bale", "movies": [{"title": "The Prestige"}, {"title": "The Dark Knight"}]}, {"name": "Tom Hanks", "movies": [{"title": "Forrest Gump"}, {"title": "Finch"}, {"title": "The Circle"}]}] + [{"name": "Tom Hanks", "movies": [{"title": "Forrest Gump"}, {"title": "Finch"}, {"title": "The Circle"}]}, {"name": "Christian Bale", "movies": [{"title": "The Prestige"}, {"title": "The Dark Knight"}]}] (1 row) -- drop diff --git a/regress/sql/age_load.sql b/regress/sql/age_load.sql index 425ca5417..180248bf1 100644 --- a/regress/sql/age_load.sql +++ b/regress/sql/age_load.sql @@ -34,9 +34,6 @@ SELECT * FROM cypher('agload_test_graph', $$CREATE (n:Country {__id__:1}) RETURN SELECT load_labels_from_file('agload_test_graph', 'Country', 'age_load/countries.csv', true); --- A temporary table should have been created with 54 ids; 1 from CREATE and 53 from file -SELECT COUNT(*)=54 FROM "_agload_test_graph_ag_vertex_ids"; - -- Sequence should be equal to max entry id i.e. 248 SELECT currval('agload_test_graph."Country_id_seq"')=248; @@ -52,9 +49,6 @@ SELECT load_labels_from_file('agload_test_graph', 'Country', SELECT load_labels_from_file('agload_test_graph', 'City', 'age_load/cities.csv', true); --- Temporary table should have 54+72485 rows now -SELECT COUNT(*)=54+72485 FROM "_agload_test_graph_ag_vertex_ids"; - -- Sequence should be equal to max entry id i.e. 146941 SELECT currval('agload_test_graph."City_id_seq"')=146941; diff --git a/regress/sql/index.sql b/regress/sql/index.sql index aac1dc40e..d9a4331a4 100644 --- a/regress/sql/index.sql +++ b/regress/sql/index.sql @@ -166,26 +166,8 @@ SELECT * FROM cypher('cypher_index', $$ (mx)<-[:has_city]-(:City {city_id: 10, name:"Tijuana", west_coast: false, country_code:"MX"}) $$) as (n agtype); -ALTER TABLE cypher_index."Country" ADD PRIMARY KEY (id); - -CREATE UNIQUE INDEX CONCURRENTLY cntry_id_idx ON cypher_index."Country" (id); -ALTER TABLE cypher_index."Country" CLUSTER ON cntry_id_idx; - -ALTER TABLE cypher_index."City" ADD PRIMARY KEY (id); - -CREATE UNIQUE INDEX city_id_idx ON cypher_index."City" (id); - -ALTER TABLE cypher_index."City" CLUSTER ON city_id_idx; - -ALTER TABLE cypher_index.has_city -ADD CONSTRAINT has_city_end_fk FOREIGN KEY (end_id) -REFERENCES cypher_index."Country"(id) MATCH FULL; - -CREATE INDEX load_has_city_eid_idx ON cypher_index.has_city (end_id); - -CREATE INDEX load_has_city_sid_idx ON cypher_index.has_city (start_id); - -ALTER TABLE cypher_index."has_city" CLUSTER ON load_has_city_eid_idx; +-- Verify that the incices are created on id columns +SELECT indexname, indexdef FROM pg_indexes WHERE schemaname= 'cypher_index'; SET enable_mergejoin = ON; SET enable_hashjoin = OFF; @@ -196,6 +178,11 @@ SELECT COUNT(*) FROM cypher('cypher_index', $$ RETURN e $$) as (n agtype); +SELECT COUNT(*) FROM cypher('cypher_index', $$ + EXPLAIN (costs off) MATCH (a:Country)<-[e:has_city]-() + RETURN e +$$) as (n agtype); + SET enable_mergejoin = OFF; SET enable_hashjoin = ON; SET enable_nestloop = OFF; @@ -205,12 +192,17 @@ SELECT COUNT(*) FROM cypher('cypher_index', $$ RETURN e $$) as (n agtype); +SELECT COUNT(*) FROM cypher('cypher_index', $$ + EXPLAIN (costs off) MATCH (a:Country)<-[e:has_city]-() + RETURN e +$$) as (n agtype); + SET enable_mergejoin = OFF; SET enable_hashjoin = OFF; SET enable_nestloop = ON; SELECT COUNT(*) FROM cypher('cypher_index', $$ - MATCH (a:Country)<-[e:has_city]-() + EXPLAIN (costs off) MATCH (a:Country)<-[e:has_city]-() RETURN e $$) as (n agtype); diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index f603ec97c..c9fdfad89 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -79,6 +79,10 @@ static void range_var_callback_for_remove_relation(const RangeVar *rel, Oid rel_oid, Oid odl_rel_oid, void *arg); +static void create_index_on_column(char *schema_name, + char *rel_name, + char *colname, + bool unique); PG_FUNCTION_INFO_V1(age_is_valid_label_name); @@ -379,16 +383,24 @@ static void create_table_for_label(char *graph_name, char *label_name, * inheritance system. */ if (list_length(parents) != 0) + { create_stmt->tableElts = NIL; + } else if (label_type == LABEL_TYPE_EDGE) + { create_stmt->tableElts = create_edge_table_elements( graph_name, label_name, schema_name, rel_name, seq_name); + } else if (label_type == LABEL_TYPE_VERTEX) + { create_stmt->tableElts = create_vertex_table_elements( graph_name, label_name, schema_name, rel_name, seq_name); + } else + { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("undefined label type \'%c\'", label_type))); + } create_stmt->inhRelations = parents; create_stmt->partbound = NULL; @@ -409,7 +421,69 @@ static void create_table_for_label(char *graph_name, char *label_name, ProcessUtility(wrapper, "(generated CREATE TABLE command)", false, PROCESS_UTILITY_SUBCOMMAND, NULL, NULL, None_Receiver, NULL); - /* CommandCounterIncrement() is called in ProcessUtility() */ + + /* Create index on id columns */ + if (label_type == LABEL_TYPE_VERTEX) + { + create_index_on_column(schema_name, rel_name, "id", true); + } + else if (label_type == LABEL_TYPE_EDGE) + { + create_index_on_column(schema_name, rel_name, "start_id", false); + create_index_on_column(schema_name, rel_name, "end_id", false); + } +} + +static void create_index_on_column(char *schema_name, + char *rel_name, + char *colname, + bool unique) +{ + IndexStmt *index_stmt; + IndexElem *index_col; + PlannedStmt *index_wrapper; + + index_stmt = makeNode(IndexStmt); + index_col = makeNode(IndexElem); + index_col->name = colname; + index_col->expr = NULL; + index_col->indexcolname = NULL; + index_col->collation = InvalidOid; + index_col->opclass = list_make1(makeString("graphid_ops")); + index_col->opclassopts = NIL; + index_col->ordering = SORTBY_DEFAULT; + index_col->nulls_ordering = SORTBY_NULLS_DEFAULT; + + index_stmt->relation = makeRangeVar(schema_name, rel_name, -1); + index_stmt->accessMethod = "btree"; + index_stmt->tableSpace = NULL; + index_stmt->indexParams = list_make1(index_col); + index_stmt->options = NIL; + index_stmt->whereClause = NULL; + index_stmt->excludeOpNames = NIL; + index_stmt->idxcomment = NULL; + index_stmt->indexOid = InvalidOid; + index_stmt->unique = unique; + index_stmt->nulls_not_distinct = false; + index_stmt->primary = unique; + index_stmt->isconstraint = unique; + index_stmt->deferrable = false; + index_stmt->initdeferred = false; + index_stmt->transformed = false; + index_stmt->concurrent = false; + index_stmt->if_not_exists = false; + index_stmt->reset_default_tblspc = false; + + index_wrapper = makeNode(PlannedStmt); + index_wrapper->commandType = CMD_UTILITY; + index_wrapper->canSetTag = false; + index_wrapper->utilityStmt = (Node *)index_stmt; + index_wrapper->stmt_location = -1; + index_wrapper->stmt_len = 0; + + ProcessUtility(index_wrapper, "(generated CREATE INDEX command)", false, + PROCESS_UTILITY_SUBCOMMAND, NULL, NULL, None_Receiver, + NULL); } /* @@ -468,7 +542,7 @@ static List *create_vertex_table_elements(char *graph_name, char *label_name, /* "id" graphid PRIMARY KEY DEFAULT "ag_catalog"."_graphid"(...) */ id = makeColumnDef(AG_VERTEX_COLNAME_ID, GRAPHIDOID, -1, InvalidOid); - id->constraints = list_make2(build_pk_constraint(), + id->constraints = list_make2(build_not_null_constraint(), build_id_default(graph_name, label_name, schema_name, seq_name)); diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 7e10d9afc..f86c6126b 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -341,6 +341,10 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, } /* increment the command counter */ CommandCounterIncrement(); + + /* Update command id in estate */ + estate->es_snapshot->curcid = GetCurrentCommandId(false); + estate->es_output_cid = GetCurrentCommandId(false); } else if (lock_result != TM_Invisible && lock_result != TM_SelfModified) { diff --git a/src/backend/utils/load/ag_load_edges.c b/src/backend/utils/load/ag_load_edges.c index 30dc4761d..67049431c 100644 --- a/src/backend/utils/load/ag_load_edges.c +++ b/src/backend/utils/load/ag_load_edges.c @@ -22,11 +22,6 @@ #include "utils/load/ag_load_edges.h" #include "utils/load/csv.h" -void init_edge_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid); -void finish_edge_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid); - void edge_field_cb(void *field, size_t field_len, void *data) { @@ -131,7 +126,7 @@ void edge_row_cb(int delim __attribute__((unused)), void *data) if (batch_state->num_tuples >= batch_state->max_tuples) { /* Insert the batch when it is full (i.e. BATCH_SIZE) */ - insert_batch(batch_state, cr->label_name, cr->graph_oid); + insert_batch(batch_state); batch_state->num_tuples = 0; } } @@ -223,7 +218,7 @@ int create_edges_from_csv_file(char *file_path, cr.load_as_agtype = load_as_agtype; /* Initialize the batch insert state */ - init_edge_batch_insert(&cr.batch_state, label_name, graph_oid); + init_batch_insert(&cr.batch_state, label_name, graph_oid); while ((bytes_read=fread(buf, 1, 1024, fp)) > 0) { @@ -238,7 +233,7 @@ int create_edges_from_csv_file(char *file_path, csv_fini(&p, edge_field_cb, edge_row_cb, &cr); /* Finish any remaining batch inserts */ - finish_edge_batch_insert(&cr.batch_state, label_name, graph_oid); + finish_batch_insert(&cr.batch_state); if (ferror(fp)) { @@ -250,66 +245,4 @@ int create_edges_from_csv_file(char *file_path, free(cr.fields); csv_free(&p); return EXIT_SUCCESS; -} - -/* - * Initialize the batch insert state for edges. - */ -void init_edge_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid) -{ - Relation relation; - int i; - - // Open a temporary relation to get the tuple descriptor - relation = table_open(get_label_relation(label_name, graph_oid), AccessShareLock); - - // Initialize the batch insert state - *batch_state = (batch_insert_state *) palloc0(sizeof(batch_insert_state)); - (*batch_state)->max_tuples = BATCH_SIZE; - (*batch_state)->slots = palloc(sizeof(TupleTableSlot *) * BATCH_SIZE); - (*batch_state)->num_tuples = 0; - - // Create slots - for (i = 0; i < BATCH_SIZE; i++) - { - (*batch_state)->slots[i] = MakeSingleTupleTableSlot( - RelationGetDescr(relation), - &TTSOpsHeapTuple); - } - - table_close(relation, AccessShareLock); -} - -/* - * Finish the batch insert for edges. Insert the - * remaining tuples in the batch state and clean up. - */ -void finish_edge_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid) -{ - int i; - Relation relation; - - if ((*batch_state)->num_tuples > 0) - { - insert_batch(*batch_state, label_name, graph_oid); - (*batch_state)->num_tuples = 0; - } - - // Open a temporary relation to ensure resources are properly cleaned up - relation = table_open(get_label_relation(label_name, graph_oid), AccessShareLock); - - // Free slots - for (i = 0; i < BATCH_SIZE; i++) - { - ExecDropSingleTupleTableSlot((*batch_state)->slots[i]); - } - - // Clean up batch state - pfree_if_not_null((*batch_state)->slots); - pfree_if_not_null(*batch_state); - *batch_state = NULL; - - table_close(relation, AccessShareLock); -} +} \ No newline at end of file diff --git a/src/backend/utils/load/ag_load_labels.c b/src/backend/utils/load/ag_load_labels.c index 2ab223346..4a04f3cd8 100644 --- a/src/backend/utils/load/ag_load_labels.c +++ b/src/backend/utils/load/ag_load_labels.c @@ -24,18 +24,6 @@ #include "utils/load/ag_load_labels.h" #include "utils/load/csv.h" -static void setup_temp_table_for_vertex_ids(char *graph_name); -static void insert_batch_in_temp_table(batch_insert_state *batch_state, - Oid graph_oid, Oid relid); -static void init_vertex_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid, - Oid temp_table_relid); -static void finish_vertex_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid, - Oid temp_table_relid); -static void insert_vertex_batch(batch_insert_state *batch_state, char *label_name, - Oid graph_oid, Oid temp_table_relid); - void vertex_field_cb(void *field, size_t field_len, void *data) { @@ -75,7 +63,6 @@ void vertex_row_cb(int delim __attribute__((unused)), void *data) graphid vertex_id; int64 entry_id; TupleTableSlot *slot; - TupleTableSlot *temp_id_slot; n_fields = cr->cur_field; @@ -114,11 +101,9 @@ void vertex_row_cb(int delim __attribute__((unused)), void *data) /* Get the appropriate slot from the batch state */ slot = batch_state->slots[batch_state->num_tuples]; - temp_id_slot = batch_state->temp_id_slots[batch_state->num_tuples]; /* Clear the slots contents */ ExecClearTuple(slot); - ExecClearTuple(temp_id_slot); /* Fill the values in the slot */ slot->tts_values[0] = GRAPHID_GET_DATUM(vertex_id); @@ -129,20 +114,15 @@ void vertex_row_cb(int delim __attribute__((unused)), void *data) slot->tts_isnull[0] = false; slot->tts_isnull[1] = false; - temp_id_slot->tts_values[0] = GRAPHID_GET_DATUM(vertex_id); - temp_id_slot->tts_isnull[0] = false; - /* Make the slot as containing virtual tuple */ ExecStoreVirtualTuple(slot); - ExecStoreVirtualTuple(temp_id_slot); batch_state->num_tuples++; if (batch_state->num_tuples >= batch_state->max_tuples) { /* Insert the batch when it is full (i.e. BATCH_SIZE) */ - insert_vertex_batch(batch_state, cr->label_name, cr->graph_oid, - cr->temp_table_relid); + insert_batch(batch_state); batch_state->num_tuples = 0; } } @@ -202,7 +182,6 @@ int create_labels_from_csv_file(char *file_path, unsigned char options = 0; csv_vertex_reader cr; char *label_seq_name; - Oid temp_table_relid; if (csv_init(&p, options) != 0) { @@ -210,13 +189,6 @@ int create_labels_from_csv_file(char *file_path, (errmsg("Failed to initialize csv parser\n"))); } - temp_table_relid = RelnameGetRelid(GET_TEMP_VERTEX_ID_TABLE(graph_name)); - if (!OidIsValid(temp_table_relid)) - { - setup_temp_table_for_vertex_ids(graph_name); - temp_table_relid = RelnameGetRelid(GET_TEMP_VERTEX_ID_TABLE(graph_name)); - } - csv_set_space_func(&p, is_space); csv_set_term_func(&p, is_term); @@ -243,7 +215,6 @@ int create_labels_from_csv_file(char *file_path, cr.id_field_exists = id_field_exists; cr.label_seq_relid = get_relname_relid(label_seq_name, graph_oid); cr.load_as_agtype = load_as_agtype; - cr.temp_table_relid = temp_table_relid; if (cr.id_field_exists) { @@ -258,8 +229,7 @@ int create_labels_from_csv_file(char *file_path, } /* Initialize the batch insert state */ - init_vertex_batch_insert(&cr.batch_state, label_name, graph_oid, - cr.temp_table_relid); + init_batch_insert(&cr.batch_state, label_name, graph_oid); while ((bytes_read=fread(buf, 1, 1024, fp)) > 0) { @@ -274,8 +244,7 @@ int create_labels_from_csv_file(char *file_path, csv_fini(&p, vertex_field_cb, vertex_row_cb, &cr); /* Finish any remaining batch inserts */ - finish_vertex_batch_insert(&cr.batch_state, label_name, graph_oid, - cr.temp_table_relid); + finish_batch_insert(&cr.batch_state); if (ferror(fp)) { @@ -288,180 +257,4 @@ int create_labels_from_csv_file(char *file_path, free(cr.fields); csv_free(&p); return EXIT_SUCCESS; -} - -static void insert_vertex_batch(batch_insert_state *batch_state, char *label_name, - Oid graph_oid, Oid temp_table_relid) -{ - insert_batch_in_temp_table(batch_state, graph_oid, temp_table_relid); - insert_batch(batch_state, label_name, graph_oid); -} - -/* - * Create and populate a temporary table with vertex ids that are already - * present in the graph. This table will be used to check if the new vertex - * id generated by loader is a duplicate. - * Unique index is created to enforce uniqueness of the ids. - * - * We dont need this for loading edges since the ids are generated using - * sequence and are unique. - */ -static void setup_temp_table_for_vertex_ids(char *graph_name) -{ - char *create_as_query; - char *index_query; - - create_as_query = psprintf("CREATE TEMP TABLE IF NOT EXISTS %s AS " - "SELECT DISTINCT id FROM \"%s\".%s", - GET_TEMP_VERTEX_ID_TABLE(graph_name), graph_name, - AG_DEFAULT_LABEL_VERTEX); - - index_query = psprintf("CREATE UNIQUE INDEX ON %s (id)", - GET_TEMP_VERTEX_ID_TABLE(graph_name)); - SPI_connect(); - SPI_execute(create_as_query, false, 0); - SPI_execute(index_query, false, 0); - - SPI_finish(); -} - -/* - * Inserts batch of tuples into the temporary table. - * This function also updates the index to check for - * uniqueness of the ids. - */ -static void insert_batch_in_temp_table(batch_insert_state *batch_state, - Oid graph_oid, Oid relid) -{ - int i; - EState *estate; - ResultRelInfo *resultRelInfo; - Relation rel; - List *result; - - rel = table_open(relid, RowExclusiveLock); - - /* Initialize executor state */ - estate = CreateExecutorState(); - - /* Initialize result relation information */ - resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(resultRelInfo, rel, 1, NULL, estate->es_instrument); - estate->es_result_relations = &resultRelInfo; - - /* Open the indices */ - ExecOpenIndices(resultRelInfo, false); - - /* Insert the batch into the temporary table */ - heap_multi_insert(rel, batch_state->temp_id_slots, batch_state->num_tuples, - GetCurrentCommandId(true), 0, NULL); - - for (i = 0; i < batch_state->num_tuples; i++) - { - result = ExecInsertIndexTuples(resultRelInfo, batch_state->temp_id_slots[i], - estate, false, true, NULL, NIL, false); - /* Check if the unique cnstraint is violated */ - if (list_length(result) != 0) - { - Datum id; - bool isnull; - - id = slot_getattr(batch_state->temp_id_slots[i], 1, &isnull); - ereport(ERROR, (errmsg("Cannot insert duplicate vertex id: %ld", - DATUM_GET_GRAPHID(id)), - errhint("Entry id %ld is already used", - get_graphid_entry_id(id)))); - } - } - /* Clean up and close the indices */ - ExecCloseIndices(resultRelInfo); - - FreeExecutorState(estate); - table_close(rel, RowExclusiveLock); - - CommandCounterIncrement(); -} - -/* - * Initialize the batch insert state for vertices. - */ -static void init_vertex_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid, - Oid temp_table_relid) -{ - Relation relation; - Oid relid; - - Relation temp_table_relation; - int i; - - /* Open a temporary relation to get the tuple descriptor */ - relid = get_label_relation(label_name, graph_oid); - relation = table_open(relid, AccessShareLock); - - temp_table_relation = table_open(temp_table_relid, AccessShareLock); - - /* Initialize the batch insert state */ - *batch_state = (batch_insert_state *) palloc0(sizeof(batch_insert_state)); - (*batch_state)->max_tuples = BATCH_SIZE; - (*batch_state)->slots = palloc(sizeof(TupleTableSlot *) * BATCH_SIZE); - (*batch_state)->temp_id_slots = palloc(sizeof(TupleTableSlot *) * BATCH_SIZE); - (*batch_state)->num_tuples = 0; - - /* Create slots */ - for (i = 0; i < BATCH_SIZE; i++) - { - (*batch_state)->slots[i] = MakeSingleTupleTableSlot( - RelationGetDescr(relation), - &TTSOpsHeapTuple); - (*batch_state)->temp_id_slots[i] = MakeSingleTupleTableSlot( - RelationGetDescr(temp_table_relation), - &TTSOpsHeapTuple); - } - - table_close(relation, AccessShareLock); - table_close(temp_table_relation, AccessShareLock); -} - -/* - * Finish the batch insert for vertices. Insert the - * remaining tuples in the batch state and clean up. - */ -static void finish_vertex_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid, - Oid temp_table_relid) -{ - Relation relation; - Oid relid; - - Relation temp_table_relation; - int i; - - if ((*batch_state)->num_tuples > 0) - { - insert_vertex_batch(*batch_state, label_name, graph_oid, temp_table_relid); - (*batch_state)->num_tuples = 0; - } - - /* Open a temporary relation to ensure resources are properly cleaned up */ - relid = get_label_relation(label_name, graph_oid); - relation = table_open(relid, AccessShareLock); - - temp_table_relation = table_open(temp_table_relid, AccessShareLock); - - /* Free slots */ - for (i = 0; i < BATCH_SIZE; i++) - { - ExecDropSingleTupleTableSlot((*batch_state)->slots[i]); - ExecDropSingleTupleTableSlot((*batch_state)->temp_id_slots[i]); - } - - /* Clean up batch state */ - pfree_if_not_null((*batch_state)->slots); - pfree_if_not_null((*batch_state)->temp_id_slots); - pfree_if_not_null(*batch_state); - *batch_state = NULL; - - table_close(relation, AccessShareLock); - table_close(temp_table_relation, AccessShareLock); -} +} \ No newline at end of file diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index 41233ff18..307ec335a 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -18,11 +18,14 @@ */ #include "postgres.h" +#include "catalog/indexing.h" +#include "executor/executor.h" #include "utils/json.h" #include "utils/load/ag_load_edges.h" #include "utils/load/ag_load_labels.h" #include "utils/load/age_load.h" +#include "utils/rel.h" static agtype_value *csv_value_to_agtype_value(char *csv_val); static Oid get_or_create_graph(const Name graph_name); @@ -217,18 +220,35 @@ void insert_edge_simple(Oid graph_oid, char *label_name, graphid edge_id, errmsg("label %s already exists as vertex label", label_name))); } + /* Open the relation */ + label_relation = table_open(get_label_relation(label_name, graph_oid), + RowExclusiveLock); + + /* Form the tuple */ values[0] = GRAPHID_GET_DATUM(edge_id); values[1] = GRAPHID_GET_DATUM(start_id); values[2] = GRAPHID_GET_DATUM(end_id); values[3] = AGTYPE_P_GET_DATUM((edge_properties)); - - label_relation = table_open(get_label_relation(label_name, graph_oid), - RowExclusiveLock); - tuple = heap_form_tuple(RelationGetDescr(label_relation), values, nulls); - heap_insert(label_relation, tuple, - GetCurrentCommandId(true), 0, NULL); + + if (RelationGetForm(label_relation)->relhasindex) + { + /* + * CatalogTupleInsertWithInfo() is originally for PostgreSQL's + * catalog. However, it is used here for convenience. + */ + CatalogIndexState indstate = CatalogOpenIndexes(label_relation); + CatalogTupleInsertWithInfo(label_relation, tuple, indstate); + CatalogCloseIndexes(indstate); + } + else + { + heap_insert(label_relation, tuple, GetCurrentCommandId(true), + 0, NULL); + } + + /* Close the relation */ table_close(label_relation, RowExclusiveLock); CommandCounterIncrement(); } @@ -246,46 +266,75 @@ void insert_vertex_simple(Oid graph_oid, char *label_name, graphid vertex_id, if (get_label_kind(label_name, graph_oid) == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("label %s already exists as edge label", label_name))); + errmsg("label %s already exists as edge label", + label_name))); } - values[0] = GRAPHID_GET_DATUM(vertex_id); - values[1] = AGTYPE_P_GET_DATUM((vertex_properties)); - + /* Open the relation */ label_relation = table_open(get_label_relation(label_name, graph_oid), RowExclusiveLock); + + /* Form the tuple */ + values[0] = GRAPHID_GET_DATUM(vertex_id); + values[1] = AGTYPE_P_GET_DATUM((vertex_properties)); tuple = heap_form_tuple(RelationGetDescr(label_relation), values, nulls); - heap_insert(label_relation, tuple, - GetCurrentCommandId(true), 0, NULL); + + if (RelationGetForm(label_relation)->relhasindex) + { + /* + * CatalogTupleInsertWithInfo() is originally for PostgreSQL's + * catalog. However, it is used here for convenience. + */ + CatalogIndexState indstate = CatalogOpenIndexes(label_relation); + CatalogTupleInsertWithInfo(label_relation, tuple, indstate); + CatalogCloseIndexes(indstate); + } + else + { + heap_insert(label_relation, tuple, GetCurrentCommandId(true), + 0, NULL); + } + + /* Close the relation */ table_close(label_relation, RowExclusiveLock); CommandCounterIncrement(); } -void insert_batch(batch_insert_state *batch_state, char *label_name, - Oid graph_oid) +void insert_batch(batch_insert_state *batch_state) { - Relation label_relation; - BulkInsertState bistate; - Oid relid; - - // Get the relation OID - relid = get_label_relation(label_name, graph_oid); - - // Open the relation - label_relation = table_open(relid, RowExclusiveLock); - - // Prepare the BulkInsertState - bistate = GetBulkInsertState(); - - // Perform the bulk insert - heap_multi_insert(label_relation, batch_state->slots, - batch_state->num_tuples, GetCurrentCommandId(true), - 0, bistate); + List *result; + int i; - // Clean up - FreeBulkInsertState(bistate); - table_close(label_relation, RowExclusiveLock); + /* Insert the tuples */ + heap_multi_insert(batch_state->resultRelInfo->ri_RelationDesc, + batch_state->slots, batch_state->num_tuples, + GetCurrentCommandId(true), 0, NULL); + + /* Insert index entries for the tuples */ + if (batch_state->resultRelInfo->ri_NumIndices > 0) + { + for (i = 0; i < batch_state->num_tuples; i++) + { + result = ExecInsertIndexTuples(batch_state->resultRelInfo, + batch_state->slots[i], + batch_state->estate, false, + true, NULL, NIL, false); + + /* Check if the unique constraint is violated */ + if (list_length(result) != 0) + { + Datum id; + bool isnull; + + id = slot_getattr(batch_state->slots[i], 1, &isnull); + ereport(ERROR, (errmsg("Cannot insert duplicate vertex id: %ld", + DATUM_GET_GRAPHID(id)), + errhint("Entry id %ld is already used", + get_graphid_entry_id(id)))); + } + } + } CommandCounterIncrement(); } @@ -475,3 +524,79 @@ static int32 get_or_create_label(Oid graph_oid, char *graph_name, return label_id; } + +/* + * Initialize the batch insert state. + */ +void init_batch_insert(batch_insert_state **batch_state, + char *label_name, Oid graph_oid) +{ + Relation relation; + Oid relid; + EState *estate; + ResultRelInfo *resultRelInfo; + int i; + + /* Open the relation */ + relid = get_label_relation(label_name, graph_oid); + relation = table_open(relid, RowExclusiveLock); + + /* Initialize executor state */ + estate = CreateExecutorState(); + + /* Initialize resultRelInfo */ + resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(resultRelInfo, relation, 1, NULL, estate->es_instrument); + estate->es_result_relations = &resultRelInfo; + + /* Open the indices */ + ExecOpenIndices(resultRelInfo, false); + + /* Initialize the batch insert state */ + *batch_state = (batch_insert_state *) palloc0(sizeof(batch_insert_state)); + (*batch_state)->slots = palloc(sizeof(TupleTableSlot *) * BATCH_SIZE); + (*batch_state)->estate = estate; + (*batch_state)->resultRelInfo = resultRelInfo; + (*batch_state)->max_tuples = BATCH_SIZE; + (*batch_state)->num_tuples = 0; + + /* Create slots */ + for (i = 0; i < BATCH_SIZE; i++) + { + (*batch_state)->slots[i] = MakeSingleTupleTableSlot( + RelationGetDescr(relation), + &TTSOpsHeapTuple); + } +} + +/* + * Finish the batch insert for vertices. Insert the + * tuples remaining in the batch state and clean up. + */ +void finish_batch_insert(batch_insert_state **batch_state) +{ + int i; + + if ((*batch_state)->num_tuples > 0) + { + insert_batch(*batch_state); + (*batch_state)->num_tuples = 0; + } + + /* Free slots */ + for (i = 0; i < BATCH_SIZE; i++) + { + ExecDropSingleTupleTableSlot((*batch_state)->slots[i]); + } + + /* Clean up, close the indices and relation */ + ExecCloseIndices((*batch_state)->resultRelInfo); + table_close((*batch_state)->resultRelInfo->ri_RelationDesc, + RowExclusiveLock); + + /* Clean up batch state */ + FreeExecutorState((*batch_state)->estate); + pfree((*batch_state)->slots); + pfree(*batch_state); + *batch_state = NULL; +} \ No newline at end of file diff --git a/src/include/utils/load/ag_load_labels.h b/src/include/utils/load/ag_load_labels.h index 3a70a5c05..b8ed1572e 100644 --- a/src/include/utils/load/ag_load_labels.h +++ b/src/include/utils/load/ag_load_labels.h @@ -24,10 +24,6 @@ #include "access/heapam.h" #include "utils/load/age_load.h" -#define AGE_VERTIX 1 -#define AGE_EDGE 2 - - struct counts { long unsigned fields; long unsigned allvalues; @@ -51,7 +47,6 @@ typedef struct { char *label_name; int label_id; Oid label_seq_relid; - Oid temp_table_relid; bool id_field_exists; bool load_as_agtype; int curr_seq_num; diff --git a/src/include/utils/load/age_load.h b/src/include/utils/load/age_load.h index b1335581b..72f11493d 100644 --- a/src/include/utils/load/age_load.h +++ b/src/include/utils/load/age_load.h @@ -30,16 +30,13 @@ #ifndef AGE_ENTITY_CREATOR_H #define AGE_ENTITY_CREATOR_H -#define TEMP_VERTEX_ID_TABLE_SUFFIX "_ag_vertex_ids" -#define GET_TEMP_VERTEX_ID_TABLE(graph_name) \ - psprintf("_%s%s", graph_name, TEMP_VERTEX_ID_TABLE_SUFFIX) - #define BATCH_SIZE 1000 -typedef struct +typedef struct batch_insert_state { + EState *estate; + ResultRelInfo *resultRelInfo; TupleTableSlot **slots; - TupleTableSlot **temp_id_slots; int num_tuples; int max_tuples; } batch_insert_state; @@ -57,7 +54,10 @@ void insert_vertex_simple(Oid graph_oid, char *label_name, graphid vertex_id, void insert_edge_simple(Oid graph_oid, char *label_name, graphid edge_id, graphid start_id, graphid end_id, agtype* end_properties); -void insert_batch(batch_insert_state *batch_state, char *label_name, - Oid graph_oid); +void insert_batch(batch_insert_state *batch_state); + +void init_batch_insert(batch_insert_state **batch_state, + char *label_name, Oid graph_oid); +void finish_batch_insert(batch_insert_state **batch_state); #endif /* AGE_ENTITY_CREATOR_H */ From 1d9df48caeb7ab584108034bb1d027cf05a1b6a1 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Tue, 9 Dec 2025 10:51:01 -0800 Subject: [PATCH 07/14] Fix Issue 2256: segmentation fault when calling coalesce function (#2259) Fixed issue 2256: A segmentation fault occurs when calling the coalesce function in PostgreSQL version 17. This likely predates 17 and includes other similar types of "functions". See issues 1124 (PR 1125) and 1303 (PR 1317) for more details. This issue is due to coalesce() being processed differently from other functions. Additionally, greatest() was found to exhibit the same behavior. They were added to the list of types to ignore during the cypher analyze phase. A few others were added: CaseExpr, XmlExpr, ArrayExpr, & RowExpr. Although, I wasn't able to find cases where these caused crashes. Added regression tests. modified: regress/expected/cypher.out modified: regress/sql/cypher.sql modified: src/backend/parser/cypher_analyze.c --- regress/expected/cypher.out | 16 ++++++++++++++++ regress/sql/cypher.sql | 7 +++++++ src/backend/parser/cypher_analyze.c | 26 +++++++++++++++++++------- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/regress/expected/cypher.out b/regress/expected/cypher.out index 31bafc6cf..53ea9f0c1 100644 --- a/regress/expected/cypher.out +++ b/regress/expected/cypher.out @@ -169,6 +169,22 @@ CREATE TABLE my_edges AS -- create a table of 4 columns, u, e, v, p. should be 5 rows CREATE TABLE my_detailed_paths AS (SELECT * FROM cypher('issue_1767', $$ MATCH p=(u)-[e]->(v) RETURN u,e,v,p $$) as (u agtype, e agtype, v agtype, p agtype)); +-- +-- Issue 2256: A segmentation fault occurs when calling the coalesce function +-- This also occurs with the greatest function too. +-- +SELECT * FROM coalesce(1, 0); + coalesce +---------- + 1 +(1 row) + +SELECT * FROM greatest(1, 0); + greatest +---------- + 1 +(1 row) + -- dump out the tables SELECT * FROM my_vertices; u diff --git a/regress/sql/cypher.sql b/regress/sql/cypher.sql index 7ded61ee6..090c7e704 100644 --- a/regress/sql/cypher.sql +++ b/regress/sql/cypher.sql @@ -94,6 +94,13 @@ CREATE TABLE my_edges AS CREATE TABLE my_detailed_paths AS (SELECT * FROM cypher('issue_1767', $$ MATCH p=(u)-[e]->(v) RETURN u,e,v,p $$) as (u agtype, e agtype, v agtype, p agtype)); +-- +-- Issue 2256: A segmentation fault occurs when calling the coalesce function +-- This also occurs with the greatest function too. +-- +SELECT * FROM coalesce(1, 0); +SELECT * FROM greatest(1, 0); + -- dump out the tables SELECT * FROM my_vertices; SELECT * FROM my_edges; diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index af3e83c87..a408eea6e 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -171,14 +171,20 @@ static bool convert_cypher_walker(Node *node, ParseState *pstate) * JsonConstructorExpr - wrapper over FuncExpr/Aggref/WindowFunc for * SQL/JSON constructors * - * These are a special case that needs to be ignored. + * Added the following, although only the first 2 caused crashes in tests - + * CoalesceExpr, MinMaxExpr, CaseExpr, XmlExpr, ArrayExpr, RowExpr + * + * These are all special case that needs to be ignored. * */ if (IsA(funcexpr, SQLValueFunction) - || IsA(funcexpr, CoerceViaIO) - || IsA(funcexpr, Var) || IsA(funcexpr, OpExpr) - || IsA(funcexpr, Const) || IsA(funcexpr, BoolExpr) - || IsA(funcexpr, JsonConstructorExpr)) + || IsA(funcexpr, CoerceViaIO) + || IsA(funcexpr, Var) || IsA(funcexpr, OpExpr) + || IsA(funcexpr, Const) || IsA(funcexpr, BoolExpr) + || IsA(funcexpr, JsonConstructorExpr) + || IsA(funcexpr, CoalesceExpr) || IsA(funcexpr, MinMaxExpr) + || IsA(funcexpr, CaseExpr) || IsA(funcexpr, XmlExpr) + || IsA(funcexpr, ArrayExpr) || IsA(funcexpr, RowExpr)) { return false; } @@ -346,14 +352,20 @@ static bool is_func_cypher(FuncExpr *funcexpr) * JsonConstructorExpr - wrapper over FuncExpr/Aggref/WindowFunc for * SQL/JSON constructors * - * These are a special case that needs to be ignored. + * Added the following, although only the first 2 caused crashes in tests - + * CoalesceExpr, MinMaxExpr, CaseExpr, XmlExpr, ArrayExpr, RowExpr + * + * These are all special case that needs to be ignored. * */ if (IsA(funcexpr, SQLValueFunction) || IsA(funcexpr, CoerceViaIO) || IsA(funcexpr, Var) || IsA(funcexpr, OpExpr) || IsA(funcexpr, Const) || IsA(funcexpr, BoolExpr) - || IsA(funcexpr, JsonConstructorExpr)) + || IsA(funcexpr, JsonConstructorExpr) + || IsA(funcexpr, CoalesceExpr) || IsA(funcexpr, MinMaxExpr) + || IsA(funcexpr, CaseExpr) || IsA(funcexpr, XmlExpr) + || IsA(funcexpr, ArrayExpr) || IsA(funcexpr, RowExpr)) { return false; } From 2ccdc6b951dc5e92e32ce32a41263c0b23e565e7 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Tue, 9 Dec 2025 11:15:26 -0800 Subject: [PATCH 08/14] Adjust 'could not find rte for' ERROR message (#2266) Adjusted the following type of error message. It was mentioned in issue 2263 as being incorrect, which it isn't. However, it did need some clarification added - ERROR: could not find rte for Added a HINT for additional clarity - HINT: variable does not exist within scope of usage For example: CREATE p0=(n0), (n1{k:EXISTS{WITH p0}}) RETURN 1 ERROR: could not find rte for p0 LINE 3: CREATE p0=(n0), (n1{k:EXISTS{WITH p0}}) ^ HINT: variable p0 does not exist within scope of usage Additionally, added pstate->p_expr_kind == EXPR_KIND_INSERT_TARGET to transform_cypher_clause_as_subquery. Updated existing regression tests. Added regression tests from issue. modified: regress/expected/cypher_call.out modified: regress/expected/cypher_subquery.out modified: regress/expected/cypher_union.out modified: regress/expected/cypher_with.out modified: regress/expected/expr.out modified: regress/expected/list_comprehension.out modified: regress/expected/scan.out modified: src/backend/parser/cypher_clause.c modified: src/backend/parser/cypher_expr.c --- regress/expected/cypher_call.out | 2 + regress/expected/cypher_subquery.out | 4 ++ regress/expected/cypher_union.out | 1 + regress/expected/cypher_with.out | 4 ++ regress/expected/expr.out | 66 +++++++++++++++++++++++++ regress/expected/list_comprehension.out | 2 + regress/expected/scan.out | 6 +++ regress/sql/expr.sql | 27 ++++++++++ src/backend/parser/cypher_clause.c | 1 + src/backend/parser/cypher_expr.c | 8 +-- 10 files changed, 118 insertions(+), 3 deletions(-) diff --git a/regress/expected/cypher_call.out b/regress/expected/cypher_call.out index bb1185b96..08f97ba41 100644 --- a/regress/expected/cypher_call.out +++ b/regress/expected/cypher_call.out @@ -125,6 +125,7 @@ SELECT * FROM cypher('cypher_call', $$CALL sqrt(64) YIELD sqrt WHERE a = 8 RETUR ERROR: could not find rte for a LINE 2: ...r('cypher_call', $$CALL sqrt(64) YIELD sqrt WHERE a = 8 RETU... ^ +HINT: variable a does not exist within scope of usage /* MATCH CALL RETURN, should fail */ SELECT * FROM cypher('cypher_call', $$ MATCH (a) CALL sqrt(64) RETURN sqrt $$) as (sqrt agtype); ERROR: Procedure call inside a query does not support naming results implicitly @@ -171,6 +172,7 @@ SELECT * FROM cypher('cypher_call', $$ MATCH (a) CALL sqrt(64) YIELD sqrt WHERE ERROR: could not find rte for b LINE 1: ...all', $$ MATCH (a) CALL sqrt(64) YIELD sqrt WHERE b = 8 RETU... ^ +HINT: variable b does not exist within scope of usage /* CALL MATCH YIELD WHERE UPDATE/RETURN */ SELECT * FROM cypher('cypher_call', $$ CALL sqrt(64) YIELD sqrt WHERE sqrt > 1 CREATE ({n:'c'}) $$) as (a agtype); a diff --git a/regress/expected/cypher_subquery.out b/regress/expected/cypher_subquery.out index 934549f82..456b3a2c9 100644 --- a/regress/expected/cypher_subquery.out +++ b/regress/expected/cypher_subquery.out @@ -135,6 +135,7 @@ SELECT * FROM cypher('subquery', $$ MATCH (a:person) ERROR: could not find rte for c LINE 5: RETURN c ^ +HINT: variable c does not exist within scope of usage --union, no returns SELECT * FROM cypher('subquery', $$ MATCH (a:person) WHERE EXISTS { @@ -341,6 +342,7 @@ SELECT * FROM cypher('subquery', $$ RETURN 1, ERROR: could not find rte for a LINE 4: RETURN a ^ +HINT: variable a does not exist within scope of usage --- COUNT --count pattern subquery in where SELECT * FROM cypher('subquery', $$ MATCH (a:person) @@ -540,6 +542,7 @@ SELECT * FROM cypher('subquery', $$ MATCH (a:person) ERROR: could not find rte for b LINE 2: RETURN a.name, COUNT{MATCH (a) RETURN b} $$) ^ +HINT: variable b does not exist within scope of usage --incorrect nested variable reference SELECT * FROM cypher('subquery', $$ MATCH (a:person) RETURN a.name, COUNT{MATCH (a) @@ -549,6 +552,7 @@ SELECT * FROM cypher('subquery', $$ MATCH (a:person) ERROR: could not find rte for b LINE 4: RETURN b} $$) ^ +HINT: variable b does not exist within scope of usage --count nested with exists SELECT * FROM cypher('subquery', $$ MATCH (a:person) RETURN a.name, diff --git a/regress/expected/cypher_union.out b/regress/expected/cypher_union.out index 063354ddb..14fa56e67 100644 --- a/regress/expected/cypher_union.out +++ b/regress/expected/cypher_union.out @@ -141,6 +141,7 @@ SELECT * FROM cypher('cypher_union', $$MATCH (n) RETURN n UNION ALL MATCH (m) RE ERROR: could not find rte for n LINE 2: ..., $$MATCH (n) RETURN n UNION ALL MATCH (m) RETURN n$$) AS (r... ^ +HINT: variable n does not exist within scope of usage /* *UNION and UNION ALL, type casting */ diff --git a/regress/expected/cypher_with.out b/regress/expected/cypher_with.out index e5f82aa21..99ea320a0 100644 --- a/regress/expected/cypher_with.out +++ b/regress/expected/cypher_with.out @@ -267,6 +267,7 @@ $$) AS (a agtype, b agtype); ERROR: could not find rte for b LINE 4: RETURN m,b ^ +HINT: variable b does not exist within scope of usage SELECT * FROM cypher('cypher_with', $$ MATCH (m)-[]->(b) WITH m AS start_node,b AS end_node @@ -278,6 +279,7 @@ $$) AS (id agtype, node agtype); ERROR: could not find rte for end_node LINE 7: RETURN id(start_node),end_node.name ^ +HINT: variable end_node does not exist within scope of usage -- Clean up SELECT drop_graph('cypher_with', true); NOTICE: drop cascades to 4 other objects @@ -320,6 +322,7 @@ $$) AS (n agtype, d agtype); ERROR: could not find rte for d LINE 8: RETURN c,d ^ +HINT: variable d does not exist within scope of usage -- Issue 396 (should error out) SELECT * FROM cypher('graph',$$ CREATE (v),(u),(w), @@ -338,6 +341,7 @@ $$) as (a agtype,b agtype); ERROR: could not find rte for v LINE 4: RETURN v,path_length ^ +HINT: variable v does not exist within scope of usage -- Clean up SELECT drop_graph('graph', true); NOTICE: drop cascades to 6 other objects diff --git a/regress/expected/expr.out b/regress/expected/expr.out index d0546d8a3..4be3bf90f 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -3370,6 +3370,7 @@ $$) AS (toBooleanList agtype); ERROR: could not find rte for fail LINE 2: RETURN toBooleanList(fail) ^ +HINT: variable fail does not exist within scope of usage SELECT * FROM cypher('expr', $$ RETURN toBooleanList("fail") $$) AS (toBooleanList agtype); @@ -3513,6 +3514,7 @@ $$) AS (toFloatList agtype); ERROR: could not find rte for failed LINE 2: RETURN toFloatList([failed]) ^ +HINT: variable failed does not exist within scope of usage SELECT * FROM cypher('expr', $$ RETURN toFloatList("failed") $$) AS (toFloatList agtype); @@ -3892,12 +3894,14 @@ $$) AS (toStringList agtype); ERROR: could not find rte for b LINE 2: RETURN toStringList([['a', b]]) ^ +HINT: variable b does not exist within scope of usage SELECT * FROM cypher('expr', $$ RETURN toStringList([test]) $$) AS (toStringList agtype); ERROR: could not find rte for test LINE 2: RETURN toStringList([test]) ^ +HINT: variable test does not exist within scope of usage -- -- reverse(string) -- @@ -7923,6 +7927,7 @@ SELECT * FROM cypher('list', $$ RETURN tail(abc) $$) AS (tail agtype); ERROR: could not find rte for abc LINE 1: SELECT * FROM cypher('list', $$ RETURN tail(abc) $$) AS (tai... ^ +HINT: variable abc does not exist within scope of usage SELECT * FROM cypher('list', $$ RETURN tail() $$) AS (tail agtype); ERROR: function ag_catalog.age_tail() does not exist LINE 1: SELECT * FROM cypher('list', $$ RETURN tail() $$) AS (tail a... @@ -9011,9 +9016,70 @@ SELECT agtype_hash_cmp(agtype_in('[null, null, null, null, null]')); -505290721 (1 row) +-- +-- Issue 2263: AGE returns incorrect error message for EXISTS subquery outer variable reference +-- +-- NOTE: There isn't really anything incorrect about the message. However, +-- it could be more clear. +-- +SELECT * FROM create_graph('issue_2263'); +NOTICE: graph "issue_2263" has been created + create_graph +-------------- + +(1 row) + +SELECT * FROM cypher('issue_2263', $$ + CREATE a=()-[:T]->(), p=({k:exists{return a}})-[:T]->() + RETURN 1 +$$) AS (one agtype); +ERROR: could not find rte for a +LINE 2: CREATE a=()-[:T]->(), p=({k:exists{return a}})-[:T]->() + ^ +HINT: variable a does not exist within scope of usage +SELECT * FROM cypher('issue_2263', $$ + CREATE p0=(n0), (n1{k:EXISTS{WITH p0}}) + RETURN 1 +$$) AS (one agtype); +ERROR: could not find rte for p0 +LINE 2: CREATE p0=(n0), (n1{k:EXISTS{WITH p0}}) + ^ +HINT: variable p0 does not exist within scope of usage +SELECT * FROM cypher('issue_2263', $$ + CREATE ()-[r4 :T6]->(), ({k2:COUNT{WITH r4.k AS a3 UNWIND [] AS a4 WITH DISTINCT NULL AS a5}}) + RETURN 1 +$$) AS (one agtype); +ERROR: could not find rte for r4 +LINE 2: CREATE ()-[r4 :T6]->(), ({k2:COUNT{WITH r4.k AS a3 UNWIN... + ^ +HINT: variable r4 does not exist within scope of usage +SELECT * FROM cypher('issue_2263', $$ + CREATE (x), ({a1:EXISTS { RETURN COUNT(0) AS a2, keys(x) AS a4 }}) +$$) AS (out agtype); +ERROR: could not find rte for x +LINE 2: ...TE (x), ({a1:EXISTS { RETURN COUNT(0) AS a2, keys(x) AS a4 }... + ^ +HINT: variable x does not exist within scope of usage +SELECT * FROM cypher('issue_2263', $$ + CREATE x = (), ({ a0:COUNT { MATCH () WHERE CASE WHEN true THEN (x IS NULL) END RETURN 0 } }) +$$) AS (out agtype); +ERROR: could not find rte for x +LINE 2: ...({ a0:COUNT { MATCH () WHERE CASE WHEN true THEN (x IS NULL)... + ^ +HINT: variable x does not exist within scope of usage -- -- Cleanup -- +SELECT * FROM drop_graph('issue_2263', true); +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table issue_2263._ag_label_vertex +drop cascades to table issue_2263._ag_label_edge +NOTICE: graph "issue_2263" has been dropped + drop_graph +------------ + +(1 row) + SELECT * FROM drop_graph('issue_1988', true); NOTICE: drop cascades to 4 other objects DETAIL: drop cascades to table issue_1988._ag_label_vertex diff --git a/regress/expected/list_comprehension.out b/regress/expected/list_comprehension.out index 07f777707..5a3756422 100644 --- a/regress/expected/list_comprehension.out +++ b/regress/expected/list_comprehension.out @@ -569,10 +569,12 @@ SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$ ERROR: could not find rte for i LINE 1: ..._comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (... ^ +HINT: variable i does not exist within scope of usage SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (result agtype, i agtype); ERROR: could not find rte for i LINE 1: ...$$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (... ^ +HINT: variable i does not exist within scope of usage -- Invalid list comprehension SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) WHERE 2>5] $$) AS (result agtype); ERROR: Syntax error at or near IN diff --git a/regress/expected/scan.out b/regress/expected/scan.out index d8105a053..46d5676d0 100644 --- a/regress/expected/scan.out +++ b/regress/expected/scan.out @@ -437,36 +437,42 @@ $$) AS t(id text); ERROR: could not find rte for _$09A_z LINE 2: RETURN _$09A_z ^ +HINT: variable _$09A_z does not exist within scope of usage SELECT * FROM cypher('scan', $$ RETURN A $$) AS t(id text); ERROR: could not find rte for A LINE 2: RETURN A ^ +HINT: variable A does not exist within scope of usage SELECT * FROM cypher('scan', $$ RETURN z $$) AS t(id text); ERROR: could not find rte for z LINE 2: RETURN z ^ +HINT: variable z does not exist within scope of usage SELECT * FROM cypher('scan', $$ RETURN `$` $$) AS t(id text); ERROR: could not find rte for $ LINE 2: RETURN `$` ^ +HINT: variable $ does not exist within scope of usage SELECT * FROM cypher('scan', $$ RETURN `0` $$) AS t(id text); ERROR: could not find rte for 0 LINE 2: RETURN `0` ^ +HINT: variable 0 does not exist within scope of usage SELECT * FROM cypher('scan', $$ RETURN ```` $$) AS t(id text); ERROR: could not find rte for ` LINE 2: RETURN ```` ^ +HINT: variable ` does not exist within scope of usage -- zero-length quoted identifier SELECT * FROM cypher('scan', $$ RETURN `` diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 83f21856e..466bace41 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -3634,9 +3634,36 @@ SELECT * FROM cypher('issue_1988', $$ SELECT agtype_access_operator(agtype_in('[null, null]')); SELECT agtype_hash_cmp(agtype_in('[null, null, null, null, null]')); +-- +-- Issue 2263: AGE returns incorrect error message for EXISTS subquery outer variable reference +-- +-- NOTE: There isn't really anything incorrect about the message. However, +-- it could be more clear. +-- +SELECT * FROM create_graph('issue_2263'); +SELECT * FROM cypher('issue_2263', $$ + CREATE a=()-[:T]->(), p=({k:exists{return a}})-[:T]->() + RETURN 1 +$$) AS (one agtype); +SELECT * FROM cypher('issue_2263', $$ + CREATE p0=(n0), (n1{k:EXISTS{WITH p0}}) + RETURN 1 +$$) AS (one agtype); +SELECT * FROM cypher('issue_2263', $$ + CREATE ()-[r4 :T6]->(), ({k2:COUNT{WITH r4.k AS a3 UNWIND [] AS a4 WITH DISTINCT NULL AS a5}}) + RETURN 1 +$$) AS (one agtype); +SELECT * FROM cypher('issue_2263', $$ + CREATE (x), ({a1:EXISTS { RETURN COUNT(0) AS a2, keys(x) AS a4 }}) +$$) AS (out agtype); +SELECT * FROM cypher('issue_2263', $$ + CREATE x = (), ({ a0:COUNT { MATCH () WHERE CASE WHEN true THEN (x IS NULL) END RETURN 0 } }) +$$) AS (out agtype); + -- -- Cleanup -- +SELECT * FROM drop_graph('issue_2263', true); SELECT * FROM drop_graph('issue_1988', true); SELECT * FROM drop_graph('issue_1953', true); SELECT * FROM drop_graph('expanded_map', true); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 971f8c3d2..5c5024cf7 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -6315,6 +6315,7 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, pstate->p_expr_kind == EXPR_KIND_OTHER || pstate->p_expr_kind == EXPR_KIND_WHERE || pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET || + pstate->p_expr_kind == EXPR_KIND_INSERT_TARGET || pstate->p_expr_kind == EXPR_KIND_FROM_SUBSELECT); /* diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 993957c83..5f4de86b9 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -423,9 +423,11 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref) else { ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("could not find rte for %s", colname), - parser_errposition(pstate, cref->location))); + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("could not find rte for %s", colname), + errhint("variable %s does not exist within scope of usage", + colname), + parser_errposition(pstate, cref->location))); } if (node == NULL) From 419eb13cc2923f04d12a337356855429d1139607 Mon Sep 17 00:00:00 2001 From: Aleksey Konovkin Date: Tue, 9 Dec 2025 22:49:05 +0300 Subject: [PATCH 09/14] Fix possible memory and file descriptors leaks (#2258) - Used postgres memory allocation functions instead of standard ones. - Wrapped main loop of csv loader in PG_TRY block for better error handling. --- src/backend/utils/adt/age_global_graph.c | 6 +- src/backend/utils/adt/agtype.c | 39 ++++---- src/backend/utils/load/ag_load_edges.c | 91 ++++++++++-------- src/backend/utils/load/ag_load_labels.c | 115 ++++++++++++----------- src/include/utils/agtype.h | 1 + 5 files changed, 135 insertions(+), 117 deletions(-) diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index 6f30060ae..c34e51ee3 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -1237,12 +1237,10 @@ Datum age_delete_global_graphs(PG_FUNCTION_ARGS) { char *graph_name = NULL; - graph_name = strndup(agtv_temp->val.string.val, - agtv_temp->val.string.len); + graph_name = pnstrdup(agtv_temp->val.string.val, + agtv_temp->val.string.len); success = delete_specific_GRAPH_global_contexts(graph_name); - - free(graph_name); } else { diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index be838cbd0..02fc3221c 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -181,6 +181,17 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo); agtype_value *agtype_composite_to_agtype_value_binary(agtype *a); static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr); + +void *repalloc_check(void *ptr, size_t len) +{ + if (ptr != NULL) + { + return repalloc(ptr, len); + } + + return palloc(len); +} + /* * Due to how pfree can be implemented, it may not check for a passed NULL. This * wrapper does just that, it will only call pfree is the pointer passed is not @@ -5580,7 +5591,7 @@ static char *get_label_name(const char *graph_name, graphid element_graphid) result = NameStr(*DatumGetName(heap_getattr(tuple, Anum_ag_label_name, tupdesc, &column_is_null))); /* duplicate it */ - result = strdup(result); + result = pstrdup(result); /* end the scan and close the relation */ systable_endscan(scan_desc); @@ -5673,8 +5684,8 @@ Datum age_startnode(PG_FUNCTION_ARGS) Assert(AGT_ROOT_IS_SCALAR(agt_arg)); agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); Assert(agtv_object->type == AGTV_STRING); - graph_name = strndup(agtv_object->val.string.val, - agtv_object->val.string.len); + graph_name = pnstrdup(agtv_object->val.string.val, + agtv_object->val.string.len); /* get the edge */ agt_arg = AG_GET_ARG_AGTYPE_P(1); @@ -5708,8 +5719,6 @@ Datum age_startnode(PG_FUNCTION_ARGS) result = get_vertex(graph_name, label_name, start_id); - free(label_name); - return result; } @@ -5738,8 +5747,8 @@ Datum age_endnode(PG_FUNCTION_ARGS) Assert(AGT_ROOT_IS_SCALAR(agt_arg)); agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); Assert(agtv_object->type == AGTV_STRING); - graph_name = strndup(agtv_object->val.string.val, - agtv_object->val.string.len); + graph_name = pnstrdup(agtv_object->val.string.val, + agtv_object->val.string.len); /* get the edge */ agt_arg = AG_GET_ARG_AGTYPE_P(1); @@ -5773,8 +5782,6 @@ Datum age_endnode(PG_FUNCTION_ARGS) result = get_vertex(graph_name, label_name, end_id); - free(label_name); - return result; } @@ -6401,11 +6408,10 @@ Datum age_tofloat(PG_FUNCTION_ARGS) NumericGetDatum(agtv_value->val.numeric))); else if (agtv_value->type == AGTV_STRING) { - string = strndup(agtv_value->val.string.val, - agtv_value->val.string.len); + string = pnstrdup(agtv_value->val.string.val, + agtv_value->val.string.len); result = float8in_internal_null(string, NULL, "double precision", string, &is_valid); - free(string); if (!is_valid) PG_RETURN_NULL(); } @@ -6703,8 +6709,8 @@ Datum age_tointeger(PG_FUNCTION_ARGS) { char *endptr; /* we need a null terminated cstring */ - string = strndup(agtv_value->val.string.val, - agtv_value->val.string.len); + string = pnstrdup(agtv_value->val.string.val, + agtv_value->val.string.len); /* convert it if it is a regular integer string */ result = strtoi64(string, &endptr, 10); @@ -6718,7 +6724,6 @@ Datum age_tointeger(PG_FUNCTION_ARGS) f = float8in_internal_null(string, NULL, "double precision", string, &is_valid); - free(string); /* * If the conversions failed or it's a special float value, * return null. @@ -6731,10 +6736,6 @@ Datum age_tointeger(PG_FUNCTION_ARGS) result = (int64) f; } - else - { - free(string); - } } else { diff --git a/src/backend/utils/load/ag_load_edges.c b/src/backend/utils/load/ag_load_edges.c index 67049431c..931c6e0dc 100644 --- a/src/backend/utils/load/ag_load_edges.c +++ b/src/backend/utils/load/ag_load_edges.c @@ -36,8 +36,8 @@ void edge_field_cb(void *field, size_t field_len, void *data) if (cr->cur_field == cr->alloc) { cr->alloc *= 2; - cr->fields = realloc(cr->fields, sizeof(char *) * cr->alloc); - cr->fields_len = realloc(cr->header, sizeof(size_t *) * cr->alloc); + cr->fields = repalloc_check(cr->fields, sizeof(char *) * cr->alloc); + cr->fields_len = repalloc_check(cr->header, sizeof(size_t *) * cr->alloc); if (cr->fields == NULL) { cr->error = 1; @@ -48,7 +48,7 @@ void edge_field_cb(void *field, size_t field_len, void *data) } cr->fields_len[cr->cur_field] = field_len; cr->curr_row_length += field_len; - cr->fields[cr->cur_field] = strndup((char*)field, field_len); + cr->fields[cr->cur_field] = pnstrdup((char*)field, field_len); cr->cur_field += 1; } @@ -78,13 +78,13 @@ void edge_row_cb(int delim __attribute__((unused)), void *data) { cr->header_num = cr->cur_field; cr->header_row_length = cr->curr_row_length; - cr->header_len = (size_t* )malloc(sizeof(size_t *) * cr->cur_field); - cr->header = malloc((sizeof (char*) * cr->cur_field)); + cr->header_len = (size_t* )palloc(sizeof(size_t *) * cr->cur_field); + cr->header = palloc((sizeof (char*) * cr->cur_field)); for (i = 0; icur_field; i++) { cr->header_len[i] = cr->fields_len[i]; - cr->header[i] = strndup(cr->fields[i], cr->header_len[i]); + cr->header[i] = pnstrdup(cr->fields[i], cr->header_len[i]); } } else @@ -133,7 +133,7 @@ void edge_row_cb(int delim __attribute__((unused)), void *data) for (i = 0; i < n_fields; ++i) { - free(cr->fields[i]); + pfree_if_not_null(cr->fields[i]); } if (cr->error) @@ -192,6 +192,10 @@ int create_edges_from_csv_file(char *file_path, (errmsg("Failed to initialize csv parser\n"))); } + p.malloc_func = palloc; + p.realloc_func = repalloc_check; + p.free_func = pfree_if_not_null; + csv_set_space_func(&p, is_space); csv_set_term_func(&p, is_term); @@ -202,47 +206,52 @@ int create_edges_from_csv_file(char *file_path, (errmsg("Failed to open %s\n", file_path))); } - label_seq_name = get_label_seq_relation_name(label_name); - - memset((void*)&cr, 0, sizeof(csv_edge_reader)); - cr.alloc = 128; - cr.fields = malloc(sizeof(char *) * cr.alloc); - cr.fields_len = malloc(sizeof(size_t *) * cr.alloc); - cr.header_row_length = 0; - cr.curr_row_length = 0; - cr.graph_name = graph_name; - cr.graph_oid = graph_oid; - cr.label_name = label_name; - cr.label_id = label_id; - cr.label_seq_relid = get_relname_relid(label_seq_name, graph_oid); - cr.load_as_agtype = load_as_agtype; - - /* Initialize the batch insert state */ - init_batch_insert(&cr.batch_state, label_name, graph_oid); - - while ((bytes_read=fread(buf, 1, 1024, fp)) > 0) + PG_TRY(); { - if (csv_parse(&p, buf, bytes_read, edge_field_cb, - edge_row_cb, &cr) != bytes_read) + label_seq_name = get_label_seq_relation_name(label_name); + + memset((void*)&cr, 0, sizeof(csv_edge_reader)); + cr.alloc = 128; + cr.fields = palloc(sizeof(char *) * cr.alloc); + cr.fields_len = palloc(sizeof(size_t *) * cr.alloc); + cr.header_row_length = 0; + cr.curr_row_length = 0; + cr.graph_name = graph_name; + cr.graph_oid = graph_oid; + cr.label_name = label_name; + cr.label_id = label_id; + cr.label_seq_relid = get_relname_relid(label_seq_name, graph_oid); + cr.load_as_agtype = load_as_agtype; + + /* Initialize the batch insert state */ + init_batch_insert(&cr.batch_state, label_name, graph_oid); + + while ((bytes_read=fread(buf, 1, 1024, fp)) > 0) { - ereport(ERROR, (errmsg("Error while parsing file: %s\n", - csv_strerror(csv_error(&p))))); + if (csv_parse(&p, buf, bytes_read, edge_field_cb, + edge_row_cb, &cr) != bytes_read) + { + ereport(ERROR, (errmsg("Error while parsing file: %s\n", + csv_strerror(csv_error(&p))))); + } } - } - csv_fini(&p, edge_field_cb, edge_row_cb, &cr); + csv_fini(&p, edge_field_cb, edge_row_cb, &cr); - /* Finish any remaining batch inserts */ - finish_batch_insert(&cr.batch_state); + /* Finish any remaining batch inserts */ + finish_batch_insert(&cr.batch_state); - if (ferror(fp)) + if (ferror(fp)) + { + ereport(ERROR, (errmsg("Error while reading file %s\n", file_path))); + } + } + PG_FINALLY(); { - ereport(ERROR, (errmsg("Error while reading file %s\n", file_path))); + fclose(fp); + csv_free(&p); } + PG_END_TRY(); - fclose(fp); - - free(cr.fields); - csv_free(&p); return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/src/backend/utils/load/ag_load_labels.c b/src/backend/utils/load/ag_load_labels.c index 4a04f3cd8..1e86bbda4 100644 --- a/src/backend/utils/load/ag_load_labels.c +++ b/src/backend/utils/load/ag_load_labels.c @@ -39,8 +39,8 @@ void vertex_field_cb(void *field, size_t field_len, void *data) if (cr->cur_field == cr->alloc) { cr->alloc *= 2; - cr->fields = realloc(cr->fields, sizeof(char *) * cr->alloc); - cr->fields_len = realloc(cr->header, sizeof(size_t *) * cr->alloc); + cr->fields = repalloc_check(cr->fields, sizeof(char *) * cr->alloc); + cr->fields_len = repalloc_check(cr->header, sizeof(size_t *) * cr->alloc); if (cr->fields == NULL) { cr->error = 1; @@ -51,7 +51,7 @@ void vertex_field_cb(void *field, size_t field_len, void *data) } cr->fields_len[cr->cur_field] = field_len; cr->curr_row_length += field_len; - cr->fields[cr->cur_field] = strndup((char *) field, field_len); + cr->fields[cr->cur_field] = pnstrdup((char *) field, field_len); cr->cur_field += 1; } @@ -70,13 +70,13 @@ void vertex_row_cb(int delim __attribute__((unused)), void *data) { cr->header_num = cr->cur_field; cr->header_row_length = cr->curr_row_length; - cr->header_len = (size_t* )malloc(sizeof(size_t *) * cr->cur_field); - cr->header = malloc((sizeof (char*) * cr->cur_field)); + cr->header_len = (size_t* )palloc(sizeof(size_t *) * cr->cur_field); + cr->header = palloc((sizeof (char*) * cr->cur_field)); for (i = 0; icur_field; i++) { cr->header_len[i] = cr->fields_len[i]; - cr->header[i] = strndup(cr->fields[i], cr->header_len[i]); + cr->header[i] = pnstrdup(cr->fields[i], cr->header_len[i]); } } else @@ -129,7 +129,7 @@ void vertex_row_cb(int delim __attribute__((unused)), void *data) for (i = 0; i < n_fields; ++i) { - free(cr->fields[i]); + pfree_if_not_null(cr->fields[i]); } if (cr->error) @@ -189,6 +189,10 @@ int create_labels_from_csv_file(char *file_path, (errmsg("Failed to initialize csv parser\n"))); } + p.malloc_func = palloc; + p.realloc_func = repalloc_check; + p.free_func = pfree_if_not_null; + csv_set_space_func(&p, is_space); csv_set_term_func(&p, is_term); @@ -199,62 +203,67 @@ int create_labels_from_csv_file(char *file_path, (errmsg("Failed to open %s\n", file_path))); } - label_seq_name = get_label_seq_relation_name(label_name); - - memset((void*)&cr, 0, sizeof(csv_vertex_reader)); - - cr.alloc = 2048; - cr.fields = malloc(sizeof(char *) * cr.alloc); - cr.fields_len = malloc(sizeof(size_t *) * cr.alloc); - cr.header_row_length = 0; - cr.curr_row_length = 0; - cr.graph_name = graph_name; - cr.graph_oid = graph_oid; - cr.label_name = label_name; - cr.label_id = label_id; - cr.id_field_exists = id_field_exists; - cr.label_seq_relid = get_relname_relid(label_seq_name, graph_oid); - cr.load_as_agtype = load_as_agtype; - - if (cr.id_field_exists) + PG_TRY(); { - /* - * Set the curr_seq_num since we will need it to compare with - * incoming entry_id. - * - * We cant use currval because it will error out if nextval was - * not called before in the session. - */ - cr.curr_seq_num = nextval_internal(cr.label_seq_relid, true); - } + label_seq_name = get_label_seq_relation_name(label_name); + + memset((void*)&cr, 0, sizeof(csv_vertex_reader)); + + cr.alloc = 2048; + cr.fields = palloc(sizeof(char *) * cr.alloc); + cr.fields_len = palloc(sizeof(size_t *) * cr.alloc); + cr.header_row_length = 0; + cr.curr_row_length = 0; + cr.graph_name = graph_name; + cr.graph_oid = graph_oid; + cr.label_name = label_name; + cr.label_id = label_id; + cr.id_field_exists = id_field_exists; + cr.label_seq_relid = get_relname_relid(label_seq_name, graph_oid); + cr.load_as_agtype = load_as_agtype; + + if (cr.id_field_exists) + { + /* + * Set the curr_seq_num since we will need it to compare with + * incoming entry_id. + * + * We cant use currval because it will error out if nextval was + * not called before in the session. + */ + cr.curr_seq_num = nextval_internal(cr.label_seq_relid, true); + } - /* Initialize the batch insert state */ - init_batch_insert(&cr.batch_state, label_name, graph_oid); + /* Initialize the batch insert state */ + init_batch_insert(&cr.batch_state, label_name, graph_oid); - while ((bytes_read=fread(buf, 1, 1024, fp)) > 0) - { - if (csv_parse(&p, buf, bytes_read, vertex_field_cb, - vertex_row_cb, &cr) != bytes_read) + while ((bytes_read=fread(buf, 1, 1024, fp)) > 0) { - ereport(ERROR, (errmsg("Error while parsing file: %s\n", - csv_strerror(csv_error(&p))))); + if (csv_parse(&p, buf, bytes_read, vertex_field_cb, + vertex_row_cb, &cr) != bytes_read) + { + ereport(ERROR, (errmsg("Error while parsing file: %s\n", + csv_strerror(csv_error(&p))))); + } } - } - csv_fini(&p, vertex_field_cb, vertex_row_cb, &cr); + csv_fini(&p, vertex_field_cb, vertex_row_cb, &cr); - /* Finish any remaining batch inserts */ - finish_batch_insert(&cr.batch_state); + /* Finish any remaining batch inserts */ + finish_batch_insert(&cr.batch_state); - if (ferror(fp)) + if (ferror(fp)) + { + ereport(ERROR, (errmsg("Error while reading file %s\n", + file_path))); + } + } + PG_FINALLY(); { - ereport(ERROR, (errmsg("Error while reading file %s\n", - file_path))); + fclose(fp); + csv_free(&p); } + PG_END_TRY(); - fclose(fp); - - free(cr.fields); - csv_free(&p); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index 486775320..ab2ba08cc 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -556,6 +556,7 @@ void pfree_agtype_value(agtype_value* value); void pfree_agtype_value_content(agtype_value* value); void pfree_agtype_in_state(agtype_in_state* value); void pfree_if_not_null(void *ptr); +void *repalloc_check(void *ptr, size_t len); agtype_value *agtype_value_from_cstring(char *str, int len); /* Oid accessors for AGTYPE */ Oid get_AGTYPEOID(void); From b9d35d5f4c8ef8b10ffebe3de1e3a17a8e8b7c15 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Wed, 10 Dec 2025 09:08:36 -0800 Subject: [PATCH 10/14] Fix ORDER BY alias resolution with AS in Cypher queries (#2269) NOTE: This PR was partially created with AI tools and reviewed by a human. ORDER BY clauses failed when referencing column aliases from RETURN: MATCH (p:Person) RETURN p.age AS age ORDER BY age DESC ERROR: could not find rte for age Added SQL-99 compliant alias matching to find_target_list_entry() that checks if ORDER BY identifier matches a target list alias before attempting expression transformation. This enables standard SQL behavior for sorting by aliased columns with DESC/DESCENDING/ASC/ASCENDING. Updated regression tests. Added regression tests. modified: regress/expected/cypher_match.out modified: regress/expected/expr.out modified: regress/sql/expr.sql modified: src/backend/parser/cypher_clause.c --- regress/expected/cypher_match.out | 24 ++++---- regress/expected/expr.out | 88 ++++++++++++++++++++++++++++++ regress/sql/expr.sql | 30 ++++++++++ src/backend/parser/cypher_clause.c | 26 +++++++++ 4 files changed, 156 insertions(+), 12 deletions(-) diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index a34f59196..ea425e463 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -1655,10 +1655,10 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (u agtype, m agtype, l agtype); u | m | l ------------+---------------+------------ - "someone" | "opt_match_e" | "somebody" - "somebody" | "opt_match_e" | "someone" "anybody" | "opt_match_e" | "nobody" "nobody" | "opt_match_e" | "anybody" + "somebody" | "opt_match_e" | "someone" + "someone" | "opt_match_e" | "somebody" (4 rows) SELECT * FROM cypher('cypher_match', $$ @@ -1670,8 +1670,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype); n | r | p | m | s | q -----------+---------------+------------+-----------+---------------+------------ - "someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody" "anybody" | "opt_match_e" | "nobody" | "someone" | "opt_match_e" | "somebody" + "someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody" (2 rows) SELECT * FROM cypher('cypher_match', $$ @@ -1684,18 +1684,18 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype); n | r | p | m | s | q ------------+---------------+------------+------------+---------------+------------ - "someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody" - "someone" | | | "somebody" | | - "someone" | | | "nobody" | | - "somebody" | | | "someone" | | - "somebody" | | | "anybody" | | - "somebody" | | | "nobody" | | "anybody" | "opt_match_e" | "nobody" | "someone" | "opt_match_e" | "somebody" - "anybody" | | | "somebody" | | "anybody" | | | "nobody" | | - "nobody" | | | "someone" | | - "nobody" | | | "somebody" | | + "anybody" | | | "somebody" | | "nobody" | | | "anybody" | | + "nobody" | | | "somebody" | | + "nobody" | | | "someone" | | + "somebody" | | | "anybody" | | + "somebody" | | | "nobody" | | + "somebody" | | | "someone" | | + "someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody" + "someone" | | | "nobody" | | + "someone" | | | "somebody" | | (12 rows) -- Tests to catch match following optional match logic diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 4be3bf90f..926a958d6 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -6949,6 +6949,94 @@ $$) AS (i agtype); {"key": "value"} (9 rows) +-- +-- Test ORDER BY with AS +-- +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'John', age: 38}) $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Jill', age: 23}) $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Ion', age: 34}) $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Mary', age: 57}) $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Jerry', age: 34}) $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name +$$) AS (name agtype, age agtype); + name | age +---------+----- + "Ion" | 34 + "Jerry" | 34 + "Jill" | 23 + "John" | 38 + "Mary" | 57 +(5 rows) + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name ASC +$$) AS (name agtype, age agtype); + name | age +---------+----- + "Ion" | 34 + "Jerry" | 34 + "Jill" | 23 + "John" | 38 + "Mary" | 57 +(5 rows) + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name DESC +$$) AS (name agtype, age agtype); + name | age +---------+----- + "Mary" | 57 + "John" | 38 + "Jill" | 23 + "Jerry" | 34 + "Ion" | 34 +(5 rows) + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY age ASC, name DESCENDING +$$) AS (name agtype, age agtype); + name | age +---------+----- + "Jill" | 23 + "Jerry" | 34 + "Ion" | 34 + "John" | 38 + "Mary" | 57 +(5 rows) + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY age DESC, name ASCENDING +$$) AS (name agtype, age agtype); + name | age +---------+----- + "Mary" | 57 + "John" | 38 + "Ion" | 34 + "Jerry" | 34 + "Jill" | 23 +(5 rows) + --CASE SELECT create_graph('case_statement'); NOTICE: graph "case_statement" has been created diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 466bace41..7bf1f26b2 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -2823,6 +2823,7 @@ SELECT * FROM cypher('order_by', $$CREATE ({i: false})$$) AS (result agtype); SELECT * FROM cypher('order_by', $$CREATE ({i: {key: 'value'}})$$) AS (result agtype); SELECT * FROM cypher('order_by', $$CREATE ({i: [1]})$$) AS (result agtype); + SELECT * FROM cypher('order_by', $$ MATCH (u) RETURN u.i @@ -2835,6 +2836,35 @@ SELECT * FROM cypher('order_by', $$ ORDER BY u.i DESC $$) AS (i agtype); +-- +-- Test ORDER BY with AS +-- +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'John', age: 38}) $$) AS (result agtype); +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Jill', age: 23}) $$) AS (result agtype); +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Ion', age: 34}) $$) AS (result agtype); +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Mary', age: 57}) $$) AS (result agtype); +SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Jerry', age: 34}) $$) AS (result agtype); + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name +$$) AS (name agtype, age agtype); + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name ASC +$$) AS (name agtype, age agtype); + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name DESC +$$) AS (name agtype, age agtype); + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY age ASC, name DESCENDING +$$) AS (name agtype, age agtype); + +SELECT * FROM cypher('order_by', $$ + MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY age DESC, name ASCENDING +$$) AS (name agtype, age agtype); + --CASE SELECT create_graph('case_statement'); SELECT * FROM cypher('case_statement', $$CREATE ({id: 1, i: 1, j: null})-[:connected_to {id: 1, k:0}]->({id: 2, i: 'a', j: 'b'})$$) AS (result agtype); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 5c5024cf7..acc52349d 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -2296,6 +2296,32 @@ static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, ListCell *lt; TargetEntry *te; + /* + * If the ORDER BY item is a simple identifier, check if it matches + * an alias in the target list. This implements SQL99-compliant + * alias matching for ORDER BY clauses. + */ + if (IsA(node, ColumnRef)) + { + ColumnRef *cref = (ColumnRef *)node; + + if (list_length(cref->fields) == 1) + { + char *name = strVal(linitial(cref->fields)); + + /* Try to match an alias in the target list */ + foreach (lt, *target_list) + { + te = lfirst(lt); + + if (te->resname != NULL && strcmp(te->resname, name) == 0) + { + return te; + } + } + } + } + expr = transform_cypher_expr(cpstate, node, expr_kind); foreach (lt, *target_list) From 7432ac630eb5bca2eed0ae4b8bc0de84e17bd8bd Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Thu, 11 Dec 2025 07:32:13 -0800 Subject: [PATCH 11/14] Update grammar file for maintainability (#2270) Consolidated duplicate code, added helper functions, and reviewed the grammar file for issues. NOTE: I used an AI tool to review and cleanup the grammar file. I have reviewed all of the work it did. Improvements: 1. Added KEYWORD_STRDUP macro to eliminate hardcoded string lengths 2. Consolidated EXPLAIN statement handling into make_explain_stmt helper 3. Extracted WITH clause validation into validate_return_item_aliases helper 4. Created make_default_return_node helper for subquery return-less logic Benefits: - Reduced code duplication by ~150 lines - Improved maintainability with helper functions - Eliminated manual string length calculations (error-prone) All 29 existing regression tests pass modified: src/backend/parser/cypher_gram.y --- src/backend/parser/cypher_gram.y | 303 ++++++++++++++----------------- 1 file changed, 140 insertions(+), 163 deletions(-) diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index 0bafefe1f..5ba1e6354 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -41,6 +41,9 @@ #define YYMALLOC palloc #define YYFREE pfree + +/* Helper macro for keyword string duplication */ +#define KEYWORD_STRDUP(kw) pnstrdup((kw), strlen(kw)) %} %locations @@ -276,6 +279,11 @@ static Node *build_list_comprehension_node(Node *var, Node *expr, Node *where, Node *mapping_expr, int location); +/* helper functions */ +static ExplainStmt *make_explain_stmt(List *options); +static void validate_return_item_aliases(List *items, ag_scanner_t scanner); +static cypher_return *make_default_return_node(int location); + %} %% @@ -300,81 +308,59 @@ stmt: * Throw syntax error in this case. */ if (yychar != YYEOF) + { yyerror(&yylloc, scanner, extra, "syntax error"); + } extra->result = $1; extra->extra = NULL; } | EXPLAIN cypher_stmt semicolon_opt { - ExplainStmt *estmt = NULL; - if (yychar != YYEOF) + { yyerror(&yylloc, scanner, extra, "syntax error"); - + } extra->result = $2; - - estmt = makeNode(ExplainStmt); - estmt->query = NULL; - estmt->options = NIL; - extra->extra = (Node *)estmt; + extra->extra = (Node *)make_explain_stmt(NIL); } | EXPLAIN VERBOSE cypher_stmt semicolon_opt { - ExplainStmt *estmt = NULL; - if (yychar != YYEOF) + { yyerror(&yylloc, scanner, extra, "syntax error"); - + } extra->result = $3; - - estmt = makeNode(ExplainStmt); - estmt->query = NULL; - estmt->options = list_make1(makeDefElem("verbose", NULL, @2));; - extra->extra = (Node *)estmt; + extra->extra = (Node *)make_explain_stmt( + list_make1(makeDefElem("verbose", NULL, @2))); } | EXPLAIN ANALYZE cypher_stmt semicolon_opt { - ExplainStmt *estmt = NULL; - if (yychar != YYEOF) + { yyerror(&yylloc, scanner, extra, "syntax error"); - + } extra->result = $3; - - estmt = makeNode(ExplainStmt); - estmt->query = NULL; - estmt->options = list_make1(makeDefElem("analyze", NULL, @2));; - extra->extra = (Node *)estmt; + extra->extra = (Node *)make_explain_stmt( + list_make1(makeDefElem("analyze", NULL, @2))); } | EXPLAIN ANALYZE VERBOSE cypher_stmt semicolon_opt { - ExplainStmt *estmt = NULL; - if (yychar != YYEOF) yyerror(&yylloc, scanner, extra, "syntax error"); - extra->result = $4; - - estmt = makeNode(ExplainStmt); - estmt->query = NULL; - estmt->options = list_make2(makeDefElem("analyze", NULL, @2), - makeDefElem("verbose", NULL, @3));; - extra->extra = (Node *)estmt; + extra->extra = (Node *)make_explain_stmt( + list_make2(makeDefElem("analyze", NULL, @2), + makeDefElem("verbose", NULL, @3))); } | EXPLAIN '(' utility_option_list ')' cypher_stmt semicolon_opt { - ExplainStmt *estmt = NULL; - if (yychar != YYEOF) + { yyerror(&yylloc, scanner, extra, "syntax error"); - + } extra->result = $5; - - estmt = makeNode(ExplainStmt); - estmt->query = NULL; - estmt->options = $3; - extra->extra = (Node *)estmt; + extra->extra = (Node *)make_explain_stmt($3); } ; @@ -652,57 +638,20 @@ single_subquery: single_subquery_no_return: subquery_part_init reading_clause_list { - ColumnRef *cr; - ResTarget *rt; cypher_return *n; /* * since subqueries allow return-less clauses, we add a * return node manually to reflect that syntax */ - cr = makeNode(ColumnRef); - cr->fields = list_make1(makeNode(A_Star)); - cr->location = @1; - - rt = makeNode(ResTarget); - rt->name = NULL; - rt->indirection = NIL; - rt->val = (Node *)cr; - rt->location = @1; - - n = make_ag_node(cypher_return); - n->distinct = false; - n->items = list_make1((Node *)rt); - n->order_by = NULL; - n->skip = NULL; - n->limit = NULL; - + n = make_default_return_node(@1); $$ = list_concat($1, lappend($2, n)); - } - | subquery_pattern + | subquery_pattern { - ColumnRef *cr; - ResTarget *rt; cypher_return *n; - cr = makeNode(ColumnRef); - cr->fields = list_make1(makeNode(A_Star)); - cr->location = @1; - - rt = makeNode(ResTarget); - rt->name = NULL; - rt->indirection = NIL; - rt->val = (Node *)cr; - rt->location = @1; - - n = make_ag_node(cypher_return); - n->distinct = false; - n->items = list_make1((Node *)rt); - n->order_by = NULL; - n->skip = NULL; - n->limit = NULL; - + n = make_default_return_node(@1); $$ = lappend(list_make1($1), n); } ; @@ -958,24 +907,10 @@ limit_opt: with: WITH DISTINCT return_item_list order_by_opt skip_opt limit_opt where_opt { - ListCell *li; cypher_with *n; /* check expressions are aliased */ - foreach(li, $3) - { - ResTarget *item = lfirst(li); - - /* variable does not have to be aliased */ - if (IsA(item->val, ColumnRef) || item->name) - continue; - - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("expression item must be aliased"), - errhint("Items can be aliased by using AS."), - ag_scanner_errposition(item->location, scanner))); - } + validate_return_item_aliases($3, scanner); n = make_ag_node(cypher_with); n->distinct = true; @@ -987,27 +922,12 @@ with: $$ = (Node *)n; } - | WITH return_item_list order_by_opt skip_opt limit_opt - where_opt + | WITH return_item_list order_by_opt skip_opt limit_opt where_opt { - ListCell *li; cypher_with *n; /* check expressions are aliased */ - foreach (li, $2) - { - ResTarget *item = lfirst(li); - - /* variable does not have to be aliased */ - if (IsA(item->val, ColumnRef) || item->name) - continue; - - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("expression item must be aliased"), - errhint("Items can be aliased by using AS."), - ag_scanner_errposition(item->location, scanner))); - } + validate_return_item_aliases($2, scanner); n = make_ag_node(cypher_with); n->distinct = false; @@ -2449,58 +2369,58 @@ qual_op: */ safe_keywords: - ALL { $$ = pnstrdup($1, 3); } - | ANALYZE { $$ = pnstrdup($1, 7); } - | AND { $$ = pnstrdup($1, 3); } - | AS { $$ = pnstrdup($1, 2); } - | ASC { $$ = pnstrdup($1, 3); } - | ASCENDING { $$ = pnstrdup($1, 9); } - | BY { $$ = pnstrdup($1, 2); } - | CALL { $$ = pnstrdup($1, 4); } - | CASE { $$ = pnstrdup($1, 4); } - | COALESCE { $$ = pnstrdup($1, 8); } - | CONTAINS { $$ = pnstrdup($1, 8); } - | COUNT { $$ = pnstrdup($1 ,5); } - | CREATE { $$ = pnstrdup($1, 6); } - | DELETE { $$ = pnstrdup($1, 6); } - | DESC { $$ = pnstrdup($1, 4); } - | DESCENDING { $$ = pnstrdup($1, 10); } - | DETACH { $$ = pnstrdup($1, 6); } - | DISTINCT { $$ = pnstrdup($1, 8); } - | ELSE { $$ = pnstrdup($1, 4); } - | ENDS { $$ = pnstrdup($1, 4); } - | EXISTS { $$ = pnstrdup($1, 6); } - | EXPLAIN { $$ = pnstrdup($1, 7); } - | IN { $$ = pnstrdup($1, 2); } - | IS { $$ = pnstrdup($1, 2); } - | LIMIT { $$ = pnstrdup($1, 6); } - | MATCH { $$ = pnstrdup($1, 6); } - | MERGE { $$ = pnstrdup($1, 6); } - | NOT { $$ = pnstrdup($1, 3); } - | OPERATOR { $$ = pnstrdup($1, 8); } - | OPTIONAL { $$ = pnstrdup($1, 8); } - | OR { $$ = pnstrdup($1, 2); } - | ORDER { $$ = pnstrdup($1, 5); } - | REMOVE { $$ = pnstrdup($1, 6); } - | RETURN { $$ = pnstrdup($1, 6); } - | SET { $$ = pnstrdup($1, 3); } - | SKIP { $$ = pnstrdup($1, 4); } - | STARTS { $$ = pnstrdup($1, 6); } - | THEN { $$ = pnstrdup($1, 4); } - | UNION { $$ = pnstrdup($1, 5); } - | WHEN { $$ = pnstrdup($1, 4); } - | VERBOSE { $$ = pnstrdup($1, 7); } - | WHERE { $$ = pnstrdup($1, 5); } - | WITH { $$ = pnstrdup($1, 4); } - | XOR { $$ = pnstrdup($1, 3); } - | YIELD { $$ = pnstrdup($1, 5); } + ALL { $$ = KEYWORD_STRDUP($1); } + | ANALYZE { $$ = KEYWORD_STRDUP($1); } + | AND { $$ = KEYWORD_STRDUP($1); } + | AS { $$ = KEYWORD_STRDUP($1); } + | ASC { $$ = KEYWORD_STRDUP($1); } + | ASCENDING { $$ = KEYWORD_STRDUP($1); } + | BY { $$ = KEYWORD_STRDUP($1); } + | CALL { $$ = KEYWORD_STRDUP($1); } + | CASE { $$ = KEYWORD_STRDUP($1); } + | COALESCE { $$ = KEYWORD_STRDUP($1); } + | CONTAINS { $$ = KEYWORD_STRDUP($1); } + | COUNT { $$ = KEYWORD_STRDUP($1); } + | CREATE { $$ = KEYWORD_STRDUP($1); } + | DELETE { $$ = KEYWORD_STRDUP($1); } + | DESC { $$ = KEYWORD_STRDUP($1); } + | DESCENDING { $$ = KEYWORD_STRDUP($1); } + | DETACH { $$ = KEYWORD_STRDUP($1); } + | DISTINCT { $$ = KEYWORD_STRDUP($1); } + | ELSE { $$ = KEYWORD_STRDUP($1); } + | ENDS { $$ = KEYWORD_STRDUP($1); } + | EXISTS { $$ = KEYWORD_STRDUP($1); } + | EXPLAIN { $$ = KEYWORD_STRDUP($1); } + | IN { $$ = KEYWORD_STRDUP($1); } + | IS { $$ = KEYWORD_STRDUP($1); } + | LIMIT { $$ = KEYWORD_STRDUP($1); } + | MATCH { $$ = KEYWORD_STRDUP($1); } + | MERGE { $$ = KEYWORD_STRDUP($1); } + | NOT { $$ = KEYWORD_STRDUP($1); } + | OPERATOR { $$ = KEYWORD_STRDUP($1); } + | OPTIONAL { $$ = KEYWORD_STRDUP($1); } + | OR { $$ = KEYWORD_STRDUP($1); } + | ORDER { $$ = KEYWORD_STRDUP($1); } + | REMOVE { $$ = KEYWORD_STRDUP($1); } + | RETURN { $$ = KEYWORD_STRDUP($1); } + | SET { $$ = KEYWORD_STRDUP($1); } + | SKIP { $$ = KEYWORD_STRDUP($1); } + | STARTS { $$ = KEYWORD_STRDUP($1); } + | THEN { $$ = KEYWORD_STRDUP($1); } + | UNION { $$ = KEYWORD_STRDUP($1); } + | WHEN { $$ = KEYWORD_STRDUP($1); } + | VERBOSE { $$ = KEYWORD_STRDUP($1); } + | WHERE { $$ = KEYWORD_STRDUP($1); } + | WITH { $$ = KEYWORD_STRDUP($1); } + | XOR { $$ = KEYWORD_STRDUP($1); } + | YIELD { $$ = KEYWORD_STRDUP($1); } ; conflicted_keywords: - END_P { $$ = pnstrdup($1, 5); } - | FALSE_P { $$ = pnstrdup($1, 7); } - | NULL_P { $$ = pnstrdup($1, 6); } - | TRUE_P { $$ = pnstrdup($1, 6); } + END_P { $$ = KEYWORD_STRDUP($1); } + | FALSE_P { $$ = KEYWORD_STRDUP($1); } + | NULL_P { $$ = KEYWORD_STRDUP($1); } + | TRUE_P { $$ = KEYWORD_STRDUP($1); } ; %% @@ -3384,7 +3304,7 @@ static Node *build_list_comprehension_node(Node *var, Node *expr, /* * Build an ARRAY sublink and attach list_comp as sub-select, - * it will be transformed in to query tree by us and reattached for + * it will be transformed in to query tree by us and reattached for * pg to process. */ sub = makeNode(SubLink); @@ -3396,3 +3316,60 @@ static Node *build_list_comprehension_node(Node *var, Node *expr, return (Node *) node_to_agtype((Node *)sub, "agtype[]", location); } + +/* Helper function to create an ExplainStmt node */ +static ExplainStmt *make_explain_stmt(List *options) +{ + ExplainStmt *estmt = makeNode(ExplainStmt); + estmt->query = NULL; + estmt->options = options; + return estmt; +} + +/* Helper function to validate that return items are properly aliased */ +static void validate_return_item_aliases(List *items, ag_scanner_t scanner) +{ + ListCell *li; + + foreach(li, items) + { + ResTarget *item = lfirst(li); + + /* variable does not have to be aliased */ + if (IsA(item->val, ColumnRef) || item->name) + continue; + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("expression item must be aliased"), + errhint("Items can be aliased by using AS."), + ag_scanner_errposition(item->location, scanner))); + } +} + +/* Helper function to create a default return node (RETURN *) */ +static cypher_return *make_default_return_node(int location) +{ + ColumnRef *cr; + ResTarget *rt; + cypher_return *n; + + cr = makeNode(ColumnRef); + cr->fields = list_make1(makeNode(A_Star)); + cr->location = location; + + rt = makeNode(ResTarget); + rt->name = NULL; + rt->indirection = NIL; + rt->val = (Node *)cr; + rt->location = location; + + n = make_ag_node(cypher_return); + n->distinct = false; + n->items = list_make1((Node *)rt); + n->order_by = NULL; + n->skip = NULL; + n->limit = NULL; + + return n; +} From d4e9b9cc36ed61b31e1ec20be6de734f6f0d6398 Mon Sep 17 00:00:00 2001 From: jsell-rh Date: Thu, 11 Dec 2025 10:56:37 -0500 Subject: [PATCH 12/14] Convert string to raw string to remove invalid escape sequence warning (#2267) - Changed '\s' to r'\s' --- drivers/python/age/age.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/python/age/age.py b/drivers/python/age/age.py index 817cc6e5a..b1aa82158 100644 --- a/drivers/python/age/age.py +++ b/drivers/python/age/age.py @@ -26,7 +26,7 @@ _EXCEPTION_NoConnection = NoConnection() _EXCEPTION_GraphNotSet = GraphNotSet() -WHITESPACE = re.compile('\s') +WHITESPACE = re.compile(r'\s') class AgeDumper(psycopg.adapt.Dumper): @@ -233,3 +233,4 @@ def cypher(self, cursor:psycopg.cursor, cypherStmt:str, cols:list=None, params:t # def queryCypher(self, cypherStmt:str, columns:list=None , params:tuple=None) -> psycopg.cursor : # return queryCypher(self.connection, self.graphName, cypherStmt, columns, params) + From d8fc867903e5d9f823f1ac5fa067720f8c7c447a Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Thu, 11 Dec 2025 21:51:12 +0500 Subject: [PATCH 13/14] Migrate python driver configuration to pyproject.toml (#2272) - Add pyproject.toml with package configuration - Simplify setup.py to minimal backward-compatible wrapper. - Updated CI workflow and .gitignore. - Resolves warning about using setup.py directly. --- .github/workflows/python-driver.yaml | 4 +-- .gitignore | 2 ++ drivers/python/README.md | 2 +- drivers/python/pyproject.toml | 48 ++++++++++++++++++++++++++++ drivers/python/setup.py | 28 +++------------- 5 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 drivers/python/pyproject.toml diff --git a/.github/workflows/python-driver.yaml b/.github/workflows/python-driver.yaml index ef6cf96a1..860cbcf25 100644 --- a/.github/workflows/python-driver.yaml +++ b/.github/workflows/python-driver.yaml @@ -24,7 +24,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.12' - name: Install pre-requisites run: | @@ -33,7 +33,7 @@ jobs: - name: Build run: | - python setup.py install + pip install . - name: Test run: | diff --git a/.gitignore b/.gitignore index a8e809dda..03923b03e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,7 @@ age--*.*.*.sql !age--*--*sql __pycache__ **/__pycache__ +**/.venv +**/apache_age_python.egg-info drivers/python/build diff --git a/drivers/python/README.md b/drivers/python/README.md index 184652275..749b44bfb 100644 --- a/drivers/python/README.md +++ b/drivers/python/README.md @@ -62,7 +62,7 @@ python -m unittest -v test_agtypes.py ### Build from source ``` -python setup.py install +pip install . ``` ### For more information about [Apache AGE](https://age.apache.org/) diff --git a/drivers/python/pyproject.toml b/drivers/python/pyproject.toml new file mode 100644 index 000000000..18112381c --- /dev/null +++ b/drivers/python/pyproject.toml @@ -0,0 +1,48 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "apache-age-python" +version = "0.0.7" +description = "Python driver support for Apache AGE" +readme = "README.md" +requires-python = ">=3.9" +license = "Apache-2.0" +keywords = ["Graph Database", "Apache AGE", "PostgreSQL"] +authors = [ + {name = "Ikchan Kwon, Apache AGE", email = "dev-subscribe@age.apache.org"} +] +classifiers = [ + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +dependencies = [ + "psycopg", + "antlr4-python3-runtime==4.11.1", +] + +[project.urls] +Homepage = "https://github.com/apache/age/tree/master/drivers/python" +Download = "https://github.com/apache/age/releases" + +[tool.setuptools] +packages = ["age", "age.gen", "age.networkx"] diff --git a/drivers/python/setup.py b/drivers/python/setup.py index 1da49d9cb..d0eed26be 100644 --- a/drivers/python/setup.py +++ b/drivers/python/setup.py @@ -13,28 +13,10 @@ # specific language governing permissions and limitations # under the License. -from setuptools import setup, find_packages -from age import VERSION +# This setup.py is maintained for backward compatibility. +# All package configuration is in pyproject.toml. For installation, +# use: pip install . -with open("README.md", "r", encoding='utf8') as fh: - long_description = fh.read() +from setuptools import setup -setup( - name = 'apache-age-python', - version = '0.0.7', - description = 'Python driver support for Apache AGE', - long_description=long_description, - long_description_content_type="text/markdown", - author = 'Ikchan Kwon, Apache AGE', - author_email = 'dev-subscribe@age.apache.org', - url = 'https://github.com/apache/age/tree/master/drivers/python', - download_url = 'https://github.com/apache/age/releases' , - license = 'Apache2.0', - install_requires = [ 'psycopg', 'antlr4-python3-runtime==4.11.1'], - packages = ['age', 'age.gen','age.networkx'], - keywords = ['Graph Database', 'Apache AGE', 'PostgreSQL'], - python_requires = '>=3.9', - classifiers = [ - 'Programming Language :: Python :: 3.9' - ] -) +setup() From 3d54a1ce78011f4ac712c36d1e9f704f1f7f5438 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Tue, 16 Dec 2025 08:33:28 -0800 Subject: [PATCH 14/14] Restrict age_load commands (#2274) This PR applies restrictions to the following age_load commands - load_labels_from_file() load_edges_from_file() They are now tied to a specific root directory and are required to have a specific file extension to eliminate any attempts to force them to access any other files. Nothing else has changed with the actual command formats or parameters, only that they work out of the /tmp/age directory and only access files with an extension of .csv. Added regression tests and updated the location of the csv files for those regression tests. modified: regress/expected/age_load.out modified: regress/sql/age_load.sql modified: src/backend/utils/load/age_load.c --- regress/expected/age_load.out | 44 +++++++++++++++++- regress/sql/age_load.sql | 38 +++++++++++++++- src/backend/utils/load/age_load.c | 76 ++++++++++++++++++++++++++++--- 3 files changed, 149 insertions(+), 9 deletions(-) diff --git a/regress/expected/age_load.out b/regress/expected/age_load.out index 5f2bdab78..55d1ff1d6 100644 --- a/regress/expected/age_load.out +++ b/regress/expected/age_load.out @@ -16,7 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -\! cp -r regress/age_load/data regress/instance/data/age_load +\! rm -rf /tmp/age/age_load +\! mkdir -p /tmp/age +\! cp -r regress/age_load/data /tmp/age/age_load LOAD 'age'; SET search_path TO ag_catalog; -- Create a country using CREATE clause @@ -401,6 +403,43 @@ SELECT * FROM cypher('agload_conversion', $$ MATCH ()-[e:Edges2]->() RETURN prop {"bool": "false", "string": "nUll", "numeric": "3.14"} (6 rows) +-- +-- Check sandbox +-- +-- check null file name +SELECT load_labels_from_file('agload_conversion', 'Person1', NULL, true, true); +ERROR: file path must not be NULL +SELECT load_edges_from_file('agload_conversion', 'Edges1', NULL, true); +ERROR: file path must not be NULL +-- check no file name +SELECT load_labels_from_file('agload_conversion', 'Person1', '', true, true); +ERROR: file name cannot be zero length +SELECT load_edges_from_file('agload_conversion', 'Edges1', '', true); +ERROR: file name cannot be zero length +-- check for file/path does not exist +SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load_xxx/conversion_vertices.csv', true, true); +ERROR: File or path does not exist [/tmp/age/age_load_xxx/conversion_vertices.csv] +SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load_xxx/conversion_edges.csv', true); +ERROR: File or path does not exist [/tmp/age/age_load_xxx/conversion_edges.csv] +SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load/conversion_vertices.txt', true, true); +ERROR: File or path does not exist [/tmp/age/age_load/conversion_vertices.txt] +SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load/conversion_edges.txt', true); +ERROR: File or path does not exist [/tmp/age/age_load/conversion_edges.txt] +-- check wrong extension +\! touch /tmp/age/age_load/conversion_vertices.txt +\! touch /tmp/age/age_load/conversion_edges.txt +SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load/conversion_vertices.txt', true, true); +ERROR: You can only load files with extension [.csv]. +SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load/conversion_edges.txt', true); +ERROR: You can only load files with extension [.csv]. +-- check outside sandbox directory +SELECT load_labels_from_file('agload_conversion', 'Person1', '../../etc/passwd', true, true); +ERROR: You can only load files located in [/tmp/age/]. +SELECT load_edges_from_file('agload_conversion', 'Edges1', '../../etc/passwd', true); +ERROR: You can only load files located in [/tmp/age/]. +-- +-- Cleanup +-- SELECT drop_graph('agload_conversion', true); NOTICE: drop cascades to 6 other objects DETAIL: drop cascades to table agload_conversion._ag_label_vertex @@ -415,3 +454,6 @@ NOTICE: graph "agload_conversion" has been dropped (1 row) +-- +-- End +-- diff --git a/regress/sql/age_load.sql b/regress/sql/age_load.sql index 180248bf1..cefcfb4ca 100644 --- a/regress/sql/age_load.sql +++ b/regress/sql/age_load.sql @@ -17,7 +17,9 @@ * under the License. */ -\! cp -r regress/age_load/data regress/instance/data/age_load +\! rm -rf /tmp/age/age_load +\! mkdir -p /tmp/age +\! cp -r regress/age_load/data /tmp/age/age_load LOAD 'age'; @@ -160,4 +162,38 @@ SELECT create_elabel('agload_conversion','Edges2'); SELECT load_edges_from_file('agload_conversion', 'Edges2', 'age_load/conversion_edges.csv', false); SELECT * FROM cypher('agload_conversion', $$ MATCH ()-[e:Edges2]->() RETURN properties(e) $$) as (a agtype); +-- +-- Check sandbox +-- +-- check null file name +SELECT load_labels_from_file('agload_conversion', 'Person1', NULL, true, true); +SELECT load_edges_from_file('agload_conversion', 'Edges1', NULL, true); + +-- check no file name +SELECT load_labels_from_file('agload_conversion', 'Person1', '', true, true); +SELECT load_edges_from_file('agload_conversion', 'Edges1', '', true); + +-- check for file/path does not exist +SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load_xxx/conversion_vertices.csv', true, true); +SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load_xxx/conversion_edges.csv', true); +SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load/conversion_vertices.txt', true, true); +SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load/conversion_edges.txt', true); + +-- check wrong extension +\! touch /tmp/age/age_load/conversion_vertices.txt +\! touch /tmp/age/age_load/conversion_edges.txt +SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load/conversion_vertices.txt', true, true); +SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load/conversion_edges.txt', true); + +-- check outside sandbox directory +SELECT load_labels_from_file('agload_conversion', 'Person1', '../../etc/passwd', true, true); +SELECT load_edges_from_file('agload_conversion', 'Edges1', '../../etc/passwd', true); + +-- +-- Cleanup +-- SELECT drop_graph('agload_conversion', true); + +-- +-- End +-- diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index 307ec335a..c7cf0677f 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -31,6 +31,62 @@ static agtype_value *csv_value_to_agtype_value(char *csv_val); static Oid get_or_create_graph(const Name graph_name); static int32 get_or_create_label(Oid graph_oid, char *graph_name, char *label_name, char label_kind); +static char *build_safe_filename(char *name); + +#define AGE_BASE_CSV_DIRECTORY "/tmp/age/" +#define AGE_CSV_FILE_EXTENSION ".csv" + +static char *build_safe_filename(char *name) +{ + int length; + char path[PATH_MAX]; + char *resolved; + + if (name == NULL) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("file name cannot be NULL"))); + + } + + length = strlen(name); + + if (length == 0) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("file name cannot be zero length"))); + + } + + snprintf(path, sizeof(path), "%s%s", AGE_BASE_CSV_DIRECTORY, name); + + resolved = realpath(path, NULL); + + if (resolved == NULL) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("File or path does not exist [%s]", path))); + } + + if (strncmp(resolved, AGE_BASE_CSV_DIRECTORY, + strlen(AGE_BASE_CSV_DIRECTORY)) != 0) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("You can only load files located in [%s].", + AGE_BASE_CSV_DIRECTORY))); + } + + length = strlen(resolved) - 4; + if (strncmp(resolved+length, AGE_CSV_FILE_EXTENSION, + strlen(AGE_CSV_FILE_EXTENSION)) != 0) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("You can only load files with extension [%s].", + AGE_CSV_FILE_EXTENSION))); + } + + return resolved; +} agtype *create_empty_agtype(void) { @@ -344,7 +400,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) { Name graph_name; Name label_name; - text* file_path; + text* file_name; char* graph_name_str; char* label_name_str; char* file_path_str; @@ -373,7 +429,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) graph_name = PG_GETARG_NAME(0); label_name = PG_GETARG_NAME(1); - file_path = PG_GETARG_TEXT_P(2); + file_name = PG_GETARG_TEXT_P(2); id_field_exists = PG_GETARG_BOOL(3); load_as_agtype = PG_GETARG_BOOL(4); @@ -385,7 +441,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) label_name_str = AG_DEFAULT_LABEL_VERTEX; } - file_path_str = text_to_cstring(file_path); + file_path_str = build_safe_filename(text_to_cstring(file_name)); graph_oid = get_or_create_graph(graph_name); label_id = get_or_create_label(graph_oid, graph_name_str, @@ -394,6 +450,9 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) create_labels_from_csv_file(file_path_str, graph_name_str, graph_oid, label_name_str, label_id, id_field_exists, load_as_agtype); + + free(file_path_str); + PG_RETURN_VOID(); } @@ -403,7 +462,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) Name graph_name; Name label_name; - text* file_path; + text* file_name; char* graph_name_str; char* label_name_str; char* file_path_str; @@ -431,7 +490,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) graph_name = PG_GETARG_NAME(0); label_name = PG_GETARG_NAME(1); - file_path = PG_GETARG_TEXT_P(2); + file_name = PG_GETARG_TEXT_P(2); load_as_agtype = PG_GETARG_BOOL(3); graph_name_str = NameStr(*graph_name); @@ -442,7 +501,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) label_name_str = AG_DEFAULT_LABEL_EDGE; } - file_path_str = text_to_cstring(file_path); + file_path_str = build_safe_filename(text_to_cstring(file_name)); graph_oid = get_or_create_graph(graph_name); label_id = get_or_create_label(graph_oid, graph_name_str, @@ -450,6 +509,9 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) create_edges_from_csv_file(file_path_str, graph_name_str, graph_oid, label_name_str, label_id, load_as_agtype); + + free(file_path_str); + PG_RETURN_VOID(); } @@ -599,4 +661,4 @@ void finish_batch_insert(batch_insert_state **batch_state) pfree((*batch_state)->slots); pfree(*batch_state); *batch_state = NULL; -} \ No newline at end of file +}