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/installcheck.yaml b/.github/workflows/installcheck.yaml index da266f0da..dcb2057df 100644 --- a/.github/workflows/installcheck.yaml +++ b/.github/workflows/installcheck.yaml @@ -3,6 +3,7 @@ name: Build / Regression on: push: branches: [ "master" ] + pull_request: branches: [ "master" ] @@ -11,53 +12,53 @@ jobs: 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/README.md b/README.md index 9f99f6ddb..d6c4c1d39 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@   - - + +   @@ -125,7 +125,7 @@ Apache AGE is intended to be simple to install and run. It can be installed with  Install PostgreSQL -You will need to install an AGE compatible version of Postgres, for now AGE supports Postgres 11, 12, 13, 14, 15, 16 & 17. Supporting the latest versions is on AGE roadmap. +You will need to install an AGE compatible version of Postgres, for now AGE supports Postgres 11, 12, 13, 14, 15, 16, 17, & 18. Supporting the latest versions is on AGE roadmap.

 Installation via Package Manager @@ -143,7 +143,7 @@ sudo apt install postgresql  Installation From Source Code

-You can
download the Postgres source code and install your own instance of Postgres. You can read instructions on how to install from source code for different versions on the official Postgres Website. +You can download the Postgres source code and install your own instance of Postgres. You can read instructions on how to install from source code for different versions on the official Postgres Website. @@ -152,7 +152,7 @@ You can download the Postgres Clone the github repository or download the download an official release. -Run the pg_config utility and check the version of PostgreSQL. Currently, only PostgreSQL versions 11, 12, 13, 14, 15, 16 & 17 are supported. If you have any other version of Postgres, you will need to install PostgreSQL version 11, 12, 13, 14, 15, 16 & 17. +Run the pg_config utility and check the version of PostgreSQL. Currently, only PostgreSQL versions 11, 12, 13, 14, 15, 16, 17, & 18 are supported. If you have any other version of Postgres, you will need to install PostgreSQL version 11, 12, 13, 14, 15, 16, 17, & 18.
```bash 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/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index a0e284beb..ea425e463 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -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) -- @@ -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); 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 a90c2a196..495eb3a08 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -19,6 +19,7 @@ #include "postgres.h" +#include "executor/executor.h" #include "utils/rls.h" #include "catalog/ag_label.h" diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 4766c6e7a..0b486ad5e 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -19,9 +19,10 @@ #include "postgres.h" +#include "executor/executor.h" +#include "storage/bufmgr.h" #include "common/hashfn.h" #include "miscadmin.h" -#include "storage/bufmgr.h" #include "utils/acl.h" #include "utils/rls.h" @@ -260,7 +261,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 e0d6c78e8..a1bb4686c 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -19,6 +19,7 @@ #include "postgres.h" +#include "executor/executor.h" #include "utils/datum.h" #include "utils/rls.h" diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index 40cf2b232..a1063af32 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -105,6 +105,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; @@ -116,7 +117,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; @@ -151,7 +161,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; @@ -170,7 +183,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) { @@ -320,7 +336,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; } @@ -435,7 +451,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), @@ -669,7 +685,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/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 7b636a3d4..991e3f785 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -2729,9 +2729,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); diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index f9634668c..e4f10d7e4 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -809,7 +809,7 @@ void init_batch_insert(batch_insert_state **batch_state, perminfos = list_make1(perminfo); /* Initialize range table in executor state */ - ExecInitRangeTable(estate, range_table, perminfos); + ExecInitRangeTable(estate, range_table, perminfos, NULL); /* Initialize resultRelInfo - this opens the relation */ resultRelInfo = makeNode(ResultRelInfo); 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