From 8bfee7674dad8c7cc13e0ee7a06e520915ea8a4b Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Tue, 27 May 2025 10:12:00 -0700 Subject: [PATCH 01/56] Add the beginning backbone of the vertex am --- Makefile | 1 + .../vertex_heap/vertex_heapam_handler.c | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/backend/access/vertex_heap/vertex_heapam_handler.c diff --git a/Makefile b/Makefile index 20ea2aa2..5779f82e 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ OBJS = src/backend/postgraph.o \ src/backend/access/ivfflat/ivfscan.o \ src/backend/access/ivfflat/ivfutils.o \ src/backend/access/ivfflat/ivfvacuum.o \ + src/backend/access/vertex_heap/vertex_heapam_handler.o \ src/backend/catalog/ag_catalog.o \ src/backend/catalog/ag_graph.o \ src/backend/catalog/ag_label.o \ diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c new file mode 100644 index 00000000..5cbe38ec --- /dev/null +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -0,0 +1,137 @@ +/*------------------------------------------------------------------------- + * + * vertex_heapam_handler.c + * vertex heap access method code + * + * Portions Copyright (c) 2025, PostGraphDB + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/vertex_heap/vertex_heapam_handler.c + * + * + * NOTES + * This files wires up the lower level heapam.c et al routines with the + * tableam abstraction. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/heaptoast.h" +#include "access/multixact.h" +#include "access/rewriteheap.h" +#include "access/syncscan.h" +#include "access/tableam.h" +#include "access/tsmapi.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/index.h" +#include "catalog/storage.h" +#include "catalog/storage_xlog.h" +#include "commands/progress.h" +#include "executor/executor.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "storage/bufmgr.h" +#include "storage/bufpage.h" +#include "storage/lmgr.h" +#include "storage/predicate.h" +#include "storage/procarray.h" +#include "storage/smgr.h" +#include "utils/builtins.h" +#include "utils/rel.h" + + +/* ------------------------------------------------------------------------ + * Slot related callbacks for vertex heap AM + * ------------------------------------------------------------------------ + */ + +static const TupleTableSlotOps * +vertex_heapam_slot_callbacks(Relation relation) +{ + return &TTSOpsBufferHeapTuple; +} + + +/* ------------------------------------------------------------------------ + * Definition of the heap table access method. + * ------------------------------------------------------------------------ + */ + +static const TableAmRoutine vertex_heapam_methods = { + .type = T_TableAmRoutine, + + .slot_callbacks = vertex_heapam_slot_callbacks, + + .scan_begin = NULL, + .scan_end = NULL, + .scan_rescan = NULL, + .scan_getnextslot = NULL, + + .scan_set_tidrange = NULL, + .scan_getnextslot_tidrange = NULL, + + .parallelscan_estimate = NULL, + .parallelscan_initialize = NULL, + .parallelscan_reinitialize = NULL, + + .index_fetch_begin = NULL, + .index_fetch_reset = NULL, + .index_fetch_end = NULL, + .index_fetch_tuple = NULL, + + .tuple_insert = NULL, + .tuple_insert_speculative = NULL, + .tuple_complete_speculative = NULL, + .multi_insert = NULL, + .tuple_delete = NULL, + .tuple_update = NULL, + .tuple_lock = NULL, + + .tuple_fetch_row_version = NULL, + .tuple_get_latest_tid = NULL, + .tuple_tid_valid = NULL, + .tuple_satisfies_snapshot = NULL, + .index_delete_tuples = NULL, + + .relation_set_new_filenode = NULL, + .relation_nontransactional_truncate = NULL, + .relation_copy_data = NULL, + .relation_copy_for_cluster = NULL, + .relation_vacuum = NULL, + .scan_analyze_next_block = NULL, + .scan_analyze_next_tuple = NULL, + .index_build_range_scan = NULL, + .index_validate_scan = NULL, + + .relation_size = NULL, + .relation_needs_toast_table = NULL, + .relation_toast_am = NULL, + .relation_fetch_toast_slice = NULL, + + .relation_estimate_size = NULL, + + .scan_bitmap_next_block = NULL, + .scan_bitmap_next_tuple = NULL, + .scan_sample_next_block = NULL, + .scan_sample_next_tuple = NULL +}; + + +const TableAmRoutine * +GetVertexHeapamTableAmRoutine(void) +{ + return &vertex_heapam_methods; +} + +Datum +vertex_heap_tableam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&vertex_heapam_methods); +} From ad21475df9172a3c8597a9008e3a70925446eef4 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Fri, 30 May 2025 14:46:22 -0700 Subject: [PATCH 02/56] Add Vertex AM to .sql file --- sql/postgraph.sql.in | 12 ++++++++++++ .../access/vertex_heap/vertex_heapam_handler.c | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/sql/postgraph.sql.in b/sql/postgraph.sql.in index 4d86cadb..12aa61ea 100644 --- a/sql/postgraph.sql.in +++ b/sql/postgraph.sql.in @@ -99,6 +99,18 @@ CREATE UNIQUE INDEX session_graph_use_index ON session_graph_use USING btree (pid, graph_oid); + +------------------------------------- +-- Table AM interface functions +------------------------------------- +CREATE FUNCTION vertex_tableam_handler(internal) +RETURNS table_am_handler +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE ACCESS METHOD vertex TYPE TABLE +HANDLER vertex_tableam_handler; + -- -- catalog lookup functions -- diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 5cbe38ec..f8f31819 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -130,8 +130,10 @@ GetVertexHeapamTableAmRoutine(void) return &vertex_heapam_methods; } +PG_FUNCTION_INFO_V1(vertex_tableam_handler); Datum -vertex_heap_tableam_handler(PG_FUNCTION_ARGS) +vertex_tableam_handler(PG_FUNCTION_ARGS) { PG_RETURN_POINTER(&vertex_heapam_methods); } + From a63e3fa9eab6eccbf507851c8743816efd667f0f Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Fri, 30 May 2025 16:41:25 -0700 Subject: [PATCH 03/56] Add the functions to the vertex am nothing works, but the asserts won't crash anymore --- Makefile | 1 + regress/expected/vertex_am.out | 1 + regress/sql/vertex_am.sql | 23 + .../vertex_heap/vertex_heapam_handler.c | 802 ++++++++++++++++-- 4 files changed, 764 insertions(+), 63 deletions(-) create mode 100644 regress/expected/vertex_am.out create mode 100644 regress/sql/vertex_am.sql diff --git a/Makefile b/Makefile index 5779f82e..d1bf7ce5 100644 --- a/Makefile +++ b/Makefile @@ -89,6 +89,7 @@ EXTENSION = postgraph DATA = postgraph--0.1.0.sql REGRESS = new_cypher \ + vertex_am \ graphid \ gtype \ expr \ diff --git a/regress/expected/vertex_am.out b/regress/expected/vertex_am.out new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/regress/expected/vertex_am.out @@ -0,0 +1 @@ + diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql new file mode 100644 index 00000000..77903347 --- /dev/null +++ b/regress/sql/vertex_am.sql @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +-- Regression tests don't preload extensions, gotta load it first + +LOAD 'postgraph'; + + + +CREATE TABLE tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex; diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index f8f31819..50823aa2 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * vertex_heapam_handler.c - * vertex heap access method code + * vertex heap access method code * * Portions Copyright (c) 2025, PostGraphDB * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group @@ -9,12 +9,12 @@ * * * IDENTIFICATION - * src/backend/access/vertex_heap/vertex_heapam_handler.c + * src/backend/access/vertex_heap/vertex_heapam_handler.c * * * NOTES - * This files wires up the lower level heapam.c et al routines with the - * tableam abstraction. + * This files wires up the lower level heapam.c et al routines with the + * tableam abstraction. * *------------------------------------------------------------------------- */ @@ -55,9 +55,685 @@ static const TupleTableSlotOps * vertex_heapam_slot_callbacks(Relation relation) { - return &TTSOpsBufferHeapTuple; + return &TTSOpsBufferHeapTuple; } +/* ------------------------------------------------------------------------ + * Functions related to scaning + * ------------------------------------------------------------------------ + */ +TableScanDesc vertex_scan_begin(Relation rel, Snapshot snapshot, int nkeys, + struct ScanKeyData *key, + ParallelTableScanDesc pscan, uint32 flags) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_scan_begin not implemented"))); + return NULL; +} + +void vertex_scan_end(TableScanDesc scan) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_scan_end not implemented"))); +} + +/* + * Restart relation scan. If set_params is set to true, allow_{strat, + * sync, pagemode} (see scan_begin) changes should be taken into account. + */ +void vertex_scan_rescan(TableScanDesc scan, struct ScanKeyData *key, + bool set_params, bool allow_strat, + bool allow_sync, bool allow_pagemode) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_rescan not implemented"))); +} + +/* + * Return next tuple from `scan`, store in slot. + */ +bool vertex_scan_getnextslot(TableScanDesc scan, ScanDirection direction, + TupleTableSlot *slot) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_scan_getnextslot not implemented"))); + return false; +} + +/* ------------------------------------------------------------------------ + * Parallel table scan related functions. + * ------------------------------------------------------------------------ + */ + +/* + * Estimate the size of shared memory needed for a parallel scan of this + * relation. The snapshot does not need to be accounted for. + */ +Size vertex_parallelscan_estimate(Relation rel) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_estimate not implemented"))); + + return NULL; +} + +/* + * Initialize ParallelTableScanDesc for a parallel scan of this relation. + * `pscan` will be sized according to parallelscan_estimate() for the same + * relation. + */ +Size vertex_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_initialize not implemented"))); + + return NULL; +} + +/* + * Reinitialize `pscan` for a new scan. `rel` will be the same relation as + * when `pscan` was initialized by parallelscan_initialize. + */ +void vertex_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + +} + + +/* ------------------------------------------------------------------------ + * Index Scan Callbacks + * ------------------------------------------------------------------------ + */ + +/* + * Prepare to fetch tuples from the relation, as needed when fetching + * tuples for an index scan. The callback has to return an + * IndexFetchTableData, which the AM will typically embed in a larger + * structure with additional information. + * + * Tuples for an index scan can then be fetched via index_fetch_tuple. + */ +struct IndexFetchTableData *vertex_index_fetch_begin(Relation rel) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + return NULL; +} + +/* + * Reset index fetch. Typically this will release cross index fetch + * resources held in IndexFetchTableData. + */ +void vertex_index_fetch_reset(struct IndexFetchTableData *data) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + +} + +/* + * Release resources and deallocate index fetch. + */ +void vertex_index_fetch_end(struct IndexFetchTableData *data) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + +} + +/* + * Fetch tuple at `tid` into `slot`, after doing a visibility test + * according to `snapshot`. If a tuple was found and passed the visibility + * test, return true, false otherwise. + * + * Note that AMs that do not necessarily update indexes when indexed + * columns do not change, need to return the current/correct version of + * the tuple that is visible to the snapshot, even if the tid points to an + * older version of the tuple. + * + * *call_again is false on the first call to index_fetch_tuple for a tid. + * If there potentially is another tuple matching the tid, *call_again + * needs to be set to true by index_fetch_tuple, signaling to the caller + * that index_fetch_tuple should be called again for the same tid. + * + * *all_dead, if all_dead is not NULL, should be set to true by + * index_fetch_tuple iff it is guaranteed that no backend needs to see + * that tuple. Index AMs can use that to avoid returning that tid in + * future searches. + */ +bool vertex_index_fetch_tuple(struct IndexFetchTableData *scan, + ItemPointer tid, Snapshot snapshot, + TupleTableSlot *slot, + bool *call_again, bool *all_dead) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + return false; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for non-modifying operations on individual tuples + * ------------------------------------------------------------------------ + */ + +/* + * Fetch tuple at `tid` into `slot`, after doing a visibility test + * according to `snapshot`. If a tuple was found and passed the visibility + * test, returns true, false otherwise. + */ +bool vertex_tuple_fetch_row_version(Relation rel, ItemPointer tid, + Snapshot snapshot, TupleTableSlot *slot) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + return false; +} + +/* + * Is tid valid for a scan of this relation. + */ +bool vertex_tuple_tid_valid(TableScanDesc scan, ItemPointer tid) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + return false; +} + +/* + * Return the latest version of the tuple at `tid`, by updating `tid` to + * point at the newest version. + */ +void vertex_tuple_get_latest_tid(TableScanDesc scan, ItemPointer tid) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + +} + +/* + * Does the tuple in `slot` satisfy `snapshot`? The slot needs to be of + * the appropriate type for the AM. + */ +bool vertex_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, + Snapshot snapshot) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + return false; +} + +/* see table_index_delete_tuples() */ +TransactionId vertex_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + return NULL; +} + +/* ------------------------------------------------------------------------ + * Manipulations of physical tuples. + * ------------------------------------------------------------------------ + */ + +/* see table_tuple_insert() for reference about parameters */ +void vertex_tuple_insert(Relation rel, TupleTableSlot *slot, + CommandId cid, int options, + struct BulkInsertStateData *bistate) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + +} + +/* see table_tuple_insert_speculative() for reference about parameters */ +void vertex_tuple_insert_speculative(Relation rel, TupleTableSlot *slot, + CommandId cid, int options, + struct BulkInsertStateData *bistate, + uint32 specToken) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + +/* see table_tuple_complete_speculative() for reference about parameters */ +void vertex_tuple_complete_speculative(Relation rel, TupleTableSlot *slot, + uint32 specToken, bool succeeded) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + +/* see table_multi_insert() for reference about parameters */ +void vertex_multi_insert(Relation rel, TupleTableSlot **slots, int nslots, + CommandId cid, int options, struct BulkInsertStateData *bistate) { + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + +/* see table_tuple_delete() for reference about parameters */ +TM_Result vertex_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, + Snapshot snapshot, Snapshot crosscheck, bool wait, + TM_FailureData *tmfd, bool changingPart) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + + return 0; +} + +/* see table_tuple_update() for reference about parameters */ +TM_Result vertex_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, + CommandId cid, Snapshot snapshot, Snapshot crosscheck, + bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, + bool *update_indexes) { + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + +return 0; +} + +/* see table_tuple_lock() for reference about parameters */ +TM_Result vertex_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, + TupleTableSlot *slot, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, TM_FailureData *tmfd) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + + return 0; +} + + + +/* ------------------------------------------------------------------------ + * DDL related functionality. + * ------------------------------------------------------------------------ + */ + +/* + * This callback needs to create a new relation filenode for `rel`, with + * appropriate durability behaviour for `persistence`. + * + * Note that only the subset of the relcache filled by + * RelationBuildLocalRelation() can be relied upon and that the relation's + * catalog entries will either not yet exist (new relation), or will still + * reference the old relfilenode. + * + * As output *freezeXid, *minmulti must be set to the values appropriate + * for pg_class.{relfrozenxid, relminmxid}. For AMs that don't need those + * fields to be filled they can be set to InvalidTransactionId and + * InvalidMultiXactId, respectively. + * + * See also table_relation_set_new_filenode(). + */ +void vertex_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, + char persistence, TransactionId *freezeXid, + MultiXactId *minmulti) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_relation_set_new_filenode not implemented"))); +} + +/* + * This callback needs to remove all contents from `rel`'s current + * relfilenode. No provisions for transactional behaviour need to be made. + * Often this can be implemented by truncating the underlying storage to + * its minimal size. + * + * See also table_relation_nontransactional_truncate(). + */ +void vertex_relation_nontransactional_truncate(Relation rel) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + +/* + * See table_relation_copy_data(). + * + * This can typically be implemented by directly copying the underlying + * storage, unless it contains references to the tablespace internally. + */ +void vertex_relation_copy_data(Relation rel, const RelFileNode *newrnode) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + +/* See table_relation_copy_for_cluster() */ +void vertex_relation_copy_for_cluster(Relation OldTable, Relation NewTable, + Relation OldIndex, bool use_sort, + TransactionId OldestXmin, TransactionId *xid_cutoff, + MultiXactId *multi_cutoff, double *num_tuples, + double *tups_vacuumed, double *tups_recently_dead) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + +/* + * React to VACUUM command on the relation. The VACUUM can be triggered by + * a user or by autovacuum. The specific actions performed by the AM will + * depend heavily on the individual AM. + * + * On entry a transaction is already established, and the relation is + * locked with a ShareUpdateExclusive lock. + * + * Note that neither VACUUM FULL (and CLUSTER), nor ANALYZE go through + * this routine, even if (for ANALYZE) it is part of the same VACUUM + * command. + * + * There probably, in the future, needs to be a separate callback to + * integrate with autovacuum's scheduling. + */ +void vertex_relation_vacuum(Relation rel, struct VacuumParams *params, + BufferAccessStrategy bstrategy) { + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + +/* + * Prepare to analyze block `blockno` of `scan`. The scan has been started + * with table_beginscan_analyze(). See also + * table_scan_analyze_next_block(). + * + * The callback may acquire resources like locks that are held until + * table_scan_analyze_next_tuple() returns false. It e.g. can make sense + * to hold a lock until all tuples on a block have been analyzed by + * scan_analyze_next_tuple. + * + * The callback can return false if the block is not suitable for + * sampling, e.g. because it's a metapage that could never contain tuples. + * + * XXX: This obviously is primarily suited for block-based AMs. It's not + * clear what a good interface for non block based AMs would be, so there + * isn't one yet. + */ +bool vertex_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, + BufferAccessStrategy bstrategy) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + + return false; +} + +/* + * See table_scan_analyze_next_tuple(). + * + * Not every AM might have a meaningful concept of dead rows, in which + * case it's OK to not increment *deadrows - but note that that may + * influence autovacuum scheduling (see comment for relation_vacuum + * callback). + */ +bool vertex_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, + double *liverows, double *deadrows, TupleTableSlot *slot) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + + return false; +} + +/* see table_index_build_range_scan for reference about parameters */ +double vertex_index_build_range_scan(Relation table_rel, Relation index_rel, + struct IndexInfo *index_info, bool allow_sync, + bool anyvisible, bool progress, BlockNumber start_blockno, + BlockNumber numblocks, IndexBuildCallback callback, + void *callback_state, TableScanDesc scan) { + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + + return 0; +} + +/* see table_index_validate_scan for reference about parameters */ +void vertex_index_validate_scan(Relation table_rel, Relation index_rel, + struct IndexInfo *index_info, Snapshot snapshot, + struct ValidateIndexState *state) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + + +/* ------------------------------------------------------------------------ + * Miscellaneous functions. + * ------------------------------------------------------------------------ + */ + +/* + * See table_relation_size(). + * + * Note that currently a few callers use the MAIN_FORKNUM size to figure + * out the range of potentially interesting blocks (brin, analyze). It's + * probable that we'll need to revise the interface for those at some + * point. + */ +uint64 vertex_relation_size(Relation rel, ForkNumber forkNumber) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + + return 0; +} + + +/* + * This callback should return true if the relation requires a TOAST table + * and false if it does not. It may wish to examine the relation's tuple + * descriptor before making a decision, but if it uses some other method + * of storing large values (or if it does not support them) it can simply + * return false. + */ +bool vertex_relation_needs_toast_table(Relation rel) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + + return false; +} + +/* + * This callback should return the OID of the table AM that implements + * TOAST tables for this AM. If the relation_needs_toast_table callback + * always returns false, this callback is not required. + */ +Oid vertex_relation_toast_am(Relation rel) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + + return InvalidOid; +} + +/* + * This callback is invoked when detoasting a value stored in a toast + * table implemented by this AM. See table_relation_fetch_toast_slice() + * for more details. + */ +void vertex_relation_fetch_toast_slice(Relation toastrel, Oid valueid, + int32 attrsize, int32 sliceoffset, + int32 slicelength, struct varlena *result) { + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + + +} + + +/* ------------------------------------------------------------------------ + * Planner related functions. + * ------------------------------------------------------------------------ + */ + +/* + * See table_relation_estimate_size(). + * + * While block oriented, it shouldn't be too hard for an AM that doesn't + * internally use blocks to convert into a usable representation. + * + * This differs from the relation_size callback by returning size + * estimates (both relation size and tuple count) for planning purposes, + * rather than returning a currently correct estimate. + */ +void vertex_relation_estimate_size(Relation rel, int32 *attr_widths, + BlockNumber *pages, double *tuples, + double *allvisfrac) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); +} + + + +/* ------------------------------------------------------------------------ + * Executor related functions. + * ------------------------------------------------------------------------ + */ + +/* + * Prepare to fetch / check / return tuples from `tbmres->blockno` as part + * of a bitmap table scan. `scan` was started via table_beginscan_bm(). + * Return false if there are no tuples to be found on the page, true + * otherwise. + * + * This will typically read and pin the target block, and do the necessary + * work to allow scan_bitmap_next_tuple() to return tuples (e.g. it might + * make sense to perform tuple visibility checks at this time). For some + * AMs it will make more sense to do all the work referencing `tbmres` + * contents here, for others it might be better to defer more work to + * scan_bitmap_next_tuple. + * + * If `tbmres->blockno` is -1, this is a lossy scan and all visible tuples + * on the page have to be returned, otherwise the tuples at offsets in + * `tbmres->offsets` need to be returned. + * + * XXX: Currently this may only be implemented if the AM uses md.c as its + * storage manager, and uses ItemPointer->ip_blkid in a manner that maps + * blockids directly to the underlying storage. nodeBitmapHeapscan.c + * performs prefetching directly using that interface. This probably + * needs to be rectified at a later point. + * + * XXX: Currently this may only be implemented if the AM uses the + * visibilitymap, as nodeBitmapHeapscan.c unconditionally accesses it to + * perform prefetching. This probably needs to be rectified at a later + * point. + * + * Optional callback, but either both scan_bitmap_next_block and + * scan_bitmap_next_tuple need to exist, or neither. + */ +bool vertex_scan_bitmap_next_block(TableScanDesc scan, struct TBMIterateResult *tbmres) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + return false; +} + +/* + * Fetch the next tuple of a bitmap table scan into `slot` and return true + * if a visible tuple was found, false otherwise. + * + * For some AMs it will make more sense to do all the work referencing + * `tbmres` contents in scan_bitmap_next_block, for others it might be + * better to defer more work to this callback. + * + * Optional callback, but either both scan_bitmap_next_block and + * scan_bitmap_next_tuple need to exist, or neither. + */ +bool vertex_scan_bitmap_next_tuple(TableScanDesc scan, struct TBMIterateResult *tbmres, + TupleTableSlot *slot) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + return false; +} + +/* + * Prepare to fetch tuples from the next block in a sample scan. Return + * false if the sample scan is finished, true otherwise. `scan` was + * started via table_beginscan_sampling(). + * + * Typically this will first determine the target block by calling the + * TsmRoutine's NextSampleBlock() callback if not NULL, or alternatively + * perform a sequential scan over all blocks. The determined block is + * then typically read and pinned. + * + * As the TsmRoutine interface is block based, a block needs to be passed + * to NextSampleBlock(). If that's not appropriate for an AM, it + * internally needs to perform mapping between the internal and a block + * based representation. + * + * Note that it's not acceptable to hold deadlock prone resources such as + * lwlocks until scan_sample_next_tuple() has exhausted the tuples on the + * block - the tuple is likely to be returned to an upper query node, and + * the next call could be off a long while. Holding buffer pins and such + * is obviously OK. + * + * Currently it is required to implement this interface, as there's no + * alternative way (contrary e.g. to bitmap scans) to implement sample + * scans. If infeasible to implement, the AM may raise an error. + */ +bool vertex_scan_sample_next_block(TableScanDesc scan, struct SampleScanState *scanstate) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + + return false; +} + +/* + * This callback, only called after scan_sample_next_block has returned + * true, should determine the next tuple to be returned from the selected + * block using the TsmRoutine's NextSampleTuple() callback. + * + * The callback needs to perform visibility checks, and only return + * visible tuples. That obviously can mean calling NextSampleTuple() + * multiple times. + * + * The TsmRoutine interface assumes that there's a maximum offset on a + * given page, so if that doesn't apply to an AM, it needs to emulate that + * assumption somehow. + */ +bool vertex_scan_sample_next_tuple(TableScanDesc scan, struct SampleScanState *scanstate, + TupleTableSlot *slot) { + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + +return false; +} /* ------------------------------------------------------------------------ * Definition of the heap table access method. @@ -65,75 +741,75 @@ vertex_heapam_slot_callbacks(Relation relation) */ static const TableAmRoutine vertex_heapam_methods = { - .type = T_TableAmRoutine, - - .slot_callbacks = vertex_heapam_slot_callbacks, - - .scan_begin = NULL, - .scan_end = NULL, - .scan_rescan = NULL, - .scan_getnextslot = NULL, - - .scan_set_tidrange = NULL, - .scan_getnextslot_tidrange = NULL, - - .parallelscan_estimate = NULL, - .parallelscan_initialize = NULL, - .parallelscan_reinitialize = NULL, - - .index_fetch_begin = NULL, - .index_fetch_reset = NULL, - .index_fetch_end = NULL, - .index_fetch_tuple = NULL, - - .tuple_insert = NULL, - .tuple_insert_speculative = NULL, - .tuple_complete_speculative = NULL, - .multi_insert = NULL, - .tuple_delete = NULL, - .tuple_update = NULL, - .tuple_lock = NULL, - - .tuple_fetch_row_version = NULL, - .tuple_get_latest_tid = NULL, - .tuple_tid_valid = NULL, - .tuple_satisfies_snapshot = NULL, - .index_delete_tuples = NULL, - - .relation_set_new_filenode = NULL, - .relation_nontransactional_truncate = NULL, - .relation_copy_data = NULL, - .relation_copy_for_cluster = NULL, - .relation_vacuum = NULL, - .scan_analyze_next_block = NULL, - .scan_analyze_next_tuple = NULL, - .index_build_range_scan = NULL, - .index_validate_scan = NULL, - - .relation_size = NULL, - .relation_needs_toast_table = NULL, - .relation_toast_am = NULL, - .relation_fetch_toast_slice = NULL, - - .relation_estimate_size = NULL, - - .scan_bitmap_next_block = NULL, - .scan_bitmap_next_tuple = NULL, - .scan_sample_next_block = NULL, - .scan_sample_next_tuple = NULL + .type = T_TableAmRoutine, + + .slot_callbacks = vertex_heapam_slot_callbacks, + + .scan_begin = vertex_scan_begin, + .scan_end = vertex_scan_end, + .scan_rescan = vertex_scan_rescan, + .scan_getnextslot = vertex_scan_getnextslot, + + .scan_set_tidrange = NULL, + .scan_getnextslot_tidrange = NULL, + + .parallelscan_estimate = vertex_parallelscan_estimate, + .parallelscan_initialize = vertex_parallelscan_initialize, + .parallelscan_reinitialize = vertex_parallelscan_reinitialize, + + .index_fetch_begin = vertex_index_fetch_begin, + .index_fetch_reset = vertex_index_fetch_reset, + .index_fetch_end = vertex_index_fetch_end, + .index_fetch_tuple = vertex_index_fetch_tuple, + + .tuple_insert = vertex_tuple_insert, + .tuple_insert_speculative = vertex_tuple_insert_speculative, + .tuple_complete_speculative = vertex_tuple_complete_speculative, + .multi_insert = vertex_multi_insert, + .tuple_delete = vertex_tuple_delete, + .tuple_update = vertex_tuple_update, + .tuple_lock = vertex_tuple_lock, + + .tuple_fetch_row_version = vertex_tuple_fetch_row_version, + .tuple_get_latest_tid = vertex_tuple_get_latest_tid, + .tuple_tid_valid = vertex_tuple_tid_valid, + .tuple_satisfies_snapshot = vertex_tuple_satisfies_snapshot, + .index_delete_tuples = vertex_index_delete_tuples, + + .relation_set_new_filenode = vertex_relation_set_new_filenode, + .relation_nontransactional_truncate = vertex_relation_nontransactional_truncate, + .relation_copy_data = vertex_relation_copy_data, + .relation_copy_for_cluster = vertex_relation_nontransactional_truncate, + .relation_vacuum = vertex_relation_vacuum, + .scan_analyze_next_block = vertex_scan_analyze_next_block, + .scan_analyze_next_tuple = vertex_scan_analyze_next_tuple, + .index_build_range_scan = vertex_index_validate_scan, + .index_validate_scan = vertex_index_validate_scan, + + .relation_size = vertex_relation_size, + .relation_needs_toast_table = vertex_relation_needs_toast_table, + .relation_toast_am = NULL, + .relation_fetch_toast_slice = NULL, + + .relation_estimate_size = vertex_relation_estimate_size, + + .scan_bitmap_next_block = vertex_scan_bitmap_next_block, + .scan_bitmap_next_tuple = vertex_scan_bitmap_next_tuple, + .scan_sample_next_block = vertex_scan_sample_next_block, + .scan_sample_next_tuple = vertex_scan_sample_next_tuple }; const TableAmRoutine * GetVertexHeapamTableAmRoutine(void) { - return &vertex_heapam_methods; + return &vertex_heapam_methods; } PG_FUNCTION_INFO_V1(vertex_tableam_handler); Datum vertex_tableam_handler(PG_FUNCTION_ARGS) { - PG_RETURN_POINTER(&vertex_heapam_methods); + PG_RETURN_POINTER(&vertex_heapam_methods); } From c6d48e58bf4aea0b37b77ed36aa53c5b8f734148 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 31 May 2025 09:41:55 -0700 Subject: [PATCH 04/56] Implement first version of vertex_relation_set_new_filenode Add the first version as this is the first version in the table am api that will get called when creating a table, for now just use the heap code where we force the persistence to be permanent and for it to be a table --- .../vertex_heap/vertex_heapam_handler.c | 75 ++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 50823aa2..eb5d20d2 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -170,7 +170,7 @@ void vertex_index_fetch_reset(struct IndexFetchTableData *data) { * Release resources and deallocate index fetch. */ void vertex_index_fetch_end(struct IndexFetchTableData *data) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); @@ -204,7 +204,7 @@ bool vertex_index_fetch_tuple(struct IndexFetchTableData *scan, errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return false; + return false; } @@ -224,7 +224,7 @@ bool vertex_tuple_fetch_row_version(Relation rel, ItemPointer tid, errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return false; + return false; } /* @@ -235,7 +235,7 @@ bool vertex_tuple_tid_valid(TableScanDesc scan, ItemPointer tid) { errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return false; + return false; } /* @@ -255,7 +255,7 @@ void vertex_tuple_get_latest_tid(TableScanDesc scan, ItemPointer tid) { */ bool vertex_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, Snapshot snapshot) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); return false; @@ -267,7 +267,7 @@ TransactionId vertex_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstat errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return NULL; + return NULL; } /* ------------------------------------------------------------------------ @@ -327,7 +327,7 @@ TM_Result vertex_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, - return 0; + return 0; } /* see table_tuple_update() for reference about parameters */ @@ -336,7 +336,7 @@ TM_Result vertex_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *sl bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, bool *update_indexes) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); @@ -352,7 +352,7 @@ TM_Result vertex_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, - return 0; + return 0; } @@ -381,8 +381,33 @@ TM_Result vertex_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, void vertex_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, char persistence, TransactionId *freezeXid, MultiXactId *minmulti) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_relation_set_new_filenode not implemented"))); + + SMgrRelation srel; + + if(persistence != RELPERSISTENCE_PERMANENT || rel->rd_rel->relkind != RELKIND_RELATION) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex am can only work on permenant tables"))); + + /* + * Initialize to the minimum XID that could put tuples in the table. We + * know that no xacts older than RecentXmin are still running, so that + * will do. + */ + *freezeXid = RecentXmin; + + /* + * Similarly, initialize the minimum Multixact to the first value that + * could possibly be stored in tuples in the table. Running transactions + * could reuse values from their local cache, so we are careful to + * consider all currently running multis. + * + * XXX this could be refined further, but is it worth the hassle? + */ + *minmulti = GetOldestMultiXactId(); + + srel = RelationCreateStorage(*newrnode, persistence); + + smgrclose(srel); } /* @@ -475,9 +500,7 @@ bool vertex_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - return false; + return false; } /* @@ -495,7 +518,7 @@ bool vertex_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin - return false; + return false; } /* see table_index_build_range_scan for reference about parameters */ @@ -510,7 +533,7 @@ double vertex_index_build_range_scan(Relation table_rel, Relation index_rel, - return 0; + return 0; } /* see table_index_validate_scan for reference about parameters */ @@ -544,7 +567,7 @@ uint64 vertex_relation_size(Relation rel, ForkNumber forkNumber) { - return 0; + return 0; } @@ -561,7 +584,7 @@ bool vertex_relation_needs_toast_table(Relation rel) { - return false; + return false; } /* @@ -575,7 +598,7 @@ Oid vertex_relation_toast_am(Relation rel) { - return InvalidOid; + return InvalidOid; } /* @@ -659,7 +682,7 @@ bool vertex_scan_bitmap_next_block(TableScanDesc scan, struct TBMIterateResult * ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return false; + return false; } /* @@ -674,11 +697,11 @@ bool vertex_scan_bitmap_next_block(TableScanDesc scan, struct TBMIterateResult * * scan_bitmap_next_tuple need to exist, or neither. */ bool vertex_scan_bitmap_next_tuple(TableScanDesc scan, struct TBMIterateResult *tbmres, - TupleTableSlot *slot) { + TupleTableSlot *slot) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return false; + return false; } /* @@ -710,7 +733,7 @@ bool vertex_scan_sample_next_block(TableScanDesc scan, struct SampleScanState *s ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return false; + return false; } /* @@ -727,12 +750,12 @@ bool vertex_scan_sample_next_block(TableScanDesc scan, struct SampleScanState *s * assumption somehow. */ bool vertex_scan_sample_next_tuple(TableScanDesc scan, struct SampleScanState *scanstate, - TupleTableSlot *slot) { - + TupleTableSlot *slot) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); -return false; +return false; } /* ------------------------------------------------------------------------ From 6dbd744703c67447891f6feda8e0d832a9b47093 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 31 May 2025 11:39:48 -0700 Subject: [PATCH 05/56] Add the initial logic for scans/SELECT Add the logic for heaps to scan tables. This will need to be massively rewritten, but I need this here to work on the integration with the rest of the project --- regress/sql/vertex_am.sql | 9 +- .../vertex_heap/vertex_heapam_handler.c | 1073 ++++++++++++++++- 2 files changed, 1037 insertions(+), 45 deletions(-) diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index 77903347..48264d2a 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -20,4 +20,11 @@ LOAD 'postgraph'; -CREATE TABLE tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex; +CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex; + +INSERT INTO vertex_am_tst (id, props) +VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); + + +SELECT * FROM vertex_am_tst; + diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index eb5d20d2..26faad5b 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -29,6 +29,7 @@ #include "access/tableam.h" #include "access/tsmapi.h" #include "access/xact.h" +#include "access/valid.h" #include "catalog/catalog.h" #include "catalog/index.h" #include "catalog/storage.h" @@ -46,6 +47,780 @@ #include "utils/builtins.h" #include "utils/rel.h" +#define HEAP_OVERHEAD_BYTES_PER_TUPLE \ + (MAXALIGN(SizeofHeapTupleHeader) + sizeof(ItemIdData)) +#define HEAP_USABLE_BYTES_PER_PAGE \ + (BLCKSZ - SizeOfPageHeaderData) + +/* ---------------------------------------------------------------- + * heap support routines + * ---------------------------------------------------------------- + */ + +/* ---------------- + * initscan - scan code common to heap_beginscan and heap_rescan + * ---------------- + */ +static void +initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock) +{ + ParallelBlockTableScanDesc bpscan = NULL; + bool allow_strat; + bool allow_sync; + + /* + * Determine the number of blocks we have to scan. + * + * It is sufficient to do this once at scan start, since any tuples added + * while the scan is in progress will be invisible to my snapshot anyway. + * (That is not true when using a non-MVCC snapshot. However, we couldn't + * guarantee to return tuples added after scan start anyway, since they + * might go into pages we already scanned. To guarantee consistent + * results for a non-MVCC snapshot, the caller must hold some higher-level + * lock that ensures the interesting tuple(s) won't change.) + */ + if (scan->rs_base.rs_parallel != NULL) + { + bpscan = (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; + scan->rs_nblocks = bpscan->phs_nblocks; + } + else + scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_base.rs_rd); + + /* + * If the table is large relative to NBuffers, use a bulk-read access + * strategy and enable synchronized scanning (see syncscan.c). Although + * the thresholds for these features could be different, we make them the + * same so that there are only two behaviors to tune rather than four. + * (However, some callers need to be able to disable one or both of these + * behaviors, independently of the size of the table; also there is a GUC + * variable that can disable synchronized scanning.) + * + * Note that table_block_parallelscan_initialize has a very similar test; + * if you change this, consider changing that one, too. + */ + if (!RelationUsesLocalBuffers(scan->rs_base.rs_rd) && scan->rs_nblocks > NBuffers / 4) { + allow_strat = (scan->rs_base.rs_flags & SO_ALLOW_STRAT) != 0; + allow_sync = (scan->rs_base.rs_flags & SO_ALLOW_SYNC) != 0; + } + else + allow_strat = allow_sync = false; + + if (allow_strat) + { + /* During a rescan, keep the previous strategy object. */ + if (scan->rs_strategy == NULL) + scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD); + } + else + { + if (scan->rs_strategy != NULL) + FreeAccessStrategy(scan->rs_strategy); + scan->rs_strategy = NULL; + } + + if (scan->rs_base.rs_parallel != NULL) + { + /* For parallel scan, believe whatever ParallelTableScanDesc says. */ + if (scan->rs_base.rs_parallel->phs_syncscan) + scan->rs_base.rs_flags |= SO_ALLOW_SYNC; + else + scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; + } + else if (keep_startblock) + { + /* + * When rescanning, we want to keep the previous startblock setting, + * so that rewinding a cursor doesn't generate surprising results. + * Reset the active syncscan setting, though. + */ + if (allow_sync && synchronize_seqscans) + scan->rs_base.rs_flags |= SO_ALLOW_SYNC; + else + scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; + } + else if (allow_sync && synchronize_seqscans) + { + scan->rs_base.rs_flags |= SO_ALLOW_SYNC; + scan->rs_startblock = ss_get_location(scan->rs_base.rs_rd, scan->rs_nblocks); + } + else + { + scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; + scan->rs_startblock = 0; + } + + scan->rs_numblocks = InvalidBlockNumber; + scan->rs_inited = false; + scan->rs_ctup.t_data = NULL; + ItemPointerSetInvalid(&scan->rs_ctup.t_self); + scan->rs_cbuf = InvalidBuffer; + scan->rs_cblock = InvalidBlockNumber; + + /* page-at-a-time fields are always invalid when not rs_inited */ + + /* + * copy the scan key, if appropriate + */ + if (key != NULL && scan->rs_base.rs_nkeys > 0) + memcpy(scan->rs_base.rs_key, key, scan->rs_base.rs_nkeys * sizeof(ScanKeyData)); + + /* + * Currently, we only have a stats counter for sequential heap scans (but + * e.g for bitmap scans the underlying bitmap index scans will be counted, + * and for sample scans we update stats for tuple fetches). + */ + if (scan->rs_base.rs_flags & SO_TYPE_SEQSCAN) + pgstat_count_heap_scan(scan->rs_base.rs_rd); +} + + + +/* ---------------- + * heapgettup - fetch next heap tuple + * + * Initialize the scan if not already done; then advance to the next + * tuple as indicated by "dir"; return the next tuple in scan->rs_ctup, + * or set scan->rs_ctup.t_data = NULL if no more tuples. + * + * dir == NoMovementScanDirection means "re-fetch the tuple indicated + * by scan->rs_ctup". + * + * Note: the reason nkeys/key are passed separately, even though they are + * kept in the scan descriptor, is that the caller may not want us to check + * the scankeys. + * + * Note: when we fall off the end of the scan in either direction, we + * reset rs_inited. This means that a further request with the same + * scan direction will restart the scan, which is a bit odd, but a + * request with the opposite scan direction will start a fresh scan + * in the proper direction. The latter is required behavior for cursors, + * while the former case is generally undefined behavior in Postgres + * so we don't care too much. + * ---------------- + */ +static void +heapgettup(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key) +{ + HeapTuple tuple = &(scan->rs_ctup); + Snapshot snapshot = scan->rs_base.rs_snapshot; + bool backward = ScanDirectionIsBackward(dir); + BlockNumber page; + bool finished; + Page dp; + int lines; + OffsetNumber lineoff; + int linesleft; + ItemId lpp; + + /* + * calculate next starting lineoff, given scan direction + */ + if (ScanDirectionIsForward(dir)) + { + if (!scan->rs_inited) + { + /* + * return null immediately if relation is empty + */ + if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + if (scan->rs_base.rs_parallel != NULL) + { + ParallelBlockTableScanDesc pbscan = + (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; + ParallelBlockTableScanWorker pbscanwork = + scan->rs_parallelworkerdata; + table_block_parallelscan_startblock_init(scan->rs_base.rs_rd, pbscanwork, pbscan); + + page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, pbscanwork, pbscan); + + /* Other processes might have already finished the scan. */ + if (page == InvalidBlockNumber) { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + } + else + page = scan->rs_startblock; /* first page */ + + heapgetpage((TableScanDesc) scan, page); + + lineoff = FirstOffsetNumber; /* first offnum */ + scan->rs_inited = true; + } + else + { + /* continue from previously returned page/tuple */ + page = scan->rs_cblock; /* current page */ + lineoff = /* next offnum */ + OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self))); + } + + LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(snapshot, scan->rs_base.rs_rd, dp); + lines = PageGetMaxOffsetNumber(dp); + /* page and lineoff now reference the physically next tid */ + + linesleft = lines - lineoff + 1; + } + else if (backward) + { + /* backward parallel scan not supported */ + Assert(scan->rs_base.rs_parallel == NULL); + + if (!scan->rs_inited) + { + /* + * return null immediately if relation is empty + */ + if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + + /* + * Disable reporting to syncscan logic in a backwards scan; it's + * not very likely anyone else is doing the same thing at the same + * time, and much more likely that we'll just bollix things for + * forward scanners. + */ + scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; + + /* + * Start from last page of the scan. Ensure we take into account + * rs_numblocks if it's been adjusted by heap_setscanlimits(). + */ + if (scan->rs_numblocks != InvalidBlockNumber) + page = (scan->rs_startblock + scan->rs_numblocks - 1) % scan->rs_nblocks; + else if (scan->rs_startblock > 0) + page = scan->rs_startblock - 1; + else + page = scan->rs_nblocks - 1; + heapgetpage((TableScanDesc) scan, page); + } + else + { + /* continue from previously returned page/tuple */ + page = scan->rs_cblock; /* current page */ + } + + LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(snapshot, scan->rs_base.rs_rd, dp); + lines = PageGetMaxOffsetNumber(dp); + + if (!scan->rs_inited) + { + lineoff = lines; /* final offnum */ + scan->rs_inited = true; + } + else + { + /* + * The previous returned tuple may have been vacuumed since the + * previous scan when we use a non-MVCC snapshot, so we must + * re-establish the lineoff <= PageGetMaxOffsetNumber(dp) + * invariant + */ + lineoff = /* previous offnum */ + Min(lines, + OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)))); + } + /* page and lineoff now reference the physically previous tid */ + + linesleft = lineoff; + } + else + { + /* + * ``no movement'' scan direction: refetch prior tuple + */ + if (!scan->rs_inited) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + + page = ItemPointerGetBlockNumber(&(tuple->t_self)); + if (page != scan->rs_cblock) + heapgetpage((TableScanDesc) scan, page); + + /* Since the tuple was previously fetched, needn't lock page here */ + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(snapshot, scan->rs_base.rs_rd, dp); + lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self)); + lpp = PageGetItemId(dp, lineoff); + Assert(ItemIdIsNormal(lpp)); + + tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); + tuple->t_len = ItemIdGetLength(lpp); + + return; + } + + /* + * advance the scan until we find a qualifying tuple or run out of stuff + * to scan + */ + lpp = PageGetItemId(dp, lineoff); + for (;;) + { + /* + * Only continue scanning the page while we have lines left. + * + * Note that this protects us from accessing line pointers past + * PageGetMaxOffsetNumber(); both for forward scans when we resume the + * table scan, and for when we start scanning a new page. + */ + while (linesleft > 0) + { + if (ItemIdIsNormal(lpp)) + { + bool valid; + + tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); + tuple->t_len = ItemIdGetLength(lpp); + ItemPointerSet(&(tuple->t_self), page, lineoff); + + /* + * if current tuple qualifies, return it. + */ + valid = HeapTupleSatisfiesVisibility(tuple, + snapshot, + scan->rs_cbuf); + + HeapCheckForSerializableConflictOut(valid, scan->rs_base.rs_rd, + tuple, scan->rs_cbuf, + snapshot); + + if (valid && key != NULL) + HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd), + nkeys, key, valid); + + if (valid) + { + LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); + return; + } + } + + /* + * otherwise move to the next item on the page + */ + --linesleft; + if (backward) + { + --lpp; /* move back in this page's ItemId array */ + --lineoff; + } + else + { + ++lpp; /* move forward in this page's ItemId array */ + ++lineoff; + } + } + + /* + * if we get here, it means we've exhausted the items on this page and + * it's time to move to the next. + */ + LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); + + /* + * advance to next/prior page and detect end of scan + */ + if (backward) + { + finished = (page == scan->rs_startblock) || + (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); + if (page == 0) + page = scan->rs_nblocks; + page--; + } + else if (scan->rs_base.rs_parallel != NULL) + { + ParallelBlockTableScanDesc pbscan = + (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; + ParallelBlockTableScanWorker pbscanwork = + scan->rs_parallelworkerdata; + + page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, + pbscanwork, pbscan); + finished = (page == InvalidBlockNumber); + } + else + { + page++; + if (page >= scan->rs_nblocks) + page = 0; + finished = (page == scan->rs_startblock) || + (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); + + /* + * Report our new scan position for synchronization purposes. We + * don't do that when moving backwards, however. That would just + * mess up any other forward-moving scanners. + * + * Note: we do this before checking for end of scan so that the + * final state of the position hint is back at the start of the + * rel. That's not strictly necessary, but otherwise when you run + * the same query multiple times the starting position would shift + * a little bit backwards on every invocation, which is confusing. + * We don't guarantee any specific ordering in general, though. + */ + if (scan->rs_base.rs_flags & SO_ALLOW_SYNC) + ss_report_location(scan->rs_base.rs_rd, page); + } + + /* + * return NULL if we've exhausted all the pages + */ + if (finished) + { + if (BufferIsValid(scan->rs_cbuf)) + ReleaseBuffer(scan->rs_cbuf); + scan->rs_cbuf = InvalidBuffer; + scan->rs_cblock = InvalidBlockNumber; + tuple->t_data = NULL; + scan->rs_inited = false; + return; + } + + heapgetpage((TableScanDesc) scan, page); + + LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(snapshot, scan->rs_base.rs_rd, dp); + lines = PageGetMaxOffsetNumber((Page) dp); + linesleft = lines; + if (backward) + { + lineoff = lines; + lpp = PageGetItemId(dp, lines); + } + else + { + lineoff = FirstOffsetNumber; + lpp = PageGetItemId(dp, FirstOffsetNumber); + } + } +} + +/* ---------------- + * * heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode + * * + * * Same API as heapgettup, but used in page-at-a-time mode + * * + * * The internal logic is much the same as heapgettup's too, but there are some + * * differences: we do not take the buffer content lock (that only needs to + * * happen inside heapgetpage), and we iterate through just the tuples listed + * * in rs_vistuples[] rather than all tuples on the page. Notice that + * * lineindex is 0-based, where the corresponding loop variable lineoff in + * * heapgettup is 1-based. + * * ---------------- + * */ +static void +heapgettup_pagemode(HeapScanDesc scan, + ScanDirection dir, + int nkeys, + ScanKey key) +{ + HeapTuple tuple = &(scan->rs_ctup); + bool backward = ScanDirectionIsBackward(dir); + BlockNumber page; + bool finished; + Page dp; + int lines; + int lineindex; + OffsetNumber lineoff; + int linesleft; + ItemId lpp; + + /* + * * calculate next starting lineindex, given scan direction + * */ + if (ScanDirectionIsForward(dir)) + { + if (!scan->rs_inited) + { + /* + * * return null immediately if relation is empty + * */ + if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + if (scan->rs_base.rs_parallel != NULL) + { + ParallelBlockTableScanDesc pbscan = + (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; + ParallelBlockTableScanWorker pbscanwork = + scan->rs_parallelworkerdata; + + table_block_parallelscan_startblock_init(scan->rs_base.rs_rd, + pbscanwork, pbscan); + + page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, + pbscanwork, pbscan); + + /* Other processes might have already finished the scan. */ + if (page == InvalidBlockNumber) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + } + else + page = scan->rs_startblock; /* first page */ + heapgetpage((TableScanDesc) scan, page); + lineindex = 0; + scan->rs_inited = true; + } + else + { + /* continue from previously returned page/tuple */ + page = scan->rs_cblock; /* current page */ + lineindex = scan->rs_cindex + 1; + } + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); + lines = scan->rs_ntuples; + /* page and lineindex now reference the next visible tid */ + + linesleft = lines - lineindex; + } + else if (backward) + { + /* backward parallel scan not supported */ + Assert(scan->rs_base.rs_parallel == NULL); + + if (!scan->rs_inited) + { + /* + * * return null immediately if relation is empty + * */ + if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + + /* + * * Disable reporting to syncscan logic in a backwards scan; it's + * * not very likely anyone else is doing the same thing at the same + * * time, and much more likely that we'll just bollix things for + * * forward scanners. + * */ + scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; + + /* + * * Start from last page of the scan. Ensure we take into account + * * rs_numblocks if it's been adjusted by heap_setscanlimits(). + * */ + if (scan->rs_numblocks != InvalidBlockNumber) + page = (scan->rs_startblock + scan->rs_numblocks - 1) % scan->rs_nblocks; + else if (scan->rs_startblock > 0) + page = scan->rs_startblock - 1; + else + page = scan->rs_nblocks - 1; + heapgetpage((TableScanDesc) scan, page); + } + else + { + /* continue from previously returned page/tuple */ + page = scan->rs_cblock; /* current page */ + } + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); + lines = scan->rs_ntuples; + + if (!scan->rs_inited) + { + lineindex = lines - 1; + scan->rs_inited = true; + } + else + { + lineindex = scan->rs_cindex - 1; + } + /* page and lineindex now reference the previous visible tid */ + + linesleft = lineindex + 1; + } + else + { + /* + * * ``no movement'' scan direction: refetch prior tuple + * */ + if (!scan->rs_inited) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + + page = ItemPointerGetBlockNumber(&(tuple->t_self)); + if (page != scan->rs_cblock) + heapgetpage((TableScanDesc) scan, page); + + /* Since the tuple was previously fetched, needn't lock page here */ + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); + lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self)); + lpp = PageGetItemId(dp, lineoff); + Assert(ItemIdIsNormal(lpp)); + + tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); + tuple->t_len = ItemIdGetLength(lpp); + + /* check that rs_cindex is in sync */ + Assert(scan->rs_cindex < scan->rs_ntuples); + Assert(lineoff == scan->rs_vistuples[scan->rs_cindex]); + + return; + } + + /* + * * advance the scan until we find a qualifying tuple or run out of stuff + * * to scan + * */ + for (;;) + { + while (linesleft > 0) + { + lineoff = scan->rs_vistuples[lineindex]; + lpp = PageGetItemId(dp, lineoff); + Assert(ItemIdIsNormal(lpp)); + + tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); + tuple->t_len = ItemIdGetLength(lpp); + ItemPointerSet(&(tuple->t_self), page, lineoff); + + /* + * * if current tuple qualifies, return it. + * */ + if (key != NULL) + { + bool valid; + + HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd), + nkeys, key, valid); + if (valid) + { + scan->rs_cindex = lineindex; + return; + } + } + else + { + scan->rs_cindex = lineindex; + return; + } + + /* + * * otherwise move to the next item on the page + * */ + --linesleft; + if (backward) + --lineindex; + else + ++lineindex; + } + + /* + * * if we get here, it means we've exhausted the items on this page and + * * it's time to move to the next. + * */ + if (backward) + { + finished = (page == scan->rs_startblock) || + (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); + if (page == 0) + page = scan->rs_nblocks; + page--; + } + else if (scan->rs_base.rs_parallel != NULL) + { + ParallelBlockTableScanDesc pbscan = + (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; + ParallelBlockTableScanWorker pbscanwork = + scan->rs_parallelworkerdata; + + page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, + pbscanwork, pbscan); + finished = (page == InvalidBlockNumber); + } + else + { + page++; + if (page >= scan->rs_nblocks) + page = 0; + finished = (page == scan->rs_startblock) || + (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); + + /* + * * Report our new scan position for synchronization purposes. We + * * don't do that when moving backwards, however. That would just + * * mess up any other forward-moving scanners. + * * + * * Note: we do this before checking for end of scan so that the + * * final state of the position hint is back at the start of the + * * rel. That's not strictly necessary, but otherwise when you run + * * the same query multiple times the starting position would shift + * * a little bit backwards on every invocation, which is confusing. + * * We don't guarantee any specific ordering in general, though. + * */ + if (scan->rs_base.rs_flags & SO_ALLOW_SYNC) + ss_report_location(scan->rs_base.rs_rd, page); + } + + /* + * * return NULL if we've exhausted all the pages + * */ + if (finished) + { + if (BufferIsValid(scan->rs_cbuf)) + ReleaseBuffer(scan->rs_cbuf); + scan->rs_cbuf = InvalidBuffer; + scan->rs_cblock = InvalidBlockNumber; + tuple->t_data = NULL; + scan->rs_inited = false; + return; + } + + heapgetpage((TableScanDesc) scan, page); + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); + lines = scan->rs_ntuples; + linesleft = lines; + if (backward) + lineindex = lines - 1; + else + lineindex = 0; + } +} + + /* ------------------------------------------------------------------------ * Slot related callbacks for vertex heap AM @@ -62,17 +837,118 @@ vertex_heapam_slot_callbacks(Relation relation) * Functions related to scaning * ------------------------------------------------------------------------ */ -TableScanDesc vertex_scan_begin(Relation rel, Snapshot snapshot, int nkeys, +TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, struct ScanKeyData *key, - ParallelTableScanDesc pscan, uint32 flags) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_scan_begin not implemented"))); - return NULL; + ParallelTableScanDesc parallel_scan, uint32 flags) { + HeapScanDesc scan; + + /* + * increment relation ref count while scanning relation + * + * This is just to make really sure the relcache entry won't go away while + * the scan has a pointer to it. Caller should be holding the rel open + * anyway, so this is redundant in all normal scenarios... + */ + RelationIncrementReferenceCount(relation); + + /* + * allocate and initialize scan descriptor + */ + scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData)); + + scan->rs_base.rs_rd = relation; + scan->rs_base.rs_snapshot = snapshot; + scan->rs_base.rs_nkeys = nkeys; + scan->rs_base.rs_flags = flags; + scan->rs_base.rs_parallel = parallel_scan; + scan->rs_strategy = NULL; /* set in initscan */ + + /* + * Disable page-at-a-time mode if it's not a MVCC-safe snapshot. + * TODO: Research Non MVCC Snapshots and Page-at-a-time mode + */ + if (!(snapshot && IsMVCCSnapshot(snapshot))) + scan->rs_base.rs_flags &= ~SO_ALLOW_PAGEMODE; + + /* + * For seqscan and sample scans in a serializable transaction, acquire a + * predicate lock on the entire relation. This is required not only to + * lock all the matching tuples, but also to conflict with new insertions + * into the table. In an indexscan, we take page locks on the index pages + * covering the range specified in the scan qual, but in a heap scan there + * is nothing more fine-grained to lock. A bitmap scan is a different + * story, there we have already scanned the index and locked the index + * pages covering the predicate. But in that case we still have to lock + * any matching heap tuples. For sample scan we could optimize the locking + * to be at least page-level granularity, but we'd need to add per-tuple + * locking for that. + */ + if (scan->rs_base.rs_flags & (SO_TYPE_SEQSCAN | SO_TYPE_SAMPLESCAN)) + { + /* + * Ensure a missing snapshot is noticed reliably, even if the + * isolation mode means predicate locking isn't performed (and + * therefore the snapshot isn't used here). + */ + Assert(snapshot); + PredicateLockRelation(relation, snapshot); + } + + /* we only need to set this up once */ + scan->rs_ctup.t_tableOid = RelationGetRelid(relation); + + /* + * Allocate memory to keep track of page allocation for parallel workers + * when doing a parallel scan. + */ + if (parallel_scan != NULL) + scan->rs_parallelworkerdata = palloc(sizeof(ParallelBlockTableScanWorkerData)); + else + scan->rs_parallelworkerdata = NULL; + + /* + * we do this here instead of in initscan() because heap_rescan also calls + * initscan() and we don't want to allocate memory again + */ + if (nkeys > 0) + scan->rs_base.rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys); + else + scan->rs_base.rs_key = NULL; + + initscan(scan, key, false); + + return (TableScanDesc) scan; } -void vertex_scan_end(TableScanDesc scan) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_scan_end not implemented"))); +void vertex_scan_end(TableScanDesc sscan) { + HeapScanDesc scan = (HeapScanDesc) sscan; + + /* Note: no locking manipulations needed */ + + /* + * unpin scan buffers + */ + if (BufferIsValid(scan->rs_cbuf)) + ReleaseBuffer(scan->rs_cbuf); + + /* + * decrement relation reference count and free scan descriptor storage + */ + RelationDecrementReferenceCount(scan->rs_base.rs_rd); + + if (scan->rs_base.rs_key) + pfree(scan->rs_base.rs_key); + + if (scan->rs_strategy != NULL) + FreeAccessStrategy(scan->rs_strategy); + + if (scan->rs_parallelworkerdata != NULL) + pfree(scan->rs_parallelworkerdata); + + if (scan->rs_base.rs_flags & SO_TEMP_SNAPSHOT) + UnregisterSnapshot(scan->rs_base.rs_snapshot); + + pfree(scan); } /* @@ -89,11 +965,30 @@ void vertex_scan_rescan(TableScanDesc scan, struct ScanKeyData *key, /* * Return next tuple from `scan`, store in slot. */ -bool vertex_scan_getnextslot(TableScanDesc scan, ScanDirection direction, +bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_scan_getnextslot not implemented"))); - return false; + HeapScanDesc scan = (HeapScanDesc) sscan; + + /* Note: no locking manipulations needed */ + if (sscan->rs_flags & SO_ALLOW_PAGEMODE) + heapgettup_pagemode(scan, direction, sscan->rs_nkeys, sscan->rs_key); + else + heapgettup(scan, direction, sscan->rs_nkeys, sscan->rs_key); + + if (scan->rs_ctup.t_data == NULL) { + ExecClearTuple(slot); + return false; + } + + /* + * if we get here it means we have a new current scan tuple, so point to + * the proper return buffer and return the tuple. + */ + + pgstat_count_heap_getnext(scan->rs_base.rs_rd); + + ExecStoreBufferHeapTuple(&scan->rs_ctup, slot, scan->rs_cbuf); + return true; } /* ------------------------------------------------------------------------ @@ -276,13 +1171,23 @@ TransactionId vertex_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstat */ /* see table_tuple_insert() for reference about parameters */ -void vertex_tuple_insert(Relation rel, TupleTableSlot *slot, - CommandId cid, int options, - struct BulkInsertStateData *bistate) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - +void vertex_tuple_insert(Relation relation, TupleTableSlot *slot, + CommandId cid, int options, + struct BulkInsertStateData *bistate) { + // TODO, this is not implemented, merely the framework of whats to come + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + + /* Update the tuple with table oid */ + slot->tts_tableOid = RelationGetRelid(relation); + tuple->t_tableOid = slot->tts_tableOid; + + /* Perform the insertion, and copy the resulting ItemPointer */ + heap_insert(relation, tuple, cid, options, bistate); + ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + + if (shouldFree) + pfree(tuple); } /* see table_tuple_insert_speculative() for reference about parameters */ @@ -291,7 +1196,7 @@ void vertex_tuple_insert_speculative(Relation rel, TupleTableSlot *slot, struct BulkInsertStateData *bistate, uint32 specToken) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_tuple_insert_speculative not implemented"))); @@ -385,8 +1290,8 @@ void vertex_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, SMgrRelation srel; if(persistence != RELPERSISTENCE_PERMANENT || rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex am can only work on permenant tables"))); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex am can only work on permenant tables"))); /* * Initialize to the minimum XID that could put tuples in the table. We @@ -562,12 +1467,17 @@ void vertex_index_validate_scan(Relation table_rel, Relation index_rel, * point. */ uint64 vertex_relation_size(Relation rel, ForkNumber forkNumber) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - + uint64 nblocks = 0; + /* InvalidForkNumber indicates returning the size for all forks */ + if (forkNumber == InvalidForkNumber) { + for (int i = 0; i < MAX_FORKNUM; i++) + nblocks += smgrnblocks(RelationGetSmgr(rel), i); + } + else + nblocks = smgrnblocks(RelationGetSmgr(rel), forkNumber); - return 0; + return nblocks * BLCKSZ; } @@ -579,11 +1489,8 @@ uint64 vertex_relation_size(Relation rel, ForkNumber forkNumber) { * return false. */ bool vertex_relation_needs_toast_table(Relation rel) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - + // XXX: for now, no toast table, this will absolutely change, but we will + // do that later return false; } @@ -593,11 +1500,6 @@ bool vertex_relation_needs_toast_table(Relation rel) { * always returns false, this callback is not required. */ Oid vertex_relation_toast_am(Relation rel) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - return InvalidOid; } @@ -609,15 +1511,10 @@ Oid vertex_relation_toast_am(Relation rel) { void vertex_relation_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, int32 sliceoffset, int32 slicelength, struct varlena *result) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - + errmsg_internal("vertex_relation_fetch_toat_slice not implemented"))); } - /* ------------------------------------------------------------------------ * Planner related functions. * ------------------------------------------------------------------------ @@ -636,8 +1533,96 @@ void vertex_relation_fetch_toast_slice(Relation toastrel, Oid valueid, void vertex_relation_estimate_size(Relation rel, int32 *attr_widths, BlockNumber *pages, double *tuples, double *allvisfrac) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + BlockNumber curpages; + BlockNumber relpages; + double reltuples; + BlockNumber relallvisible; + double density; + double overhead_bytes_per_tuple = HEAP_OVERHEAD_BYTES_PER_TUPLE; + double usable_bytes_per_page = HEAP_USABLE_BYTES_PER_PAGE; + + /* it should have storage, so we can call the smgr */ + curpages = RelationGetNumberOfBlocks(rel); + + /* coerce values in pg_class to more desirable types */ + relpages = (BlockNumber) rel->rd_rel->relpages; + reltuples = (double) rel->rd_rel->reltuples; + relallvisible = (BlockNumber) rel->rd_rel->relallvisible; + + /* + * HACK: if the relation has never yet been vacuumed, use a minimum size + * estimate of 10 pages. The idea here is to avoid assuming a + * newly-created table is really small, even if it currently is, because + * that may not be true once some data gets loaded into it. Once a vacuum + * or analyze cycle has been done on it, it's more reasonable to believe + * the size is somewhat stable. + * + * (Note that this is only an issue if the plan gets cached and used again + * after the table has been filled. What we're trying to avoid is using a + * nestloop-type plan on a table that has grown substantially since the + * plan was made. Normally, autovacuum/autoanalyze will occur once enough + * inserts have happened and cause cached-plan invalidation; but that + * doesn't happen instantaneously, and it won't happen at all for cases + * such as temporary tables.) + * + * We test "never vacuumed" by seeing whether reltuples < 0. + * + * If the table has inheritance children, we don't apply this heuristic. + * Totally empty parent tables are quite common, so we should be willing + * to believe that they are empty. + */ + if (curpages < 10 && reltuples < 0 && !rel->rd_rel->relhassubclass) + curpages = 10; + + /* report estimated # pages */ + *pages = curpages; + /* quick exit if rel is clearly empty */ + if (curpages == 0) + { + *tuples = 0; + *allvisfrac = 0; + return; + } + + /* estimate number of tuples from previous tuple density */ + if (reltuples >= 0 && relpages > 0) + density = reltuples / (double) relpages; + else { + /* + * When we have no data because the relation was never yet vacuumed, + * estimate tuple width from attribute datatypes. We assume here that + * the pages are completely full, which is OK for tables but is + * probably an overestimate for indexes. Fortunately + * get_relation_info() can clamp the overestimate to the parent + * table's size. + * + * Note: this code intentionally disregards alignment considerations, + * because (a) that would be gilding the lily considering how crude + * the estimate is, (b) it creates platform dependencies in the + * default plans which are kind of a headache for regression testing, + * and (c) different table AMs might use different padding schemes. + */ + int32 tuple_width; + + tuple_width = get_rel_data_width(rel, attr_widths); + tuple_width += overhead_bytes_per_tuple; + /* note: integer division is intentional here */ + density = usable_bytes_per_page / tuple_width; + } + *tuples = rint(density * (double) curpages); + + /* + * We use relallvisible as-is, rather than scaling it up like we do for + * the pages and tuples counts, on the theory that any pages added since + * the last VACUUM are most likely not marked all-visible. But costsize.c + * wants it converted to a fraction. + */ + if (relallvisible == 0 || curpages <= 0) + *allvisfrac = 0; + else if ((double) relallvisible >= curpages) + *allvisfrac = 1; + else + *allvisfrac = (double) relallvisible / curpages; } From 13b7b812fbc9d351772eb8dfe2fccc2fccdb7b31 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 31 May 2025 12:04:15 -0700 Subject: [PATCH 06/56] Cleanup some spacing issues --- .../vertex_heap/vertex_heapam_handler.c | 582 +++++++++--------- 1 file changed, 288 insertions(+), 294 deletions(-) diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 26faad5b..4d4eafa2 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -520,304 +520,298 @@ heapgettup(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key) } /* ---------------- - * * heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode - * * - * * Same API as heapgettup, but used in page-at-a-time mode - * * - * * The internal logic is much the same as heapgettup's too, but there are some - * * differences: we do not take the buffer content lock (that only needs to - * * happen inside heapgetpage), and we iterate through just the tuples listed - * * in rs_vistuples[] rather than all tuples on the page. Notice that - * * lineindex is 0-based, where the corresponding loop variable lineoff in - * * heapgettup is 1-based. - * * ---------------- - * */ + * heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode + * + * Same API as heapgettup, but used in page-at-a-time mode + * + * The internal logic is much the same as heapgettup's too, but there are some + * differences: we do not take the buffer content lock (that only needs to + * happen inside heapgetpage), and we iterate through just the tuples listed + * in rs_vistuples[] rather than all tuples on the page. Notice that + * lineindex is 0-based, where the corresponding loop variable lineoff in + * heapgettup is 1-based. + * ---------------- + */ static void -heapgettup_pagemode(HeapScanDesc scan, - ScanDirection dir, - int nkeys, - ScanKey key) +heapgettup_pagemode(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key) { - HeapTuple tuple = &(scan->rs_ctup); - bool backward = ScanDirectionIsBackward(dir); - BlockNumber page; - bool finished; - Page dp; - int lines; - int lineindex; - OffsetNumber lineoff; - int linesleft; - ItemId lpp; - - /* - * * calculate next starting lineindex, given scan direction - * */ - if (ScanDirectionIsForward(dir)) - { - if (!scan->rs_inited) - { - /* - * * return null immediately if relation is empty - * */ - if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - if (scan->rs_base.rs_parallel != NULL) - { - ParallelBlockTableScanDesc pbscan = - (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; - ParallelBlockTableScanWorker pbscanwork = - scan->rs_parallelworkerdata; - - table_block_parallelscan_startblock_init(scan->rs_base.rs_rd, - pbscanwork, pbscan); - - page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, - pbscanwork, pbscan); - - /* Other processes might have already finished the scan. */ - if (page == InvalidBlockNumber) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - } - else - page = scan->rs_startblock; /* first page */ - heapgetpage((TableScanDesc) scan, page); - lineindex = 0; - scan->rs_inited = true; - } - else - { - /* continue from previously returned page/tuple */ - page = scan->rs_cblock; /* current page */ - lineindex = scan->rs_cindex + 1; - } - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); - lines = scan->rs_ntuples; - /* page and lineindex now reference the next visible tid */ - - linesleft = lines - lineindex; - } - else if (backward) - { - /* backward parallel scan not supported */ - Assert(scan->rs_base.rs_parallel == NULL); - - if (!scan->rs_inited) - { - /* - * * return null immediately if relation is empty - * */ - if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - - /* - * * Disable reporting to syncscan logic in a backwards scan; it's - * * not very likely anyone else is doing the same thing at the same - * * time, and much more likely that we'll just bollix things for - * * forward scanners. - * */ - scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; - - /* - * * Start from last page of the scan. Ensure we take into account - * * rs_numblocks if it's been adjusted by heap_setscanlimits(). - * */ - if (scan->rs_numblocks != InvalidBlockNumber) - page = (scan->rs_startblock + scan->rs_numblocks - 1) % scan->rs_nblocks; - else if (scan->rs_startblock > 0) - page = scan->rs_startblock - 1; - else - page = scan->rs_nblocks - 1; - heapgetpage((TableScanDesc) scan, page); - } - else - { - /* continue from previously returned page/tuple */ - page = scan->rs_cblock; /* current page */ - } - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); - lines = scan->rs_ntuples; - - if (!scan->rs_inited) - { - lineindex = lines - 1; - scan->rs_inited = true; - } - else - { - lineindex = scan->rs_cindex - 1; - } - /* page and lineindex now reference the previous visible tid */ - - linesleft = lineindex + 1; - } - else - { - /* - * * ``no movement'' scan direction: refetch prior tuple - * */ - if (!scan->rs_inited) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - - page = ItemPointerGetBlockNumber(&(tuple->t_self)); - if (page != scan->rs_cblock) - heapgetpage((TableScanDesc) scan, page); - - /* Since the tuple was previously fetched, needn't lock page here */ - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); - lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self)); - lpp = PageGetItemId(dp, lineoff); - Assert(ItemIdIsNormal(lpp)); - - tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); - tuple->t_len = ItemIdGetLength(lpp); - - /* check that rs_cindex is in sync */ - Assert(scan->rs_cindex < scan->rs_ntuples); - Assert(lineoff == scan->rs_vistuples[scan->rs_cindex]); - - return; - } - - /* - * * advance the scan until we find a qualifying tuple or run out of stuff - * * to scan - * */ - for (;;) - { - while (linesleft > 0) - { - lineoff = scan->rs_vistuples[lineindex]; - lpp = PageGetItemId(dp, lineoff); - Assert(ItemIdIsNormal(lpp)); - - tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); - tuple->t_len = ItemIdGetLength(lpp); - ItemPointerSet(&(tuple->t_self), page, lineoff); - - /* - * * if current tuple qualifies, return it. - * */ - if (key != NULL) - { - bool valid; - - HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd), - nkeys, key, valid); - if (valid) - { - scan->rs_cindex = lineindex; - return; - } - } - else - { - scan->rs_cindex = lineindex; - return; - } - - /* - * * otherwise move to the next item on the page - * */ - --linesleft; - if (backward) - --lineindex; - else - ++lineindex; + HeapTuple tuple = &(scan->rs_ctup); + bool backward = ScanDirectionIsBackward(dir); + BlockNumber page; + bool finished; + Page dp; + int lines; + int lineindex; + OffsetNumber lineoff; + int linesleft; + ItemId lpp; + + /* + * calculate next starting lineindex, given scan direction + */ + if (ScanDirectionIsForward(dir)) + { + if (!scan->rs_inited) + { + /* + * return null immediately if relation is empty + */ + if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + if (scan->rs_base.rs_parallel != NULL) + { + ParallelBlockTableScanDesc pbscan = + (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; + ParallelBlockTableScanWorker pbscanwork = + scan->rs_parallelworkerdata; + table_block_parallelscan_startblock_init(scan->rs_base.rs_rd, pbscanwork, pbscan); + + page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, + pbscanwork, pbscan); + + /* Other processes might have already finished the scan. */ + if (page == InvalidBlockNumber) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + } + else + page = scan->rs_startblock; /* first page */ + heapgetpage((TableScanDesc) scan, page); + lineindex = 0; + scan->rs_inited = true; + } + else + { + /* continue from previously returned page/tuple */ + page = scan->rs_cblock; /* current page */ + lineindex = scan->rs_cindex + 1; + } + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); + lines = scan->rs_ntuples; + /* page and lineindex now reference the next visible tid */ + + linesleft = lines - lineindex; + } + else if (backward) + { + /* backward parallel scan not supported */ + Assert(scan->rs_base.rs_parallel == NULL); + + if (!scan->rs_inited) + { + /* + * return null immediately if relation is empty + */ + if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + + /* + * Disable reporting to syncscan logic in a backwards scan; it's + * not very likely anyone else is doing the same thing at the same + * time, and much more likely that we'll just bollix things for + * forward scanners. + */ + scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; + + /* + * Start from last page of the scan. Ensure we take into account + * rs_numblocks if it's been adjusted by heap_setscanlimits(). + */ + if (scan->rs_numblocks != InvalidBlockNumber) + page = (scan->rs_startblock + scan->rs_numblocks - 1) % scan->rs_nblocks; + else if (scan->rs_startblock > 0) + page = scan->rs_startblock - 1; + else + page = scan->rs_nblocks - 1; + heapgetpage((TableScanDesc) scan, page); + } + else + { + /* continue from previously returned page/tuple */ + page = scan->rs_cblock; /* current page */ + } + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); + lines = scan->rs_ntuples; + + if (!scan->rs_inited) + { + lineindex = lines - 1; + scan->rs_inited = true; + } + else + { + lineindex = scan->rs_cindex - 1; + } + /* page and lineindex now reference the previous visible tid */ + + linesleft = lineindex + 1; + } + else + { + /* + * ``no movement'' scan direction: refetch prior tuple + */ + if (!scan->rs_inited) + { + Assert(!BufferIsValid(scan->rs_cbuf)); + tuple->t_data = NULL; + return; + } + + page = ItemPointerGetBlockNumber(&(tuple->t_self)); + if (page != scan->rs_cblock) + heapgetpage((TableScanDesc) scan, page); + + /* Since the tuple was previously fetched, needn't lock page here */ + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); + lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self)); + lpp = PageGetItemId(dp, lineoff); + Assert(ItemIdIsNormal(lpp)); + + tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); + tuple->t_len = ItemIdGetLength(lpp); + /* check that rs_cindex is in sync */ + Assert(scan->rs_cindex < scan->rs_ntuples); + Assert(lineoff == scan->rs_vistuples[scan->rs_cindex]); + + return; + } + + /* + * advance the scan until we find a qualifying tuple or run out of stuff + * to scan + */ + for (;;) + { + while (linesleft > 0) + { + lineoff = scan->rs_vistuples[lineindex]; + lpp = PageGetItemId(dp, lineoff); + Assert(ItemIdIsNormal(lpp)); + + tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); + tuple->t_len = ItemIdGetLength(lpp); + ItemPointerSet(&(tuple->t_self), page, lineoff); + + /* + * if current tuple qualifies, return it. + */ + if (key != NULL) + { + bool valid; + + HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd), + nkeys, key, valid); + if (valid) + { + scan->rs_cindex = lineindex; + return; + } + } + else + { + scan->rs_cindex = lineindex; + return; + } + + /* + * otherwise move to the next item on the page + */ + --linesleft; + if (backward) + --lineindex; + else + ++lineindex; } - /* - * * if we get here, it means we've exhausted the items on this page and - * * it's time to move to the next. - * */ - if (backward) - { - finished = (page == scan->rs_startblock) || - (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); - if (page == 0) - page = scan->rs_nblocks; - page--; - } - else if (scan->rs_base.rs_parallel != NULL) - { - ParallelBlockTableScanDesc pbscan = - (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; - ParallelBlockTableScanWorker pbscanwork = - scan->rs_parallelworkerdata; - - page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, - pbscanwork, pbscan); - finished = (page == InvalidBlockNumber); - } - else - { - page++; - if (page >= scan->rs_nblocks) - page = 0; - finished = (page == scan->rs_startblock) || - (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); - - /* - * * Report our new scan position for synchronization purposes. We - * * don't do that when moving backwards, however. That would just - * * mess up any other forward-moving scanners. - * * - * * Note: we do this before checking for end of scan so that the - * * final state of the position hint is back at the start of the - * * rel. That's not strictly necessary, but otherwise when you run - * * the same query multiple times the starting position would shift - * * a little bit backwards on every invocation, which is confusing. - * * We don't guarantee any specific ordering in general, though. - * */ - if (scan->rs_base.rs_flags & SO_ALLOW_SYNC) - ss_report_location(scan->rs_base.rs_rd, page); - } - - /* - * * return NULL if we've exhausted all the pages - * */ - if (finished) - { - if (BufferIsValid(scan->rs_cbuf)) - ReleaseBuffer(scan->rs_cbuf); - scan->rs_cbuf = InvalidBuffer; - scan->rs_cblock = InvalidBlockNumber; - tuple->t_data = NULL; - scan->rs_inited = false; - return; - } - - heapgetpage((TableScanDesc) scan, page); - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); - lines = scan->rs_ntuples; - linesleft = lines; - if (backward) - lineindex = lines - 1; - else - lineindex = 0; - } + /* + * if we get here, it means we've exhausted the items on this page and + * it's time to move to the next. + */ + if (backward) + { + finished = (page == scan->rs_startblock) || + (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); + if (page == 0) + page = scan->rs_nblocks; + page--; + } + else if (scan->rs_base.rs_parallel != NULL) + { + ParallelBlockTableScanDesc pbscan = + (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; + ParallelBlockTableScanWorker pbscanwork = + scan->rs_parallelworkerdata; + + page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, + pbscanwork, pbscan); + finished = (page == InvalidBlockNumber); + } + else + { + page++; + if (page >= scan->rs_nblocks) + page = 0; + finished = (page == scan->rs_startblock) || + (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); + + /* + * Report our new scan position for synchronization purposes. We + * don't do that when moving backwards, however. That would just + * mess up any other forward-moving scanners. + * + * Note: we do this before checking for end of scan so that the + * final state of the position hint is back at the start of the + * rel. That's not strictly necessary, but otherwise when you run + * the same query multiple times the starting position would shift + * a little bit backwards on every invocation, which is confusing. + * We don't guarantee any specific ordering in general, though. + */ + if (scan->rs_base.rs_flags & SO_ALLOW_SYNC) + ss_report_location(scan->rs_base.rs_rd, page); + } + + /* + * return NULL if we've exhausted all the pages + */ + if (finished) + { + if (BufferIsValid(scan->rs_cbuf)) + ReleaseBuffer(scan->rs_cbuf); + scan->rs_cbuf = InvalidBuffer; + scan->rs_cblock = InvalidBlockNumber; + tuple->t_data = NULL; + scan->rs_inited = false; + return; + } + + heapgetpage((TableScanDesc) scan, page); + + dp = BufferGetPage(scan->rs_cbuf); + TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); + lines = scan->rs_ntuples; + linesleft = lines; + if (backward) + lineindex = lines - 1; + else + lineindex = 0; + } } From 3fdb4a892ebcbac706ad2b26f0a46675af22f48a Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 31 May 2025 12:34:57 -0700 Subject: [PATCH 07/56] Add what is needed for delete commands --- regress/sql/vertex_am.sql | 9 + .../vertex_heap/vertex_heapam_handler.c | 1496 ++++++++++++++++- 2 files changed, 1498 insertions(+), 7 deletions(-) diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index 48264d2a..3636c6e3 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -28,3 +28,12 @@ VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); SELECT * FROM vertex_am_tst; +DELETE FROM vertex_am_tst; + +INSERT INTO vertex_am_tst (id, props) +VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); + + +SELECT * FROM vertex_am_tst; + + diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 4d4eafa2..49978675 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -29,6 +29,8 @@ #include "access/tableam.h" #include "access/tsmapi.h" #include "access/xact.h" +#include "access/heapam_xlog.h" +#include "access/visibilitymapdefs.h" #include "access/valid.h" #include "catalog/catalog.h" #include "catalog/index.h" @@ -52,11 +54,946 @@ #define HEAP_USABLE_BYTES_PER_PAGE \ (BLCKSZ - SizeOfPageHeaderData) +static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, + uint16 *new_infomask2); + +static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); +static MultiXactStatus +get_mxact_status_for_lock(LockTupleMode mode, bool is_update); +static bool heap_acquire_tuplock(Relation relation, ItemPointer tid, + LockTupleMode mode, LockWaitPolicy wait_policy, + bool *have_tuple_lock); + +/* + * * Given infomask/infomask2, compute the bits that must be saved in the + * * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock, + * * xl_heap_lock_updated WAL records. + * * + * * See fix_infomask_from_infobits. + * */ +static uint8 +compute_infobits(uint16 infomask, uint16 infomask2) +{ + return + ((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) | + ((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) | + ((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) | + /* note we ignore HEAP_XMAX_SHR_LOCK here */ + ((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) | + ((infomask2 & HEAP_KEYS_UPDATED) != 0 ? + XLHL_KEYS_UPDATED : 0); +} + + +/* + * * MultiXactIdGetUpdateXid + * * + * * Given a multixact Xmax and corresponding infomask, which does not have the + * * HEAP_XMAX_LOCK_ONLY bit set, obtain and return the Xid of the updating + * * transaction. + * * + * * Caller is expected to check the status of the updating transaction, if + * * necessary. + * */ +static TransactionId +MultiXactIdGetUpdateXid(TransactionId xmax, uint16 t_infomask) +{ + TransactionId update_xact = InvalidTransactionId; + MultiXactMember *members; + int nmembers; + + Assert(!(t_infomask & HEAP_XMAX_LOCK_ONLY)); + Assert(t_infomask & HEAP_XMAX_IS_MULTI); + + /* + * * Since we know the LOCK_ONLY bit is not set, this cannot be a multi from + * * pre-pg_upgrade. + * */ + nmembers = GetMultiXactIdMembers(xmax, &members, false, false); + + if (nmembers > 0) + { + int i; + + for (i = 0; i < nmembers; i++) + { + /* Ignore lockers */ + if (!ISUPDATE_from_mxstatus(members[i].status)) + continue; + + /* there can be at most one updater */ + Assert(update_xact == InvalidTransactionId); + update_xact = members[i].xid; +#ifndef USE_ASSERT_CHECKING + + /* + * * in an assert-enabled build, walk the whole array to ensure + * * there's no other updater. + * */ + break; +#endif + } + + pfree(members); + } + + return update_xact; +} + + + +/* + * * Given two versions of the same t_infomask for a tuple, compare them and + * * return whether the relevant status for a tuple Xmax has changed. This is + * * used after a buffer lock has been released and reacquired: we want to ensure + * * that the tuple state continues to be the same it was when we previously + * * examined it. + * * + * * Note the Xmax field itself must be compared separately. + * */ +static inline bool +xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask) +{ + const uint16 interesting = + HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK; + + if ((new_infomask & interesting) != (old_infomask & interesting)) + return true; + + return false; +} + +/* + * * This table maps tuple lock strength values for each particular + * * MultiXactStatus value. + * */ +static const int MultiXactStatusLock[MaxMultiXactStatus + 1] = +{ + LockTupleKeyShare, /* ForKeyShare */ + LockTupleShare, /* ForShare */ + LockTupleNoKeyExclusive, /* ForNoKeyUpdate */ + LockTupleExclusive, /* ForUpdate */ + LockTupleNoKeyExclusive, /* NoKeyUpdate */ + LockTupleExclusive /* Update */ +}; + + +/* Get the LockTupleMode for a given MultiXactStatus */ +#define TUPLOCK_from_mxstatus(status) \ + (MultiXactStatusLock[(status)]) /* ---------------------------------------------------------------- * heap support routines * ---------------------------------------------------------------- */ + +/* + * * Given an original set of Xmax and infomask, and a transaction (identified by + * * add_to_xmax) acquiring a new lock of some mode, compute the new Xmax and + * * corresponding infomasks to use on the tuple. + * * + * * Note that this might have side effects such as creating a new MultiXactId. + * * + * * Most callers will have called HeapTupleSatisfiesUpdate before this function; + * * that will have set the HEAP_XMAX_INVALID bit if the xmax was a MultiXactId + * * but it was not running anymore. There is a race condition, which is that the + * * MultiXactId may have finished since then, but that uncommon case is handled + * * either here, or within MultiXactIdExpand. + * * + * * There is a similar race condition possible when the old xmax was a regular + * * TransactionId. We test TransactionIdIsInProgress again just to narrow the + * * window, but it's still possible to end up creating an unnecessary + * * MultiXactId. Fortunately this is harmless. + * */ +static void +compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask, + uint16 old_infomask2, TransactionId add_to_xmax, + LockTupleMode mode, bool is_update, + TransactionId *result_xmax, uint16 *result_infomask, + uint16 *result_infomask2) +{ + TransactionId new_xmax; + uint16 new_infomask, + new_infomask2; + + Assert(TransactionIdIsCurrentTransactionId(add_to_xmax)); + +l5: + new_infomask = 0; + new_infomask2 = 0; + if (old_infomask & HEAP_XMAX_INVALID) + { + /* + * * No previous locker; we just insert our own TransactionId. + * * + * * Note that it's critical that this case be the first one checked, + * * because there are several blocks below that come back to this one + * * to implement certain optimizations; old_infomask might contain + * * other dirty bits in those cases, but we don't really care. + * */ + if (is_update) + { + new_xmax = add_to_xmax; + if (mode == LockTupleExclusive) + new_infomask2 |= HEAP_KEYS_UPDATED; + } + else + { + new_infomask |= HEAP_XMAX_LOCK_ONLY; + switch (mode) + { + case LockTupleKeyShare: + new_xmax = add_to_xmax; + new_infomask |= HEAP_XMAX_KEYSHR_LOCK; + break; + case LockTupleShare: + new_xmax = add_to_xmax; + new_infomask |= HEAP_XMAX_SHR_LOCK; + break; + case LockTupleNoKeyExclusive: + new_xmax = add_to_xmax; + new_infomask |= HEAP_XMAX_EXCL_LOCK; + break; + case LockTupleExclusive: + new_xmax = add_to_xmax; + new_infomask |= HEAP_XMAX_EXCL_LOCK; + new_infomask2 |= HEAP_KEYS_UPDATED; + break; + default: + new_xmax = InvalidTransactionId; /* silence compiler */ + elog(ERROR, "invalid lock mode"); + } + } + } + else if (old_infomask & HEAP_XMAX_IS_MULTI) + { + MultiXactStatus new_status; + + /* + * * Currently we don't allow XMAX_COMMITTED to be set for multis, so + * * cross-check. + * */ + Assert(!(old_infomask & HEAP_XMAX_COMMITTED)); + + /* + * * A multixact together with LOCK_ONLY set but neither lock bit set + * * (i.e. a pg_upgraded share locked tuple) cannot possibly be running + * * anymore. This check is critical for databases upgraded by + * * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume + * * that such multis are never passed. + * */ + if (HEAP_LOCKED_UPGRADED(old_infomask)) + { + old_infomask &= ~HEAP_XMAX_IS_MULTI; + old_infomask |= HEAP_XMAX_INVALID; + goto l5; + } + + /* + * * If the XMAX is already a MultiXactId, then we need to expand it to + * * include add_to_xmax; but if all the members were lockers and are + * * all gone, we can do away with the IS_MULTI bit and just set + * * add_to_xmax as the only locker/updater. If all lockers are gone + * * and we have an updater that aborted, we can also do without a + * * multi. + * * + * * The cost of doing GetMultiXactIdMembers would be paid by + * * MultiXactIdExpand if we weren't to do this, so this check is not + * * incurring extra work anyhow. + * */ + if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))) + { + if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) || + !TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax, + old_infomask))) + { + /* + * * Reset these bits and restart; otherwise fall through to + * * create a new multi below. + * */ + old_infomask &= ~HEAP_XMAX_IS_MULTI; + old_infomask |= HEAP_XMAX_INVALID; + goto l5; + } + } + + new_status = get_mxact_status_for_lock(mode, is_update); + + new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax, + new_status); + GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); + } + else if (old_infomask & HEAP_XMAX_COMMITTED) + { + /* + * * It's a committed update, so we need to preserve him as updater of + * * the tuple. + * */ + MultiXactStatus status; + MultiXactStatus new_status; + + if (old_infomask2 & HEAP_KEYS_UPDATED) + status = MultiXactStatusUpdate; + else + status = MultiXactStatusNoKeyUpdate; + + new_status = get_mxact_status_for_lock(mode, is_update); + + /* + * * since it's not running, it's obviously impossible for the old + * * updater to be identical to the current one, so we need not check + * * for that case as we do in the block above. + * */ + new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status); + GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); + } + else if (TransactionIdIsInProgress(xmax)) + { + /* + * * If the XMAX is a valid, in-progress TransactionId, then we need to + * * create a new MultiXactId that includes both the old locker or + * * updater and our own TransactionId. + * */ + MultiXactStatus new_status; + MultiXactStatus old_status; + LockTupleMode old_mode; + + if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)) + { + if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask)) + old_status = MultiXactStatusForKeyShare; + else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask)) + old_status = MultiXactStatusForShare; + else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask)) + { + if (old_infomask2 & HEAP_KEYS_UPDATED) + old_status = MultiXactStatusForUpdate; + else + old_status = MultiXactStatusForNoKeyUpdate; + } + else + { + /* + * * LOCK_ONLY can be present alone only when a page has been + * * upgraded by pg_upgrade. But in that case, + * * TransactionIdIsInProgress() should have returned false. We + * * assume it's no longer locked in this case. + * */ + elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax); + old_infomask |= HEAP_XMAX_INVALID; + old_infomask &= ~HEAP_XMAX_LOCK_ONLY; + goto l5; + } + } + else + { + /* it's an update, but which kind? */ + if (old_infomask2 & HEAP_KEYS_UPDATED) + old_status = MultiXactStatusUpdate; + else + old_status = MultiXactStatusNoKeyUpdate; + } + + old_mode = TUPLOCK_from_mxstatus(old_status); + + /* + * * If the lock to be acquired is for the same TransactionId as the + * * existing lock, there's an optimization possible: consider only the + * * strongest of both locks as the only one present, and restart. + * */ + if (xmax == add_to_xmax) + { + /* + * * Note that it's not possible for the original tuple to be + * * updated: we wouldn't be here because the tuple would have been + * * invisible and we wouldn't try to update it. As a subtlety, + * * this code can also run when traversing an update chain to lock + * * future versions of a tuple. But we wouldn't be here either, + * * because the add_to_xmax would be different from the original + * * updater. + * */ + Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)); + + /* acquire the strongest of both */ + if (mode < old_mode) + mode = old_mode; + /* mustn't touch is_update */ + + old_infomask |= HEAP_XMAX_INVALID; + goto l5; + } + + /* otherwise, just fall back to creating a new multixact */ + new_status = get_mxact_status_for_lock(mode, is_update); + new_xmax = MultiXactIdCreate(xmax, old_status, + add_to_xmax, new_status); + GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); + } + else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) && + TransactionIdDidCommit(xmax)) + { + /* + * * It's a committed update, so we gotta preserve him as updater of the + * * tuple. + * */ + MultiXactStatus status; + MultiXactStatus new_status; + + if (old_infomask2 & HEAP_KEYS_UPDATED) + status = MultiXactStatusUpdate; + else + status = MultiXactStatusNoKeyUpdate; + + new_status = get_mxact_status_for_lock(mode, is_update); + + /* + * * since it's not running, it's obviously impossible for the old + * * updater to be identical to the current one, so we need not check + * * for that case as we do in the block above. + * */ + new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status); + GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); + } + else + { + /* + * * Can get here iff the locking/updating transaction was running when + * * the infomask was extracted from the tuple, but finished before + * * TransactionIdIsInProgress got to run. Deal with it as if there was + * * no locker at all in the first place. + * */ + old_infomask |= HEAP_XMAX_INVALID; + goto l5; + } + + *result_infomask = new_infomask; + *result_infomask2 = new_infomask2; + *result_xmax = new_xmax; +} + + + +/* + * * For a given MultiXactId, return the hint bits that should be set in the + * * tuple's infomask. + * * + * * Normally this should be called for a multixact that was just created, and + * * so is on our local cache, so the GetMembers call is fast. + * */ +static void +GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, + uint16 *new_infomask2) +{ + int nmembers; + MultiXactMember *members; + int i; + uint16 bits = HEAP_XMAX_IS_MULTI; + uint16 bits2 = 0; + bool has_update = false; + LockTupleMode strongest = LockTupleKeyShare; + + /* + * * We only use this in multis we just created, so they cannot be values + * * pre-pg_upgrade. + * */ + nmembers = GetMultiXactIdMembers(multi, &members, false, false); + + for (i = 0; i < nmembers; i++) + { + LockTupleMode mode; + + /* + * * Remember the strongest lock mode held by any member of the + * * multixact. + * */ + mode = TUPLOCK_from_mxstatus(members[i].status); + if (mode > strongest) + strongest = mode; + + /* See what other bits we need */ + switch (members[i].status) + { + case MultiXactStatusForKeyShare: + case MultiXactStatusForShare: + case MultiXactStatusForNoKeyUpdate: + break; + + case MultiXactStatusForUpdate: + bits2 |= HEAP_KEYS_UPDATED; + break; + + case MultiXactStatusNoKeyUpdate: + has_update = true; + break; + + case MultiXactStatusUpdate: + bits2 |= HEAP_KEYS_UPDATED; + has_update = true; + break; + } + } + + if (strongest == LockTupleExclusive || + strongest == LockTupleNoKeyExclusive) + bits |= HEAP_XMAX_EXCL_LOCK; + else if (strongest == LockTupleShare) + bits |= HEAP_XMAX_SHR_LOCK; + else if (strongest == LockTupleKeyShare) + bits |= HEAP_XMAX_KEYSHR_LOCK; + + if (!has_update) + bits |= HEAP_XMAX_LOCK_ONLY; + + if (nmembers > 0) + pfree(members); + + *new_infomask = bits; + *new_infomask2 = bits2; +} + + +/* + * * Build a heap tuple representing the configured REPLICA IDENTITY to represent + * * the old tuple in a UPDATE or DELETE. + * * + * * Returns NULL if there's no need to log an identity or if there's no suitable + * * key defined. + * * + * * Pass key_required true if any replica identity columns changed value, or if + * * any of them have any external data. Delete must always pass true. + * * + * * *copy is set to true if the returned tuple is a modified copy rather than + * * the same tuple that was passed in. + * */ +static HeapTuple +ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, + bool *copy) +{ + TupleDesc desc = RelationGetDescr(relation); + char replident = relation->rd_rel->relreplident; + Bitmapset *idattrs; + HeapTuple key_tuple; + bool nulls[MaxHeapAttributeNumber]; + Datum values[MaxHeapAttributeNumber]; + + *copy = false; + + if (!RelationIsLogicallyLogged(relation)) + return NULL; + + if (replident == REPLICA_IDENTITY_NOTHING) + return NULL; + + if (replident == REPLICA_IDENTITY_FULL) + { + /* + * * When logging the entire old tuple, it very well could contain + * * toasted columns. If so, force them to be inlined. + * */ + if (HeapTupleHasExternal(tp)) + { + *copy = true; + tp = toast_flatten_tuple(tp, desc); + } + return tp; + } + + /* if the key isn't required and we're only logging the key, we're done */ + if (!key_required) + return NULL; + + /* find out the replica identity columns */ + idattrs = RelationGetIndexAttrBitmap(relation, + INDEX_ATTR_BITMAP_IDENTITY_KEY); + + /* + * * If there's no defined replica identity columns, treat as !key_required. + * * (This case should not be reachable from heap_update, since that should + * * calculate key_required accurately. But heap_delete just passes + * * constant true for key_required, so we can hit this case in deletes.) + * */ + if (bms_is_empty(idattrs)) + return NULL; + + /* + * * Construct a new tuple containing only the replica identity columns, + * * with nulls elsewhere. While we're at it, assert that the replica + * * identity columns aren't null. + * */ + heap_deform_tuple(tp, desc, values, nulls); + + for (int i = 0; i < desc->natts; i++) + { + if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber, + idattrs)) + Assert(!nulls[i]); + else + nulls[i] = true; + } + + key_tuple = heap_form_tuple(desc, values, nulls); + *copy = true; + + bms_free(idattrs); + + /* + * * If the tuple, which by here only contains indexed columns, still has + * * toasted columns, force them to be inlined. This is somewhat unlikely + * * since there's limits on the size of indexed columns, so we don't + * * duplicate toast_flatten_tuple()s functionality in the above loop over + * * the indexed columns, even if it would be more efficient. + * */ + if (HeapTupleHasExternal(key_tuple)) + { + HeapTuple oldtup = key_tuple; + + key_tuple = toast_flatten_tuple(oldtup, desc); + heap_freetuple(oldtup); + } + + return key_tuple; +} + +/* + * * Each tuple lock mode has a corresponding heavyweight lock, and one or two + * * corresponding MultiXactStatuses (one to merely lock tuples, another one to + * * update them). This table (and the macros below) helps us determine the + * * heavyweight lock mode and MultiXactStatus values to use for any particular + * * tuple lock strength. + * * + * * Don't look at lockstatus/updstatus directly! Use get_mxact_status_for_lock + * * instead. + * */ +static const struct +{ + LOCKMODE hwlock; + int lockstatus; + int updstatus; +} + + tupleLockExtraInfo[MaxLockTupleMode + 1] = +{ + { /* LockTupleKeyShare */ + AccessShareLock, + MultiXactStatusForKeyShare, + -1 /* KeyShare does not allow updating tuples */ + }, + { /* LockTupleShare */ + RowShareLock, + MultiXactStatusForShare, + -1 /* Share does not allow updating tuples */ + }, + { /* LockTupleNoKeyExclusive */ + ExclusiveLock, + MultiXactStatusForNoKeyUpdate, + MultiXactStatusNoKeyUpdate + }, + { /* LockTupleExclusive */ + AccessExclusiveLock, + MultiXactStatusForUpdate, + MultiXactStatusUpdate + } +}; + + + +/* Get the LOCKMODE for a given MultiXactStatus */ +#define LOCKMODE_from_mxstatus(status) \ + (tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock) + +/* + * * Acquire heavyweight locks on tuples, using a LockTupleMode strength value. + * * This is more readable than having every caller translate it to lock.h's + * * LOCKMODE. + * */ +#define LockTupleTuplock(rel, tup, mode) \ + LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock) +#define UnlockTupleTuplock(rel, tup, mode) \ + UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock) +#define ConditionalLockTupleTuplock(rel, tup, mode) \ + ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock) + +#ifdef USE_PREFETCH +/* + * * heap_index_delete_tuples and index_delete_prefetch_buffer use this + * * structure to coordinate prefetching activity + * */ +typedef struct +{ + BlockNumber cur_hblkno; + int next_item; + int ndeltids; + TM_IndexDelete *deltids; +} IndexDeletePrefetchState; +#endif + +/* heap_index_delete_tuples bottom-up index deletion costing constants */ +#define BOTTOMUP_MAX_NBLOCKS 6 +#define BOTTOMUP_TOLERANCE_NBLOCKS 3 + +/* + * * heap_index_delete_tuples uses this when determining which heap blocks it + * * must visit to help its bottom-up index deletion caller + * */ +typedef struct IndexDeleteCounts +{ + int16 npromisingtids; /* Number of "promising" TIDs in group */ + int16 ntids; /* Number of TIDs in group */ + int16 ifirsttid; /* Offset to group's first deltid */ +} IndexDeleteCounts; + +/* + * * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends + * * + * * This is called after we have waited for the XMAX transaction to terminate. + * * If the transaction aborted, we guarantee the XMAX_INVALID hint bit will + * * be set on exit. If the transaction committed, we set the XMAX_COMMITTED + * * hint bit if possible --- but beware that that may not yet be possible, + * * if the transaction committed asynchronously. + * * + * * Note that if the transaction was a locker only, we set HEAP_XMAX_INVALID + * * even if it commits. + * * + * * Hence callers should look only at XMAX_INVALID. + * * + * * Note this is not allowed for tuples whose xmax is a multixact. + * */ +static void +UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid) +{ + Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid)); + Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI)); + + if (!(tuple->t_infomask & (HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID))) + { + if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) && + TransactionIdDidCommit(xid)) + HeapTupleSetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, + xid); + else + HeapTupleSetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + } +} + +/* + * * Does the given multixact conflict with the current transaction grabbing a + * * tuple lock of the given strength? + * * + * * The passed infomask pairs up with the given multixact in the tuple header. + * * + * * If current_is_member is not NULL, it is set to 'true' if the current + * * transaction is a member of the given multixact. + * */ +static bool +DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask, + LockTupleMode lockmode, bool *current_is_member) +{ + int nmembers; + MultiXactMember *members; + bool result = false; + LOCKMODE wanted = tupleLockExtraInfo[lockmode].hwlock; + + if (HEAP_LOCKED_UPGRADED(infomask)) + return false; + + nmembers = GetMultiXactIdMembers(multi, &members, false, + HEAP_XMAX_IS_LOCKED_ONLY(infomask)); + if (nmembers >= 0) + { + int i; + + for (i = 0; i < nmembers; i++) + { + TransactionId memxid; + LOCKMODE memlockmode; + + if (result && (current_is_member == NULL || *current_is_member)) + break; + + memlockmode = LOCKMODE_from_mxstatus(members[i].status); + + /* ignore members from current xact (but track their presence) */ + memxid = members[i].xid; + if (TransactionIdIsCurrentTransactionId(memxid)) + { + if (current_is_member != NULL) + *current_is_member = true; + continue; + } + else if (result) + continue; + + /* ignore members that don't conflict with the lock we want */ + if (!DoLockModesConflict(memlockmode, wanted)) + continue; + + if (ISUPDATE_from_mxstatus(members[i].status)) + { + /* ignore aborted updaters */ + if (TransactionIdDidAbort(memxid)) + continue; + } + else + { + /* ignore lockers-only that are no longer in progress */ + if (!TransactionIdIsInProgress(memxid)) + continue; + } + + /* + * * Whatever remains are either live lockers that conflict with our + * * wanted lock, and updaters that are not aborted. Those conflict + * * with what we want. Set up to return true, but keep going to + * * look for the current transaction among the multixact members, + * * if needed. + * */ + result = true; + } + pfree(members); + } + + return result; +} + +/* + * * Do_MultiXactIdWait + * * Actual implementation for the two functions below. + * * + * * 'multi', 'status' and 'infomask' indicate what to sleep on (the status is + * * needed to ensure we only sleep on conflicting members, and the infomask is + * * used to optimize multixact access in case it's a lock-only multi); 'nowait' + * * indicates whether to use conditional lock acquisition, to allow callers to + * * fail if lock is unavailable. 'rel', 'ctid' and 'oper' are used to set up + * * context information for error messages. 'remaining', if not NULL, receives + * * the number of members that are still running, including any (non-aborted) + * * subtransactions of our own transaction. + * * + * * We do this by sleeping on each member using XactLockTableWait. Any + * * members that belong to the current backend are *not* waited for, however; + * * this would not merely be useless but would lead to Assert failure inside + * * XactLockTableWait. By the time this returns, it is certain that all + * * transactions *of other backends* that were members of the MultiXactId + * * that conflict with the requested status are dead (and no new ones can have + * * been added, since it is not legal to add members to an existing + * * MultiXactId). + * * + * * But by the time we finish sleeping, someone else may have changed the Xmax + * * of the containing tuple, so the caller needs to iterate on us somehow. + * * + * * Note that in case we return false, the number of remaining members is + * * not to be trusted. + * */ +static bool +Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, + uint16 infomask, bool nowait, + Relation rel, ItemPointer ctid, XLTW_Oper oper, + int *remaining) +{ + bool result = true; + MultiXactMember *members; + int nmembers; + int remain = 0; + + /* for pre-pg_upgrade tuples, no need to sleep at all */ + nmembers = HEAP_LOCKED_UPGRADED(infomask) ? -1 : + GetMultiXactIdMembers(multi, &members, false, + HEAP_XMAX_IS_LOCKED_ONLY(infomask)); + + if (nmembers >= 0) + { + int i; + + for (i = 0; i < nmembers; i++) + { + TransactionId memxid = members[i].xid; + MultiXactStatus memstatus = members[i].status; + + if (TransactionIdIsCurrentTransactionId(memxid)) + { + remain++; + continue; + } + + if (!DoLockModesConflict(LOCKMODE_from_mxstatus(memstatus), + LOCKMODE_from_mxstatus(status))) + { + if (remaining && TransactionIdIsInProgress(memxid)) + remain++; + continue; + } + + /* + * * This member conflicts with our multi, so we have to sleep (or + * * return failure, if asked to avoid waiting.) + * * + * * Note that we don't set up an error context callback ourselves, + * * but instead we pass the info down to XactLockTableWait. This + * * might seem a bit wasteful because the context is set up and + * * tore down for each member of the multixact, but in reality it + * * should be barely noticeable, and it avoids duplicate code. + * */ + if (nowait) + { + result = ConditionalXactLockTableWait(memxid); + if (!result) + break; + } + else + XactLockTableWait(memxid, rel, ctid, oper); + } + + pfree(members); + } + + if (remaining) + *remaining = remain; + + return result; +} + +/* + * * MultiXactIdWait + * * Sleep on a MultiXactId. + * * + * * By the time we finish sleeping, someone else may have changed the Xmax + * * of the containing tuple, so the caller needs to iterate on us somehow. + * * + * * We return (in *remaining, if not NULL) the number of members that are still + * * running, including any (non-aborted) subtransactions of our own transaction. + * */ +static void +MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask, + Relation rel, ItemPointer ctid, XLTW_Oper oper, + int *remaining) +{ + (void) Do_MultiXactIdWait(multi, status, infomask, false, + rel, ctid, oper, remaining); +} + +/* + * * ConditionalMultiXactIdWait + * * As above, but only lock if we can get the lock without blocking. + * * + * * By the time we finish sleeping, someone else may have changed the Xmax + * * of the containing tuple, so the caller needs to iterate on us somehow. + * * + * * If the multixact is now all gone, return true. Returns false if some + * * transactions might still be running. + * * + * * We return (in *remaining, if not NULL) the number of members that are still + * * running, including any (non-aborted) subtransactions of our own transaction. + * */ +static bool +ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status, + uint16 infomask, Relation rel, int *remaining) +{ + return Do_MultiXactIdWait(multi, status, infomask, true, + rel, NULL, XLTW_None, remaining); +} + + + /* ---------------- * initscan - scan code common to heap_beginscan and heap_rescan * ---------------- @@ -249,9 +1186,9 @@ heapgettup(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key) else page = scan->rs_startblock; /* first page */ - heapgetpage((TableScanDesc) scan, page); + heapgetpage((TableScanDesc) scan, page); - lineoff = FirstOffsetNumber; /* first offnum */ + lineoff = FirstOffsetNumber; /* first offnum */ scan->rs_inited = true; } else @@ -1218,15 +2155,417 @@ void vertex_multi_insert(Relation rel, TupleTableSlot **slots, int nslots, } /* see table_tuple_delete() for reference about parameters */ -TM_Result vertex_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, +TM_Result vertex_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, bool changingPart) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - + + TM_Result result; + TransactionId xid = GetCurrentTransactionId(); + ItemId lp; + HeapTupleData tp; + Page page; + BlockNumber block; + Buffer buffer; + Buffer vmbuffer = InvalidBuffer; + TransactionId new_xmax; + uint16 new_infomask, + new_infomask2; + bool have_tuple_lock = false; + bool iscombo; + bool all_visible_cleared = false; + HeapTuple old_key_tuple = NULL; /* replica identity of the tuple */ + bool old_key_copied = false; + + Assert(ItemPointerIsValid(tid)); + + /* + * * Forbid this during a parallel operation, lest it allocate a combo CID. + * * Other workers might need that combo CID for visibility checks, and we + * * have no provision for broadcasting it to them. + * */ + if (IsInParallelMode()) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TRANSACTION_STATE), + errmsg("cannot delete tuples during a parallel operation"))); + + block = ItemPointerGetBlockNumber(tid); + buffer = ReadBuffer(relation, block); + page = BufferGetPage(buffer); + + /* + * * Before locking the buffer, pin the visibility map page if it appears to + * * be necessary. Since we haven't got the lock yet, someone else might be + * * in the middle of changing this, so we'll need to recheck after we have + * * the lock. + * */ + if (PageIsAllVisible(page)) + visibilitymap_pin(relation, block, &vmbuffer); + + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + + lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); + Assert(ItemIdIsNormal(lp)); + + tp.t_tableOid = RelationGetRelid(relation); + tp.t_data = (HeapTupleHeader) PageGetItem(page, lp); + tp.t_len = ItemIdGetLength(lp); + tp.t_self = *tid; + +l1: + /* + * * If we didn't pin the visibility map page and the page has become all + * * visible while we were busy locking the buffer, we'll have to unlock and + * * re-lock, to avoid holding the buffer lock across an I/O. That's a bit + * * unfortunate, but hopefully shouldn't happen often. + * */ + if (vmbuffer == InvalidBuffer && PageIsAllVisible(page)) + { + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + visibilitymap_pin(relation, block, &vmbuffer); + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + } + + result = HeapTupleSatisfiesUpdate(&tp, cid, buffer); + + if (result == TM_Invisible) + { + UnlockReleaseBuffer(buffer); + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("attempted to delete invisible tuple"))); + } + else if (result == TM_BeingModified && wait) + { + TransactionId xwait; + uint16 infomask; + + /* must copy state data before unlocking buffer */ + xwait = HeapTupleHeaderGetRawXmax(tp.t_data); + infomask = tp.t_data->t_infomask; + + /* + * * Sleep until concurrent transaction ends -- except when there's a + * * single locker and it's our own transaction. Note we don't care + * * which lock mode the locker has, because we need the strongest one. + * * + * * Before sleeping, we need to acquire tuple lock to establish our + * * priority for the tuple (see heap_lock_tuple). LockTuple will + * * release us when we are next-in-line for the tuple. + * * + * * If we are forced to "start over" below, we keep the tuple lock; + * * this arranges that we stay at the head of the line while rechecking + * * tuple state. + * */ + if (infomask & HEAP_XMAX_IS_MULTI) + { + bool current_is_member = false; + + if (DoesMultiXactIdConflict((MultiXactId) xwait, infomask, + LockTupleExclusive, ¤t_is_member)) + { + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + + /* + * * Acquire the lock, if necessary (but skip it when we're + * * requesting a lock and already have one; avoids deadlock). + * */ + if (!current_is_member) + heap_acquire_tuplock(relation, &(tp.t_self), LockTupleExclusive, + LockWaitBlock, &have_tuple_lock); + + /* wait for multixact */ + MultiXactIdWait((MultiXactId) xwait, MultiXactStatusUpdate, infomask, + relation, &(tp.t_self), XLTW_Delete, + NULL); + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + + /* + * * If xwait had just locked the tuple then some other xact + * * could update this tuple before we get to this point. Check + * * for xmax change, and start over if so. + * * + * * We also must start over if we didn't pin the VM page, and + * * the page has become all visible. + * */ + if ((vmbuffer == InvalidBuffer && PageIsAllVisible(page)) || + xmax_infomask_changed(tp.t_data->t_infomask, infomask) || + !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tp.t_data), + xwait)) + goto l1; + } + + /* + * * You might think the multixact is necessarily done here, but not + * * so: it could have surviving members, namely our own xact or + * * other subxacts of this backend. It is legal for us to delete + * * the tuple in either case, however (the latter case is + * * essentially a situation of upgrading our former shared lock to + * * exclusive). We don't bother changing the on-disk hint bits + * * since we are about to overwrite the xmax altogether. + * */ + } + else if (!TransactionIdIsCurrentTransactionId(xwait)) + { + /* + * * Wait for regular transaction to end; but first, acquire tuple + * * lock. + * */ + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + heap_acquire_tuplock(relation, &(tp.t_self), LockTupleExclusive, + LockWaitBlock, &have_tuple_lock); + XactLockTableWait(xwait, relation, &(tp.t_self), XLTW_Delete); + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + + /* + * * xwait is done, but if xwait had just locked the tuple then some + * * other xact could update this tuple before we get to this point. + * * Check for xmax change, and start over if so. + * * + * * We also must start over if we didn't pin the VM page, and the + * * page has become all visible. + * */ + if ((vmbuffer == InvalidBuffer && PageIsAllVisible(page)) || + xmax_infomask_changed(tp.t_data->t_infomask, infomask) || + !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tp.t_data), + xwait)) + goto l1; + + /* Otherwise check if it committed or aborted */ + UpdateXmaxHintBits(tp.t_data, buffer, xwait); + } + + /* + * * We may overwrite if previous xmax aborted, or if it committed but + * * only locked the tuple without updating it. + * */ + if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) || + HEAP_XMAX_IS_LOCKED_ONLY(tp.t_data->t_infomask) || + HeapTupleHeaderIsOnlyLocked(tp.t_data)) + result = TM_Ok; + else if (!ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid)) + result = TM_Updated; + else + result = TM_Deleted; + } + + /* sanity check the result HeapTupleSatisfiesUpdate() and the logic above */ + if (result != TM_Ok) + { + Assert(result == TM_SelfModified || + result == TM_Updated || + result == TM_Deleted || + result == TM_BeingModified); + Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID)); + Assert(result != TM_Updated || + !ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid)); + } + + if (crosscheck != InvalidSnapshot && result == TM_Ok) + { + /* Perform additional check for transaction-snapshot mode RI updates */ + if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer)) + result = TM_Updated; + } + + if (result != TM_Ok) + { + tmfd->ctid = tp.t_data->t_ctid; + tmfd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data); + if (result == TM_SelfModified) + tmfd->cmax = HeapTupleHeaderGetCmax(tp.t_data); + else + tmfd->cmax = InvalidCommandId; + UnlockReleaseBuffer(buffer); + if (have_tuple_lock) + UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive); + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + return result; + } + + /* + * * We're about to do the actual delete -- check for conflict first, to + * * avoid possibly having to roll back work we've just done. + * * + * * This is safe without a recheck as long as there is no possibility of + * * another process scanning the page between this check and the delete + * * being visible to the scan (i.e., an exclusive buffer content lock is + * * continuously held from this point until the tuple delete is visible). + * */ + CheckForSerializableConflictIn(relation, tid, BufferGetBlockNumber(buffer)); + + /* replace cid with a combo CID if necessary */ + HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo); + + /* + * * Compute replica identity tuple before entering the critical section so + * * we don't PANIC upon a memory allocation failure. + * */ + old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); + + /* + * * If this is the first possibly-multixact-able operation in the current + * * transaction, set my per-backend OldestMemberMXactId setting. We can be + * * certain that the transaction will never become a member of any older + * * MultiXactIds than that. (We have to do this even if we end up just + * * using our own TransactionId below, since some other backend could + * * incorporate our XID into a MultiXact immediately afterwards.) + * */ + MultiXactIdSetOldestMember(); + + compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(tp.t_data), + tp.t_data->t_infomask, tp.t_data->t_infomask2, + xid, LockTupleExclusive, true, + &new_xmax, &new_infomask, &new_infomask2); + + START_CRIT_SECTION(); + + /* + * * If this transaction commits, the tuple will become DEAD sooner or + * * later. Set flag that this page is a candidate for pruning once our xid + * * falls below the OldestXmin horizon. If the transaction finally aborts, + * * the subsequent page pruning will be a no-op and the hint will be + * * cleared. + * */ + PageSetPrunable(page, xid); + + if (PageIsAllVisible(page)) + { + all_visible_cleared = true; + PageClearAllVisible(page); + visibilitymap_clear(relation, BufferGetBlockNumber(buffer), + vmbuffer, VISIBILITYMAP_VALID_BITS); + } + + /* store transaction information of xact deleting the tuple */ + tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED); + tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED; + tp.t_data->t_infomask |= new_infomask; + tp.t_data->t_infomask2 |= new_infomask2; + HeapTupleHeaderClearHotUpdated(tp.t_data); + HeapTupleHeaderSetXmax(tp.t_data, new_xmax); + HeapTupleHeaderSetCmax(tp.t_data, cid, iscombo); + /* Make sure there is no forward chain link in t_ctid */ + tp.t_data->t_ctid = tp.t_self; + + /* Signal that this is actually a move into another partition */ + if (changingPart) + HeapTupleHeaderSetMovedPartitions(tp.t_data); + + MarkBufferDirty(buffer); + + /* + * * XLOG stuff + * * + * * NB: heap_abort_speculative() uses the same xlog record and replay + * * routines. + * */ + if (RelationNeedsWAL(relation)) + { + xl_heap_delete xlrec; + xl_heap_header xlhdr; + XLogRecPtr recptr; + + /* + * * For logical decode we need combo CIDs to properly decode the + * * catalog + * */ + if (RelationIsAccessibleInLogicalDecoding(relation)) + log_heap_new_cid(relation, &tp); + + xlrec.flags = 0; + if (all_visible_cleared) + xlrec.flags |= XLH_DELETE_ALL_VISIBLE_CLEARED; + if (changingPart) + xlrec.flags |= XLH_DELETE_IS_PARTITION_MOVE; + xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask, + tp.t_data->t_infomask2); + xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self); + xlrec.xmax = new_xmax; + + if (old_key_tuple != NULL) + { + if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL) + xlrec.flags |= XLH_DELETE_CONTAINS_OLD_TUPLE; + else + xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY; + } + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, SizeOfHeapDelete); + + XLogRegisterBuffer(0, buffer, REGBUF_STANDARD); + + /* + * * Log replica identity of the deleted tuple if there is one + * */ + if (old_key_tuple != NULL) + { + xlhdr.t_infomask2 = old_key_tuple->t_data->t_infomask2; + xlhdr.t_infomask = old_key_tuple->t_data->t_infomask; + xlhdr.t_hoff = old_key_tuple->t_data->t_hoff; + + XLogRegisterData((char *) &xlhdr, SizeOfHeapHeader); + XLogRegisterData((char *) old_key_tuple->t_data + + SizeofHeapTupleHeader, + old_key_tuple->t_len + - SizeofHeapTupleHeader); + } + + /* filtering by origin on a row level is much more efficient */ + XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN); + + recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + + /* + * * If the tuple has toasted out-of-line attributes, we need to delete + * * those items too. We have to do this before releasing the buffer + * * because we need to look at the contents of the tuple, but it's OK to + * * release the content lock on the buffer first. + * */ + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_MATVIEW) + { + /* toast table entries should never be recursively toasted */ + Assert(!HeapTupleHasExternal(&tp)); + } + else if (HeapTupleHasExternal(&tp)) + heap_toast_delete(relation, &tp, false); + + /* + * * Mark tuple for invalidation from system caches at next command + * * boundary. We have to do this before releasing the buffer because we + * * need to look at the contents of the tuple. + * */ + CacheInvalidateHeapTuple(relation, &tp, NULL); + + /* Now we can release the buffer */ + ReleaseBuffer(buffer); + + /* + * * Release the lmgr tuple lock, if we had it. + * */ + if (have_tuple_lock) + UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive); + + pgstat_count_heap_delete(relation); + + if (old_key_tuple != NULL && old_key_copied) + heap_freetuple(old_key_tuple); + + return TM_Ok; - return 0; } /* see table_tuple_update() for reference about parameters */ @@ -1815,3 +3154,146 @@ vertex_tableam_handler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(&vertex_heapam_methods); } + + +/* + * * Perform XLogInsert of an XLOG_HEAP2_NEW_CID record + * * + * * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog + * * tuples. + * */ +static XLogRecPtr +log_heap_new_cid(Relation relation, HeapTuple tup) +{ + xl_heap_new_cid xlrec; + + XLogRecPtr recptr; + HeapTupleHeader hdr = tup->t_data; + + Assert(ItemPointerIsValid(&tup->t_self)); + Assert(tup->t_tableOid != InvalidOid); + + xlrec.top_xid = GetTopTransactionId(); + xlrec.target_node = relation->rd_node; + xlrec.target_tid = tup->t_self; + + /* + * * If the tuple got inserted & deleted in the same TX we definitely have a + * * combo CID, set cmin and cmax. + * */ + if (hdr->t_infomask & HEAP_COMBOCID) + { + Assert(!(hdr->t_infomask & HEAP_XMAX_INVALID)); + Assert(!HeapTupleHeaderXminInvalid(hdr)); + xlrec.cmin = HeapTupleHeaderGetCmin(hdr); + xlrec.cmax = HeapTupleHeaderGetCmax(hdr); + xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr); + } + /* No combo CID, so only cmin or cmax can be set by this TX */ + else + { + /* + * * Tuple inserted. + * * + * * We need to check for LOCK ONLY because multixacts might be + * * transferred to the new tuple in case of FOR KEY SHARE updates in + * * which case there will be an xmax, although the tuple just got + * * inserted. + * */ + if (hdr->t_infomask & HEAP_XMAX_INVALID || + HEAP_XMAX_IS_LOCKED_ONLY(hdr->t_infomask)) + { + xlrec.cmin = HeapTupleHeaderGetRawCommandId(hdr); + xlrec.cmax = InvalidCommandId; + } + /* Tuple from a different tx updated or deleted. */ + else + { + xlrec.cmin = InvalidCommandId; + xlrec.cmax = HeapTupleHeaderGetRawCommandId(hdr); + + } + xlrec.combocid = InvalidCommandId; + } + + /* + * * Note that we don't need to register the buffer here, because this + * * operation does not modify the page. The insert/update/delete that + * * called us certainly did, but that's WAL-logged separately. + * */ + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, SizeOfHeapNewCid); + + /* will be looked at irrespective of origin */ + + recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_NEW_CID); + + return recptr; +} + + + +/* + * * Return the MultiXactStatus corresponding to the given tuple lock mode. + * */ +static MultiXactStatus +get_mxact_status_for_lock(LockTupleMode mode, bool is_update) +{ + int retval; + + if (is_update) + retval = tupleLockExtraInfo[mode].updstatus; + else + retval = tupleLockExtraInfo[mode].lockstatus; + + if (retval == -1) + elog(ERROR, "invalid lock tuple mode %d/%s", mode, + is_update ? "true" : "false"); + + return (MultiXactStatus) retval; +} + + +/* + * * Acquire heavyweight lock on the given tuple, in preparation for acquiring + * * its normal, Xmax-based tuple lock. + * * + * * have_tuple_lock is an input and output parameter: on input, it indicates + * * whether the lock has previously been acquired (and this function does + * * nothing in that case). If this function returns success, have_tuple_lock + * * has been flipped to true. + * * + * * Returns false if it was unable to obtain the lock; this can only happen if + * * wait_policy is Skip. + * */ +static bool +heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode, + LockWaitPolicy wait_policy, bool *have_tuple_lock) +{ + if (*have_tuple_lock) + return true; + + switch (wait_policy) + { + case LockWaitBlock: + LockTupleTuplock(relation, tid, mode); + break; + + case LockWaitSkip: + if (!ConditionalLockTupleTuplock(relation, tid, mode)) + return false; + break; + + case LockWaitError: + if (!ConditionalLockTupleTuplock(relation, tid, mode)) + ereport(ERROR, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on row in relation \"%s\"", + RelationGetRelationName(relation)))); + break; + } + *have_tuple_lock = true; + + return true; +} + From d90068dc21ce87da041457ea9116733fd3bcf429 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Fri, 23 May 2025 13:27:33 -0500 Subject: [PATCH 08/56] Support Update --- Makefile | 2 +- regress/sql/vertex_am.sql | 3 ++ .../vertex_heap/vertex_heapam_handler.c | 52 +++++++++++++++---- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index d1bf7ce5..7f81c379 100644 --- a/Makefile +++ b/Makefile @@ -137,7 +137,7 @@ GEN_KEYWORDLIST = $(PERL) -I ./tools/ ./tools/gen_keywordlist.pl GEN_KEYWORDLIST_DEPS = ./tools/gen_keywordlist.pl tools/PerfectHash.pm ag_include_dir = $(srcdir)/src/include -PG_CPPFLAGS = -w -I$(ag_include_dir) -I$(ag_include_dir)/parser -I$(POSTGIS_DIR) -I$(POSTGIS_DIR)/liblwgeom -I/home/v-innisjosh/postgres/contrib/ltree +PG_CPPFLAGS = -w -I$(ag_include_dir) -I$(ag_include_dir)/parser -I$(POSTGIS_DIR) -I$(POSTGIS_DIR)/liblwgeom -I/home/josh/postgres/contrib/ltree PG_LDFLAGS = -lgeos_c SHLIB_LINK= $(POSTGIS_DIR)/liblwgeom/.libs/liblwgeom.a -Wl,--no-as-needed -Wl,-l:postgis-3.so -Wl,-l:ltree.so diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index 3636c6e3..4845bf45 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -36,4 +36,7 @@ VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); SELECT * FROM vertex_am_tst; +UPDATE vertex_am_tst SET id = '2'::postgraph.graphid; + +SELECT * FROM vertex_am_tst; diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 49978675..fc80afc9 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -2044,13 +2044,24 @@ bool vertex_index_fetch_tuple(struct IndexFetchTableData *scan, * according to `snapshot`. If a tuple was found and passed the visibility * test, returns true, false otherwise. */ -bool vertex_tuple_fetch_row_version(Relation rel, ItemPointer tid, +bool vertex_tuple_fetch_row_version(Relation relation, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + Buffer buffer; - return false; + Assert(TTS_IS_BUFFERTUPLE(slot)); + + bslot->base.tupdata.t_self = *tid; + if (heap_fetch(relation, snapshot, &bslot->base.tupdata, &buffer)) + { + /* store in slot, transferring existing pin */ + ExecStorePinnedBufferHeapTuple(&bslot->base.tupdata, slot, buffer); + slot->tts_tableOid = RelationGetRelid(relation); + + return true; + } + + return false; } /* @@ -2569,16 +2580,37 @@ TM_Result vertex_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, } /* see table_tuple_update() for reference about parameters */ -TM_Result vertex_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, +TM_Result vertex_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, bool *update_indexes) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + TM_Result result; + + /* Update the tuple with table oid */ + slot->tts_tableOid = RelationGetRelid(relation); + tuple->t_tableOid = slot->tts_tableOid; + + result = heap_update(relation, otid, tuple, cid, crosscheck, wait, + tmfd, lockmode); + ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + + /* + * Decide whether new index entries are needed for the tuple + * + * Note: heap_update returns the tid (location) of the new tuple in the + * t_self field. + * + * If it's a HOT update, we mustn't insert new index entries. + */ + *update_indexes = result == TM_Ok && !HeapTupleIsHeapOnly(tuple); + + if (shouldFree) + pfree(tuple); -return 0; + return result; } /* see table_tuple_lock() for reference about parameters */ From e8cca639a69c6cd2999a37e2a36ebb10f7d4f899 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 7 Jun 2025 00:37:17 -0500 Subject: [PATCH 09/56] Shove a shared memory hash table in for the relation put the ids in there for tuples --- regress/sql/vertex_am.sql | 2 +- .../vertex_heap/vertex_heapam_handler.c | 145 +++++++++++------- 2 files changed, 94 insertions(+), 53 deletions(-) diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index 4845bf45..726d60f0 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -31,7 +31,7 @@ SELECT * FROM vertex_am_tst; DELETE FROM vertex_am_tst; INSERT INTO vertex_am_tst (id, props) -VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); +VALUES ('2'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); SELECT * FROM vertex_am_tst; diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index fc80afc9..15040cb3 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -49,6 +49,9 @@ #include "utils/builtins.h" #include "utils/rel.h" +#include "utils/graphid.h" +#include "utils/gtype.h" + #define HEAP_OVERHEAD_BYTES_PER_TUPLE \ (MAXALIGN(SizeofHeapTupleHeader) + sizeof(ItemIdData)) #define HEAP_USABLE_BYTES_PER_PAGE \ @@ -61,8 +64,16 @@ static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); static MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update); static bool heap_acquire_tuplock(Relation relation, ItemPointer tid, - LockTupleMode mode, LockWaitPolicy wait_policy, - bool *have_tuple_lock); + LockTupleMode mode, LockWaitPolicy wait_policy, + bool *have_tuple_lock); + +typedef struct vertex_hash_struct +{ + graphid id; // hash key + gtype *properties; +} vertex_hash_struct; + + /* * * Given infomask/infomask2, compute the bits that must be saved in the @@ -75,69 +86,67 @@ static uint8 compute_infobits(uint16 infomask, uint16 infomask2) { return - ((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) | - ((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) | - ((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) | - /* note we ignore HEAP_XMAX_SHR_LOCK here */ - ((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) | - ((infomask2 & HEAP_KEYS_UPDATED) != 0 ? - XLHL_KEYS_UPDATED : 0); + ((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) | + ((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) | + ((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) | + /* note we ignore HEAP_XMAX_SHR_LOCK here */ + ((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) | + ((infomask2 & HEAP_KEYS_UPDATED) != 0 ? + XLHL_KEYS_UPDATED : 0); } - /* - * * MultiXactIdGetUpdateXid - * * - * * Given a multixact Xmax and corresponding infomask, which does not have the - * * HEAP_XMAX_LOCK_ONLY bit set, obtain and return the Xid of the updating - * * transaction. - * * - * * Caller is expected to check the status of the updating transaction, if - * * necessary. - * */ + * MultiXactIdGetUpdateXid + * + * Given a multixact Xmax and corresponding infomask, which does not have the + * HEAP_XMAX_LOCK_ONLY bit set, obtain and return the Xid of the updating + * transaction. + * + * Caller is expected to check the status of the updating transaction, if + * necessary. + */ static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax, uint16 t_infomask) { - TransactionId update_xact = InvalidTransactionId; - MultiXactMember *members; - int nmembers; + TransactionId update_xact = InvalidTransactionId; + MultiXactMember *members; + int nmembers; - Assert(!(t_infomask & HEAP_XMAX_LOCK_ONLY)); - Assert(t_infomask & HEAP_XMAX_IS_MULTI); + Assert(!(t_infomask & HEAP_XMAX_LOCK_ONLY)); + Assert(t_infomask & HEAP_XMAX_IS_MULTI); - /* - * * Since we know the LOCK_ONLY bit is not set, this cannot be a multi from - * * pre-pg_upgrade. - * */ - nmembers = GetMultiXactIdMembers(xmax, &members, false, false); + /* + * Since we know the LOCK_ONLY bit is not set, this cannot be a multi from + * pre-pg_upgrade. + */ + nmembers = GetMultiXactIdMembers(xmax, &members, false, false); - if (nmembers > 0) - { - int i; + if (nmembers > 0) + { + int i; - for (i = 0; i < nmembers; i++) - { - /* Ignore lockers */ - if (!ISUPDATE_from_mxstatus(members[i].status)) - continue; + for (i = 0; i < nmembers; i++) + { + /* Ignore lockers */ + if (!ISUPDATE_from_mxstatus(members[i].status)) + continue; - /* there can be at most one updater */ - Assert(update_xact == InvalidTransactionId); - update_xact = members[i].xid; + /* there can be at most one updater */ + Assert(update_xact == InvalidTransactionId); + update_xact = members[i].xid; #ifndef USE_ASSERT_CHECKING - - /* - * * in an assert-enabled build, walk the whole array to ensure - * * there's no other updater. - * */ - break; + /* + * in an assert-enabled build, walk the whole array to ensure + * there's no other updater. + */ + break; #endif - } + } - pfree(members); - } + pfree(members); + } - return update_xact; + return update_xact; } @@ -2019,7 +2028,7 @@ void vertex_index_fetch_end(struct IndexFetchTableData *data) { * * *all_dead, if all_dead is not NULL, should be set to true by * index_fetch_tuple iff it is guaranteed that no backend needs to see - * that tuple. Index AMs can use that to avoid returning that tid in + * that tuple. Index AMs can use that to av-oid returning that tid in * future searches. */ bool vertex_index_fetch_tuple(struct IndexFetchTableData *scan, @@ -2034,7 +2043,7 @@ bool vertex_index_fetch_tuple(struct IndexFetchTableData *scan, } -/* ------------------------------------------------------------------------ +/* ----------------------------------------------------------------------- * Callbacks for non-modifying operations on individual tuples * ------------------------------------------------------------------------ */ @@ -2124,6 +2133,29 @@ void vertex_tuple_insert(Relation relation, TupleTableSlot *slot, slot->tts_tableOid = RelationGetRelid(relation); tuple->t_tableOid = slot->tts_tableOid; + + graphid id = DATUM_GET_GRAPHID(slot->tts_values[0]); + gtype *properties = DATUM_GET_GTYPE_P(slot->tts_values[1]); + + + + gtype *shmem_properties = ShmemAlloc(VARSIZE(properties)); + memcpy(shmem_properties, properties, VARSIZE(properties)); + vertex_hash_struct *ctl = ShmemAlloc(sizeof(vertex_hash_struct)); + ctl->id = id, + ctl->properties = shmem_properties; + + HASHCTL hash_ctl; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(graphid); + hash_ctl.entrysize = sizeof(vertex_hash_struct); + + HTAB *rel_hash_table = ShmemInitHash(RelationGetRelationName(relation),16, 1000, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_COMPARE); + bool found; + hash_search(rel_hash_table, ctl, HASH_ENTER, &found); + /* Perform the insertion, and copy the resulting ItemPointer */ heap_insert(relation, tuple, cid, options, bistate); ItemPointerCopy(&tuple->t_self, &slot->tts_tid); @@ -2678,6 +2710,15 @@ void vertex_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, srel = RelationCreateStorage(*newrnode, persistence); smgrclose(srel); + HASHCTL hash_ctl; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(graphid); + hash_ctl.entrysize = sizeof(vertex_hash_struct); + + HTAB *rel_hash_table = ShmemInitHash(RelationGetRelationName(rel),16, 1000, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_COMPARE); + } /* From 9e0b5237c91688db5055e1954da8d681968ae6ca Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 7 Jun 2025 00:54:53 -0500 Subject: [PATCH 10/56] Scan from the Hash Table --- regress/sql/vertex_am.sql | 2 ++ .../vertex_heap/vertex_heapam_handler.c | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index 726d60f0..3d4d9b7d 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -38,5 +38,7 @@ SELECT * FROM vertex_am_tst; UPDATE vertex_am_tst SET id = '2'::postgraph.graphid; +INSERT INTO vertex_am_tst (id, props) +VALUES ('4'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); SELECT * FROM vertex_am_tst; diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 15040cb3..f88dae11 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -74,7 +74,10 @@ typedef struct vertex_hash_struct } vertex_hash_struct; - +static vertex_hash_struct *iterator = NULL; +static HTAB *vertex_hash = NULL; +static HASH_SEQ_STATUS scanStatus; +//BlackPink /* * * Given infomask/infomask2, compute the bits that must be saved in the * * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock, @@ -1773,6 +1776,7 @@ vertex_heapam_slot_callbacks(Relation relation) return &TTSOpsBufferHeapTuple; } + /* ------------------------------------------------------------------------ * Functions related to scaning * ------------------------------------------------------------------------ @@ -1789,6 +1793,16 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, * the scan has a pointer to it. Caller should be holding the rel open * anyway, so this is redundant in all normal scenarios... */ + HASHCTL hash_ctl; +//BlackPink + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(graphid); + hash_ctl.entrysize = sizeof(vertex_hash_struct); + vertex_hash = ShmemInitHash(RelationGetRelationName(relation),16, 1000, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_COMPARE); + hash_seq_init(&scanStatus, vertex_hash); + + RelationIncrementReferenceCount(relation); /* @@ -1864,7 +1878,8 @@ void vertex_scan_end(TableScanDesc sscan) { HeapScanDesc scan = (HeapScanDesc) sscan; /* Note: no locking manipulations needed */ - +hash_seq_term(&scanStatus); +//BlackPink /* * unpin scan buffers */ @@ -1928,6 +1943,17 @@ bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, pgstat_count_heap_getnext(scan->rs_base.rs_rd); ExecStoreBufferHeapTuple(&scan->rs_ctup, slot, scan->rs_cbuf); + +//BlackPink + if ((iterator = hash_seq_search(&scanStatus)) != NULL) { + slot->tts_values[0] = GRAPHID_GET_DATUM(iterator->id); + slot->tts_values[1] = GTYPE_P_GET_DATUM(iterator->properties); + } else { + return false; + } + + + return true; } @@ -2150,7 +2176,7 @@ void vertex_tuple_insert(Relation relation, TupleTableSlot *slot, MemSet(&hash_ctl, 0, sizeof(hash_ctl)); hash_ctl.keysize = sizeof(graphid); hash_ctl.entrysize = sizeof(vertex_hash_struct); - +//BlackPink HTAB *rel_hash_table = ShmemInitHash(RelationGetRelationName(relation),16, 1000, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_COMPARE); bool found; From ef6574072ada85094a5e0a446b78662aa94008e2 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 12 Jul 2025 17:04:00 -0500 Subject: [PATCH 11/56] Remove Old Codebase This needs to be rewritten. It was a first draft and now that we want to rewrite the storage engine its time to revisit the transform logic too --- Makefile | 41 +- regress/expected/aggregation.out | 843 --- regress/expected/cypher_call.out | 130 - regress/expected/cypher_delete.out | 533 -- regress/expected/cypher_merge.out | 541 -- regress/expected/cypher_remove.out | 389 -- regress/expected/cypher_set.out | 573 -- regress/expected/cypher_setop.out | 193 - regress/expected/cypher_unwind.out | 171 - regress/expected/cypher_vle.out | 402 -- regress/expected/load_csv.out | 45 - regress/expected/order_by.out | 103 - regress/expected/postgis.out | 3880 ----------- regress/expected/traversal_functions.out | 115 - regress/expected/variable_edge_functions.out | 182 - regress/sql/aggregation.sql | 273 - regress/sql/cypher_call.sql | 57 - regress/sql/cypher_delete.sql | 236 - regress/sql/cypher_merge.sql | 479 -- regress/sql/cypher_remove.sql | 154 - regress/sql/cypher_set.sql | 190 - regress/sql/cypher_setop.sql | 85 - regress/sql/cypher_unwind.sql | 90 - regress/sql/cypher_vle.sql | 126 - regress/sql/load_csv.sql | 28 - regress/sql/order_by.sql | 48 - regress/sql/postgis.sql | 1113 --- regress/sql/variable_edge_functions.sql | 69 - sql/postgraph-gtype.sql.in | 25 +- sql/postgraph.sql.in | 49 - src/backend/access/ivfflat/ivfbuild.c | 1075 --- src/backend/access/ivfflat/ivfflat.c | 246 - src/backend/access/ivfflat/ivfinsert.c | 192 - src/backend/access/ivfflat/ivfkmeans.c | 512 -- src/backend/access/ivfflat/ivfscan.c | 363 - src/backend/access/ivfflat/ivfutils.c | 523 -- src/backend/access/ivfflat/ivfvacuum.c | 144 - src/backend/catalog/ag_label.c | 65 +- src/backend/commands/graph_commands.c | 1 - src/backend/executor/cypher_create.c | 129 +- src/backend/executor/cypher_delete.c | 483 -- src/backend/executor/cypher_merge.c | 927 --- src/backend/executor/cypher_set.c | 640 -- src/backend/executor/cypher_utils.c | 261 - src/backend/optimizer/cypher_createplan.c | 172 +- src/backend/optimizer/cypher_pathnode.c | 129 +- src/backend/optimizer/cypher_paths.c | 100 - src/backend/parser/cypher_clause.c | 5940 ++--------------- src/backend/parser/cypher_grouping.c | 874 +++ src/backend/postgraph.c | 7 +- src/backend/utils/adt/gtype.c | 70 +- src/backend/utils/adt/gtype_funcs.c | 2224 ++++++ src/backend/utils/adt/gtype_geometric.c | 290 - src/backend/utils/adt/gtype_network.c | 213 - src/backend/utils/adt/gtype_numbers.c | 489 -- src/backend/utils/adt/gtype_range.c | 158 - src/backend/utils/adt/gtype_string.c | 285 - src/backend/utils/adt/gtype_temporal.c | 831 --- src/backend/utils/adt/gtype_tsearch.c | 142 - src/backend/utils/adt/queue.c | 241 - src/backend/utils/adt/variable_edge.c | 22 - src/backend/utils/path_finding/dfs.c | 833 --- src/backend/utils/path_finding/global_graph.c | 662 -- src/include/access/ivfflat.h | 324 - src/include/access/vertex.h | 64 + src/include/executor/cypher_executor.h | 12 - src/include/executor/cypher_utils.h | 116 - src/include/optimizer/cypher_createplan.h | 10 - src/include/parser/cypher_clause.h | 7 + src/include/utils/global_graph.h | 49 - src/include/utils/path_finding.h | 39 - src/include/utils/queue.h | 67 - 72 files changed, 3851 insertions(+), 27243 deletions(-) delete mode 100644 regress/expected/aggregation.out delete mode 100644 regress/expected/cypher_call.out delete mode 100644 regress/expected/cypher_delete.out delete mode 100644 regress/expected/cypher_merge.out delete mode 100644 regress/expected/cypher_remove.out delete mode 100644 regress/expected/cypher_set.out delete mode 100644 regress/expected/cypher_setop.out delete mode 100644 regress/expected/cypher_unwind.out delete mode 100644 regress/expected/cypher_vle.out delete mode 100644 regress/expected/load_csv.out delete mode 100644 regress/expected/order_by.out delete mode 100644 regress/expected/postgis.out delete mode 100644 regress/expected/traversal_functions.out delete mode 100644 regress/expected/variable_edge_functions.out delete mode 100644 regress/sql/aggregation.sql delete mode 100644 regress/sql/cypher_call.sql delete mode 100644 regress/sql/cypher_delete.sql delete mode 100644 regress/sql/cypher_merge.sql delete mode 100644 regress/sql/cypher_remove.sql delete mode 100644 regress/sql/cypher_set.sql delete mode 100644 regress/sql/cypher_setop.sql delete mode 100644 regress/sql/cypher_unwind.sql delete mode 100644 regress/sql/cypher_vle.sql delete mode 100644 regress/sql/load_csv.sql delete mode 100644 regress/sql/order_by.sql delete mode 100644 regress/sql/postgis.sql delete mode 100644 regress/sql/variable_edge_functions.sql delete mode 100644 src/backend/access/ivfflat/ivfbuild.c delete mode 100644 src/backend/access/ivfflat/ivfflat.c delete mode 100644 src/backend/access/ivfflat/ivfinsert.c delete mode 100644 src/backend/access/ivfflat/ivfkmeans.c delete mode 100644 src/backend/access/ivfflat/ivfscan.c delete mode 100644 src/backend/access/ivfflat/ivfutils.c delete mode 100644 src/backend/access/ivfflat/ivfvacuum.c delete mode 100644 src/backend/executor/cypher_delete.c delete mode 100644 src/backend/executor/cypher_merge.c delete mode 100644 src/backend/executor/cypher_set.c delete mode 100644 src/backend/executor/cypher_utils.c create mode 100644 src/backend/parser/cypher_grouping.c create mode 100644 src/backend/utils/adt/gtype_funcs.c delete mode 100644 src/backend/utils/adt/gtype_geometric.c delete mode 100644 src/backend/utils/adt/gtype_network.c delete mode 100644 src/backend/utils/adt/gtype_numbers.c delete mode 100644 src/backend/utils/adt/gtype_range.c delete mode 100644 src/backend/utils/adt/gtype_string.c delete mode 100644 src/backend/utils/adt/gtype_temporal.c delete mode 100644 src/backend/utils/adt/gtype_tsearch.c delete mode 100644 src/backend/utils/adt/queue.c delete mode 100644 src/backend/utils/path_finding/dfs.c delete mode 100644 src/backend/utils/path_finding/global_graph.c delete mode 100644 src/include/access/ivfflat.h create mode 100644 src/include/access/vertex.h delete mode 100644 src/include/executor/cypher_utils.h delete mode 100644 src/include/utils/global_graph.h delete mode 100644 src/include/utils/path_finding.h delete mode 100644 src/include/utils/queue.h diff --git a/Makefile b/Makefile index 7f81c379..67ea474b 100644 --- a/Makefile +++ b/Makefile @@ -18,13 +18,6 @@ MODULE_big = postgraph OBJS = src/backend/postgraph.o \ - src/backend/access/ivfflat/ivfbuild.o \ - src/backend/access/ivfflat/ivfflat.o \ - src/backend/access/ivfflat/ivfinsert.o \ - src/backend/access/ivfflat/ivfkmeans.o \ - src/backend/access/ivfflat/ivfscan.o \ - src/backend/access/ivfflat/ivfutils.o \ - src/backend/access/ivfflat/ivfvacuum.o \ src/backend/access/vertex_heap/vertex_heapam_handler.o \ src/backend/catalog/ag_catalog.o \ src/backend/catalog/ag_graph.o \ @@ -33,9 +26,6 @@ OBJS = src/backend/postgraph.o \ src/backend/commands/graph_commands.o \ src/backend/commands/label_commands.o \ src/backend/executor/cypher_create.o \ - src/backend/executor/cypher_merge.o \ - src/backend/executor/cypher_set.o \ - src/backend/executor/cypher_utils.o \ src/backend/nodes/ag_nodes.o \ src/backend/nodes/cypher_copyfuncs.o \ src/backend/nodes/cypher_outfuncs.o \ @@ -46,34 +36,25 @@ OBJS = src/backend/postgraph.o \ src/backend/parser/ag_scanner.o \ src/backend/parser/cypher_analyze.o \ src/backend/parser/cypher_clause.o \ - src/backend/executor/cypher_delete.o \ src/backend/parser/sql_expr.o \ src/backend/parser/cypher_expr.o \ src/backend/parser/cypher_gram.o \ + src/backend/parser/cypher_grouping.o \ src/backend/parser/cypher_item.o \ src/backend/parser/cypher_keywords.o \ src/backend/parser/cypher_parse_agg.o \ src/backend/parser/cypher_parse_node.o \ src/backend/parser/cypher_parser.o \ src/backend/parser/cypher_transform_entity.o \ - src/backend/utils/adt/queue.o \ src/backend/utils/adt/gtype.o \ src/backend/utils/adt/gtype_ext.o \ src/backend/utils/adt/gtype_gin.o \ - src/backend/utils/adt/gtype_network.o \ src/backend/utils/adt/gtype_ops.o \ src/backend/utils/adt/gtype_parser.o \ src/backend/utils/adt/gtype_postgis.o \ - src/backend/utils/adt/gtype_range.o \ - src/backend/utils/adt/gtype_string.o \ - src/backend/utils/adt/gtype_numbers.o \ - src/backend/utils/adt/gtype_temporal.o \ - src/backend/utils/adt/gtype_tsearch.o \ + src/backend/utils/adt/gtype_funcs.o \ src/backend/utils/adt/gtype_typecasting.o \ - src/backend/utils/adt/gtype_geometric.o \ src/backend/utils/adt/gtype_util.o \ - src/backend/utils/path_finding/global_graph.o \ - src/backend/utils/path_finding/dfs.o \ src/backend/utils/adt/cypher_funcs.o \ src/backend/utils/adt/edge.o \ src/backend/utils/adt/graphid.o \ @@ -100,23 +81,7 @@ REGRESS = new_cypher \ tsearch \ range \ vector \ - cypher_create \ - cypher_match \ - cypher_with \ - cypher_call \ - cypher_set \ - cypher_remove \ - cypher_delete \ - cypher_merge \ - cypher_unwind \ - cypher_vle \ - load_csv \ - order_by \ - cypher_setop \ - aggregation \ - traversal_functions \ - variable_edge_functions \ - postgis + cypher_create srcdir=`pwd` POSTGIS_DIR ?= postgis_dir diff --git a/regress/expected/aggregation.out b/regress/expected/aggregation.out deleted file mode 100644 index 4fbb8331..00000000 --- a/regress/expected/aggregation.out +++ /dev/null @@ -1,843 +0,0 @@ -/* - * Copyright (C) 2023-2024 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -SET extra_float_digits = 0; -LOAD 'postgraph'; -SET search_path TO postgraph; -set timezone TO 'GMT'; --- --- avg(), sum(), count(), & count(*) --- -CREATE GRAPH college; -NOTICE: graph "college" has been created - create_graph --------------- - -(1 row) - -USE GRAPH college; - use_graph ------------ - -(1 row) - -CREATE (:students {name: 'Jack ', gpa: 3.0, age: 21, zip: 13275}) ; --- -(0 rows) - -CREATE (:students {name: 'Jill ', gpa: 3.5, age: 27, zip: 87962}) ; --- -(0 rows) - -CREATE (:students {name: 'Jim ', gpa: 3.75, age: 32, zip: 68752}) ; --- -(0 rows) - -CREATE (:students {name: 'Rick ', gpa: 2.5, age: 24, zip: '75612 '}) ; --- -(0 rows) - -CREATE (:students {name: 'Ann ', gpa: 3.8::numeric, age: 23}) ; --- -(0 rows) - -CREATE (:students {name: 'Derek ', gpa: 4.0, age: 19, zip: 65218}) ; --- -(0 rows) - -CREATE (:students {name: 'Jessica ', gpa: 3.9::numeric, age: 20}) ; --- -(0 rows) - -MATCH (u) RETURN corr(u.gpa, u.age) ; - corr ---------------------- - -0.0611333038221933 -(1 row) - -MATCH (u) RETURN covar_pop(u.gpa, u.age) ; - covar_pop --------------------- - -0.130612244897959 -(1 row) - -MATCH (u) RETURN covar_samp(u.gpa, u.age) ; - covar_samp --------------------- - -0.152380952380953 -(1 row) - -MATCH (u) RETURN regr_sxx(u.gpa, u.age) ; - regr_sxx ------------------- - 123.428571428571 -(1 row) - -MATCH (u) RETURN regr_syy(u.gpa, u.age) ; - regr_syy ------------------- - 1.81214285714286 -(1 row) - -MATCH (u) RETURN regr_sxy(u.gpa, u.age) ; - regr_sxy --------------------- - -0.914285714285715 -(1 row) - -MATCH (u) RETURN regr_slope(u.gpa, u.age) ; - regr_slope ----------------------- - -0.00740740740740741 -(1 row) - -MATCH (u) RETURN regr_intercept(u.gpa, u.age) ; - regr_intercept ------------------- - 3.66851851851852 -(1 row) - -MATCH (u) RETURN regr_avgx(u.gpa, u.age) ; - regr_avgx ------------------- - 23.7142857142857 -(1 row) - -MATCH (u) RETURN regr_avgy(u.gpa, u.age) ; - regr_avgy ------------------- - 3.49285714285714 -(1 row) - -MATCH (u) RETURN regr_r2(u.gpa, u.age) ; - regr_r2 ------------------- - 3.49285714285714 -(1 row) - -MATCH (u) RETURN avg(u.gpa), sum(u.gpa), sum(u.gpa)/count(u.gpa), count(u.gpa), count(*); - avg | sum | ?column? | count | count -------------------+----------------+-----------------------------+-------+------- - 3.49285714285714 | 24.45::numeric | 3.4928571428571429::numeric | 7 | 7 -(1 row) - -CREATE (:students {name: 'Dave ', age: 24}); --- -(0 rows) - -CREATE (:students {name: 'Mike ', age: 18}); --- -(0 rows) - -MATCH (u) RETURN (u); - u ------------------------------------------------------------------------------------------------------------------------ - {"id": 844424930131969, "label": "students", "properties": {"age": 21, "gpa": 3.0, "zip": 13275, "name": "Jack "}} - {"id": 844424930131970, "label": "students", "properties": {"age": 27, "gpa": 3.5, "zip": 87962, "name": "Jill "}} - {"id": 844424930131971, "label": "students", "properties": {"age": 32, "gpa": 3.75, "zip": 68752, "name": "Jim "}} - {"id": 844424930131972, "label": "students", "properties": {"age": 24, "gpa": 2.5, "zip": "75612 ", "name": "Rick "}} - {"id": 844424930131973, "label": "students", "properties": {"age": 23, "gpa": 3.8::numeric, "name": "Ann "}} - {"id": 844424930131974, "label": "students", "properties": {"age": 19, "gpa": 4.0, "zip": 65218, "name": "Derek "}} - {"id": 844424930131975, "label": "students", "properties": {"age": 20, "gpa": 3.9::numeric, "name": "Jessica "}} - {"id": 844424930131976, "label": "students", "properties": {"age": 24, "name": "Dave "}} - {"id": 844424930131977, "label": "students", "properties": {"age": 18, "name": "Mike "}} -(9 rows) - -MATCH (u) RETURN avg(u.gpa), sum(u.gpa), sum(u.gpa) / count(u.gpa), count(u.gpa), count(*); - avg | sum | ?column? | count | count -------------------+----------------+-----------------------------+-------+------- - 3.49285714285714 | 24.45::numeric | 3.4928571428571429::numeric | 7 | 9 -(1 row) - --- --- min() & max() --- -MATCH (u) RETURN min(u.gpa), max(u.gpa), count(u.gpa), count(*); - min | max | count | count ------+-----+-------+------- - 2.5 | 4.0 | 7 | 9 -(1 row) - -MATCH (u) RETURN min(u.gpa), max(u.gpa), count(u.gpa), count(*) ; - min | max | count | count ------+-----+-------+------- - 2.5 | 4.0 | 7 | 9 -(1 row) - -MATCH (u) RETURN min(u.name), max(u.name), count(u.name), count(*); - min | max | count | count ---------+---------+-------+------- - "Ann " | "Rick " | 9 | 9 -(1 row) - -MATCH (u) RETURN min(u.zip), max(u.zip), count(u.zip), count(*); - min | max | count | count -----------+-------+-------+------- - "75612 " | 87962 | 5 | 9 -(1 row) - --- --- stDev() & stDevP() --- -MATCH (u) RETURN stDev(u.gpa), stDevP(u.gpa); - stdev | stdevp --------------------+------------------- - 0.549566929066706 | 0.508800109100232 -(1 row) - --- --- percentileCont() & percentileDisc() --- -MATCH (u) RETURN percentileCont(u.gpa, .55), percentileDisc(u.gpa, .55), percentileCont(u.gpa, .9), percentileDisc(u.gpa, .9); - percentilecont | percentiledisc | percentilecont | percentiledisc -----------------+----------------+----------------+---------------- - 3.765 | 3.75 | 3.94 | 4.0 -(1 row) - -MATCH (u) RETURN percentileCont(u.gpa, .55); - percentilecont ----------------- - 3.765 -(1 row) - -MATCH (u) RETURN percentileDisc(u.gpa, .55); - percentiledisc ----------------- - 3.75 -(1 row) - --- --- collect() --- -MATCH (u) RETURN collect(u.name), collect(u.age), collect(u.gpa), collect(u.zip); - collect | collect | collect | collect --------------------------------------------------------------------------------------+--------------------------------------+--------------------------------------------------------+---------------------------------------- - ["Jack ", "Jill ", "Jim ", "Rick ", "Ann ", "Derek ", "Jessica ", "Dave ", "Mike "] | [21, 27, 32, 24, 23, 19, 20, 24, 18] | [3.0, 3.5, 3.75, 2.5, 3.8::numeric, 4.0, 3.9::numeric] | [13275, 87962, 68752, "75612 ", 65218] -(1 row) - -MATCH (u) RETURN collect(u.gpa), collect(u.gpa); - collect | collect ---------------------------------------------------------+-------------------------------------------------------- - [3.0, 3.5, 3.75, 2.5, 3.8::numeric, 4.0, 3.9::numeric] | [3.0, 3.5, 3.75, 2.5, 3.8::numeric, 4.0, 3.9::numeric] -(1 row) - -MATCH (u) RETURN collect(u.zip), collect(u.zip); - collect | collect -----------------------------------------+---------------------------------------- - [13275, 87962, 68752, "75612 ", 65218] | [13275, 87962, 68752, "75612 ", 65218] -(1 row) - -MATCH (u) WHERE u.name =~ 'billy ' RETURN collect(u.name); - collect ---------- - -(1 row) - --- --- DISTINCT inside aggregate functions --- -CREATE (:students {name: 'Sven ', gpa: 3.2, age: 27, zip: 94110}); --- -(0 rows) - -MATCH (u) RETURN (u) ; - u ------------------------------------------------------------------------------------------------------------------------ - {"id": 844424930131969, "label": "students", "properties": {"age": 21, "gpa": 3.0, "zip": 13275, "name": "Jack "}} - {"id": 844424930131970, "label": "students", "properties": {"age": 27, "gpa": 3.5, "zip": 87962, "name": "Jill "}} - {"id": 844424930131971, "label": "students", "properties": {"age": 32, "gpa": 3.75, "zip": 68752, "name": "Jim "}} - {"id": 844424930131972, "label": "students", "properties": {"age": 24, "gpa": 2.5, "zip": "75612 ", "name": "Rick "}} - {"id": 844424930131973, "label": "students", "properties": {"age": 23, "gpa": 3.8::numeric, "name": "Ann "}} - {"id": 844424930131974, "label": "students", "properties": {"age": 19, "gpa": 4.0, "zip": 65218, "name": "Derek "}} - {"id": 844424930131975, "label": "students", "properties": {"age": 20, "gpa": 3.9::numeric, "name": "Jessica "}} - {"id": 844424930131976, "label": "students", "properties": {"age": 24, "name": "Dave "}} - {"id": 844424930131977, "label": "students", "properties": {"age": 18, "name": "Mike "}} - {"id": 844424930131978, "label": "students", "properties": {"age": 27, "gpa": 3.2, "zip": 94110, "name": "Sven "}} -(10 rows) - -MATCH (u) RETURN count(u.zip), count(DISTINCT u.zip); - count | count --------+------- - 6 | 6 -(1 row) - -MATCH (u) RETURN count(u.age), count(DISTINCT u.age); - count | count --------+------- - 10 | 8 -(1 row) - --- test AUTO GROUP BY for aggregate functions -CREATE GRAPH group_by; -NOTICE: graph "group_by" has been created - create_graph --------------- - -(1 row) - -USE GRAPH group_by; - use_graph ------------ - -(1 row) - -CREATE (:row {i: 1, j: 2, k:3}); --- -(0 rows) - -CREATE (:row {i: 1, j: 2, k:4}); --- -(0 rows) - -CREATE (:row {i: 1, j: 3, k:5}); --- -(0 rows) - -CREATE (:row {i: 2, j: 3, k:6}); --- -(0 rows) - -MATCH (u:row) RETURN u.i, u.j, u.k; - i | j | k ----+---+--- - 1 | 2 | 3 - 1 | 2 | 4 - 1 | 3 | 5 - 2 | 3 | 6 -(4 rows) - -MATCH (u:row) RETURN u.i, u.j, sum(u.k); - i | j | sum ----+---+----- - 1 | 2 | 7 - 2 | 3 | 6 - 1 | 3 | 5 -(3 rows) - -CREATE (:L {a: 1, b: 2, c:3}); --- -(0 rows) - -CREATE (:L {a: 2, b: 3, c:1}); --- -(0 rows) - -CREATE (:L {a: 3, b: 1, c:2}); --- -(0 rows) - -/* - * TODO: Get the link from the opencypher website. - */ -MATCH (x:L) RETURN x.a, x.b, x.c, x.a + count(*) + x.b + count(*) + x.c; - a | b | c | ?column? ----+---+---+---------- - 3 | 1 | 2 | 8 - 2 | 3 | 1 | 8 - 1 | 2 | 3 | 8 -(3 rows) - -MATCH (x:L) RETURN x.a + x.b + x.c, x.a + x.b + x.c + count(*) + count(*) -; - ?column? | ?column? -----------+---------- - 6 | 12 -(1 row) - --- with WITH clause -MATCH (x:L) WITH x, count(x) AS cnt RETURN x.a + x.b + x.c + cnt; - ?column? ----------- - 7 - 7 - 7 -(3 rows) - -MATCH (x:L) WITH x, count(x) AS cnt RETURN x.a + x.b + x.c + cnt + cnt; - ?column? ----------- - 8 - 8 - 8 -(3 rows) - -MATCH(x:L) WITH x.a + x.b + x.c AS v, count(x) as cnt RETURN v + cnt + cnt; - ?column? ----------- - 12 -(1 row) - -MATCH (x:L) RETURN x.a, x.a + count(*) + x.b + count(*) + x.c GROUP BY x.a, x.b, x.c; -ERROR: syntax error at or near "GROUP" -LINE 1: ...RETURN x.a, x.a + count(*) + x.b + count(*) + x.c GROUP BY x... - ^ --- Why we need explicit grouping -MATCH (x:L) RETURN x.a, x.a + count(*) + x.b + count(*) + x.c; -ERROR: "x" must be either part of an explicitly listed key or used inside an aggregate function -LINE 1: MATCH (x:L) RETURN x.a, x.a + count(*) + x.b + count(*) + x.... - ^ -MATCH (x:L) RETURN x.a + count(*) + x.b + count(*) + x.c; -ERROR: "x" must be either part of an explicitly listed key or used inside an aggregate function -LINE 1: MATCH (x:L) RETURN x.a + count(*) + x.b + count(*) + x.c; - ^ --- --- Explicit GROUP BY --- -MATCH (x:L) RETURN x.a + count(*) + x.b + count(*) + x.c GROUP BY x.a, x.b, x.c; -ERROR: syntax error at or near "GROUP" -LINE 1: ...x:L) RETURN x.a + count(*) + x.b + count(*) + x.c GROUP BY x... - ^ -MATCH (x:L) RETURN x.a + x.b + x.c + count(*) + count(*) GROUP BY x.a + x.b + x.c; -ERROR: syntax error at or near "GROUP" -LINE 1: ...x:L) RETURN x.a + x.b + x.c + count(*) + count(*) GROUP BY x... - ^ -MATCH (x:L) RETURN x.a + x.b + x.c + count(*) + count(*); -ERROR: "x" must be either part of an explicitly listed key or used inside an aggregate function -LINE 1: MATCH (x:L) RETURN x.a + x.b + x.c + count(*) + count(*); - ^ -MATCH (x:L) WITH x.a + count(*) + x.b + count(*) + x.c as cnt GROUP BY x.a, x.b, x.c RETURN cnt ; - cnt ------ - 8 - 8 - 8 -(3 rows) - -MATCH (x:L) WITH x.a + x.b + x.c + count(*) + count(*) as cnt GROUP BY x.a + x.b + x.c RETURN cnt; - cnt ------ - 12 -(1 row) - --- --- Grouping Keywords --- -MATCH (x) RETURN x.i, x.j, x.k, COUNT(*) GROUP BY ROLLUP (x.i, x.j, x.k); -ERROR: syntax error at or near "GROUP" -LINE 1: MATCH (x) RETURN x.i, x.j, x.k, COUNT(*) GROUP BY ROLLUP (x.... - ^ -MATCH (x) RETURN x.i, x.j, x.k, COUNT(*) GROUP BY CUBE (x.i, x.j, x.k); -ERROR: syntax error at or near "GROUP" -LINE 1: MATCH (x) RETURN x.i, x.j, x.k, COUNT(*) GROUP BY CUBE (x.i,... - ^ --- --- HAVING Clause --- -MATCH (x) WITH count(x.i) AS cnt GROUP BY x.i RETURN cnt; - cnt ------ - 3 - 0 - 1 -(3 rows) - -MATCH (x) WITH count(x.i) AS cnt GROUP BY x.i HAVING count(x.i) > 1 RETURN cnt; - cnt ------ - 3 -(1 row) - --- --- Window Functions --- -MATCH (x) WITH x, row_number() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 2 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) WITH x, rank() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 1 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) WITH x, dense_rank() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 1 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 2 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) WITH x, percent_rank() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 0 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 0 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 1 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 0 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 0 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 0.5 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 1 -(7 rows) - -MATCH (x) WITH x, cume_dist() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+------------------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 0.666666666666667 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 0.666666666666667 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 1 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 0.333333333333333 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 0.666666666666667 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 1 -(7 rows) - -MATCH (x) WITH x, lag(x.k) OVER (PARTITION BY COALESCE(x.i, x.a) ORDER BY id(x) ) AS row_num RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 3 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 4 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 5 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 6 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | -(7 rows) - -MATCH (x) -WITH x, lag(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 3 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 4 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | -(7 rows) - -MATCH (x) -WITH x, lead(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 4 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 5 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | -(7 rows) - -MATCH (x) -WITH x, first_value(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 3 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 3 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 6 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | -(7 rows) - -MATCH (x) -WITH x, nth_value(x.k, 2) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 4 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 4 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 4 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | -(7 rows) - -MATCH (x) -WITH x, ntile(1) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 1 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 1 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 1 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 1 -(7 rows) - -MATCH (x) -WITH x, ntile(2) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 1 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 2 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 1 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 2 -(7 rows) - -MATCH (x) -WITH x, ntile(3) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 2 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) -WITH x, last_value(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 4 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 4 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 5 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 6 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | -(7 rows) - -MATCH (x) -WITH x, row_number() OVER w AS row_num WINDOW w AS (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 2 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) -WITH x, row_number() OVER w AS row_num WINDOW w AS (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 2 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) -WITH x, - row_number() OVER ( - PARTITION BY x.i - ORDER BY COALESCE(x.j, x.c) - ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 2 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) -WITH x, - row_number() OVER ( - PARTITION BY x.i - ORDER BY COALESCE(x.j, x.c) - ROWS BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING - ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 2 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) -WITH x, - row_number() OVER ( - PARTITION BY x.i - ORDER BY COALESCE(x.j, x.c) - ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING - ) AS row_num -RETURN x, row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 1 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 2 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 3 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 1 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | 1 - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | 2 - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | 3 -(7 rows) - -MATCH (x) RETURN x, last_value(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num; - x | row_num ----------------------------------------------------------------------------------+--------- - {"id": 844424930131969, "label": "row", "properties": {"i": 1, "j": 2, "k": 3}} | 4 - {"id": 844424930131970, "label": "row", "properties": {"i": 1, "j": 2, "k": 4}} | 4 - {"id": 844424930131971, "label": "row", "properties": {"i": 1, "j": 3, "k": 5}} | 5 - {"id": 844424930131972, "label": "row", "properties": {"i": 2, "j": 3, "k": 6}} | 6 - {"id": 1125899906842626, "label": "l", "properties": {"a": 2, "b": 3, "c": 1}} | - {"id": 1125899906842627, "label": "l", "properties": {"a": 3, "b": 1, "c": 2}} | - {"id": 1125899906842625, "label": "l", "properties": {"a": 1, "b": 2, "c": 3}} | -(7 rows) - -MATCH (x) RETURN x, row_number() OVER w AS row_num WINDOW w AS (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ); -ERROR: syntax error at or near "WINDOW" -LINE 1: ...ATCH (x) RETURN x, row_number() OVER w AS row_num WINDOW w A... - ^ --- --- FILTER Clause --- -MATCH (x) RETURN count(*) FILTER (WHERE x.i IS NOT NULL); - count -------- - 4 -(1 row) - --- --- Within Group --- -MATCH (x) RETURN x.i, rank(x.i, x.j) WITHIN GROUP (ORDER BY x.k) GROUP BY x.i; -ERROR: syntax error at or near "GROUP" -LINE 1: ...N x.i, rank(x.i, x.j) WITHIN GROUP (ORDER BY x.k) GROUP BY x... - ^ --- --- Aggregate Functions for Edge Type --- -CREATE GRAPH edge_aggregates; -NOTICE: graph "edge_aggregates" has been created - create_graph --------------- - -(1 row) - -USE GRAPH edge_aggregates; - use_graph ------------ - -(1 row) - -CREATE ()-[:e]->(); --- -(0 rows) - -CREATE ()-[:e]->(); --- -(0 rows) - -CREATE ()-[:e]->(); --- -(0 rows) - --- Within Group -MATCH ()-[e]->() RETURN collect(e); - collect ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"{\"id\": 844424930131969, \"start_id\": 281474976710657, \"end_id\": 281474976710658, \"label\": \"e\", \"properties\": {}}","{\"id\": 844424930131970, \"start_id\": 281474976710659, \"end_id\": 281474976710660, \"label\": \"e\", \"properties\": {}}","{\"id\": 844424930131971, \"start_id\": 281474976710661, \"end_id\": 281474976710662, \"label\": \"e\", \"properties\": {}}"} -(1 row) - -MATCH ()-[e]->() RETURN collect(e, 2); - collect ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"{\"id\": 844424930131969, \"start_id\": 281474976710657, \"end_id\": 281474976710658, \"label\": \"e\", \"properties\": {}}","{\"id\": 844424930131970, \"start_id\": 281474976710659, \"end_id\": 281474976710660, \"label\": \"e\", \"properties\": {}}"} -(1 row) - -MATCH () RETURN COUNT(*); - count -------- - 6 -(1 row) - --- --- Cleanup --- -DROP GRAPH college CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to table college._ag_label_vertex -drop cascades to table college._ag_label_edge -drop cascades to table college.students -NOTICE: graph "college" has been dropped - drop_graph ------------- - -(1 row) - -DROP GRAPH edge_aggregates CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to table edge_aggregates._ag_label_vertex -drop cascades to table edge_aggregates._ag_label_edge -drop cascades to table edge_aggregates.e -NOTICE: graph "edge_aggregates" has been dropped - drop_graph ------------- - -(1 row) - -DROP GRAPH group_by CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to table group_by._ag_label_vertex -drop cascades to table group_by._ag_label_edge -drop cascades to table group_by."row" -drop cascades to table group_by.l -NOTICE: graph "group_by" has been dropped - drop_graph ------------- - -(1 row) - --- --- End of tests --- diff --git a/regress/expected/cypher_call.out b/regress/expected/cypher_call.out deleted file mode 100644 index ccf6454b..00000000 --- a/regress/expected/cypher_call.out +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH cypher_call; -NOTICE: graph "cypher_call" has been created - create_graph --------------- - -(1 row) - -USE GRAPH cypher_call; - use_graph ------------ - -(1 row) - --- --- Create table to hold the start and end vertices to test the SRF --- -CYPHER CALL sin(1) as a RETURN a; - a --------------------- - 0.8414709848078965 -(1 row) - -CYPHER CALL sin(1) as b RETURN b; - b --------------------- - 0.8414709848078965 -(1 row) - -CYPHER WITH [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as lst CALL unnest(lst) as b RETURN b; - b ----- - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 -(10 rows) - -CYPHER WITH [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as lst CALL unnest(lst) as b WHERE b % 2 = 0 RETURN b; - b ----- - 2 - 4 - 6 - 8 - 10 -(5 rows) - -CYPHER CALL unnest([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) as b RETURN b; - b ----- - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 -(10 rows) - -CYPHER CALL pg_catalog.generate_series(1, 2, 3); -ERROR: syntax error at or near "." -LINE 1: CYPHER CALL pg_catalog.generate_series(1, 2, 3); - ^ -MATCH (n) CALL this_isnt_a_real_function(1, 2, 3); -ERROR: syntax error at or near ";" -LINE 1: MATCH (n) CALL this_isnt_a_real_function(1, 2, 3); - ^ -CYPHER CALL { RETURN 1 } RETURN 2; - ?column? ----------- - 2 -(1 row) - -CYPHER CALL { RETURN 1 as a } RETURN a; - a ---- - 1 -(1 row) - -CYPHER CALL { CREATE (n) } RETURN n; -ERROR: Call Cypher Subquery must end with a RETURN -CYPHER CALL { CREATE (n) RETURN n as n } RETURN n; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} -(1 row) - --- --- Clean up --- -DROP GRAPH cypher_call CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table cypher_call._ag_label_vertex -drop cascades to table cypher_call._ag_label_edge -NOTICE: graph "cypher_call" has been dropped - drop_graph ------------- - -(1 row) - --- --- End --- diff --git a/regress/expected/cypher_delete.out b/regress/expected/cypher_delete.out deleted file mode 100644 index b4c6643a..00000000 --- a/regress/expected/cypher_delete.out +++ /dev/null @@ -1,533 +0,0 @@ -/* - * 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. - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -SELECT create_graph('cypher_delete'); -NOTICE: graph "cypher_delete" has been created - create_graph --------------- - -(1 row) - -CREATE GRAPH cypher_delete; -ERROR: graph "cypher_delete" already exists -USE GRAPH cypher_delete; - use_graph ------------ - -(1 row) - ---Test 1: Delete Vertices -CREATE (:v); --- -(0 rows) - -CREATE (:v {i: 0, j: 5, a: 0}); --- -(0 rows) - -CREATE (:v {i: 1}); --- -(0 rows) - -MATCH(n) DELETE n RETURN n; - n -------------------------------------------------------------------------------- - {"id": 844424930131969, "label": "v", "properties": {}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 0, "j": 5}} - {"id": 844424930131971, "label": "v", "properties": {"i": 1}} -(3 rows) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 2: Delete Edges -CREATE (:v)-[:e]->(:v); --- -(0 rows) - ---Should Fail -MATCH(n1)-[e]->(n2) DELETE n1 RETURN n1; -ERROR: Cannot delete vertex n1, because it still has edges attached. To delete this vertex, you must first delete the attached edges. -MATCH(n1)-[e]->(n2) DELETE n2 RETURN n2; -ERROR: Cannot delete vertex n2, because it still has edges attached. To delete this vertex, you must first delete the attached edges. -MATCH(n1)-[e]->(n2) DELETE e RETURN e; - e ------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842625, "start_id": 844424930131972, "end_id": 844424930131973, "label": "e", "properties": {}} -(1 row) - -MATCH()-[e]->() DELETE e RETURN e; - e ---- -(0 rows) - ---Cleanup -MATCH(n) DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131972, "label": "v", "properties": {}} - {"id": 844424930131973, "label": "v", "properties": {}} -(2 rows) - ---Test 4: DETACH DELECT a vertex -CREATE (:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n1)-[e]->(n2) DETACH DELETE n1 RETURN e; - e ------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842626, "start_id": 844424930131974, "end_id": 844424930131975, "label": "e", "properties": {}} -(1 row) - ---Cleanup -MATCH(n) RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131975, "label": "v", "properties": {}} -(1 row) - ---Test 4: DETACH DELECT two vertices tied to the same edge -CREATE (:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e; - e ------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842627, "start_id": 844424930131976, "end_id": 844424930131977, "label": "e", "properties": {}} -(1 row) - ---Test 4: DETACH DELECT a vertex -CREATE (:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e; - e ------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842628, "start_id": 844424930131978, "end_id": 844424930131979, "label": "e", "properties": {}} -(1 row) - ---Test 5: DETACH DELETE a vertex that points to itself -CREATE (n:v)-[:e]->(n); --- -(0 rows) - -MATCH(n1)-[e]->() DETACH DELETE n1 RETURN e; - e ------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842629, "start_id": 844424930131980, "end_id": 844424930131980, "label": "e", "properties": {}} -(1 row) - ---Test 6: DETACH Delete a vertex twice -CREATE (n:v)-[:e]->(n); --- -(0 rows) - -MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e; - e ------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842630, "start_id": 844424930131981, "end_id": 844424930131981, "label": "e", "properties": {}} -(1 row) - ---Test 7: Test the SET Clause on DELETED node -CREATE (:v); --- -(0 rows) - -CREATE (:v {i: 0, j: 5, a: 0}); --- -(0 rows) - -CREATE (:v {i: 1}); --- -(0 rows) - -MATCH(n) DELETE n SET n.lol = 'ftw' RETURN n; - n ---------------------------------------------------------------------------------------------- - {"id": 844424930131975, "label": "v", "properties": {"lol": "ftw"}} - {"id": 844424930131982, "label": "v", "properties": {"lol": "ftw"}} - {"id": 844424930131983, "label": "v", "properties": {"a": 0, "i": 0, "j": 5, "lol": "ftw"}} - {"id": 844424930131984, "label": "v", "properties": {"i": 1, "lol": "ftw"}} -(4 rows) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 8: -CREATE (n:v)-[:e]->(:v) CREATE (n)-[:e]->(:v); --- -(0 rows) - -MATCH(n1)-[]->() DELETE n1 RETURN n1; -ERROR: Cannot delete vertex n1, because it still has edges attached. To delete this vertex, you must first delete the attached edges. ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131985, "label": "v", "properties": {}} - {"id": 844424930131986, "label": "v", "properties": {}} - {"id": 844424930131987, "label": "v", "properties": {}} -(3 rows) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 9: -CREATE (n:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n1)-[e]->() DELETE e, n1 RETURN n1; - n1 ---------------------------------------------------------- - {"id": 844424930131988, "label": "v", "properties": {}} -(1 row) - ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131989, "label": "v", "properties": {}} -(1 row) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 10: -CREATE (n:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n1)-[e]->() DELETE n1, e RETURN n1; -ERROR: Cannot delete vertex n1, because it still has edges attached. To delete this vertex, you must first delete the attached edges. ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131990, "label": "v", "properties": {}} - {"id": 844424930131991, "label": "v", "properties": {}} -(2 rows) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 11: Delete a vertex twice -CREATE (n:v)-[:e]->(:v), (n)-[:e]->(:v) RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131992, "label": "v", "properties": {}} -(1 row) - -MATCH(n1)-[e]->() DETACH DELETE n1 RETURN n1, e; - n1 | e ----------------------------------------------------------+------------------------------------------------------------------------------------------------------------------ - {"id": 844424930131992, "label": "v", "properties": {}} | {"id": 1125899906842635, "start_id": 844424930131992, "end_id": 844424930131993, "label": "e", "properties": {}} - {"id": 844424930131992, "label": "v", "properties": {}} | {"id": 1125899906842636, "start_id": 844424930131992, "end_id": 844424930131994, "label": "e", "properties": {}} -(2 rows) - ---Cleanup -MATCH(n) DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131993, "label": "v", "properties": {}} - {"id": 844424930131994, "label": "v", "properties": {}} -(2 rows) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 12: -CREATE (:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n)-[e]->() DETACH DELETE n CREATE (n)-[:e2]->(:v) RETURN e; -ERROR: vertex assigned to variable n was deleted ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131995, "label": "v", "properties": {}} - {"id": 844424930131996, "label": "v", "properties": {}} -(2 rows) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 13: -CREATE (n:v)-[:e]->(n); --- -(0 rows) - -MATCH(n)-[e]->(m) DETACH DELETE n CREATE (m)-[:e2]->(:v) RETURN e; -ERROR: vertex assigned to variable m was deleted ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131997, "label": "v", "properties": {}} -(1 row) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 14: -CREATE (n:v)-[:e]->(n); --- -(0 rows) - -CREATE (:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n)-[e]->(m) DETACH DELETE n CREATE (m)-[:e2]->(:v) RETURN e; -ERROR: vertex assigned to variable m was deleted ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930131998, "label": "v", "properties": {}} - {"id": 844424930131999, "label": "v", "properties": {}} - {"id": 844424930132000, "label": "v", "properties": {}} -(3 rows) - -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 15: -CREATE (:v); --- -(0 rows) - -MATCH(n) SET n.i = 0 DELETE n RETURN n; - n ---------------------------------------------------------------- - {"id": 844424930132001, "label": "v", "properties": {"i": 0}} -(1 row) - ---Cleanup -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 16: -CREATE (:v); --- -(0 rows) - -MATCH(n) DELETE n SET n.i = 0 RETURN n; - n ---------------------------------------------------------------- - {"id": 844424930132002, "label": "v", "properties": {"i": 0}} -(1 row) - ---Cleanup -MATCH(n) RETURN n; - n ---- -(0 rows) - ---Test 17: -CREATE (n:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n)-[e]->(m) DETACH DELETE n SET e.i = 1 RETURN e; - e ------------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842641, "start_id": 844424930132003, "end_id": 844424930132004, "label": "e", "properties": {"i": 1}} -(1 row) - ---Cleanup ---Note: Expect 1 vertex -MATCH(n) DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930132004, "label": "v", "properties": {}} -(1 row) - ---Test 18: -CREATE (n:v)-[:e]->(:v); --- -(0 rows) - -MATCH(n)-[e]->(m) SET e.i = 1 DETACH DELETE n RETURN e; - e ------------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842642, "start_id": 844424930132005, "end_id": 844424930132006, "label": "e", "properties": {"i": 1}} -(1 row) - ---Cleanup -MATCH(n) DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930132006, "label": "v", "properties": {}} -(1 row) - ---Test 19: -CREATE (n:v); --- -(0 rows) - -MATCH (n) DELETE n CREATE (n)-[:e]->(:v) RETURN n; -ERROR: vertex assigned to variable n was deleted ---Cleanup -MATCH(n) DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 844424930132007, "label": "v", "properties": {}} -(1 row) - ---Test 20 Undefined Reference: -MATCH (n) DELETE m RETURN n; -ERROR: undefined reference to variable m in DELETE clause -LINE 1: MATCH (n) DELETE m RETURN n; - ^ ---Test 21 Prepared Statements -/* -CREATE (v:v); - -PREPARE d AS MATCH (v) DELETE (v) RETURN v; -EXECUTE d; - -CREATE (v:v); -EXECUTE d; - ---Test 22 pl/pgsql Functions -CREATE (v:v); - -CREATE FUNCTION delete_test() -RETURNS TABLE(vertex vertex) -LANGUAGE plpgsql -VOLATILE -AS $BODY$ -BEGIN - RETURN QUERY MATCH (v) DELETE (v) RETURN v; -END -$BODY$; - -SELECT delete_test(); - -CREATE (v:v); -SELECT delete_test(); - --- Clean Up -MATCH(n) DETACH DELETE n RETURN n; -*/ ---Test 23 -CREATE (n:v)-[:e]->(); --- -(0 rows) - -CREATE (n:v)-[:e2]->(); --- -(0 rows) - -MATCH p=()-[]->() RETURN p; - p --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 844424930132009, "label": "v", "properties": {}}, {"id": 2251799813685249, "start_id": 844424930132009, "end_id": 281474976710658, "label": "e2", "properties": {}}, {"id": 281474976710658, "label": "", "properties": {}}] - [{"id": 844424930132008, "label": "v", "properties": {}}, {"id": 1125899906842643, "start_id": 844424930132008, "end_id": 281474976710657, "label": "e", "properties": {}}, {"id": 281474976710657, "label": "", "properties": {}}] -(2 rows) - -MATCH(n)-[e]->(m) DELETE e; --- -(0 rows) - -MATCH p=()-[]->() RETURN p; - p ---- -(0 rows) - --- Clean Up -MATCH(n) DELETE n RETURN n; - n ---------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} - {"id": 844424930132008, "label": "v", "properties": {}} - {"id": 844424930132009, "label": "v", "properties": {}} -(4 rows) - ---Test 24 -CREATE (n:v)-[:e]->(); --- -(0 rows) - -CREATE (n:v)-[:e2]->(); --- -(0 rows) - -MATCH p=()-[]->() RETURN p; - p --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 844424930132011, "label": "v", "properties": {}}, {"id": 2251799813685250, "start_id": 844424930132011, "end_id": 281474976710660, "label": "e2", "properties": {}}, {"id": 281474976710660, "label": "", "properties": {}}] - [{"id": 844424930132010, "label": "v", "properties": {}}, {"id": 1125899906842644, "start_id": 844424930132010, "end_id": 281474976710659, "label": "e", "properties": {}}, {"id": 281474976710659, "label": "", "properties": {}}] -(2 rows) - -MATCH(n)-[]->() DETACH DELETE n; --- -(0 rows) - -MATCH p=()-[]->() RETURN p; - p ---- -(0 rows) - --- Clean Up -MATCH(n) DELETE n RETURN n; - n --------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {}} - {"id": 281474976710660, "label": "", "properties": {}} -(2 rows) - --- --- Clean up --- -DROP FUNCTION delete_test; -ERROR: could not find a function named "delete_test" -SELECT drop_graph('cypher_delete', true); -ERROR: graph "ypher_delete" does not exist --- --- End --- diff --git a/regress/expected/cypher_merge.out b/regress/expected/cypher_merge.out deleted file mode 100644 index 19150bf3..00000000 --- a/regress/expected/cypher_merge.out +++ /dev/null @@ -1,541 +0,0 @@ -/* - * 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. - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH cypher_merge; -NOTICE: graph "cypher_merge" has been created - create_graph --------------- - -(1 row) - -USE GRAPH cypher_merge; - use_graph ------------ - -(1 row) - -/* - * Section 1: MERGE with single vertex - */ -/* - * test 1: Single MERGE Clause, path doesn't exist - */ ---test query -MERGE (n {i: 'Hello Merge'}); --- -(0 rows) - ---validate -MATCH (n) RETURN n; - n --------------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {"i": "Hello Merge"}} -(1 row) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 2: Single MERGE Clause, path exists - */ ---data setup -CREATE ({i: 'Hello Merge'}); --- -(0 rows) - ---test_query -MERGE ({i: 'Hello Merge'}); --- -(0 rows) - ---validate -MATCH (n) RETURN n; - n --------------------------------------------------------------------------- - {"id": 281474976710658, "label": "", "properties": {"i": "Hello Merge"}} -(1 row) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 3: Prev clause returns no results, no data created - */ ---test query -MATCH (n) MERGE ({i: n.i}); --- -(0 rows) - ---validate -MATCH (n) RETURN n; - n ---- -(0 rows) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 4: Prev clause has results, path exists - */ ---test query -CREATE ({i: 'Hello Merge'}); --- -(0 rows) - -MATCH (n) MERGE ({i: n.i}); --- -(0 rows) - ---validate -MATCH (n) RETURN n; - n --------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": "Hello Merge"}} -(1 row) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 5: Prev clause has results, path does not exist (differnt property name) - */ ---data setup -CREATE ({i: 'Hello Merge'}); --- -(0 rows) - ---test query -MATCH (n) MERGE ({j: n.i}); -ERROR: attribute 1 of type record has wrong type -DETAIL: Table has type gtype, but query expects vertex. ---validate -MATCH (n) RETURN n; - n --------------------------------------------------------------------------- - {"id": 281474976710660, "label": "", "properties": {"i": "Hello Merge"}} -(1 row) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 6: MERGE with no prev clause, filters correctly, data created - */ --- setup -CREATE ({i: 2}); --- -(0 rows) - ---test query -MERGE (n {i: 1}) RETURN n; - n --------------------------------------------------------------- - {"id": 281474976710663, "label": "", "properties": {"i": 1}} -(1 row) - ---validate -MATCH (n) RETURN n; - n --------------------------------------------------------------- - {"id": 281474976710662, "label": "", "properties": {"i": 2}} - {"id": 281474976710663, "label": "", "properties": {"i": 1}} -(2 rows) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 7: MERGE with no prev clause, filters correctly, no data created - */ --- setup -CREATE ({i: 1}); --- -(0 rows) - -CREATE ({i: 1}); --- -(0 rows) - -CREATE ({i: 2}); --- -(0 rows) - -CREATE (); --- -(0 rows) - ---test query -MERGE (n {i: 1}) RETURN n; - n --------------------------------------------------------------- - {"id": 281474976710664, "label": "", "properties": {"i": 1}} - {"id": 281474976710665, "label": "", "properties": {"i": 1}} -(2 rows) - ---validate -MATCH (n) RETURN n; - n --------------------------------------------------------------- - {"id": 281474976710664, "label": "", "properties": {"i": 1}} - {"id": 281474976710665, "label": "", "properties": {"i": 1}} - {"id": 281474976710666, "label": "", "properties": {"i": 2}} - {"id": 281474976710667, "label": "", "properties": {}} -(4 rows) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * Section 2: MERGE with edges - */ -/* - * test 8: MERGE creates edge - */ --- setup -CREATE (); --- -(0 rows) - ---test query -MATCH (n) MERGE (n)-[:e]->(:v); --- -(0 rows) - ---validate -MATCH (n)-[e:e]->(m:v) RETURN n, e, m; - n | e | m ---------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------- - {"id": 281474976710668, "label": "", "properties": {}} | {"id": 844424930131969, "start_id": 281474976710668, "end_id": 1125899906842625, "label": "e", "properties": {}} | {"id": 1125899906842625, "label": "v", "properties": {}} -(1 row) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 9: edge already exists - */ --- setup -CREATE ()-[:e]->(); --- -(0 rows) - ---test query -MERGE (n)-[:e]->(:v); --- -(0 rows) - ---validate -MATCH (n)-[e:e]->(m:v) RETURN n, e, m; - n | e | m ---------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------- - {"id": 281474976710671, "label": "", "properties": {}} | {"id": 844424930131971, "start_id": 281474976710671, "end_id": 1125899906842626, "label": "e", "properties": {}} | {"id": 1125899906842626, "label": "v", "properties": {}} -(1 row) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 10: edge doesn't exist, using MATCH - */ --- setup -CREATE (); --- -(0 rows) - ---test query -MATCH (n) MERGE (n)-[:e]->(:v); --- -(0 rows) - ---validate created correctly -MATCH (n)-[e:e]->(m:v) RETURN n, e, m; - n | e | m ---------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------- - {"id": 281474976710672, "label": "", "properties": {}} | {"id": 844424930131972, "start_id": 281474976710672, "end_id": 1125899906842627, "label": "e", "properties": {}} | {"id": 1125899906842627, "label": "v", "properties": {}} -(1 row) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 11: edge already exists, using MATCH - */ --- setup -CREATE ()-[:e]->(); --- -(0 rows) - ---test query -MATCH (n) MERGE (n)-[:e]->(:v); --- -(0 rows) - ---validate created correctly -MATCH (n)-[e:e]->(m:v) RETURN n, e, m; - n | e | m ---------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------- - {"id": 281474976710673, "label": "", "properties": {}} | {"id": 844424930131974, "start_id": 281474976710673, "end_id": 1125899906842628, "label": "e", "properties": {}} | {"id": 1125899906842628, "label": "v", "properties": {}} - {"id": 281474976710674, "label": "", "properties": {}} | {"id": 844424930131975, "start_id": 281474976710674, "end_id": 1125899906842629, "label": "e", "properties": {}} | {"id": 1125899906842629, "label": "v", "properties": {}} -(2 rows) - ---clean up -MATCH (n) DETACH DELETE n; --- -(0 rows) - -/* - * test 12: Partial Path Exists, creates whole path - */ --- setup -CREATE ()-[:e]->(); --- -(0 rows) - ---test query -MERGE ()-[:e]->()-[:e]->(); --- -(0 rows) - ---validate created correctly ---Returns 3. One for the data setup and 2 for the longer path in MERGE -SELECT count(*) FROM cypher('cypher_merge',; --- Returns 1, the path created in MERGE -SELECT count(*) FROM cypher('cypher_merge',; --- 5 vertices total should have been created -SELECT count(*) FROM cypher('cypher_merge',; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 13: edge doesn't exists (differnt label), using MATCH - */ --- setup -CREATE ()-[:e]->(); ---test query -MATCH (n) MERGE (n)-[:e_new]->(:v); ---validate created correctly -MATCH (n)-[e]->(m:v) RETURN n, e, m; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 14: edge doesn't exists (different label), without MATCH - */ --- setup -CREATE ()-[:e]->(); ---test query -MERGE (n)-[:e_new]->(:v); ---validate created correctly -MATCH (n)-[e]->(m:v) RETURN n, e, m; ---clean up -MATCH (n) DETACH DELETE n; -/* - * Section 3: MERGE with writing clauses - */ -/* - * test 15: - */ ---test query -CREATE () MERGE (n); ---validate created correctly -MATCH (n) RETURN n; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 16: - */ ---test query -CREATE (n) WITH n as a MERGE (a)-[:e]->(); ---validate created correctly -MATCH p=()-[:e]->() RETURN p; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 17: - * TODO - */ ---test query -CREATE (n) MERGE (n)-[:e]->(); ---validate created correctly -MATCH p=()-[:e]->() RETURN p; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 18: - */ ---test query -CREATE (n {i : 1}) SET n.i = 2 MERGE ({i: 2}); ---validate created correctly -MATCH (a) RETURN a; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 19: - */ ---test query -CREATE (n {i : 1}) SET n.i = 2 WITH n as a MERGE ({i: 2}); ---validate created correctly -MATCH (a) RETURN a; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 20: - */ ---data setup -CREATE (n {i : 1}); ---test query -MATCH (n {i : 1}) SET n.i = 2 WITH n as a MERGE ({i: 2}); ---validate created correctly -MATCH (a) RETURN a; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 21: - */ ---data setup -CREATE (n {i : 1}); ---test query -MATCH (n {i : 1}) DELETE n MERGE (n)-[:e]->(); ---validate, transaction was rolled back because of the error message -MATCH (a) RETURN a; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 22: - * MERGE after MERGE - */ -CREATE (n:Person {name : 'Rob Reiner', bornIn: 'New York'}); -CREATE (n:Person {name : 'Michael Douglas', bornIn: 'New Jersey'}); -CREATE (n:Person {name : 'Martin Sheen', bornIn: 'Ohio'}); ---test query -MATCH (person:Person) -MERGE (city:City {name: person.bornIn}) -MERGE (person)-[r:BORN_IN]->(city) -RETURN person.name, person.bornIn, city; ---validate -MATCH (a) RETURN a; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 23: - */ -MERGE ()-[:e]-(); ---validate -MATCH p=()-[]->() RETURN p; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 24: - */ -MERGE (a) RETURN a; ---validate -MATCH (a) RETURN a; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 25: - */ -MERGE p=()-[:e]-() RETURN p; ---validate -MATCH p=()-[]->() RETURN p; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 26: - */ -MERGE (a)-[:e]-(b) RETURN a; ---validate -MATCH p=()-[]->() RETURN p; ---clean up -MATCH (n) DETACH DELETE n; -/* - * test 27: - */ -SELECT * FROM cypher('cypher_merge',; -MERGE p=()-[:e]-() RETURN p; ---clean up -MATCH (n) DETACH DELETE n; -/* - * Section 4: Error Messages - */ -/* - * test 28: - * Only single paths allowed - */ -MERGE (n), (m) RETURN n, m; -/* - * test 29: - * Edges cannot reference existing variables - */ -MATCH ()-[e]-() MERGE ()-[e]->(); -/* - * test 30: - * NULL vertex given to MERGE - */ ---data setup -CREATE (n); ---test query -MATCH (n) OPTIONAL MATCH (n)-[:e]->(m) MERGE (m); --- validate only 1 vertex exits -MATCH (n) RETURN n; --- --- MERGE/SET test - CREATE (n:node {name: 'Jason'}) RETURN n; - MATCH (n:node) RETURN n; - MERGE (n:node {name: 'Jason'}) SET n.name = 'Lisa' RETURN n; - MATCH (n:node) RETURN n; --- Node doesn't exist, is created, then set - MATCH (n:node) DELETE n; - MATCH (n:node) RETURN n; - MERGE (n:node {name: 'Jason'}) SET n.name = 'Lisa' RETURN n; - MATCH (n:node) RETURN n; --- Multiple SETs - MERGE (n:node {name: 'Lisa'}) SET n.age = 23, n.gender = 'Female' RETURN n; - MATCH (n:node) RETURN n; - MERGE (n:node {name: 'Jason'}) SET n.name = 'Lisa', n.age = 23, n.gender = 'Female' RETURN n; - MATCH (n:node) RETURN n; ---clean up -MATCH (n) DETACH DELETE n; -/* - * Clean up graph - */ -DROP GRAPH cypher_merge CASCADE; -ERROR: syntax error at or near "cypher" -LINE 1: SELECT count(*) FROM cypher('cypher_merge',; - ^ diff --git a/regress/expected/cypher_remove.out b/regress/expected/cypher_remove.out deleted file mode 100644 index b888fe2a..00000000 --- a/regress/expected/cypher_remove.out +++ /dev/null @@ -1,389 +0,0 @@ -/* - * 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. - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH cypher_remove; -NOTICE: graph "cypher_remove" has been created - create_graph --------------- - -(1 row) - -USE GRAPH cypher_remove; - use_graph ------------ - -(1 row) - ---test 1 -CREATE (:test_1); --- -(0 rows) - -CREATE (:test_1 {i: 0, j: 5, a: 0}); --- -(0 rows) - -CREATE (:test_1 {i: 1}); --- -(0 rows) - -MATCH (n:test_1) REMOVE n.i ; --- -(0 rows) - -MATCH (n:test_1) RETURN n; - n ----------------------------------------------------------------------------- - {"id": 844424930131969, "label": "test_1", "properties": {}} - {"id": 844424930131970, "label": "test_1", "properties": {"a": 0, "j": 5}} - {"id": 844424930131971, "label": "test_1", "properties": {}} -(3 rows) - ---test 2 -CREATE (:test_2); --- -(0 rows) - -CREATE (:test_2 {i: 0, j: 5, a: 0}); --- -(0 rows) - -CREATE (:test_2 {i: 1}); --- -(0 rows) - -MATCH (n:test_2) REMOVE n.j RETURN n; - n ------------------------------------------------------------------------------ - {"id": 1125899906842625, "label": "test_2", "properties": {}} - {"id": 1125899906842626, "label": "test_2", "properties": {"a": 0, "i": 0}} - {"id": 1125899906842627, "label": "test_2", "properties": {"i": 1}} -(3 rows) - -MATCH (n:test_2) RETURN n; - n ------------------------------------------------------------------------------ - {"id": 1125899906842625, "label": "test_2", "properties": {}} - {"id": 1125899906842626, "label": "test_2", "properties": {"a": 0, "i": 0}} - {"id": 1125899906842627, "label": "test_2", "properties": {"i": 1}} -(3 rows) - ---test 3 Validate Paths are updated -CREATE (:test_3 { i : 20 } )-[:test_3_edge {j:20}]->(:test_3 {i:10}); --- -(0 rows) - -MATCH p=(n)-[:test_3_edge]->() REMOVE n.i RETURN p; - p ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - [{"id": 1407374883553281, "label": "test_3", "properties": {"i": 20}}, {"id": 1688849860263937, "start_id": 1407374883553281, "end_id": 1407374883553282, "label": "test_3_edge", "properties": {"j": 20}}, {"id": 1407374883553282, "label": "test_3", "properties": {"i": 10}}] -(1 row) - ---test 4 Edges -MATCH (n) REMOVE n.i RETURN n; - n ----------------------------------------------------------------------------- - {"id": 844424930131969, "label": "test_1", "properties": {}} - {"id": 844424930131970, "label": "test_1", "properties": {"a": 0, "j": 5}} - {"id": 844424930131971, "label": "test_1", "properties": {}} - {"id": 1125899906842625, "label": "test_2", "properties": {}} - {"id": 1125899906842626, "label": "test_2", "properties": {"a": 0}} - {"id": 1125899906842627, "label": "test_2", "properties": {}} - {"id": 1407374883553282, "label": "test_3", "properties": {}} - {"id": 1407374883553281, "label": "test_3", "properties": {}} -(8 rows) - -CREATE (:test_4 { i : 20 } )-[:test_4_edge {j:20}]->(:test_4 {i:10}); --- -(0 rows) - -MATCH ()-[n]->(:test_4) REMOVE n.i RETURN n; -ERROR: operator does not exist: label_id postgraph.= integer -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. -MATCH ()-[n]->(:test_4) RETURN n; -ERROR: operator does not exist: label_id postgraph.= integer -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. ---test 5 two REMOVE clauses -CREATE (:test_5 {i: 1, j : 2, k : 3}) ; --- -(0 rows) - -MATCH (n:test_5) -REMOVE n.i -REMOVE n.j -RETURN n -; - n ---------------------------------------------------------------------- - {"id": 2533274790395905, "label": "test_5", "properties": {"k": 3}} -(1 row) - -MATCH (n:test_5) -RETURN n -; - n ---------------------------------------------------------------------- - {"id": 2533274790395905, "label": "test_5", "properties": {"k": 3}} -(1 row) - ---test 6 Create a loop and see that set can work after create -CREATE (:test_6 {j: 5, y: 99}); --- -(0 rows) - -MATCH (n {j: 5}) -CREATE p=(n)-[e:e {j:34}]->(n) -REMOVE n.y -RETURN n, p -; - n | p -----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 844424930131970, "label": "test_1", "properties": {"a": 0, "j": 5}} | [{"id": 844424930131970, "label": "test_1", "properties": {"a": 0, "j": 5}}, {"id": 3096224743817217, "start_id": 844424930131970, "end_id": 844424930131970, "label": "e", "properties": {"j": 34}}, {"id": 844424930131970, "label": "test_1", "properties": {"a": 0, "j": 5}}] - {"id": 2814749767106561, "label": "test_6", "properties": {"j": 5}} | [{"id": 2814749767106561, "label": "test_6", "properties": {"j": 5, "y": 99}}, {"id": 3096224743817218, "start_id": 2814749767106561, "end_id": 2814749767106561, "label": "e", "properties": {"j": 34}}, {"id": 2814749767106561, "label": "test_6", "properties": {"j": 5, "y": 99}}] -(2 rows) - -MATCH (n:test_6) RETURN n; - n ---------------------------------------------------------------------- - {"id": 2814749767106561, "label": "test_6", "properties": {"j": 5}} -(1 row) - ---test 7 Create a loop and see that set can work after create -CREATE (:test_7)-[e:e {j:34}]->() -REMOVE e.y -RETURN e -; - e --------------------------------------------------------------------------------------------------------------------------- - {"id": 3096224743817219, "start_id": 3377699720527873, "end_id": 281474976710657, "label": "e", "properties": {"j": 34}} -(1 row) - -MATCH (n:test_7) RETURN n; - n ---------------------------------------------------------------- - {"id": 3377699720527873, "label": "test_7", "properties": {}} -(1 row) - ---test 8 -MATCH (n:test_7) -MATCH (n)-[e:e {j:34}]->() -REMOVE n.y -RETURN n -; - n ---------------------------------------------------------------- - {"id": 3377699720527873, "label": "test_7", "properties": {}} -(1 row) - ---Handle Inheritance -CREATE ( {i : 1 }); --- -(0 rows) - -MATCH (n) REMOVE n.i RETURN n; - n ----------------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} - {"id": 844424930131969, "label": "test_1", "properties": {}} - {"id": 844424930131971, "label": "test_1", "properties": {}} - {"id": 844424930131970, "label": "test_1", "properties": {"a": 0, "j": 5}} - {"id": 1125899906842625, "label": "test_2", "properties": {}} - {"id": 1125899906842626, "label": "test_2", "properties": {"a": 0}} - {"id": 1125899906842627, "label": "test_2", "properties": {}} - {"id": 1407374883553282, "label": "test_3", "properties": {}} - {"id": 1407374883553281, "label": "test_3", "properties": {}} - {"id": 1970324836974593, "label": "test_4", "properties": {}} - {"id": 1970324836974594, "label": "test_4", "properties": {}} - {"id": 2533274790395905, "label": "test_5", "properties": {"k": 3}} - {"id": 2814749767106561, "label": "test_6", "properties": {"j": 5}} - {"id": 3377699720527873, "label": "test_7", "properties": {}} -(15 rows) - -MATCH (n) RETURN n; - n ----------------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} - {"id": 844424930131969, "label": "test_1", "properties": {}} - {"id": 844424930131971, "label": "test_1", "properties": {}} - {"id": 844424930131970, "label": "test_1", "properties": {"a": 0, "j": 5}} - {"id": 1125899906842625, "label": "test_2", "properties": {}} - {"id": 1125899906842626, "label": "test_2", "properties": {"a": 0}} - {"id": 1125899906842627, "label": "test_2", "properties": {}} - {"id": 1407374883553282, "label": "test_3", "properties": {}} - {"id": 1407374883553281, "label": "test_3", "properties": {}} - {"id": 1970324836974593, "label": "test_4", "properties": {}} - {"id": 1970324836974594, "label": "test_4", "properties": {}} - {"id": 2533274790395905, "label": "test_5", "properties": {"k": 3}} - {"id": 2814749767106561, "label": "test_6", "properties": {"j": 5}} - {"id": 3377699720527873, "label": "test_7", "properties": {}} -(15 rows) - --- prepared statements -/* -CREATE ( {i : 1 }); -PREPARE p_1 AS MATCH (n) REMOVE n.i RETURN n ; -EXECUTE p_1; - -CREATE ( {i : 1 }); -EXECUTE p_1; --- pl/pgsql -CREATE ( {i : 1 }); - -CREATE FUNCTION remove_test() -RETURNS TABLE(vertex vertex) -LANGUAGE plpgsql -VOLATILE -AS $BODY$ -BEGIN - RETURN QUERY MATCH (n) REMOVE n.i RETURN n; -END -$BODY$; - -SELECT remove_test(); - -CREATE ( {i : 1 }); -SELECT remove_test(); - -*/ --- --- Updating Multiple Fields --- -MATCH (n) RETURN n; - n ----------------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} - {"id": 844424930131969, "label": "test_1", "properties": {}} - {"id": 844424930131971, "label": "test_1", "properties": {}} - {"id": 844424930131970, "label": "test_1", "properties": {"a": 0, "j": 5}} - {"id": 1125899906842625, "label": "test_2", "properties": {}} - {"id": 1125899906842626, "label": "test_2", "properties": {"a": 0}} - {"id": 1125899906842627, "label": "test_2", "properties": {}} - {"id": 1407374883553282, "label": "test_3", "properties": {}} - {"id": 1407374883553281, "label": "test_3", "properties": {}} - {"id": 1970324836974593, "label": "test_4", "properties": {}} - {"id": 1970324836974594, "label": "test_4", "properties": {}} - {"id": 2533274790395905, "label": "test_5", "properties": {"k": 3}} - {"id": 2814749767106561, "label": "test_6", "properties": {"j": 5}} - {"id": 3377699720527873, "label": "test_7", "properties": {}} -(15 rows) - -MATCH (n) REMOVE n.i, n.j, n.k RETURN n; - n ---------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} - {"id": 844424930131969, "label": "test_1", "properties": {}} - {"id": 844424930131971, "label": "test_1", "properties": {}} - {"id": 844424930131970, "label": "test_1", "properties": {"a": 0}} - {"id": 1125899906842625, "label": "test_2", "properties": {}} - {"id": 1125899906842626, "label": "test_2", "properties": {"a": 0}} - {"id": 1125899906842627, "label": "test_2", "properties": {}} - {"id": 1407374883553282, "label": "test_3", "properties": {}} - {"id": 1407374883553281, "label": "test_3", "properties": {}} - {"id": 1970324836974593, "label": "test_4", "properties": {}} - {"id": 1970324836974594, "label": "test_4", "properties": {}} - {"id": 2533274790395905, "label": "test_5", "properties": {}} - {"id": 2814749767106561, "label": "test_6", "properties": {}} - {"id": 3377699720527873, "label": "test_7", "properties": {}} -(15 rows) - -MATCH (n) RETURN n; - n ---------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} - {"id": 844424930131969, "label": "test_1", "properties": {}} - {"id": 844424930131971, "label": "test_1", "properties": {}} - {"id": 844424930131970, "label": "test_1", "properties": {"a": 0}} - {"id": 1125899906842625, "label": "test_2", "properties": {}} - {"id": 1125899906842626, "label": "test_2", "properties": {"a": 0}} - {"id": 1125899906842627, "label": "test_2", "properties": {}} - {"id": 1407374883553282, "label": "test_3", "properties": {}} - {"id": 1407374883553281, "label": "test_3", "properties": {}} - {"id": 1970324836974593, "label": "test_4", "properties": {}} - {"id": 1970324836974594, "label": "test_4", "properties": {}} - {"id": 2533274790395905, "label": "test_5", "properties": {}} - {"id": 2814749767106561, "label": "test_6", "properties": {}} - {"id": 3377699720527873, "label": "test_7", "properties": {}} -(15 rows) - -CREATE ()-[:edge_multi_property { i: 5, j: 20}]->(); --- -(0 rows) - -MATCH ()-[e:edge_multi_property]-() RETURN e; - e ---------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 3659174697238529, "start_id": 281474976710659, "end_id": 281474976710660, "label": "edge_multi_property", "properties": {"i": 5, "j": 20}} - {"id": 3659174697238529, "start_id": 281474976710659, "end_id": 281474976710660, "label": "edge_multi_property", "properties": {"i": 5, "j": 20}} -(2 rows) - -MATCH ()-[e:edge_multi_property]-() REMOVE e.i, e.j RETURN e; - e ------------------------------------------------------------------------------------------------------------------------------------- - {"id": 3659174697238529, "start_id": 281474976710659, "end_id": 281474976710660, "label": "edge_multi_property", "properties": {}} - {"id": 3659174697238529, "start_id": 281474976710659, "end_id": 281474976710660, "label": "edge_multi_property", "properties": {}} -(2 rows) - ---Errors -REMOVE n.i; -ERROR: syntax error at or near "REMOVE" -LINE 1: REMOVE n.i; - ^ -MATCH (n) REMOVE n.i = NULL; -ERROR: REMOVE clause must be in the format: REMOVE variable.property_name -LINE 1: MATCH (n) REMOVE n.i = NULL; - ^ -MATCH (n) REMOVE wrong_var.i; -ERROR: undefined reference to variable wrong_var in REMOVE clause -LINE 1: MATCH (n) REMOVE wrong_var.i; - ^ --- --- Clean up --- -DROP FUNCTION remove_test; -ERROR: could not find a function named "remove_test" -DROP GRAPH cypher_remove CASCADE; -NOTICE: drop cascades to 13 other objects -DETAIL: drop cascades to table cypher_remove._ag_label_vertex -drop cascades to table cypher_remove._ag_label_edge -drop cascades to table cypher_remove.test_1 -drop cascades to table cypher_remove.test_2 -drop cascades to table cypher_remove.test_3 -drop cascades to table cypher_remove.test_3_edge -drop cascades to table cypher_remove.test_4 -drop cascades to table cypher_remove.test_4_edge -drop cascades to table cypher_remove.test_5 -drop cascades to table cypher_remove.test_6 -drop cascades to table cypher_remove.e -drop cascades to table cypher_remove.test_7 -drop cascades to table cypher_remove.edge_multi_property -NOTICE: graph "cypher_remove" has been dropped - drop_graph ------------- - -(1 row) - --- --- End --- diff --git a/regress/expected/cypher_set.out b/regress/expected/cypher_set.out deleted file mode 100644 index d83f0849..00000000 --- a/regress/expected/cypher_set.out +++ /dev/null @@ -1,573 +0,0 @@ -/* - * 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. - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH cypher_set; -NOTICE: graph "cypher_set" has been created - create_graph --------------- - -(1 row) - -USE GRAPH cypher_set; - use_graph ------------ - -(1 row) - -CREATE (:v); --- -(0 rows) - -CREATE (:v {i: 0, j: 5, a: 0}); --- -(0 rows) - -CREATE (:v {i: 1}); --- -(0 rows) - ---Simple SET test case -MATCH (n) SET n.i = 3; --- -(0 rows) - -MATCH (n) WHERE n.j = 5 SET n.i = NULL RETURN n; - n ------------------------------------------------------------------------ - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "j": 5}} -(1 row) - -MATCH (n) RETURN n; - n ------------------------------------------------------------------------ - {"id": 844424930131969, "label": "v", "properties": {"i": 3}} - {"id": 844424930131971, "label": "v", "properties": {"i": 3}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "j": 5}} -(3 rows) - -MATCH (n) SET n.i = NULL RETURN n; - n ------------------------------------------------------------------------ - {"id": 844424930131969, "label": "v", "properties": {}} - {"id": 844424930131971, "label": "v", "properties": {}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "j": 5}} -(3 rows) - -MATCH (n) RETURN n; - n ------------------------------------------------------------------------ - {"id": 844424930131969, "label": "v", "properties": {}} - {"id": 844424930131971, "label": "v", "properties": {}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "j": 5}} -(3 rows) - -MATCH (n) SET n.i = 3 RETURN n; - n -------------------------------------------------------------------------------- - {"id": 844424930131969, "label": "v", "properties": {"i": 3}} - {"id": 844424930131971, "label": "v", "properties": {"i": 3}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5}} -(3 rows) - -MATCH (n) RETURN n; - n -------------------------------------------------------------------------------- - {"id": 844424930131969, "label": "v", "properties": {"i": 3}} - {"id": 844424930131971, "label": "v", "properties": {"i": 3}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5}} -(3 rows) - ---Handle Inheritance -CREATE (); --- -(0 rows) - -MATCH (n) SET n.i = 3 RETURN n; - n -------------------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {"i": 3}} - {"id": 844424930131969, "label": "v", "properties": {"i": 3}} - {"id": 844424930131971, "label": "v", "properties": {"i": 3}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5}} -(4 rows) - -MATCH (n) RETURN n; - n -------------------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {"i": 3}} - {"id": 844424930131969, "label": "v", "properties": {"i": 3}} - {"id": 844424930131971, "label": "v", "properties": {"i": 3}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5}} -(4 rows) - ---Validate Paths are updated -MATCH (n) CREATE (n)-[:e {j:20}]->(:other_v {k:10}) RETURN n; - n -------------------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {"i": 3}} - {"id": 844424930131969, "label": "v", "properties": {"i": 3}} - {"id": 844424930131971, "label": "v", "properties": {"i": 3}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5}} -(4 rows) - -MATCH p=(n)-[]->() SET n.i = 50 RETURN p; - p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710657, "label": "", "properties": {"i": 3}}, {"id": 1125899906842625, "start_id": 281474976710657, "end_id": 1407374883553281, "label": "e", "properties": {"j": 20}}, {"id": 1407374883553281, "label": "other_v", "properties": {"k": 10}}] - [{"id": 844424930131969, "label": "v", "properties": {"i": 3}}, {"id": 1125899906842626, "start_id": 844424930131969, "end_id": 1407374883553282, "label": "e", "properties": {"j": 20}}, {"id": 1407374883553282, "label": "other_v", "properties": {"k": 10}}] - [{"id": 844424930131971, "label": "v", "properties": {"i": 3}}, {"id": 1125899906842627, "start_id": 844424930131971, "end_id": 1407374883553283, "label": "e", "properties": {"j": 20}}, {"id": 1407374883553283, "label": "other_v", "properties": {"k": 10}}] - [{"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5}}, {"id": 1125899906842628, "start_id": 844424930131970, "end_id": 1407374883553284, "label": "e", "properties": {"j": 20}}, {"id": 1407374883553284, "label": "other_v", "properties": {"k": 10}}] -(4 rows) - ---Edges -MATCH ()-[n]-(:other_v) SET n.i = 3 RETURN n; - n ----------------------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842625, "start_id": 281474976710657, "end_id": 1407374883553281, "label": "e", "properties": {"i": 3, "j": 20}} - {"id": 1125899906842626, "start_id": 844424930131969, "end_id": 1407374883553282, "label": "e", "properties": {"i": 3, "j": 20}} - {"id": 1125899906842627, "start_id": 844424930131971, "end_id": 1407374883553283, "label": "e", "properties": {"i": 3, "j": 20}} - {"id": 1125899906842628, "start_id": 844424930131970, "end_id": 1407374883553284, "label": "e", "properties": {"i": 3, "j": 20}} -(4 rows) - -MATCH ()-[n]->(:other_v) RETURN n; -ERROR: operator does not exist: label_id postgraph.= integer -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. -MATCH (n {j: 5}) SET n.y = 50 SET n.z = 99 RETURN n; - n --------------------------------------------------------------------------------------------------- - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 50, "z": 99}} -(1 row) - -MATCH (n {j: 5}) RETURN n; - n --------------------------------------------------------------------------------------------------- - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 50, "z": 99}} -(1 row) - ---Create a loop and see that set can work after create -MATCH (n {j: 5}) CREATE p=(n)-[e:e {j:34}]->(n) SET n.y = 99 RETURN n, p; - n | p ---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 99, "z": 99}} | [{"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 50, "z": 99}}, {"id": 1125899906842629, "start_id": 844424930131970, "end_id": 844424930131970, "label": "e", "properties": {"j": 34}}, {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 50, "z": 99}}] -(1 row) - ---Create a loop and see that set can work after create -CREATE ()-[e:e {j:34}]->() SET e.y = 99 RETURN e; - e ----------------------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842630, "start_id": 281474976710658, "end_id": 281474976710659, "label": "e", "properties": {"j": 34, "y": 99}} -(1 row) - -MATCH (n) MATCH (n)-[e:e {j:34}]->() SET n.y = 1 RETURN n; - n -------------------------------------------------------------------------------------------------- - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 1, "z": 99}} - {"id": 281474976710658, "label": "", "properties": {"y": 1}} -(2 rows) - -MATCH (n) MATCH ()-[e:e {j:34}]->(n) SET n.y = 2 RETURN n; - n -------------------------------------------------------------------------------------------------- - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 2, "z": 99}} - {"id": 281474976710659, "label": "", "properties": {"y": 2}} -(2 rows) - -MATCH (n)-[]->(n) SET n.y = 99 RETURN n; - n --------------------------------------------------------------------------------------------------- - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 99, "z": 99}} -(1 row) - -MATCH (n) MATCH (n)-[]->(m) SET n.t = 150 RETURN n; - n ------------------------------------------------------------------------------------------------------------- - {"id": 281474976710658, "label": "", "properties": {"t": 150, "y": 1}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 281474976710657, "label": "", "properties": {"i": 50, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": 50, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": 50, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "t": 150, "y": 99, "z": 99}} -(6 rows) - --- prepared statements -/* -PREPARE p_1 AS MATCH (n) SET n.i = 3 RETURN n ; -EXECUTE p_1; - -EXECUTE p_1; - -PREPARE p_2 AS MATCH (n) SET n.i = $var_name RETURN n ; -EXECUTE p_2('{"var_name": 4}'); - -EXECUTE p_2('{"var_name": 6}'); - -CREATE FUNCTION set_test() -RETURNS TABLE(vertex vertex) -LANGUAGE plpgsql -VOLATILE -AS $BODY$ -BEGIN - RETURN QUERY MATCH (n) SET n.i = 7 RETURN n ; -END -$BODY$; - -SELECT set_test(); - -SELECT set_test(); -*/ --- --- Updating multiple fieds --- -MATCH (n) SET n.i = 3, n.j = 5 RETURN n ; - n ------------------------------------------------------------------------------------------------------------ - {"id": 281474976710659, "label": "", "properties": {"i": 3, "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": 3, "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": 3, "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": 3, "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": 3, "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": 3, "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": 3, "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": 3, "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": 3, "j": 5, "k": 10}} -(10 rows) - -MATCH (n)-[m]->(n) SET m.y = n.y RETURN n, m; - n | m ------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------- - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5, "t": 150, "y": 99, "z": 99}} | {"id": 1125899906842629, "start_id": 844424930131970, "end_id": 844424930131970, "label": "e", "properties": {"j": 34, "y": 99}} -(1 row) - ---Errors -SET n.i = NULL; -ERROR: syntax error at or near "NULL" -LINE 1: SET n.i = NULL; - ^ -MATCH (n) SET wrong_var.i = 3; -ERROR: undefined reference to variable wrong_var in SET clause -LINE 1: MATCH (n) SET wrong_var.i = 3; - ^ -MATCH (n) SET i = 3; -ERROR: undefined reference to variable i in SET clause -LINE 1: MATCH (n) SET i = 3; - ^ --- --- SET refactor regression tests --- --- INSERT INTO -/* -CREATE TABLE tbl (result vertex); - - -MATCH (u:vertices) return u ; -MATCH (u:begin)-[:edge]->(v:end) return u, v ; - -INSERT INTO tbl (MATCH (u:vertices) SET u.i = 7 return u ; -INSERT INTO tbl (MATCH (u:vertices) SET u.i = 13 return u ; - -SELECT * FROM tbl; -*/ -CREATE (u:vertices) ; --- -(0 rows) - -CREATE (u:begin)-[:edge]->(v:end) ; --- -(0 rows) - -MATCH (u:vertices) return u ; - u ------------------------------------------------------------------ - {"id": 1688849860263937, "label": "vertices", "properties": {}} -(1 row) - -BEGIN; -MATCH (u:vertices) SET u.i = 1, u.j = 3, u.k = 5 return u ; - u ---------------------------------------------------------------------------------------- - {"id": 1688849860263937, "label": "vertices", "properties": {"i": 1, "j": 3, "k": 5}} -(1 row) - -MATCH (u:vertices) return u ; - u ---------------------------------------------------------------------------------------- - {"id": 1688849860263937, "label": "vertices", "properties": {"i": 1, "j": 3, "k": 5}} -(1 row) - -MATCH (u:vertices) SET u.i = 2, u.j = 4, u.k = 6 return u ; - u ---------------------------------------------------------------------------------------- - {"id": 1688849860263937, "label": "vertices", "properties": {"i": 2, "j": 4, "k": 6}} -(1 row) - -MATCH (u:vertices) return u ; - u ---------------------------------------------------------------------------------------- - {"id": 1688849860263937, "label": "vertices", "properties": {"i": 2, "j": 4, "k": 6}} -(1 row) - -MATCH (u:vertices) SET u.i = 3, u.j = 6, u.k = 9 return u ; - u ---------------------------------------------------------------------------------------- - {"id": 1688849860263937, "label": "vertices", "properties": {"i": 3, "j": 6, "k": 9}} -(1 row) - -MATCH (u:vertices) return u ; - u ---------------------------------------------------------------------------------------- - {"id": 1688849860263937, "label": "vertices", "properties": {"i": 3, "j": 6, "k": 9}} -(1 row) - -MATCH (u:begin)-[:edge]->(v:end) SET u.i = 1, v.i = 2, u.j = 3, v.j = 4 return u, v ; - u | v -----------------------------------------------------------------------------+-------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": 1, "j": 3}} | {"id": 2533274790395905, "label": "end", "properties": {"i": 2, "j": 4}} -(1 row) - -MATCH (u:begin)-[:edge]->(v:end) return u, v ; - u | v -----------------------------------------------------------------------------+-------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": 1, "j": 3}} | {"id": 2533274790395905, "label": "end", "properties": {"i": 2, "j": 4}} -(1 row) - -MATCH (u:begin)-[:edge]->(v:end) SET u.i = 2, v.i = 1, u.j = 4, v.j = 3 return u, v ; - u | v -----------------------------------------------------------------------------+-------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": 2, "j": 4}} | {"id": 2533274790395905, "label": "end", "properties": {"i": 1, "j": 3}} -(1 row) - -MATCH (u:begin)-[:edge]->(v:end) return u, v ; - u | v -----------------------------------------------------------------------------+-------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": 2, "j": 4}} | {"id": 2533274790395905, "label": "end", "properties": {"i": 1, "j": 3}} -(1 row) - -END; -MATCH (u:vertices) return u ; - u ---------------------------------------------------------------------------------------- - {"id": 1688849860263937, "label": "vertices", "properties": {"i": 3, "j": 6, "k": 9}} -(1 row) - -MATCH (u:begin)-[:edge]->(v:end) return u, v ; - u | v -----------------------------------------------------------------------------+-------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": 2, "j": 4}} | {"id": 2533274790395905, "label": "end", "properties": {"i": 1, "j": 3}} -(1 row) - --- test lists -MATCH (n) SET n.i = [3, 'test', [1, 2, 3], {id: 1}, 1.0, 1.0::numeric] RETURN n; - n ------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "k": 10}} - {"id": 1688849860263937, "label": "vertices", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 6, "k": 9}} - {"id": 1970324836974593, "label": "begin", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 4}} - {"id": 2533274790395905, "label": "end", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 3}} -(13 rows) - -MATCH (n) RETURN n; - n ------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 5, "k": 10}} - {"id": 1688849860263937, "label": "vertices", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 6, "k": 9}} - {"id": 1970324836974593, "label": "begin", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 4}} - {"id": 2533274790395905, "label": "end", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 3}} -(13 rows) - --- test that lists get updated in paths -MATCH p=(u:begin)-[:edge]->(v:end) SET u.i = [1, 2, 3] return u, p ; - u | p -------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": [1, 2, 3], "j": 4}} | [{"id": 1970324836974593, "label": "begin", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 4}}, {"id": 2251799813685249, "start_id": 1970324836974593, "end_id": 2533274790395905, "label": "edge", "properties": {}}, {"id": 2533274790395905, "label": "end", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 3}}] -(1 row) - -MATCH p=(u:begin)-[:edge]->(v:end) return u, p ; - u | p -------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": [1, 2, 3], "j": 4}} | [{"id": 1970324836974593, "label": "begin", "properties": {"i": [1, 2, 3], "j": 4}}, {"id": 2251799813685249, "start_id": 1970324836974593, "end_id": 2533274790395905, "label": "edge", "properties": {}}, {"id": 2533274790395905, "label": "end", "properties": {"i": [3, "test", [1, 2, 3], {"id": 1}, 1.0, 1::numeric], "j": 3}}] -(1 row) - --- test empty lists -MATCH (n) SET n.i = [] RETURN n; - n ------------------------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": [], "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": [], "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": [], "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": [], "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": [], "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": [], "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": [], "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": [], "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": [], "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": [], "j": 5, "k": 10}} - {"id": 1688849860263937, "label": "vertices", "properties": {"i": [], "j": 6, "k": 9}} - {"id": 1970324836974593, "label": "begin", "properties": {"i": [], "j": 4}} - {"id": 2533274790395905, "label": "end", "properties": {"i": [], "j": 3}} -(13 rows) - -MATCH (n) RETURN n; - n ------------------------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": [], "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": [], "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": [], "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": [], "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": [], "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": [], "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": [], "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": [], "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": [], "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": [], "j": 5, "k": 10}} - {"id": 1688849860263937, "label": "vertices", "properties": {"i": [], "j": 6, "k": 9}} - {"id": 1970324836974593, "label": "begin", "properties": {"i": [], "j": 4}} - {"id": 2533274790395905, "label": "end", "properties": {"i": [], "j": 3}} -(13 rows) - --- test maps -MATCH (n) SET n.i = {prop1: 3, prop2:'test', prop3: [1, 2, 3], prop4: {id: 1}, prop5: 1.0, prop6:1.0::numeric} RETURN n; - n ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "k": 10}} - {"id": 1688849860263937, "label": "vertices", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 6, "k": 9}} - {"id": 1970324836974593, "label": "begin", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 4}} - {"id": 2533274790395905, "label": "end", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 3}} -(13 rows) - -MATCH (n) RETURN n; - n ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 5, "k": 10}} - {"id": 1688849860263937, "label": "vertices", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 6, "k": 9}} - {"id": 1970324836974593, "label": "begin", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 4}} - {"id": 2533274790395905, "label": "end", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 3}} -(13 rows) - --- test maps in paths -MATCH p=(u:begin)-[:edge]->(v:end) SET u.i = {prop1: 1, prop2: 2, prop3: 3} return u, p ; - u | p ----------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": {"prop1": 1, "prop2": 2, "prop3": 3}, "j": 4}} | [{"id": 1970324836974593, "label": "begin", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 4}}, {"id": 2251799813685249, "start_id": 1970324836974593, "end_id": 2533274790395905, "label": "edge", "properties": {}}, {"id": 2533274790395905, "label": "end", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 3}}] -(1 row) - -MATCH p=(u:begin)-[:edge]->(v:end) return u, p ; - u | p ----------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 1970324836974593, "label": "begin", "properties": {"i": {"prop1": 1, "prop2": 2, "prop3": 3}, "j": 4}} | [{"id": 1970324836974593, "label": "begin", "properties": {"i": {"prop1": 1, "prop2": 2, "prop3": 3}, "j": 4}}, {"id": 2251799813685249, "start_id": 1970324836974593, "end_id": 2533274790395905, "label": "edge", "properties": {}}, {"id": 2533274790395905, "label": "end", "properties": {"i": {"prop1": 3, "prop2": "test", "prop3": [1, 2, 3], "prop4": {"id": 1}, "prop5": 1.0, "prop6": 1::numeric}, "j": 3}}] -(1 row) - --- test empty maps -MATCH (n) SET n.i = {} RETURN n; - n ------------------------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": {}, "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": {}, "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": {}, "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": {}, "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": {}, "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": {}, "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": {}, "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": {}, "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": {}, "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": {}, "j": 5, "k": 10}} - {"id": 1688849860263937, "label": "vertices", "properties": {"i": {}, "j": 6, "k": 9}} - {"id": 1970324836974593, "label": "begin", "properties": {"i": {}, "j": 4}} - {"id": 2533274790395905, "label": "end", "properties": {"i": {}, "j": 3}} -(13 rows) - -MATCH (n) RETURN n; - n ------------------------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"i": {}, "j": 5, "y": 2}} - {"id": 281474976710658, "label": "", "properties": {"i": {}, "j": 5, "t": 150, "y": 1}} - {"id": 281474976710657, "label": "", "properties": {"i": {}, "j": 5, "t": 150}} - {"id": 844424930131969, "label": "v", "properties": {"i": {}, "j": 5, "t": 150}} - {"id": 844424930131971, "label": "v", "properties": {"i": {}, "j": 5, "t": 150}} - {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": {}, "j": 5, "t": 150, "y": 99, "z": 99}} - {"id": 1407374883553281, "label": "other_v", "properties": {"i": {}, "j": 5, "k": 10}} - {"id": 1407374883553282, "label": "other_v", "properties": {"i": {}, "j": 5, "k": 10}} - {"id": 1407374883553283, "label": "other_v", "properties": {"i": {}, "j": 5, "k": 10}} - {"id": 1407374883553284, "label": "other_v", "properties": {"i": {}, "j": 5, "k": 10}} - {"id": 1688849860263937, "label": "vertices", "properties": {"i": {}, "j": 6, "k": 9}} - {"id": 1970324836974593, "label": "begin", "properties": {"i": {}, "j": 4}} - {"id": 2533274790395905, "label": "end", "properties": {"i": {}, "j": 3}} -(13 rows) - --- --- Clean up --- -DROP TABLE tbl; -ERROR: table "tbl" does not exist -DROP FUNCTION set_test; -ERROR: could not find a function named "set_test" -DROP GRAPH cypher_set CASCADE; -NOTICE: drop cascades to 9 other objects -DETAIL: drop cascades to table cypher_set._ag_label_vertex -drop cascades to table cypher_set._ag_label_edge -drop cascades to table cypher_set.v -drop cascades to table cypher_set.e -drop cascades to table cypher_set.other_v -drop cascades to table cypher_set.vertices -drop cascades to table cypher_set.begin -drop cascades to table cypher_set.edge -drop cascades to table cypher_set."end" -NOTICE: graph "cypher_set" has been dropped - drop_graph ------------- - -(1 row) - diff --git a/regress/expected/cypher_setop.out b/regress/expected/cypher_setop.out deleted file mode 100644 index a9317fbd..00000000 --- a/regress/expected/cypher_setop.out +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2024 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH set_op; -NOTICE: graph "set_op" has been created - create_graph --------------- - -(1 row) - -USE GRAPH set_op; - use_graph ------------ - -(1 row) - -CREATE (); --- -(0 rows) - -MATCH (n) RETURN n UNION MATCH (n) RETURN n; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} -(1 row) - -MATCH (n) RETURN n UNION ALL MATCH (n) RETURN n; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710657, "label": "", "properties": {}} -(2 rows) - -MATCH (n) RETURN n UNION RETURN 1; -ERROR: UNION could not convert type gtype to vertex -LINE 1: MATCH (n) RETURN n UNION RETURN 1; - ^ -MATCH (n) RETURN n UNION RETURN NULL; -ERROR: UNION could not convert type gtype to vertex -LINE 1: MATCH (n) RETURN n UNION RETURN NULL; - ^ -RETURN [1,2,3] UNION RETURN 1; - ?column? ------------ - [1, 2, 3] - 1 -(2 rows) - -RETURN NULL UNION RETURN NULL; - ?column? ----------- - -(1 row) - -RETURN NULL UNION ALL RETURN NULL; - ?column? ----------- - - -(2 rows) - -MATCH (n) RETURN n UNION MATCH (n) RETURN n UNION MATCH (n) RETURN n; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} -(1 row) - -MATCH (n) RETURN n UNION ALL MATCH (n) RETURN n UNION ALL MATCH(n) RETURN n; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710657, "label": "", "properties": {}} -(3 rows) - -MATCH (n) RETURN n UNION ALL MATCH (n) RETURN n UNION MATCH(n) RETURN n; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} -(1 row) - -MATCH (n) RETURN n UNION MATCH (n) RETURN n UNION ALL MATCH(n) RETURN n; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710657, "label": "", "properties": {}} -(2 rows) - -RETURN NULL UNION ALL RETURN NULL UNION ALL RETURN NULL; - ?column? ----------- - - - -(3 rows) - -RETURN NULL UNION ALL RETURN NULL UNION RETURN NULL; - ?column? ----------- - -(1 row) - -RETURN NULL UNION RETURN NULL UNION ALL RETURN NULL; - ?column? ----------- - - -(2 rows) - -RETURN 1.0::int UNION RETURN 1::float UNION ALL RETURN 2.0::float; - ?column? ----------- - 1 - 2.0 -(2 rows) - -RETURN 1.0::int UNION RETURN 1.0::float UNION ALL RETURN 1::int; - ?column? ----------- - 1 - 1 -(2 rows) - -RETURN 1.0::float UNION RETURN 1::int UNION RETURN 1::float; - ?column? ----------- - 1.0 -(1 row) - --- Intersect -MATCH (n) RETURN n INTERSECT MATCH (m) RETURN m; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} -(1 row) - -MATCH (n) RETURN n INTERSECT ALL MATCH (m) RETURN m; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} -(1 row) - --- Except -MATCH (n) RETURN n EXCEPT MATCH (m) RETURN m; - n ---- -(0 rows) - -MATCH (n) RETURN n EXCEPT ALL MATCH (m) RETURN m; - n ---- -(0 rows) - --- Operator Precedence -RETURN 2.0::int UNION (RETURN 1::float UNION ALL RETURN 1.0::float); - ?column? ----------- - 1.0 - 2 -(2 rows) - -(RETURN 2.0::int UNION RETURN 1::float ) UNION ALL RETURN 1.0::float; - ?column? ----------- - 1.0 - 2 - 1.0 -(3 rows) - --- Errors -MATCH (n) RETURN n UNION ALL MATCH (m) RETURN n; -ERROR: could not find rte for n -LINE 1: MATCH (n) RETURN n UNION ALL MATCH (m) RETURN n; - ^ -DROP GRAPH set_op; -ERROR: syntax error at or near ";" -LINE 1: DROP GRAPH set_op; - ^ diff --git a/regress/expected/cypher_unwind.out b/regress/expected/cypher_unwind.out deleted file mode 100644 index 51e59f96..00000000 --- a/regress/expected/cypher_unwind.out +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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. - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH cypher_unwind; -NOTICE: graph "cypher_unwind" has been created - create_graph --------------- - -(1 row) - -USE GRAPH cypher_unwind; - use_graph ------------ - -(1 row) - -UNWIND [1, 2, 3] AS i RETURN i; - i ---- - 1 - 2 - 3 -(3 rows) - -CREATE ({a: [1, 2, 3]}), ({a: [4, 5, 6]}); --- -(0 rows) - -MATCH (n) WITH n.a AS a UNWIND a AS i RETURN *; - a | i ------------+--- - [1, 2, 3] | 1 - [1, 2, 3] | 2 - [1, 2, 3] | 3 - [4, 5, 6] | 4 - [4, 5, 6] | 5 - [4, 5, 6] | 6 -(6 rows) - -CYPHER WITH [[1, 2], [3, 4], 5] AS nested -UNWIND nested AS x -UNWIND x AS y -RETURN y; - y ---- - 1 - 2 - 3 - 4 - 5 -(5 rows) - -CYPHER WITH [[1, 2], [3, 4], 5] AS nested -UNWIND nested AS x -UNWIND x AS y -RETURN x, y; - x | y ---------+--- - [1, 2] | 1 - [1, 2] | 2 - [3, 4] | 3 - [3, 4] | 4 - 5 | 5 -(5 rows) - -CYPHER WITH [1, 2, 3, 4, 5] AS lst -UNWIND lst AS x WHERE x % 2 = 0 -RETURN x; - x ---- - 2 - 4 -(2 rows) - --- TODO -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -SET a.i = 1 -RETURN a; -ERROR: SET clause can only update vertex and edges --- TODO -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -RETURN a; - a ----------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {"a": [1, 2, 3]}} - {"id": 281474976710658, "label": "", "properties": {"a": [4, 5, 6]}} -(2 rows) - -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -RETURN a; - a ----------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {"a": [1, 2, 3]}} - {"id": 281474976710658, "label": "", "properties": {"a": [4, 5, 6]}} -(2 rows) - -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -SET a.i = 1 -RETURN a; -ERROR: SET clause can only update vertex and edges -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -WITH a -SET a.i = 1 -RETURN a; -ERROR: SET clause can only update vertex and edges -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -CREATE ({i: a.i}) -RETURN a; - a ----------------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {"a": [1, 2, 3]}} - {"id": 281474976710658, "label": "", "properties": {"a": [4, 5, 6]}} -(2 rows) - -CREATE ()-[:e {a: [1, 2, 3]}]->(); --- -(0 rows) - -CREATE ()-[:e {a: [1, 2, 3]}]->(); --- -(0 rows) - -MATCH t=()-[:e]->() -UNWIND relationships(t) as rel -RETURN rel; - rel -------------------------------------------------------------------------------------------------------------------------------- - {"id": 844424930131969, "start_id": 281474976710661, "end_id": 281474976710662, "label": "e", "properties": {"a": [1, 2, 3]}} - {"id": 844424930131970, "start_id": 281474976710663, "end_id": 281474976710664, "label": "e", "properties": {"a": [1, 2, 3]}} -(2 rows) - -DROP GRAPH cypher_unwind CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to table cypher_unwind._ag_label_vertex -drop cascades to table cypher_unwind._ag_label_edge -drop cascades to table cypher_unwind.e -NOTICE: graph "cypher_unwind" has been dropped - drop_graph ------------- - -(1 row) - diff --git a/regress/expected/cypher_vle.out b/regress/expected/cypher_vle.out deleted file mode 100644 index 652ed362..00000000 --- a/regress/expected/cypher_vle.out +++ /dev/null @@ -1,402 +0,0 @@ -/* - * 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. - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH cypher_vle; -NOTICE: graph "cypher_vle" has been created - create_graph --------------- - -(1 row) - -USE GRAPH cypher_vle; - use_graph ------------ - -(1 row) - --- Create a graph to test -CREATE (b:begin)-[:edge {name: 'main edge'}]->(u1:middle)-[:edge {name: 'main edge'}]->(u2:middle)-[:edge {name: 'main edge'}]->(u3:middle)-[:edge {name: 'main edge'}]->(e:end), - (u1)-[:self_loop {name: 'self loop'}]->(u1), - (e)-[:self_loop {name: 'self loop'}]->(e), - (b)-[:alternate_edge {name: 'alternate edge'}]->(u1), - (u2)-[:alternate_edge {name: 'alternate edge'}]->(u3), - (u3)-[:alternate_edge {name: 'alternate edge'}]->(e), - (u2)-[:bypass_edge {name: 'bypass edge'}]->(e), - (e)-[:alternate_edge {name: 'backup edge'}]->(u3), - (u3)-[:alternate_edge {name: 'backup edge'}]->(u2), - (u2)-[:bypass_edge {name: 'bypass edge'}]->(b) -RETURN b, e; - b | e --------------------------------------------------------------+------------------------------------------------------------ - {"id": 844424930131969, "label": "begin", "properties": {}} | {"id": 1688849860263937, "label": "end", "properties": {}} -(1 row) - --- Test the VLE match integration --- Each should find 400 -MATCH (u:begin)-[*]->(v:end) RETURN count(*); - count -------- - 400 -(1 row) - -MATCH (u:begin)-[*..]->(v:end) RETURN count(*); - count -------- - 400 -(1 row) - -MATCH (u:begin)-[*0..]->(v:end) RETURN count(*); - count -------- - 400 -(1 row) - -MATCH (u:begin)-[*1..]->(v:end) RETURN count(*); - count -------- - 400 -(1 row) - -MATCH (u:begin)-[*1..200]->(v:end) RETURN count(*); - count -------- - 400 -(1 row) - --- Each should find 2 -MATCH (u:begin)<-[*]-(v:end) RETURN count(*); - count -------- - 2 -(1 row) - -MATCH (u:begin)<-[*..]-(v:end) RETURN count(*); - count -------- - 2 -(1 row) - -MATCH (u:begin)<-[*0..]-(v:end) RETURN count(*); - count -------- - 2 -(1 row) - -MATCH (u:begin)<-[*1..]-(v:end) RETURN count(*); - count -------- - 2 -(1 row) - -MATCH (u:begin)<-[*1..200]-(v:end) RETURN count(*); - count -------- - 2 -(1 row) - --- Each should find 7092 -MATCH (u:begin)-[*]-(v:end) RETURN count(*); - count -------- - 7092 -(1 row) - -MATCH (u:begin)-[*..]-(v:end) RETURN count(*); - count -------- - 7092 -(1 row) - -MATCH (u:begin)-[*0..]-(v:end) RETURN count(*); - count -------- - 7092 -(1 row) - -MATCH (u:begin)-[*1..]-(v:end) RETURN count(*); - count -------- - 7092 -(1 row) - -MATCH (u:begin)-[*1..200]-(v:end) RETURN count(*); - count -------- - 7092 -(1 row) - --- Each should find 1 -MATCH (u:begin)-[:edge*]-(v:end) RETURN count(*); - count -------- - 1 -(1 row) - -MATCH (u:begin)-[:edge* {name: 'main edge'}]-(v:end) RETURN count(*); - count -------- - 1 -(1 row) - -MATCH (u:begin)-[* {name: 'main edge'}]-(v:end) RETURN count(*); - count -------- - 1 -(1 row) - --- Each should find 1 -MATCH ()<-[*4..4 {name: 'main edge'}]-() RETURN count(*); - count -------- - 1 -(1 row) - -MATCH (u)<-[*4..4 {name: 'main edge'}]-() RETURN count(*); - count -------- - 1 -(1 row) - -MATCH ()<-[*4..4 {name: 'main edge'}]-(v) RETURN count(*); - count -------- - 1 -(1 row) - --- Each should find 2922 -MATCH ()-[*]->() RETURN count(*); - count -------- - 2922 -(1 row) - -MATCH (u)-[*]->() RETURN count(*); - count -------- - 2922 -(1 row) - -MATCH ()-[*]->(v) RETURN count(*); - count -------- - 2922 -(1 row) - --- Should find 2 -MATCH (u:begin)<-[e*]-(v:end) RETURN e; - e ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}] - [{"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}, {"id": 1970324836974594, "start_id": 1688849860263937, "end_id": 1688849860263937, "label": "self_loop", "properties": {"name": "self loop"}}] -(2 rows) - --- Should find 5 -MATCH p=(:begin)<-[*1..1]-()-[]-() RETURN p ORDER BY p; -ERROR: syntax error at or near "ORDER" -LINE 1: MATCH p=(:begin)<-[*1..1]-()-[]-() RETURN p ORDER BY p; - ^ --- Should find 2922 -MATCH p=()-[*]->(v) RETURN count(*); - count -------- - 2922 -(1 row) - --- Should find 2 -MATCH p=(u:begin)-[*3..3]->(v:end) RETURN p; - p --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2251799813685249, "start_id": 844424930131969, "end_id": 1407374883553281, "label": "alternate_edge", "properties": {"name": "alternate edge"}}, {"id": 1407374883553281, "label": "middle", "properties": {}}, {"id": 1125899906842627, "start_id": 1407374883553281, "end_id": 1407374883553282, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553281, "label": "middle", "properties": {}}, {"id": 2533274790395905, "start_id": 1407374883553282, "end_id": 1688849860263937, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 1125899906842628, "start_id": 844424930131969, "end_id": 1407374883553281, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553281, "label": "middle", "properties": {}}, {"id": 1125899906842627, "start_id": 1407374883553281, "end_id": 1407374883553282, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553281, "label": "middle", "properties": {}}, {"id": 2533274790395905, "start_id": 1407374883553282, "end_id": 1688849860263937, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] -(2 rows) - --- Should find 12 -MATCH p=(u:begin)-[*3..3]-(v:end) RETURN p; - p ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685251, "start_id": 1407374883553283, "end_id": 1688849860263937, "label": "alternate_edge", "properties": {"name": "alternate edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 1125899906842625, "start_id": 1407374883553283, "end_id": 1688849860263937, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2533274790395905, "start_id": 1407374883553282, "end_id": 1688849860263937, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 1970324836974594, "start_id": 1688849860263937, "end_id": 1688849860263937, "label": "self_loop", "properties": {"name": "self loop"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685250, "start_id": 1407374883553282, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "alternate edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685250, "start_id": 1407374883553282, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "alternate edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685251, "start_id": 1407374883553283, "end_id": 1688849860263937, "label": "alternate_edge", "properties": {"name": "alternate edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685250, "start_id": 1407374883553282, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "alternate edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 1125899906842625, "start_id": 1407374883553283, "end_id": 1688849860263937, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 1125899906842626, "start_id": 1407374883553282, "end_id": 1407374883553283, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 1125899906842626, "start_id": 1407374883553282, "end_id": 1407374883553283, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685251, "start_id": 1407374883553283, "end_id": 1688849860263937, "label": "alternate_edge", "properties": {"name": "alternate edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 1125899906842626, "start_id": 1407374883553282, "end_id": 1407374883553283, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 1125899906842625, "start_id": 1407374883553283, "end_id": 1688849860263937, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2251799813685249, "start_id": 844424930131969, "end_id": 1407374883553281, "label": "alternate_edge", "properties": {"name": "alternate edge"}}, {"id": 1407374883553281, "label": "middle", "properties": {}}, {"id": 1125899906842627, "start_id": 1407374883553281, "end_id": 1407374883553282, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553281, "label": "middle", "properties": {}}, {"id": 2533274790395905, "start_id": 1407374883553282, "end_id": 1688849860263937, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 1125899906842628, "start_id": 844424930131969, "end_id": 1407374883553281, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553281, "label": "middle", "properties": {}}, {"id": 1125899906842627, "start_id": 1407374883553281, "end_id": 1407374883553282, "label": "edge", "properties": {"name": "main edge"}}, {"id": 1407374883553281, "label": "middle", "properties": {}}, {"id": 2533274790395905, "start_id": 1407374883553282, "end_id": 1688849860263937, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] -(12 rows) - --- Each should find 2 -MATCH p=(u:begin)<-[*]-(v:end) RETURN p; - p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}, {"id": 1970324836974594, "start_id": 1688849860263937, "end_id": 1688849860263937, "label": "self_loop", "properties": {"name": "self loop"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] -(2 rows) - -MATCH p=(u:begin)<-[e*]-(v:end) RETURN p; - p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}, {"id": 1970324836974594, "start_id": 1688849860263937, "end_id": 1688849860263937, "label": "self_loop", "properties": {"name": "self loop"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] -(2 rows) - -MATCH p=(u:begin)<-[e*]-(v:end) RETURN e; - e ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}] - [{"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}, {"id": 1970324836974594, "start_id": 1688849860263937, "end_id": 1688849860263937, "label": "self_loop", "properties": {"name": "self loop"}}] -(2 rows) - -MATCH p=(:begin)<-[*]-()<-[]-(:end) RETURN p; - p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] - [{"id": 844424930131969, "label": "begin", "properties": {}}, {"id": 2533274790395906, "start_id": 1407374883553282, "end_id": 844424930131969, "label": "bypass_edge", "properties": {"name": "bypass edge"}}, {"id": 1407374883553282, "label": "middle", "properties": {}}, {"id": 2251799813685253, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1407374883553283, "label": "middle", "properties": {}}, {"id": 2251799813685252, "start_id": 1688849860263937, "end_id": 1407374883553283, "label": "alternate_edge", "properties": {"name": "backup edge"}}, {"id": 1688849860263937, "label": "end", "properties": {}}, {"id": 1970324836974594, "start_id": 1688849860263937, "end_id": 1688849860263937, "label": "self_loop", "properties": {"name": "self loop"}}, {"id": 1688849860263937, "label": "end", "properties": {}}] -(2 rows) - --- Each should return 31 -MATCH ()-[e1]->(v)-[e2]->() RETURN count(*); - count -------- - 31 -(1 row) - -MATCH ()-[e1*1..1]->(v)-[e2*1..1]->() RETURN count(*); - count -------- - 31 -(1 row) - -MATCH (v)-[e1*1..1]->()-[e2*1..1]->() RETURN count(*); - count -------- - 31 -(1 row) - -MATCH ()-[e1]->(v)-[e2*1..1]->() RETURN count(*); - count -------- - 31 -(1 row) - -MATCH ()-[e1]->()-[e2*1..1]->() RETURN count(*); - count -------- - 31 -(1 row) - -MATCH ()-[e1*1..1]->(v)-[e2]->() RETURN count(*); - count -------- - 31 -(1 row) - -MATCH ()-[e1*1..1]->()-[e2]->() RETURN count(*); - count -------- - 31 -(1 row) - -MATCH (a)-[e1]->(a)-[e2*1..1]->() RETURN count(*); - count -------- - 2 -(1 row) - -MATCH (a) MATCH (a)-[e1*1..1]->(v) RETURN count(*); - count -------- - 13 -(1 row) - -MATCH (a) MATCH ()-[e1*1..1]->(a) RETURN count(*); - count -------- - 13 -(1 row) - -MATCH (a)-[e*1..1]->() RETURN count(*); - count -------- - 13 -(1 row) - --- Should return 1 path - MATCH p=()<-[e1*]-(:end)-[e2*]->(:begin) RETURN p $$) AS (result traversal); --- Each should return 3 -MATCH (u:begin)-[e*0..1]->(v) RETURN id(u), e, id(v) $$) AS (u gtype, e variable_edge, v gtype); -ERROR: syntax error at or near ")" -LINE 1: MATCH p=()<-[e1*]-(:end)-[e2*]->(:begin) RETURN p $$) AS (re... - ^ -MATCH p=(u:begin)-[e*0..1]->(v) RETURN p $$) AS (p traversal); --- Each should return 5 -MATCH (u)-[e*0..0]->(v) RETURN id(u), e, id(v) $$) AS (u gtype, e variable_edge, v gtype); -ERROR: syntax error at or near ")" -LINE 1: MATCH p=(u:begin)-[e*0..1]->(v) RETURN p $$) AS (p traversal... - ^ -MATCH p=(u)-[e*0..0]->(v) RETURN id(u), p, id(v) $$) AS (u gtype, p traversal, v gtype); --- Each should return 13 and will be the same -MATCH p=()-[*0..0]->()-[]->() RETURN p $$) AS (p traversal); -ERROR: syntax error at or near ")" -LINE 1: MATCH p=(u)-[e*0..0]->(v) RETURN id(u), p, id(v) $$) AS (u g... - ^ -MATCH p=()-[]->()-[*0..0]->() RETURN p $$) AS (p traversal); - -MATCH (u)-[*]-(v) RETURN count(*); -MATCH (u)-[*0..1]-(v) RETURN count(*); -MATCH (u)-[*..1]-(v) RETURN count(*); -MATCH (u)-[*..5]-(v) RETURN count(*); - -MATCH p=(:begin)<-[ve1*]-(:end), (:end)-[ve2*]->(:begin) RETURN ve1 && ve2; -MATCH p=()<-[ve1:edge*]-(), ()-[ve2:alternate_edge*]->() RETURN ve1 && ve2; -SELECT * FROM ag_label; - --- --- Clean up --- -DROP GRAPH cypher_vle CASCADE; - --- --- End --- -ERROR: unterminated dollar quoted string at or near "$$) AS (p traversal); - -MATCH (u)-[*]-(v) RETURN count(*); -MATCH (u)-[*0..1]-(v) RETURN count(*); -MATCH (u)-[*..1]-(v) RETURN count(*); -MATCH (u)-[*..5]-(v) RETURN count(*); - -MATCH p=(:begin)<-[ve1*]-(:end), (:end)-[ve2*]->(:begin) RETURN ve1 && ve2; -MATCH p=()<-[ve1:edge*]-(), ()-[ve2:alternate_edge*]->() RETURN ve1 && ve2; -SELECT * FROM ag_label; - --- --- Clean up --- -DROP GRAPH cypher_vle CASCADE; - --- --- End ---" -LINE 1: MATCH p=()-[]->()-[*0..0]->() RETURN p $$) AS (p traversal); - ^ diff --git a/regress/expected/load_csv.out b/regress/expected/load_csv.out deleted file mode 100644 index ce964d30..00000000 --- a/regress/expected/load_csv.out +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023-2025 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -LOAD 'postgraph'; -CREATE GRAPH load_csv; -NOTICE: graph "load_csv" has been created - create_graph --------------- - -(1 row) - -USE GRAPH load_csv; - use_graph ------------ - -(1 row) - -CYPHER LOAD CSV '~/postgraph/regress/test.csv' -RETURN *; --- -(0 rows) - -DROP GRAPH load_csv CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table load_csv._ag_label_vertex -drop cascades to table load_csv._ag_label_edge -NOTICE: graph "load_csv" has been dropped - drop_graph ------------- - -(1 row) - diff --git a/regress/expected/order_by.out b/regress/expected/order_by.out deleted file mode 100644 index 9c0c1150..00000000 --- a/regress/expected/order_by.out +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2023-2024 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * Portions Copyright (c) 2020-2023, Apache Software Foundation - * Portions Copyright (c) 2019-2020, Bitnine Global - */ -LOAD 'postgraph'; -CREATE GRAPH order_by; -NOTICE: graph "order_by" has been created - create_graph --------------- - -(1 row) - -USE GRAPH order_by; - use_graph ------------ - -(1 row) - -CREATE (); --- -(0 rows) - -CREATE ({i: '1', j: 1}); --- -(0 rows) - -CREATE ({i: 1}); --- -(0 rows) - -CREATE ({i: 1.0}); --- -(0 rows) - -CREATE ({i: 1::numeric}); --- -(0 rows) - -CREATE ({i: true}); --- -(0 rows) - -CREATE ({i: false}); --- -(0 rows) - -CREATE ({i: {key: 'value'}}); --- -(0 rows) - -CREATE ({i: [1]}); --- -(0 rows) - -MATCH (u) RETURN u.i ORDER BY u.i; -ERROR: syntax error at or near "ORDER" -LINE 1: MATCH (u) RETURN u.i ORDER BY u.i; - ^ -MATCH (u) RETURN u.i ORDER BY u.i DESC; -ERROR: syntax error at or near "ORDER" -LINE 1: MATCH (u) RETURN u.i ORDER BY u.i DESC; - ^ -MATCH (u) RETURN u.i ORDER BY u.i ASC; -ERROR: syntax error at or near "ORDER" -LINE 1: MATCH (u) RETURN u.i ORDER BY u.i ASC; - ^ -MATCH (x) RETURN x.j ORDER BY x.j NULLS FIRST; -ERROR: syntax error at or near "ORDER" -LINE 1: MATCH (x) RETURN x.j ORDER BY x.j NULLS FIRST; - ^ -MATCH (x) RETURN x.j ORDER BY x.j NULLS LAST; -ERROR: syntax error at or near "ORDER" -LINE 1: MATCH (x) RETURN x.j ORDER BY x.j NULLS LAST; - ^ -MATCH (x) RETURN x.i ORDER BY x.i USING <; -ERROR: syntax error at or near "ORDER" -LINE 1: MATCH (x) RETURN x.i ORDER BY x.i USING <; - ^ -DROP GRAPH order_by CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table order_by._ag_label_vertex -drop cascades to table order_by._ag_label_edge -NOTICE: graph "order_by" has been dropped - drop_graph ------------- - -(1 row) - diff --git a/regress/expected/postgis.out b/regress/expected/postgis.out deleted file mode 100644 index 67656fb0..00000000 --- a/regress/expected/postgis.out +++ /dev/null @@ -1,3880 +0,0 @@ -/* - * PostGraph - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -SET extra_float_digits = 0; -LOAD 'postgraph'; -LOAD 'postgis-3'; -SET search_path TO postgraph, public; -set timezone TO 'GMT'; -CREATE GRAPH postgis; -NOTICE: graph "postgis" has been created - create_graph --------------- - -(1 row) - -USE GRAPH postgis; - use_graph ------------ - -(1 row) - -RETURN 'box(1 2, 5 6)'::box2d; - ?column? --------------- - BOX(1 2,5 6) -(1 row) - -RETURN 'BOX3D(1 2 3, 4 5 6)'::box3d; - ?column? --------------------- - BOX3D(1 2 3,4 5 6) -(1 row) - -RETURN 'SPHEROID["WGS 84",6378137,298.257223563]'::spheroid; - ?column? ------------------------------------------- - SPHEROID("WGS 84",6378137,298.257223563) -(1 row) - -RETURN 'POLYHEDRALSURFACE Z ( - ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), - ((0 0 0, 0 1 0, 0 1 1, 0 0 1, 0 0 0)), - ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), - ((1 1 1, 1 0 1, 0 0 1, 0 1 1, 1 1 1)), - ((1 1 1, 1 0 1, 1 0 0, 1 1 0, 1 1 1)), - ((1 1 1, 1 1 0, 0 1 0, 0 1 1, 1 1 1)) -)'::geometry; - ?column? --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 010F00008006000000010300008001000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000010300008001000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000000001030000800100000005000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000000001030000800100000005000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000F03F01030000800100000005000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F01030000800100000005000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000F03F -(1 row) - --- --- Box3D --- -WITH u AS (SELECT 'BOX3D(1 2 3, 4 5 6)'::box3d AS g) -SELECT ST_XMin((g)), ST_YMin(g), ST_ZMin(g), ST_XMax(g), ST_YMax(g), ST_ZMax(g)from u; - st_xmin | st_ymin | st_zmin | st_xmax | st_ymax | st_zmax ----------+---------+---------+---------+---------+--------- - 1 | 2 | 3 | 4 | 5 | 6 -(1 row) - -WITH 'BOX3D(1 2 3, 4 5 6)'::box3d AS g -RETURN ST_XMin((g)), ST_YMin(g), ST_ZMin(g), ST_XMax(g), ST_YMax(g), ST_ZMax(g); -ERROR: syntax error at or near "::" -LINE 1: WITH 'BOX3D(1 2 3, 4 5 6)'::box3d AS g - ^ --- --- Geometry --- --- --- I/O Routines --- --- NOTE: The first two queries for each type is the PostGIS Geometry type, used to validate I/O is exactly the same --- --- Point (2 Dimensional) I/O -SELECT 'POINT( 1 2 )'::geometry; - geometry --------------------------------------------- - 0101000000000000000000F03F0000000000000040 -(1 row) - -SELECT ST_AsEWKT('POINT( 1 2 )'::geometry); - st_asewkt ------------- - POINT(1 2) -(1 row) - -RETURN 'POINT( 1 2 )'::geometry; - ?column? --------------------------------------------- - 0101000000000000000000F03F0000000000000040 -(1 row) - -RETURN ST_AsEWKT('POINT( 1 2 )'::geometry) ; - st_asewkt --------------- - "POINT(1 2)" -(1 row) - -RETURN ST_AsEWKT('POINT( 1 2 )') ; - st_asewkt --------------- - "POINT(1 2)" -(1 row) - --- Point (3 Dimensional) I/O -SELECT 'POINT( 1 2 3 )'::geometry; - geometry ------------------------------------------------------------- - 0101000080000000000000F03F00000000000000400000000000000840 -(1 row) - -SELECT ST_AsEWKT('POINT( 1 2 3)'::geometry); - st_asewkt --------------- - POINT(1 2 3) -(1 row) - -RETURN 'POINT( 1 2 3 )'::geometry; - ?column? ------------------------------------------------------------- - 0101000080000000000000F03F00000000000000400000000000000840 -(1 row) - -RETURN ST_AsEWKT('POINT( 1 2 3 )'::geometry) ; - st_asewkt ----------------- - "POINT(1 2 3)" -(1 row) - -RETURN ST_AsEWKT('POINT( 1 2 3 )') ; - st_asewkt ----------------- - "POINT(1 2 3)" -(1 row) - --- LineString (2D, 4 Points) I/O -SELECT 'LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)'::geometry; - geometry ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 01020000000500000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000004000000000000000400000000000000840000000000000084000000000000010400000000000001040 -(1 row) - -SELECT ST_AsEWKT('LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)'::geometry); - st_asewkt ---------------------------------- - LINESTRING(0 0,1 1,2 2,3 3,4 4) -(1 row) - -RETURN 'LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)'::geometry; - ?column? ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 01020000000500000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000004000000000000000400000000000000840000000000000084000000000000010400000000000001040 -(1 row) - -RETURN ST_AsEWKT('LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)'::geometry) ; - st_asewkt ------------------------------------ - "LINESTRING(0 0,1 1,2 2,3 3,4 4)" -(1 row) - -RETURN ST_AsEWKT('LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)') ; - st_asewkt ------------------------------------ - "LINESTRING(0 0,1 1,2 2,3 3,4 4)" -(1 row) - --- LineString (3D, 4 Points) I/O -SELECT 'LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3, 4 4 4)'::geometry; - geometry --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 010200008005000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000004000000000000000400000000000000040000000000000084000000000000008400000000000000840000000000000104000000000000010400000000000001040 -(1 row) - -SELECT ST_AsEWKT('LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3 , 4 4 4)'::geometry); - st_asewkt -------------------------------------------- - LINESTRING(0 0 0,1 1 1,2 2 2,3 3 3,4 4 4) -(1 row) - -RETURN 'LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3 , 4 4 4)'::geometry; - ?column? --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 010200008005000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000004000000000000000400000000000000040000000000000084000000000000008400000000000000840000000000000104000000000000010400000000000001040 -(1 row) - -RETURN ST_AsEWKT('LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3, 4 4 4)'::geometry) ; - st_asewkt ---------------------------------------------- - "LINESTRING(0 0 0,1 1 1,2 2 2,3 3 3,4 4 4)" -(1 row) - -RETURN ST_AsEWKT('LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3 , 4 4 4)') ; - st_asewkt ---------------------------------------------- - "LINESTRING(0 0 0,1 1 1,2 2 2,3 3 3,4 4 4)" -(1 row) - --- LineString (3D, 2 Points) I/O -SELECT 'LINESTRING (1 2 3, 10 20 30)'::geometry; - geometry --------------------------------------------------------------------------------------------------------------------- - 010200008002000000000000000000F03F00000000000000400000000000000840000000000000244000000000000034400000000000003E40 -(1 row) - -SELECT ST_AsEWKT('LINESTRING (1 2 3, 10 20 30)'::geometry); - st_asewkt ----------------------------- - LINESTRING(1 2 3,10 20 30) -(1 row) - -RETURN 'LINESTRING (1 2 3, 10 20 30)'::geometry; - ?column? --------------------------------------------------------------------------------------------------------------------- - 010200008002000000000000000000F03F00000000000000400000000000000840000000000000244000000000000034400000000000003E40 -(1 row) - -RETURN ST_AsEWKT('LINESTRING (1 2 3, 10 20 30)'::geometry) ; - st_asewkt ------------------------------- - "LINESTRING(1 2 3,10 20 30)" -(1 row) - -RETURN ST_AsEWKT('LINESTRING (1 2 3, 10 20 30)') ; - st_asewkt ------------------------------- - "LINESTRING(1 2 3,10 20 30)" -(1 row) - --- Polygon (2D) I/O -SELECT 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry; - geometry --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 010300000001000000050000000000000000000000000000000000000000000000000024400000000000000000000000000000244000000000000024400000000000000000000000000000244000000000000000000000000000000000 -(1 row) - -SELECT ST_AsEWKT('POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry); - st_asewkt ------------------------------------- - POLYGON((0 0,10 0,10 10,0 10,0 0)) -(1 row) - -RETURN 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry; - ?column? --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 010300000001000000050000000000000000000000000000000000000000000000000024400000000000000000000000000000244000000000000024400000000000000000000000000000244000000000000000000000000000000000 -(1 row) - -RETURN ST_AsEWKT('POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry) ; - st_asewkt --------------------------------------- - "POLYGON((0 0,10 0,10 10,0 10,0 0))" -(1 row) - -RETURN ST_AsEWKT('POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )') ; - st_asewkt --------------------------------------- - "POLYGON((0 0,10 0,10 10,0 10,0 0))" -(1 row) - --- Polygon (3D) I/O -SELECT 'POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )'::geometry; - geometry ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 0103000080010000000500000000000000000000000000000000000000000000000000F03F00000000000024400000000000000000000000000000F03F00000000000024400000000000002440000000000000F03F00000000000000000000000000002440000000000000F03F00000000000000000000000000000000000000000000F03F -(1 row) - -SELECT ST_AsEWKT('POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )'::geometry); - st_asewkt ----------------------------------------------- - POLYGON((0 0 1,10 0 1,10 10 1,0 10 1,0 0 1)) -(1 row) - -RETURN 'POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )'::geometry; - ?column? ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 0103000080010000000500000000000000000000000000000000000000000000000000F03F00000000000024400000000000000000000000000000F03F00000000000024400000000000002440000000000000F03F00000000000000000000000000002440000000000000F03F00000000000000000000000000000000000000000000F03F -(1 row) - -RETURN ST_AsEWKT('POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )'::geometry) ; - st_asewkt ------------------------------------------------- - "POLYGON((0 0 1,10 0 1,10 10 1,0 10 1,0 0 1))" -(1 row) - -RETURN ST_AsEWKT('POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )') ; - st_asewkt ------------------------------------------------- - "POLYGON((0 0 1,10 0 1,10 10 1,0 10 1,0 0 1))" -(1 row) - --- --- Constructors --- --- --- ST_MakePoint --- -SELECT ST_MakePoint(1, 0); - st_makepoint --------------------------------------------- - 0101000000000000000000F03F0000000000000000 -(1 row) - -SELECT ST_MakePoint(1, 0, 2); - st_makepoint ------------------------------------------------------------- - 0101000080000000000000F03F00000000000000000000000000000040 -(1 row) - -SELECT ST_MakePoint(1, 0, 2, 3); - st_makepoint ----------------------------------------------------------------------------- - 01010000C0000000000000F03F000000000000000000000000000000400000000000000840 -(1 row) - - RETURN ST_MakePoint(1, 0) ; - st_makepoint --------------------------------------------- - 0101000000000000000000F03F0000000000000000 -(1 row) - - RETURN ST_MakePoint(1, 0, 2) ; - st_makepoint ------------------------------------------------------------- - 0101000080000000000000F03F00000000000000000000000000000040 -(1 row) - - RETURN ST_MakePoint(1, 0, 2, 3) ; - st_makepoint ----------------------------------------------------------------------------- - 01010000C0000000000000F03F000000000000000000000000000000400000000000000840 -(1 row) - --- --- ST_MakePointM --- -SELECT ST_MakePointM(1, 0, 2); - st_makepointm ------------------------------------------------------------- - 0101000040000000000000F03F00000000000000000000000000000040 -(1 row) - - RETURN ST_MakePointM(1, 0, 2) ; - st_makepointm ------------------------------------------------------------- - 0101000040000000000000F03F00000000000000000000000000000040 -(1 row) - --- --- addbbox --- -SELECT ST_AsEwkt(postgis_addbbox('SRID=4326;POINT(1e+15 1e+15)'::geometry)); - st_asewkt ------------------------------- - SRID=4326;POINT(1e+15 1e+15) -(1 row) - -RETURN ST_AsEwkt(postgis_addbbox('SRID=4326;POINT(1e+15 1e+15)'::geometry)) ; - st_asewkt --------------------------------- - "SRID=4326;POINT(1e+15 1e+15)" -(1 row) - --- --- Comparison Operators --- -WITH p0 AS (SELECT 'POINT(0 0)'::geometry::gtype geom), -p1 AS (SELECT 'POINT(1 1)'::geometry::gtype geom) -SELECT '#4445', - p0.geom < p0.geom, - p0.geom <= p0.geom, - p0.geom = p0.geom, - p0.geom >= p0.geom, - p0.geom > p0.geom, - p0.geom < p1.geom, - p0.geom <= p1.geom, - p0.geom = p1.geom, - p0.geom >= p1.geom, - p0.geom > p1.geom, - p1.geom < p0.geom, - p1.geom <= p0.geom, - p1.geom = p0.geom, - p1.geom >= p0.geom, - p1.geom > p0.geom -FROM p0, p1; - ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? -----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+---------- - #4445 | f | t | t | t | f | t | t | f | f | f | f | f | f | t | t -(1 row) - --- --- 2D Operators --- --- ~= -select 'POINT(1 1)'::geometry ~= 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry ~= 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry ~= 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 1,2 2)'::geometry ~= 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(2 2, 1 1)'::geometry ~= 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry ~= 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1,2 2)'::geometry ~= 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry ~= 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry ~= 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(1 1)'::geometry ~= 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry ~= 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry ~= 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 1,2 2)'::geometry ~= 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry ~= 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry ~= 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1,2 2)'::geometry ~= 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry ~= 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry ~= 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - f -(1 row) - --- @ -select 'POINT(1 1)'::geometry @ 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry @ 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry @ 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 1,2 2)'::geometry @ 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(2 2, 1 1)'::geometry @ 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry @ 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1,2 2)'::geometry @ 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry @ 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry @ 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1)'::geometry @ 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry @ 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry @ 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 1,2 2)'::geometry @ 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry @ 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry @ 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1,2 2)'::geometry @ 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry @ 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry @ 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - --- &< -select 'POINT(1 1)'::geometry &< 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry &< 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry &< 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 1,2 2)'::geometry &< 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(2 2, 1 1)'::geometry &< 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &< 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1,2 2)'::geometry &< 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry &< 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry &< 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1)'::geometry &< 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry &< 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry &< 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 1,2 2)'::geometry &< 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry &< 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &< 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1,2 2)'::geometry &< 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry &< 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry &< 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - ---<<| -select 'POINT(1 1)'::geometry <<| 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'POINT(1 1 0)'::geometry <<| 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'POINT(1 1 0)'::geometry <<| 'POINT(1 1 0)'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(1 1,2 2)'::geometry <<| 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(2 2, 1 1)'::geometry <<| 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - f -(1 row) - -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry <<| 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1,2 2)'::geometry <<| 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry <<| 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry <<| 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(1 1)'::geometry <<| 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(1 1 0)'::geometry <<| 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(1 1 0)'::geometry <<| 'POINT(1 1 0)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(1 1,2 2)'::geometry <<| 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry <<| 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry <<| 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1,2 2)'::geometry <<| 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry <<| 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry <<| 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - f -(1 row) - --- &<| -select 'POINT(1 1)'::geometry &<| 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry &<| 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry &<| 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 1,2 2)'::geometry &<| 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(2 2, 1 1)'::geometry &<| 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &<| 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1,2 2)'::geometry &<| 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry &<| 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry &<| 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1)'::geometry &<| 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry &<| 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry &<| 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 1,2 2)'::geometry &<| 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry &<| 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &<| 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1,2 2)'::geometry &<| 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry &<| 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry &<| 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - --- &> -select 'POINT(1 1)'::geometry &> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry &> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry &> 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 1,2 2)'::geometry &> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(2 2, 1 1)'::geometry &> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1,2 2)'::geometry &> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry &> 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry &> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1)'::geometry &> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry &> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry &> 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 1,2 2)'::geometry &> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry &> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1,2 2)'::geometry &> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry &> 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry &> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - --- |&> -select 'POINT(1 1)'::geometry |&> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry |&> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'POINT(1 1 0)'::geometry |&> 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 1,2 2)'::geometry |&> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(2 2, 1 1)'::geometry |&> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |&> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1,2 2)'::geometry |&> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry |&> 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry |&> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1)'::geometry |&> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry |&> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(1 1 0)'::geometry |&> 'POINT(1 1 0)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 1,2 2)'::geometry |&> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry |&> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |&> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1,2 2)'::geometry |&> 'POINT(1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry |&> 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry |&> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - t -(1 row) - --- --- |>> --- -select 'POINT(1 1)'::geometry |>> 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'POINT(1 1 0)'::geometry |>> 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'POINT(1 1 0)'::geometry |>> 'POINT(1 1 0)'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(1 1,2 2)'::geometry |>> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(2 2, 1 1)'::geometry |>> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - f -(1 row) - -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |>> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1,2 2)'::geometry |>> 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry |>> 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - f -(1 row) - -select 'LINESTRING(1 1, 2 2)'::geometry |>> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(1 1)'::geometry |>> 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(1 1 0)'::geometry |>> 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(1 1 0)'::geometry |>> 'POINT(1 1 0)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(1 1,2 2)'::geometry |>> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry |>> 'MULTIPOINT(1 1,2 2)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |>> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1,2 2)'::geometry |>> 'POINT(1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry |>> 'LINESTRING(2 2, 1 1)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(1 1, 2 2)'::geometry |>> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ?column? ----------- - f -(1 row) - --- --- N-D Operators --- --- nd overlap &&& -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(3 3 3 5)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(3 3 5 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(3 5 3 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(5 3 3 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(3 3 3 3)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(2 4 2 4)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(4 2 4 2)'::geometry; - ?column? ----------- - t -(1 row) - --- nd contains ~~ -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(3 3 3 5)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(3 3 5 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(3 5 3 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(5 3 3 3)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(3 3 3 3)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(2 4 2 4)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(4 2 4 2)'::geometry; - ?column? ----------- - t -(1 row) - --- nd within @@ -RETURN 'POINT(3 3 3 5)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(3 3 5 3)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(3 5 3 3)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(5 3 3 3)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; - ?column? ----------- - f -(1 row) - -RETURN 'POINT(3 3 3 3)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(2 4 2 4)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; - ?column? ----------- - t -(1 row) - -RETURN 'POINT(4 2 4 2)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; - ?column? ----------- - t -(1 row) - --- nd same ~~= - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'POINT(3 3 3 3)'::geometry; - ?column? ----------- - f -(1 row) - - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 2, 4 4 4 5)'::geometry; - ?column? ----------- - f -(1 row) - - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 2, 4 4 5 4)'::geometry; - ?column? ----------- - f -(1 row) - - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 2, 4 5 4 4)'::geometry; - ?column? ----------- - f -(1 row) - - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 2, 5 4 4 4)'::geometry; - ?column? ----------- - f -(1 row) - - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(4 4 4 4, 2 2 2 2)'::geometry; - ?column? ----------- - t -(1 row) - - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 4, 2 2 4 2, 2 4 2 2, 4 2 2 2)'::geometry; - ?column? ----------- - t -(1 row) - --- --- Point Cordinates --- --- --- ST_X --- -SELECT ST_X('POINT (0 0)'::geometry); - st_x ------- - 0 -(1 row) - -RETURN ST_X('POINT (0 0)'::geometry) ; - st_x ------- - 0.0 -(1 row) - -RETURN X('POINT (0 0)'::geometry) ; - x ------ - 0.0 -(1 row) - -SELECT ST_X('POINTZ (1 2 3)'::geometry); - st_x ------- - 1 -(1 row) - -RETURN ST_X('POINTZ (1 2 3)'::geometry) ; - st_x ------- - 1.0 -(1 row) - -RETURN X('POINTZ (1 2 3)'::geometry) ; - x ------ - 1.0 -(1 row) - -SELECT ST_X('POINTM (6 7 8)'::geometry); - st_x ------- - 6 -(1 row) - -RETURN ST_X('POINTM (6 7 8)'::geometry) ; - st_x ------- - 6.0 -(1 row) - -RETURN X('POINTM (6 7 8)'::geometry) ; - x ------ - 6.0 -(1 row) - -SELECT ST_X('POINTZM (10 11 12 13)'::geometry); - st_x ------- - 10 -(1 row) - -RETURN ST_X('POINTZM (10 11 12 13)'::geometry) ; - st_x ------- - 10.0 -(1 row) - -RETURN X('POINTZM (10 11 12 13)'::geometry) ; - x ------- - 10.0 -(1 row) - -SELECT ST_X('MULTIPOINT ((0 0), (1 1))'::geometry); -ERROR: Argument to ST_X() must have type POINT -RETURN ST_X('MULTIPOINT ((0 0), (1 1))'::geometry) ; -ERROR: Argument to ST_X() must have type POINT -RETURN X('MULTIPOINT ((0 0), (1 1))'::geometry) ; -ERROR: Argument to ST_X() must have type POINT -SELECT ST_X('LINESTRING (0 0, 1 1)'::geometry); -ERROR: Argument to ST_X() must have type POINT -RETURN ST_X('LINESTRING (0 0, 1 1)'::geometry) ; -ERROR: Argument to ST_X() must have type POINT -RETURN X('LINESTRING (0 0, 1 1)'::geometry) ; -ERROR: Argument to ST_X() must have type POINT -SELECT ST_X('GEOMETRYCOLLECTION (POINT(0 0))'::geometry); -ERROR: Argument to ST_X() must have type POINT -RETURN ST_X('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -ERROR: Argument to ST_X() must have type POINT -RETURN X('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -ERROR: Argument to ST_X() must have type POINT -SELECT ST_X('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry); -ERROR: Argument to ST_X() must have type POINT -RETURN ST_X('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -ERROR: Argument to ST_X() must have type POINT -RETURN X('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -ERROR: Argument to ST_X() must have type POINT --- --- ST_Y --- -SELECT ST_Y('POINT (0 0)'::geometry); - st_y ------- - 0 -(1 row) - -RETURN ST_Y('POINT (0 0)'::geometry) ; - st_y ------- - 0.0 -(1 row) - -RETURN Y('POINT (0 0)'::geometry) ; - y ------ - 0.0 -(1 row) - -SELECT ST_Y('POINTZ (1 2 3)'::geometry); - st_y ------- - 2 -(1 row) - -RETURN ST_Y('POINTZ (1 2 3)'::geometry) ; - st_y ------- - 2.0 -(1 row) - -RETURN Y('POINTZ (1 2 3)'::geometry) ; - y ------ - 2.0 -(1 row) - -SELECT ST_Y('POINTM (6 7 8)'::geometry); - st_y ------- - 7 -(1 row) - -RETURN ST_Y('POINTM (6 7 8)'::geometry) ; - st_y ------- - 7.0 -(1 row) - -RETURN Y('POINTM (6 7 8)'::geometry) ; - y ------ - 7.0 -(1 row) - -SELECT ST_Y('POINTZM (10 11 12 13)'::geometry); - st_y ------- - 11 -(1 row) - -RETURN ST_Y('POINTZM (10 11 12 13)'::geometry) ; - st_y ------- - 11.0 -(1 row) - -RETURN Y('POINTZM (10 11 12 13)'::geometry) ; - y ------- - 11.0 -(1 row) - -SELECT ST_Y('MULTIPOINT ((0 0), (1 1))'::geometry); -ERROR: Argument to ST_Y() must have type POINT -RETURN ST_Y('MULTIPOINT ((0 0), (1 1))'::geometry) ; -ERROR: Argument to ST_Y() must have type POINT -RETURN Y('MULTIPOINT ((0 0), (1 1))'::geometry) ; -ERROR: Argument to ST_Y() must have type POINT -SELECT ST_Y('LINESTRING (0 0, 1 1)'::geometry); -ERROR: Argument to ST_Y() must have type POINT -RETURN ST_Y('LINESTRING (0 0, 1 1)'::geometry) ; -ERROR: Argument to ST_Y() must have type POINT -RETURN Y('LINESTRING (0 0, 1 1)'::geometry) ; -ERROR: Argument to ST_Y() must have type POINT -SELECT ST_Y('GEOMETRYCOLLECTION (POINT(0 0))'::geometry); -ERROR: Argument to ST_Y() must have type POINT -RETURN ST_Y('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -ERROR: Argument to ST_Y() must have type POINT -RETURN Y('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -ERROR: Argument to ST_Y() must have type POINT -SELECT ST_Y('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry); -ERROR: Argument to ST_Y() must have type POINT -RETURN ST_Y('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -ERROR: Argument to ST_Y() must have type POINT -RETURN Y('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -ERROR: Argument to ST_Y() must have type POINT --- --- ST_Z --- -SELECT ST_Z('POINT (0 0)'::geometry); - st_z ------- - -(1 row) - -RETURN ST_Z('POINT (0 0)'::geometry) ; - st_z ------- - -(1 row) - -RETURN Z('POINT (0 0)'::geometry) ; - z ---- - -(1 row) - -SELECT ST_Z('POINTZ (1 2 3)'::geometry); - st_z ------- - 3 -(1 row) - -RETURN ST_Z('POINTZ (1 2 3)'::geometry) ; - st_z ------- - 3.0 -(1 row) - -RETURN Z('POINTZ (1 2 3)'::geometry) ; - z ------ - 3.0 -(1 row) - -SELECT ST_Z('POINTM (6 7 8)'::geometry); - st_z ------- - -(1 row) - -RETURN ST_Z('POINTM (6 7 8)'::geometry) ; - st_z ------- - -(1 row) - -RETURN Z('POINTM (6 7 8)'::geometry) ; - z ---- - -(1 row) - -SELECT ST_Z('POINTZM (10 11 12 13)'::geometry); - st_z ------- - 12 -(1 row) - -RETURN ST_Z('POINTZM (10 11 12 13)'::geometry) ; - st_z ------- - 12.0 -(1 row) - -RETURN Z('POINTZM (10 11 12 13)'::geometry) ; - z ------- - 12.0 -(1 row) - -SELECT ST_Z('MULTIPOINT ((0 0), (1 1))'::geometry); -ERROR: Argument to ST_Z() must have type POINT -RETURN ST_Z('MULTIPOINT ((0 0), (1 1))'::geometry) ; -ERROR: Argument to ST_Z() must have type POINT -RETURN Z('MULTIPOINT ((0 0), (1 1))'::geometry) ; -ERROR: Argument to ST_Z() must have type POINT -SELECT ST_Z('LINESTRING (0 0, 1 1)'::geometry); -ERROR: Argument to ST_Z() must have type POINT -RETURN ST_Z('LINESTRING (0 0, 1 1)'::geometry) ; -ERROR: Argument to ST_Z() must have type POINT -RETURN Z('LINESTRING (0 0, 1 1)'::geometry) ; -ERROR: Argument to ST_Z() must have type POINT -SELECT ST_Z('GEOMETRYCOLLECTION (POINT(0 0))'::geometry); -ERROR: Argument to ST_Z() must have type POINT -RETURN ST_Z('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -ERROR: Argument to ST_Z() must have type POINT -RETURN Z('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -ERROR: Argument to ST_Z() must have type POINT -SELECT ST_Z('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry); -ERROR: Argument to ST_Z() must have type POINT -RETURN ST_Z('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -ERROR: Argument to ST_Z() must have type POINT -RETURN Z('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -ERROR: Argument to ST_Z() must have type POINT --- --- ST_M --- -SELECT ST_M('POINT (0 0)'::geometry); - st_m ------- - -(1 row) - -RETURN ST_M('POINT (0 0)'::geometry) ; - st_m ------- - -(1 row) - -RETURN M('POINT (0 0)'::geometry) ; - m ---- - -(1 row) - -SELECT ST_M('POINTZ (1 2 3)'::geometry); - st_m ------- - -(1 row) - -RETURN ST_M('POINTZ (1 2 3)'::geometry) ; - st_m ------- - -(1 row) - -RETURN M('POINTZ (1 2 3)'::geometry) ; - m ---- - -(1 row) - -SELECT ST_M('POINTM (6 7 8)'::geometry); - st_m ------- - 8 -(1 row) - -RETURN ST_M('POINTM (6 7 8)'::geometry) ; - st_m ------- - 8.0 -(1 row) - -RETURN M('POINTM (6 7 8)'::geometry) ; - m ------ - 8.0 -(1 row) - -SELECT ST_M('POINTZM (10 11 12 13)'::geometry); - st_m ------- - 13 -(1 row) - -RETURN ST_M('POINTZM (10 11 12 13)'::geometry) ; - st_m ------- - 13.0 -(1 row) - -RETURN M('POINTZM (10 11 12 13)'::geometry) ; - m ------- - 13.0 -(1 row) - -SELECT ST_M('MULTIPOINT ((0 0), (1 1))'::geometry); -ERROR: Argument to ST_M() must have type POINT -RETURN ST_M('MULTIPOINT ((0 0), (1 1))'::geometry) ; -ERROR: Argument to ST_M() must have type POINT -RETURN M('MULTIPOINT ((0 0), (1 1))'::geometry) ; -ERROR: Argument to ST_M() must have type POINT -SELECT ST_M('LINESTRING (0 0, 1 1)'::geometry); -ERROR: Argument to ST_M() must have type POINT -RETURN ST_M('LINESTRING (0 0, 1 1)'::geometry) ; -ERROR: Argument to ST_M() must have type POINT -RETURN M('LINESTRING (0 0, 1 1)'::geometry) ; -ERROR: Argument to ST_M() must have type POINT -SELECT ST_M('GEOMETRYCOLLECTION (POINT(0 0))'::geometry); -ERROR: Argument to ST_M() must have type POINT -RETURN ST_M('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -ERROR: Argument to ST_M() must have type POINT -RETURN M('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -ERROR: Argument to ST_M() must have type POINT -SELECT ST_M('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry); -ERROR: Argument to ST_M() must have type POINT -RETURN ST_M('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -ERROR: Argument to ST_M() must have type POINT -RETURN M('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -ERROR: Argument to ST_M() must have type POINT --- --- Affines --- --- ST_Scale -select ST_asewkt(ST_Scale('POINT(1 1)'::geometry, 5, 5)); - st_asewkt ------------- - POINT(5 5) -(1 row) - -select ST_asewkt(ST_Scale('POINT(1 1)'::geometry, 3, 2)); - st_asewkt ------------- - POINT(3 2) -(1 row) - -select ST_asewkt(ST_Scale('POINT(10 20 -5)'::geometry, 4, 2, -8)); - st_asewkt ------------------ - POINT(40 40 40) -(1 row) - -select ST_asewkt(ST_Scale('POINT(10 20 -5 3)'::geometry, ST_MakePoint(4, 2, -8))); - st_asewkt -------------------- - POINT(40 40 40 3) -(1 row) - -select ST_asewkt(ST_Scale('POINT(-2 -1 3 2)'::geometry, ST_MakePointM(-2, 3, 4))); - st_asewkt ------------------ - POINT(4 -3 3 8) -(1 row) - -select ST_asewkt(ST_Scale('POINT(10 20 -5 3)'::geometry, ST_MakePoint(-3, 2, -1, 3))); - st_asewkt -------------------- - POINT(-30 40 5 9) -(1 row) - -select st_astext(st_scale('LINESTRING(1 1, 2 2)'::geometry, 'POINT(2 2)'::geometry, 'POINT(1 1)'::geometry)); - st_astext ---------------------- - LINESTRING(1 1,3 3) -(1 row) - -RETURN ST_asewkt(ST_Scale('POINT(1 1)'::geometry, 5, 5)); -ERROR: cannot cast gtype integer to type geometry -RETURN ST_asewkt(ST_Scale('POINT(1 1)'::geometry, 3, 2)); -ERROR: cannot cast gtype integer to type geometry -RETURN ST_asewkt(ST_Scale('POINT(10 20 -5)'::geometry, 4, 2, -8)); - st_asewkt -------------------- - "POINT(40 40 40)" -(1 row) - -RETURN ST_asewkt(ST_Scale('POINT(10 20 -5 3)'::geometry, ST_MakePoint(4, 2, -8))); - st_asewkt ---------------------- - "POINT(40 40 40 3)" -(1 row) - -RETURN ST_asewkt(ST_Scale('POINT(-2 -1 3 2)'::geometry, ST_MakePointM(-2, 3, 4))); - st_asewkt -------------------- - "POINT(4 -3 3 8)" -(1 row) - -RETURN ST_asewkt(ST_Scale('POINT(10 20 -5 3)'::geometry, ST_MakePoint(-3, 2, -1, 3))); - st_asewkt ---------------------- - "POINT(-30 40 5 9)" -(1 row) - -RETURN ST_asewkt(st_scale('LINESTRING(1 1, 2 2)'::geometry, 'POINT(2 2)'::geometry, 'POINT(1 1)'::geometry)); - st_asewkt ------------------------ - "LINESTRING(1 1,3 3)" -(1 row) - -RETURN '200', ST_Expand(null::geometry, 1); - ?column? | st_expand -----------+----------- - "200" | -(1 row) - -RETURN '201', ST_AsText(ST_Expand('LINESTRING (1 2 3, 10 20 30)'::geometry, 1)); - ?column? | st_astext -----------+----------------------------------------------------- - "201" | "POLYGON Z ((0 1 2,0 21 2,11 21 31,11 1 31,0 1 2))" -(1 row) - -RETURN '202', ST_AsText(ST_Expand('LINESTRINGM (1 2 3, 10 20 30)'::geometry, 1)); - ?column? | st_astext -----------+----------------------------------------------------- - "202" | "POLYGON M ((0 1 2,0 21 2,11 21 31,11 1 31,0 1 2))" -(1 row) - -RETURN '203', ST_AsText(ST_Expand('LINESTRING (1 2, 10 20)'::geometry, 3)); - ?column? | st_astext -----------+-------------------------------------------- - "203" | "POLYGON((-2 -1,-2 23,13 23,13 -1,-2 -1))" -(1 row) - -RETURN '204', ST_AsText(ST_Expand('POLYGON EMPTY'::geometry, 4)); - ?column? | st_astext -----------+----------------- - "204" | "POLYGON EMPTY" -(1 row) - -RETURN '205', ST_AsText(ST_Expand('POINT EMPTY'::geometry, 2)); - ?column? | st_astext -----------+--------------- - "205" | "POINT EMPTY" -(1 row) - -RETURN '206', ST_AsText(ST_Expand('POINT (2 3)'::geometry, 0)); - ?column? | st_astext -----------+---------------------------------- - "206" | "POLYGON((2 3,2 3,2 3,2 3,2 3))" -(1 row) - -RETURN '207', ST_AsText(ST_Expand('LINESTRING (1 2, 3 4)'::geometry, 0)); - ?column? | st_astext -----------+---------------------------------- - "207" | "POLYGON((1 2,1 4,3 4,3 2,1 2))" -(1 row) - -RETURN '208', ST_AsText(ST_Expand('POINT (0 0)'::geometry, -1)); - ?column? | st_astext -----------+-------------------------------------- - "208" | "POLYGON((1 1,1 -1,-1 -1,-1 1,1 1))" -(1 row) - -RETURN '209', ST_AsText(ST_Expand('LINESTRING (0 0, 10 10)'::geometry, -4)); - ?column? | st_astext -----------+---------------------------------- - "209" | "POLYGON((4 4,4 6,6 6,6 4,4 4))" -(1 row) - -RETURN '210', ST_Expand(null::box3d, 1); - ?column? | st_expand -----------+----------- - "210" | -(1 row) - -RETURN '211', ST_Expand('BOX3D(-1 3 5, -1 6 8)'::BOX3D, 1); - ?column? | st_expand -----------+--------------------- - "211" | BOX3D(-2 2 4,0 7 9) -(1 row) - -RETURN '212', ST_Expand(null::box2d, 1); - ?column? | st_expand -----------+----------- - "212" | -(1 row) - -RETURN '213', ST_Expand('BOX(-2 3, -1 6'::BOX2D, 4); - ?column? | st_expand -----------+----------------- - "213" | BOX(-6 -1,3 10) -(1 row) - -RETURN '214', ST_Expand(null::geometry, 1, 1, 1, 1); - ?column? | st_expand -----------+----------- - "214" | -(1 row) - -RETURN '215', ST_AsText(ST_Expand('LINESTRING (1 2 3, 10 20 30)'::geometry, 1, 4, 2, 7)); - ?column? | st_astext -----------+-------------------------------------------------------- - "215" | "POLYGON Z ((0 -2 1,0 24 1,11 24 32,11 -2 32,0 -2 1))" -(1 row) - -RETURN '216', ST_AsText(ST_Expand('LINESTRINGM (1 2 3, 10 20 30)'::geometry, 1, 4, 2, 7)); - ?column? | st_astext -----------+----------------------------------------------------------- - "216" | "POLYGON M ((0 -2 -4,0 24 -4,11 24 37,11 -2 37,0 -2 -4))" -(1 row) - -RETURN '217', ST_AsText(ST_Expand('LINESTRING (1 2, 10 20)'::geometry, 1, 4, 2, 7)); - ?column? | st_astext -----------+----------------------------------------- - "217" | "POLYGON((0 -2,0 24,11 24,11 -2,0 -2))" -(1 row) - -RETURN '218', ST_AsText(ST_Expand('POLYGON EMPTY'::geometry, 4, 3, 1, 1)); - ?column? | st_astext -----------+----------------- - "218" | "POLYGON EMPTY" -(1 row) - -RETURN '219', ST_AsText(ST_Expand('POINT EMPTY'::geometry, 2, 3, 1, -1)); - ?column? | st_astext -----------+--------------- - "219" | "POINT EMPTY" -(1 row) - -RETURN '220', ST_AsText(ST_Expand('POINT (2 3)'::geometry, 0, 4, -2, 8)); - ?column? | st_astext -----------+------------------------------------- - "220" | "POLYGON((2 -1,2 7,2 7,2 -1,2 -1))" -(1 row) - -RETURN '221', ST_AsText(ST_Expand('POINT (0 0)'::geometry, -1, -2)); - ?column? | st_astext -----------+-------------------------------------- - "221" | "POLYGON((1 2,1 -2,-1 -2,-1 2,1 2))" -(1 row) - -RETURN '222', ST_Expand(null::box3d, 1, 1, 1); - ?column? | st_expand -----------+----------- - "222" | -(1 row) - -RETURN '223', ST_Expand('BOX3D(-1 3 5, -1 6 8)'::BOX3D, 1, -1, 7); - ?column? | st_expand -----------+----------------------- - "223" | BOX3D(-2 4 -2,0 5 15) -(1 row) - -RETURN '224', ST_Expand(null::box2d, 1, 1); - ?column? | st_expand -----------+----------- - "224" | -(1 row) - -RETURN '225', ST_Expand('BOX(-2 3, -1 6'::BOX2D, 4, 2); - ?column? | st_expand -----------+--------------- - "225" | BOX(-6 1,3 8) -(1 row) - -RETURN '226', ST_SRID(ST_Expand('SRID=4326;POINT (0 0)'::geometry, 1))=4326; -ERROR: function postgraph.st_srid(gtype) does not exist -LINE 1: RETURN '226', ST_SRID(ST_Expand('SRID=4326;POINT (0 0)'::geo... - ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. -RETURN '#3069', postgis_getbbox('SRID=0;POINT(1 1)'::geometry); - ?column? | postgis_getbbox -----------+----------------- - "#3069" | BOX(1 1,1 1) -(1 row) - -RETURN '#3069', postgis_getbbox('SRID=0;LINESTRING(0 0, 1 1)'::geometry); - ?column? | postgis_getbbox -----------+----------------- - "#3069" | BOX(0 0,1 1) -(1 row) - -RETURN '#3069', postgis_getbbox('SRID=0;MULTILINESTRING((0 0, 1 1))'::geometry); - ?column? | postgis_getbbox -----------+----------------- - "#3069" | BOX(0 0,1 1) -(1 row) - -RETURN '#3069', postgis_getbbox('SRID=0;MULTIPOINT(1 1)'::geometry); - ?column? | postgis_getbbox -----------+----------------- - "#3069" | BOX(1 1,1 1) -(1 row) - -RETURN '#3069', postgis_getbbox('SRID=0;MULTILINESTRING((0 0,1 1))'::geometry); - ?column? | postgis_getbbox -----------+----------------- - "#3069" | BOX(0 0,1 1) -(1 row) - --- postgis-users/2006-July/012788.html -RETURN ST_makebox2d('SRID=3;POINT(0 0)', 'SRID=3;POINT(1 1)'); - st_makebox2d --------------- - BOX(0 0,1 1) -(1 row) - -RETURN ST_makebox2d('POINT(0 0)', 'SRID=3;POINT(1 1)'); -ERROR: Operation on mixed SRID geometries --- --- Measures --- --- --- ST_IsPolygonCW --- --- Non-applicable types -SELECT ST_IsPolygonCW('POINT (0 0)'::geometry); - st_ispolygoncw ----------------- - t -(1 row) - -SELECT ST_IsPolygonCW('MULTIPOINT ((0 0), (1 1))'::geometry); - st_ispolygoncw ----------------- - t -(1 row) - -SELECT ST_IsPolygonCW('LINESTRING (1 1, 2 2)'::geometry); - st_ispolygoncw ----------------- - t -(1 row) - -SELECT ST_IsPolygonCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))'::geometry); - st_ispolygoncw ----------------- - t -(1 row) - --- EMPTY handling -SELECT ST_IsPolygonCW('POLYGON EMPTY'::geometry); - st_ispolygoncw ----------------- - t -(1 row) - --- Single polygon, ccw exterior ring only -SELECT ST_IsPolygonCW('POLYGON ((0 0, 1 0, 1 1, 0 0))'::geometry); - st_ispolygoncw ----------------- - f -(1 row) - --- Single polygon, cw exterior ring only -SELECT ST_IsPolygonCW('POLYGON ((0 0, 1 1, 1 0, 0 0))'::geometry); - st_ispolygoncw ----------------- - t -(1 row) - --- Single polygon, ccw exterior ring, cw interior rings -SELECT ST_IsPolygonCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); - st_ispolygoncw ----------------- - f -(1 row) - --- Single polygon, cw exterior ring, ccw interior rings -SELECT ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); - st_ispolygoncw ----------------- - t -(1 row) - --- Single polygon, ccw exerior ring, mixed interior rings -SELECT ST_IsPolygonCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); - st_ispolygoncw ----------------- - f -(1 row) - --- Single polygon, cw exterior ring, mixed interior rings -SELECT ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); - st_ispolygoncw ----------------- - f -(1 row) - --- MultiPolygon, ccw exterior rings only -SELECT ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))'::geometry); - st_ispolygoncw ----------------- - f -(1 row) - --- MultiPolygon, cw exterior rings only -SELECT ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - st_ispolygoncw ----------------- - t -(1 row) - --- MultiPolygon, mixed exterior rings -SELECT ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - st_ispolygoncw ----------------- - f -(1 row) - -RETURN ST_IsPolygonCW('POINT (0 0)'::geometry); - st_ispolygoncw ----------------- - true -(1 row) - -RETURN ST_IsPolygonCW('MULTIPOINT ((0 0), (1 1))'::geometry); - st_ispolygoncw ----------------- - true -(1 row) - -RETURN ST_IsPolygonCW('LINESTRING (1 1, 2 2)'::geometry); - st_ispolygoncw ----------------- - true -(1 row) - -RETURN ST_IsPolygonCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))'::geometry); - st_ispolygoncw ----------------- - true -(1 row) - --- EMPTY handling -RETURN ST_IsPolygonCW('POLYGON EMPTY'::geometry); - st_ispolygoncw ----------------- - true -(1 row) - --- Single polygon, ccw exterior ring only -RETURN ST_IsPolygonCW('POLYGON ((0 0, 1 0, 1 1, 0 0))'::geometry); - st_ispolygoncw ----------------- - false -(1 row) - --- Single polygon, cw exterior ring only -RETURN ST_IsPolygonCW('POLYGON ((0 0, 1 1, 1 0, 0 0))'::geometry); - st_ispolygoncw ----------------- - true -(1 row) - --- Single polygon, ccw exterior ring, cw interior rings -RETURN ST_IsPolygonCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); - st_ispolygoncw ----------------- - false -(1 row) - --- Single polygon, cw exterior ring, ccw interior rings -RETURN ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); - st_ispolygoncw ----------------- - true -(1 row) - --- Single polygon, ccw exerior ring, mixed interior rings -RETURN ST_IsPolygonCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); - st_ispolygoncw ----------------- - false -(1 row) - --- Single polygon, cw exterior ring, mixed interior rings -RETURN ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); - st_ispolygoncw ----------------- - false -(1 row) - --- MultiPolygon, ccw exterior rings only -RETURN ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))'::geometry); - st_ispolygoncw ----------------- - false -(1 row) - --- MultiPolygon, cw exterior rings only -RETURN ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - st_ispolygoncw ----------------- - true -(1 row) - --- MultiPolygon, mixed exterior rings -RETURN ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - st_ispolygoncw ----------------- - false -(1 row) - --- --- ST_IsPolygonCCW --- --- Non-applicable types -SELECT ST_IsPolygonCCW('POINT (0 0)'::geometry); - st_ispolygonccw ------------------ - t -(1 row) - -SELECT ST_IsPolygonCCW('MULTIPOINT ((0 0), (1 1))'::geometry); - st_ispolygonccw ------------------ - t -(1 row) - -SELECT ST_IsPolygonCCW('LINESTRING (1 1, 2 2)'::geometry); - st_ispolygonccw ------------------ - t -(1 row) - -SELECT ST_IsPolygonCCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))'::geometry); - st_ispolygonccw ------------------ - t -(1 row) - --- EMPTY handling -SELECT ST_IsPolygonCCW('POLYGON EMPTY'::geometry); - st_ispolygonccw ------------------ - t -(1 row) - --- Single polygon, ccw exterior ring only -SELECT ST_IsPolygonCCW('POLYGON ((0 0, 1 0, 1 1, 0 0))'::geometry); - st_ispolygonccw ------------------ - t -(1 row) - --- Single polygon, cw exterior ring only -SELECT ST_IsPolygonCCW('POLYGON ((0 0, 1 1, 1 0, 0 0))'::geometry); - st_ispolygonccw ------------------ - f -(1 row) - --- Single polygon, ccw exterior ring, cw interior rings -SELECT ST_IsPolygonCCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); - st_ispolygonccw ------------------ - t -(1 row) - --- Single polygon, cw exterior ring, ccw interior rings -SELECT ST_IsPolygonCCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); - st_ispolygonccw ------------------ - f -(1 row) - --- Single polygon, ccw exerior ring, mixed interior rings -SELECT ST_IsPolygonCCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); - st_ispolygonccw ------------------ - f -(1 row) - --- Single polygon, cw exterior ring, mixed interior rings -SELECT ST_IsPolygonCCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); - st_ispolygonccw ------------------ - f -(1 row) - --- MultiPolygon, ccw exterior rings only -SELECT ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))'::geometry); - st_ispolygonccw ------------------ - t -(1 row) - --- MultiPolygon, cw exterior rings only -SELECT ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - st_ispolygonccw ------------------ - f -(1 row) - --- MultiPolygon, mixed exterior rings -SELECT ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - st_ispolygonccw ------------------ - f -(1 row) - -RETURN ST_IsPolygonCCW('POINT (0 0)'::geometry); - st_ispolygonccw ------------------ - true -(1 row) - -RETURN ST_IsPolygonCCW('MULTIPOINT ((0 0), (1 1))'::geometry); - st_ispolygonccw ------------------ - true -(1 row) - -RETURN ST_IsPolygonCCW('LINESTRING (1 1, 2 2)'::geometry); - st_ispolygonccw ------------------ - true -(1 row) - -RETURN ST_IsPolygonCCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))'::geometry); - st_ispolygonccw ------------------ - true -(1 row) - --- EMPTY handling -RETURN ST_IsPolygonCCW('POLYGON EMPTY'::geometry); - st_ispolygonccw ------------------ - true -(1 row) - --- Single polygon, ccw exterior ring only -RETURN ST_IsPolygonCCW('POLYGON ((0 0, 1 0, 1 1, 0 0))'::geometry); - st_ispolygonccw ------------------ - true -(1 row) - --- Single polygon, cw exterior ring only -RETURN ST_IsPolygonCCW('POLYGON ((0 0, 1 1, 1 0, 0 0))'::geometry); - st_ispolygonccw ------------------ - false -(1 row) - --- Single polygon, ccw exterior ring, cw interior rings -RETURN ST_IsPolygonCCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); - st_ispolygonccw ------------------ - true -(1 row) - --- Single polygon, cw exterior ring, ccw interior rings -RETURN ST_IsPolygonCCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); - st_ispolygonccw ------------------ - false -(1 row) - --- Single polygon, ccw exerior ring, mixed interior rings -RETURN ST_IsPolygonCCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); - st_ispolygonccw ------------------ - false -(1 row) - --- Single polygon, cw exterior ring, mixed interior rings -RETURN ST_IsPolygonCCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); - st_ispolygonccw ------------------ - false -(1 row) - --- MultiPolygon, ccw exterior rings only -RETURN ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))'::geometry); - st_ispolygonccw ------------------ - true -(1 row) - --- MultiPolygon, cw exterior rings only -RETURN ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - st_ispolygonccw ------------------ - false -(1 row) - --- MultiPolygon, mixed exterior rings -RETURN ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - st_ispolygonccw ------------------ - false -(1 row) - --- --- ST_DistanceSpheroid --- -SELECT round(ST_DistanceSpheroid('MULTIPOLYGON(((-10 40,-10 55,-10 70,5 40,-10 40)))'::geometry, - 'MULTIPOINT(20 40,20 55,20 70,35 40,35 55,35 70,50 40,50 55,50 70)', - 'SPHEROID["GRS_1980",6378137,298.257222101]')); - round ---------- - 1121395 -(1 row) - -SELECT round(ST_DistanceSpheroid('MULTIPOLYGON(((-10 40,-10 55,-10 70,5 40,-10 40)))'::geometry, - 'MULTIPOINT(20 40,20 55,20 70,35 40,35 55,35 70,50 40,50 55,50 70)')); - round ---------- - 1121395 -(1 row) - -RETURN round(ST_DistanceSpheroid('MULTIPOLYGON(((-10 40,-10 55,-10 70,5 40,-10 40)))'::geometry, - 'MULTIPOINT(20 40,20 55,20 70,35 40,35 55,35 70,50 40,50 55,50 70)', - 'SPHEROID["GRS_1980",6378137,298.257222101]')); - round ------------ - 1121395.0 -(1 row) - -RETURN round(ST_DistanceSpheroid('MULTIPOLYGON(((-10 40,-10 55,-10 70,5 40,-10 40)))'::geometry, - 'MULTIPOINT(20 40,20 55,20 70,35 40,35 55,35 70,50 40,50 55,50 70)')); - round ------------ - 1121395.0 -(1 row) - --- --- Temporal --- --- --- ST_ClosestPointOfApproach --- --- Converging -select ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'::geometry); - st_closestpointofapproach ---------------------------- - 10 -(1 row) - -RETURN ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)'::geometry, 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'); - st_closestpointofapproach ---------------------------- - 10.0 -(1 row) - --- Following -select ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry); - st_closestpointofapproach ---------------------------- - 5 -(1 row) - -RETURN ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry); - st_closestpointofapproach ---------------------------- - 5.0 -(1 row) - --- Crossing -select ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry); - st_closestpointofapproach ---------------------------- - 5.5 -(1 row) - -RETURN ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry); - st_closestpointofapproach ---------------------------- - 5.5 -(1 row) - --- Meeting -select ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry); - st_closestpointofapproach ---------------------------- - 10 -(1 row) - -RETURN ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry); - st_closestpointofapproach ---------------------------- - 10.0 -(1 row) - --- Disjoint -select ST_ClosestPointOfApproach( 'LINESTRINGM(0 0 0, 0 0 4)', 'LINESTRINGM(0 0 5, 0 0 10)'::geometry); - st_closestpointofapproach ---------------------------- - -(1 row) - -RETURN ST_ClosestPointOfApproach('LINESTRINGM(0 0 0, 0 0 4)', 'LINESTRINGM(0 0 5, 0 0 10)'::geometry); - st_closestpointofapproach ---------------------------- - -(1 row) - --- --- ST_DistanceCPA --- --- Converging -select ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'::geometry); - st_distancecpa ----------------- - 0 -(1 row) - -RETURN ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)'::geometry, 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'); - st_distancecpa ----------------- - 0.0 -(1 row) - --- Following -select ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry); - st_distancecpa ------------------- - 8.66025403784439 -(1 row) - -RETURN ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry); - st_distancecpa ------------------- - 8.66025403784439 -(1 row) - --- Crossing -select ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry); - st_distancecpa ----------------- - 5 -(1 row) - -RETURN ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry); - st_distancecpa ----------------- - 5.0 -(1 row) - --- Meeting -select ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry); - st_distancecpa ----------------- - 5 -(1 row) - -RETURN ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry); - st_distancecpa ----------------- - 5.0 -(1 row) - --- Disjoint -select ST_DistanceCPA( 'LINESTRINGM(0 0 0, 0 0 4)', 'LINESTRINGM(0 0 5, 0 0 10)'::geometry); - st_distancecpa ----------------- - -(1 row) - -RETURN ST_DistanceCPA('LINESTRINGM(0 0 0, 0 0 4)', 'LINESTRINGM(0 0 5, 0 0 10)'::geometry); - st_distancecpa ----------------- - -(1 row) - --- --- |=| Operator --- --- Converging -select 'LINESTRINGZM(0 0 0 0, 10 10 10 10)' |=| 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'::geometry; - ?column? ----------- - 0 -(1 row) - -RETURN 'LINESTRINGZM(0 0 0 0, 10 10 10 10)'::geometry |=| 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'; - ?column? ----------- - 0.0 -(1 row) - --- Following -select 'LINESTRINGZM(0 0 0 0, 10 10 10 10)' |=| 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry; - ?column? ------------------- - 8.66025403784439 -(1 row) - -RETURN 'LINESTRINGZM(0 0 0 0, 10 10 10 10)' |=| 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry; - ?column? ------------------- - 8.66025403784439 -(1 row) - --- Crossing -select 'LINESTRINGZM(0 0 0 0, 0 0 0 10)' |=| 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry; - ?column? ----------- - 5 -(1 row) - -RETURN 'LINESTRINGZM(0 0 0 0, 0 0 0 10)' |=| 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry; - ?column? ----------- - 5.0 -(1 row) - --- Meeting -select 'LINESTRINGZM(0 0 0 0, 0 0 0 10)' |=| 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry; - ?column? ----------- - 5 -(1 row) - -RETURN 'LINESTRINGZM(0 0 0 0, 0 0 0 10)' |=| 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry; - ?column? ----------- - 5.0 -(1 row) - --- Disjoint -select 'LINESTRINGM(0 0 0, 0 0 4)' |=| 'LINESTRINGM(0 0 5, 0 0 10)'::geometry; - ?column? ----------- - -(1 row) - -RETURN 'LINESTRINGM(0 0 0, 0 0 4)' |=| 'LINESTRINGM(0 0 5, 0 0 10)'::geometry; - ?column? ----------- - -(1 row) - --- --- ST_IsValidTrajectory --- -SELECT ST_IsValidTrajectory('POINTM(0 0 0)'::geometry); -NOTICE: Geometry is not a LINESTRING - st_isvalidtrajectory ----------------------- - f -(1 row) - -SELECT ST_IsValidTrajectory('LINESTRINGZ(0 0 0,1 1 1)'::geometry); -NOTICE: Line does not have M dimension - st_isvalidtrajectory ----------------------- - f -(1 row) - -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 0)'::geometry); -NOTICE: Measure of vertex 1 (0) not bigger than measure of vertex 0 (0) - st_isvalidtrajectory ----------------------- - f -(1 row) - -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 1,1 1 0)'::geometry); -NOTICE: Measure of vertex 1 (0) not bigger than measure of vertex 0 (1) - st_isvalidtrajectory ----------------------- - f -(1 row) - -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1,1 1 2,1 0 1)'::geometry); -NOTICE: Measure of vertex 3 (1) not bigger than measure of vertex 2 (2) - st_isvalidtrajectory ----------------------- - f -(1 row) - -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1)'::geometry); - st_isvalidtrajectory ----------------------- - t -(1 row) - -SELECT ST_IsValidTrajectory('LINESTRINGM EMPTY'::geometry); - st_isvalidtrajectory ----------------------- - t -(1 row) - -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1,1 1 2)'::geometry); - st_isvalidtrajectory ----------------------- - t -(1 row) - -RETURN ST_IsValidTrajectory('POINTM(0 0 0)'::geometry); -NOTICE: Geometry is not a LINESTRING - st_isvalidtrajectory ----------------------- - false -(1 row) - -RETURN ST_IsValidTrajectory('LINESTRINGZ(0 0 0,1 1 1)'::geometry); -NOTICE: Line does not have M dimension - st_isvalidtrajectory ----------------------- - false -(1 row) - -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 0)'::geometry); -NOTICE: Measure of vertex 1 (0) not bigger than measure of vertex 0 (0) - st_isvalidtrajectory ----------------------- - false -(1 row) - -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 1,1 1 0)'::geometry); -NOTICE: Measure of vertex 1 (0) not bigger than measure of vertex 0 (1) - st_isvalidtrajectory ----------------------- - false -(1 row) - -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1,1 1 2,1 0 1)'::geometry); -NOTICE: Measure of vertex 3 (1) not bigger than measure of vertex 2 (2) - st_isvalidtrajectory ----------------------- - false -(1 row) - -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1)'::geometry); - st_isvalidtrajectory ----------------------- - true -(1 row) - -RETURN ST_IsValidTrajectory('LINESTRINGM EMPTY'::geometry); - st_isvalidtrajectory ----------------------- - true -(1 row) - -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1,1 1 2)'::geometry); - st_isvalidtrajectory ----------------------- - true -(1 row) - --- --- ST_CPAWithin --- -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 0.0); - st_cpawithin --------------- - t -(1 row) - -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1.0); - st_cpawithin --------------- - t -(1 row) - -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 0.5); - st_cpawithin --------------- - t -(1 row) - -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1); - st_cpawithin --------------- - t -(1 row) - -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2); - st_cpawithin --------------- - t -(1 row) - -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1.9); - st_cpawithin --------------- - t -(1 row) - -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2); - st_cpawithin --------------- - t -(1 row) - -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2.0001); - st_cpawithin --------------- - t -(1 row) - --- temporary disjoint -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 10 0 10)'::geometry ,'LINESTRINGM(10 0 11, 10 10 20)'::geometry, 1e15); - st_cpawithin --------------- - f -(1 row) - -SELECT ST_CPAWithin( 'LINESTRING(0 0 0, 1 0 0)'::geometry ,'LINESTRING(0 0 3 0, 1 0 2 1)'::geometry, 1e16); -ERROR: Both input geometries must have a measure dimension -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 0.0); - st_cpawithin --------------- - true -(1 row) - -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1.0); - st_cpawithin --------------- - true -(1 row) - -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 0.5); - st_cpawithin --------------- - true -(1 row) - -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1); - st_cpawithin --------------- - true -(1 row) - -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2); - st_cpawithin --------------- - true -(1 row) - -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1.9); - st_cpawithin --------------- - true -(1 row) - -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2); - st_cpawithin --------------- - true -(1 row) - -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2.0001); - st_cpawithin --------------- - true -(1 row) - --- temporary disjoint -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 10 0 10)'::geometry ,'LINESTRINGM(10 0 11, 10 10 20)'::geometry, 1e15); - st_cpawithin --------------- - true -(1 row) - -RETURN ST_CPAWithin( 'LINESTRING(0 0 0, 1 0 0)'::geometry ,'LINESTRING(0 0 3 0, 1 0 2 1)'::geometry, 1e16); -NOTICE: Line does not have M dimension - st_cpawithin --------------- - false -(1 row) - --- --- GEOS --- --- --- ST_Intersection --- -SELECT ST_Intersection('MULTIPOINT ((0 0), (1 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry); - st_intersection --------------------------------------------------------------------------------------------------------- - 0104000000020000000101000000000000000000000000000000000000000101000000000000000000F03F000000000000F03F -(1 row) - -SELECT ST_AsEWKT(ST_Intersection('MULTIPOINT ((0 0), (1 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry)); - st_asewkt ---------------------- - MULTIPOINT(0 0,1 1) -(1 row) - -RETURN ST_Intersection('MULTIPOINT ((0 0), (1 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry, -1.0); - st_intersection --------------------------------------------------------------------------------------------------------- - 0104000000020000000101000000000000000000000000000000000000000101000000000000000000F03F000000000000F03F -(1 row) - -RETURN ST_AsEWKT(ST_Intersection('MULTIPOINT ((0 0), (1 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry, -1.0)); - st_asewkt ------------------------ - "MULTIPOINT(0 0,1 1)" -(1 row) - -RETURN ST_AsEWKT(ST_Intersection('MULTIPOINT ((2 2), (5 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry, -1.0)); - st_asewkt ---------------- - "POINT EMPTY" -(1 row) - -RETURN ST_AsEWKT(ST_Intersection('MULTIPOINT ((2 2), (5 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry)); - st_asewkt ---------------- - "POINT EMPTY" -(1 row) - --- --- Algorithms --- --- --- ST_Simplify --- -SELECT ST_AsEWKT(ST_Simplify('POLYGON((0 0,1 1,1 3,0 4,-2 3,-1 1,0 0))'::geometry, 1)); - st_asewkt ------------------------------ - POLYGON((0 0,0 4,-2 3,0 0)) -(1 row) - -SELECT ST_AsEWKT(ST_Simplify('POLYGON((0 0,1 1,1 3,2 3,2 0,0 0))'::geometry, 1)); - st_asewkt ----------------------------- - POLYGON((0 0,2 3,2 0,0 0)) -(1 row) - -RETURN ST_AsEWKT(ST_Simplify('POLYGON((0 0,1 1,1 3,0 4,-2 3,-1 1,0 0))', 1)); - st_asewkt -------------------------------- - "POLYGON((0 0,0 4,-2 3,0 0))" -(1 row) - -RETURN ST_AsEWKT(ST_Simplify('POLYGON((0 0,1 1,1 3,2 3,2 0,0 0))'::geometry, 1)); - st_asewkt ------------------------------- - "POLYGON((0 0,2 3,2 0,0 0))" -(1 row) - -/* - * 3D Functions - */ ---3D Distance functions -CYPHER WITH 'POINT(1 1 1)'::geometry as a, 'POINT(3 2 7)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - st_3ddistance | ?column? | st_3ddwithin | st_3ddfullywithin | st_asewkt | st_asewkt | st_asewkt -------------------+---------------------------+--------------+-------------------+---------------------------+----------------+--------------------------- - 6.40312423743285 | 6.40312423743285::numeric | false | false | "LINESTRING(1 1 1,3 2 7)" | "POINT(1 1 1)" | "LINESTRING(1 1 1,3 2 7)" -(1 row) - -CYPHER WITH 'POINT(1 1 1)'::geometry as a, 'LINESTRING(0 0 0, 2 2 2)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - st_3ddistance | ?column? | st_3ddwithin | st_3ddfullywithin | st_asewkt | st_asewkt | st_asewkt ----------------+---------------------------+--------------+-------------------+---------------------------+----------------+--------------------------- - 0.0 | 1.73205080756888::numeric | true | true | "LINESTRING(1 1 1,1 1 1)" | "POINT(1 1 1)" | "LINESTRING(1 1 1,0 0 0)" -(1 row) - -CYPHER WITH 'POINT(1 1 1)'::geometry as a, 'LINESTRING(5 2 6, -3 -2 4)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - st_3ddistance | ?column? | st_3ddwithin | st_3ddfullywithin | st_asewkt | st_asewkt | st_asewkt -------------------+---------------------------+--------------+-------------------+----------------------------------------------------------------------------+----------------+--------------------------- - 4.09994192757944 | 6.48074069840786::numeric | true | false | "LINESTRING(1 1 1,0.619047619047619 -0.190476190476191 4.904761904761905)" | "POINT(1 1 1)" | "LINESTRING(1 1 1,5 2 6)" -(1 row) - -CYPHER WITH 'LINESTRING(1 1 3, 5 7 8)'::geometry as a, 'POINT(1 1 1)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - st_3ddistance | ?column? | st_3ddwithin | st_3ddfullywithin | st_asewkt | st_asewkt | st_asewkt ----------------+---------------------------+--------------+-------------------+---------------------------+----------------+--------------------------- - 2.0 | 10.0498756211209::numeric | true | false | "LINESTRING(1 1 3,1 1 1)" | "POINT(1 1 3)" | "LINESTRING(5 7 8,1 1 1)" -(1 row) - -CYPHER WITH 'LINESTRING(1 0 5, 11 0 5)'::geometry as a, 'LINESTRING(5 2 0, 5 2 10, 5 0 13)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - st_3ddistance | ?column? | st_3ddwithin | st_3ddfullywithin | st_asewkt | st_asewkt | st_asewkt ----------------+-------------+--------------+-------------------+---------------------------+----------------+----------------------------- - 2.0 | 10::numeric | true | false | "LINESTRING(5 0 5,5 2 5)" | "POINT(5 0 5)" | "LINESTRING(11 0 5,5 0 13)" -(1 row) - -CYPHER WITH 'LINESTRING(1 1 1 , 2 2 2)'::geometry as a, 'POLYGON((0 0 0, 2 2 2, 3 3 3, 0 0 0))'::geometry as b -RETURN ST_3DDistance(a,b); - st_3ddistance ---------------- - 0.0 -(1 row) - -CYPHER WITH 'LINESTRING(1 1 1 , 2 2 2)'::geometry as a, 'POLYGON((0 0 0, 2 2 2, 3 3 1, 0 0 0))'::geometry as b -RETURN ST_3DDistance(a,b); - st_3ddistance ---------------- - 0.0 -(1 row) - --- 3D mixed dimmentionality #2034 ---closestpoint with 2d as first point and 3d as second -RETURN st_astext(st_3dclosestpoint('linestring(0 0,1 1,2 0)'::geometry, 'linestring(0 2 3, 3 2 3)'::geometry)); -NOTICE: One or both of the geometries is missing z-value. The unknown z-value will be regarded as "any value" - st_astext -------------------- - "POINT Z (1 1 3)" -(1 row) - ---closestpoint with 3d as first point and 2d as second -RETURN st_astext(st_3dclosestpoint('linestring(0 0 1,1 1 2,2 0 3)'::geometry, 'linestring(0 2, 3 2)'::geometry)); -NOTICE: One or both of the geometries is missing z-value. The unknown z-value will be regarded as "any value" - st_astext -------------------- - "POINT Z (1 1 2)" -(1 row) - ---shortestline with 2d as first point and 3d as second -RETURN st_astext(st_3dshortestline('linestring(0 0,1 1,2 0)'::geometry, 'linestring(0 2 3, 3 2 3)'::geometry)); -NOTICE: One or both of the geometries is missing z-value. The unknown z-value will be regarded as "any value" - st_astext ------------------------------- - "LINESTRING Z (1 1 3,1 2 3)" -(1 row) - ---shortestline with 3d as first point and 2d as second -RETURN st_astext(st_3dshortestline('linestring(0 0 1,1 1 2,2 0 3)'::geometry, 'linestring(0 2, 3 2)'::geometry)); -NOTICE: One or both of the geometries is missing z-value. The unknown z-value will be regarded as "any value" - st_astext ------------------------------- - "LINESTRING Z (1 1 2,1 2 2)" -(1 row) - ---distance with 2d as first point and 3d as second -RETURN st_3ddistance('linestring(0 0,1 1,2 0)'::geometry, 'linestring(0 2 3, 3 2 3)'::geometry); - st_3ddistance ---------------- - 1.0 -(1 row) - ---distance with 3d as first point and 2d as second -RETURN st_3ddistance('linestring(0 0 1,1 1 2,2 0 3)'::geometry, 'linestring(0 2, 3 2)'::geometry); - st_3ddistance ---------------- - 1.0 -(1 row) - -RETURN ST_AsText(ST_3DClosestPoint('POINT(0 0 0)', 'POINT(0 0)')); -NOTICE: One or both of the geometries is missing z-value. The unknown z-value will be regarded as "any value" - st_astext -------------------- - "POINT Z (0 0 0)" -(1 row) - -RETURN ST_AsText(ST_3DShortestLine('LINESTRING(2 1, 3 0)', 'LINESTRING(0 0 2, 3 3 -4)')); -NOTICE: One or both of the geometries is missing z-value. The unknown z-value will be regarded as "any value" - st_astext ------------------------------------- - "LINESTRING Z (2 1 -1,1.5 1.5 -1)" -(1 row) - -/* - * Polyheadral Surface - */ - -- ST_Dimension on 2D: not closed -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry); - st_dimension --------------- - 2 -(1 row) - -RETURN ST_Dimension('GEOMETRYCOLLECTION(POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0))))'::geometry); - st_dimension --------------- - 2 -(1 row) - --- ST_Dimension on 3D: closed -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry); - st_dimension --------------- - 3 -(1 row) - -RETURN ST_Dimension('GEOMETRYCOLLECTION(POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0))))'::geometry); - st_dimension --------------- - 3 -(1 row) - --- ST_Dimension on 4D: closed -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0)))'::geometry); - st_dimension --------------- - 3 -(1 row) - -RETURN ST_Dimension('GEOMETRYCOLLECTION(POLYHEDRALSURFACE(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0))))'::geometry); - st_dimension --------------- - 3 -(1 row) - --- ST_Dimension on 3D: invalid polyedron (a single edge is shared 3 times) -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,0 1 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry); - st_dimension --------------- - 2 -(1 row) - --- ST_Dimension on 3D: invalid polyedron (redundant point inside each face) -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0 0,1 0 0,1 0 0,0 0 0)),((0 0 1,1 0 1,1 0 1,0 0 1)),((0 0 2,1 0 2,1 0 2,0 0 2)),((0 0 3,1 0 3,1 0 3,0 0 3)))'::geometry); - st_dimension --------------- - 2 -(1 row) - --- ST_NumPatches -RETURN ST_NumPatches('POLYHEDRALSURFACE EMPTY'::geometry); - st_numpatches ---------------- - 0 -(1 row) - -RETURN ST_NumPatches('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry); - st_numpatches ---------------- - 1 -(1 row) - -RETURN ST_NumPatches('POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry); - st_numpatches ---------------- - 4 -(1 row) - --- ST_PatchN -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE EMPTY'::geometry, 1)); - st_asewkt ------------ - -(1 row) - -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry, 1)); - st_asewkt ------------------------------- - "POLYGON((0 0,0 0,0 1,0 0))" -(1 row) - -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry, 0)); - st_asewkt ------------ - -(1 row) - -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry, 2)); - st_asewkt ------------ - -(1 row) - -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry, 2)); - st_asewkt --------------------------------------- - "POLYGON((0 0 0,0 1 0,1 0 0,0 0 0))" -(1 row) - --- PolyedralSurface (TODO) -RETURN ST_AsText(ST_Reverse('POLYHEDRALSURFACE EMPTY'::geometry)); - st_astext ---------------------------- - "POLYHEDRALSURFACE EMPTY" -(1 row) - -RETURN ST_AsText(ST_Reverse('POLYHEDRALSURFACE (((0 0,0 0,0 1,0 0)),((0 0,0 1,1 0,0 0)),((0 0,1 0,0 0,0 0)),((1 0,0 1,0 0,1 0)))'::geometry)); - st_astext ------------------------------------------------------------------------------------------------------- - "POLYHEDRALSURFACE(((0 0,0 1,0 0,0 0)),((0 0,1 0,0 1,0 0)),((0 0,0 0,1 0,0 0)),((1 0,0 0,0 1,1 0)))" -(1 row) - -RETURN ST_AsText(ST_ShiftLongitude('SRID=4326;POINT(270 0)'::geometry)); - st_astext ----------------- - "POINT(-90 0)" -(1 row) - -RETURN ST_AsText(ST_ShiftLongitude('SRID=4326;POINT(-90 0)'::geometry)); - st_astext ----------------- - "POINT(270 0)" -(1 row) - -RETURN ST_AsText(ST_ShiftLongitude('SRID=4326;LINESTRING(174 12, 182 13)'::geometry)); - st_astext ------------------------------- - "LINESTRING(174 12,-178 13)" -(1 row) - --- TODO KNN - WITH ST_GeomFromText( -'PolyhedralSurface( -((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), -((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), -((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) -)') as a -RETURN ST_Translate(a,100, 450,1000); -ERROR: syntax error at or near ")" -LINE 2: 'PolyhedralSurface( - ^ - WITH ST_GeomFromText( -'PolyhedralSurface( -((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), -((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )') as a -RETURN ST_Translate(a,100, 450,1000); -ERROR: syntax error at or near ")" -LINE 2: 'PolyhedralSurface( - ^ --- #68 -- ---RETURN '#68a', ST_AsText(ST_ShiftLongitude(ST_GeomFromText('MULTIPOINT(1 3, 4 5)'))); ---RETURN '#68b', ST_AsText(ST_ShiftLongitude(ST_GeomFromText('CIRCULARSTRING(1 3, 4 5, 6 7)'))); -CREATE (:j {j: 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'::geometry }) ; --- -(0 rows) - -CREATE (:j {j: 'PolyhedralSurface( -((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), -((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), -((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) -)'::geometry }) ; --- -(0 rows) - -SET enable_mergejoin = ON; -SET enable_hashjoin = ON; -SET enable_nestloop = ON; -SET enable_seqscan = false; -SET enable_sort = false; -CREATE INDEX ON postgis.j USING gist((properties->'"j"') gist_geometry_ops_nd); -\d+ postgis.j - Table "postgis.j" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description -------------+---------+-----------+----------+-------------------------------------------------------------------------------------------------+----------+--------------+------------- - id | graphid | | not null | _graphid(_label_id('postgis'::name, 'j'::name)::integer, nextval('postgis.j_id_seq'::regclass)) | plain | | - properties | gtype | | not null | gtype_build_map() | extended | | -Indexes: - "j_expr_idx" gist ((properties -> '"j"'::gtype) gist_geometry_ops_nd) -Inherits: postgis._ag_label_vertex - -EXPLAIN (costs off) - MATCH (a:j) - WHERE a.j &&& 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'::geometry -RETURN a; -ERROR: syntax error at or near "MATCH" -LINE 2: MATCH (a:j) - ^ - MATCH (a:j) - WHERE a.j &&& 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'::geometry -RETURN a; - a --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 844424930131969, "label": "j", "properties": {"j": 010F000080020000000103000080010000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000010300008001000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000}} - {"id": 844424930131970, "label": "j", "properties": {"j": 010F000080060000000103000080010000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000010300008001000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000000000000000000001030000800100000005000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000000001030000800100000005000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F03F0000000000000000010300008001000000050000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F03F00000000000000000103000080010000000500000000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F03F}} -(2 rows) - -EXPLAIN (costs off) - MATCH (a:j) -RETURN a ORDER BY a.j <<->> 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'; -ERROR: syntax error at or near "MATCH" -LINE 2: MATCH (a:j) - ^ - MATCH (a:j) -RETURN a ORDER BY a.j <<->> 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'; -ERROR: syntax error at or near "ORDER" -LINE 2: RETURN a ORDER BY a.j <<->> 'PolyhedralSurface(((0 0 0, 0 0 ... - ^ --- --- 2D Gist Indices --- -CREATE (:i {i: 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry }); --- -(0 rows) - -CREATE (:i {i: 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry }) ; --- -(0 rows) - -CREATE (:i {i: 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry }) ; --- -(0 rows) - -SELECT create_vlabel('postgis', 'i'); -ERROR: label "i" already exists -CREATE INDEX ON postgis.i USING gist ((properties->'"i"') gist_geometry_ops_2d); -CREATE (:i {i: 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry }); --- -(0 rows) - -/* - - - - - - - MATCH (i:i) - WHERE i.i << 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry -RETURN i; -EXPLAIN - - MATCH (i:i) - WHERE i.i << 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry -RETURN i; -*/ --- --- Typecasting --- --- --- To Geometry --- -RETURN topoint('(1, 2)')::geometry ; - ?column? --------------------------------------------- - 0101000000000000000000F03F0000000000000040 -(1 row) - -RETURN topath('(1, 2), (3, 4)')::geometry ; - ?column? ------------------------------------------------------------------------------------- - 010200000002000000000000000000F03F000000000000004000000000000008400000000000001040 -(1 row) - -RETURN topolygon('(1,1), (2,2), (3, 3), (4, 4)')::geometry ; - ?column? --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 01030000000100000005000000000000000000F03F000000000000F03F000000000000004000000000000000400000000000000840000000000000084000000000000010400000000000001040000000000000F03F000000000000F03F -(1 row) - -SELECT 'POLYGON((0 0,1 1,1 3,2 3,2 0,0 0))'::geometry::gtype; - gtype ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 0103000000010000000600000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000840000000000000004000000000000008400000000000000040000000000000000000000000000000000000000000000000 -(1 row) - -RETURN topoint('POINT(1 1)'::geometry); - topoint ---------- - (1,1) -(1 row) - -RETURN 'POINT(1 1)'::geometry; - ?column? --------------------------------------------- - 0101000000000000000000F03F000000000000F03F -(1 row) - --- --- From Box3D --- --- gtype Box3D to box3d -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)'); - tobox3d --------------------- - BOX3D(1 2 3,4 5 6) -(1 row) - --- gtype string to box3d -RETURN 'BOX3D(1 2 3, 4 5 6)' ; - ?column? ------------------------ - "BOX3D(1 2 3, 4 5 6)" -(1 row) - --- gtype Box3D to gtype box2d -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)')::box2d ; - ?column? --------------- - BOX(1 2,4 5) -(1 row) - --- gtype Box3D to gtype geometry -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)')::geometry; - ?column? --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 010F0000800600000001030000800100000005000000000000000000F03F00000000000000400000000000000840000000000000F03F00000000000014400000000000000840000000000000104000000000000014400000000000000840000000000000104000000000000000400000000000000840000000000000F03F0000000000000040000000000000084001030000800100000005000000000000000000F03F00000000000000400000000000001840000000000000104000000000000000400000000000001840000000000000104000000000000014400000000000001840000000000000F03F00000000000014400000000000001840000000000000F03F0000000000000040000000000000184001030000800100000005000000000000000000F03F00000000000000400000000000000840000000000000F03F00000000000000400000000000001840000000000000F03F00000000000014400000000000001840000000000000F03F00000000000014400000000000000840000000000000F03F000000000000004000000000000008400103000080010000000500000000000000000010400000000000000040000000000000084000000000000010400000000000001440000000000000084000000000000010400000000000001440000000000000184000000000000010400000000000000040000000000000184000000000000010400000000000000040000000000000084001030000800100000005000000000000000000F03F00000000000000400000000000000840000000000000104000000000000000400000000000000840000000000000104000000000000000400000000000001840000000000000F03F00000000000000400000000000001840000000000000F03F0000000000000040000000000000084001030000800100000005000000000000000000F03F00000000000014400000000000000840000000000000F03F00000000000014400000000000001840000000000000104000000000000014400000000000001840000000000000104000000000000014400000000000000840000000000000F03F00000000000014400000000000000840 -(1 row) - --- gtype Box3D to geometry -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)'); - tobox3d --------------------- - BOX3D(1 2 3,4 5 6) -(1 row) - --- --- From Box2D --- --- gtype Box2d to Box2d -RETURN toBox2D('box(1 2, 5 6)') ; - tobox2d --------------- - BOX(1 2,5 6) -(1 row) - --- gtype string to Box2d -RETURN 'box(1 2, 5 6)' ; - ?column? ------------------ - "box(1 2, 5 6)" -(1 row) - --- gtype Box2d to geometry -RETURN toBox2D('box(1 2, 5 6)') ; - tobox2d --------------- - BOX(1 2,5 6) -(1 row) - --- gtype Box3D to box2d -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)') ; - tobox3d --------------------- - BOX3D(1 2 3,4 5 6) -(1 row) - -DROP GRAPH postgis CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to table postgis._ag_label_vertex -drop cascades to table postgis._ag_label_edge -drop cascades to table postgis.j -drop cascades to table postgis.i -NOTICE: graph "postgis" has been dropped - drop_graph ------------- - -(1 row) - diff --git a/regress/expected/traversal_functions.out b/regress/expected/traversal_functions.out deleted file mode 100644 index 4702369a..00000000 --- a/regress/expected/traversal_functions.out +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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. - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH traversal_functions; -NOTICE: graph "traversal_functions" has been created - create_graph --------------- - -(1 row) - -USE GRAPH traversal_functions; - use_graph ------------ - -(1 row) - --- --- Create table to hold the start and end vertices to test the SRF --- --- Create a graph to test -CREATE (b:begin)-[:edge]->(u1:middle)-[:edge]->(u2:middle)-[:edge]->(u3:middle)-[:edge]->(e:end), - (u1)-[:self_loop]->(u1), - (e)-[:self_loop]->(e), - (b)-[:alternate_edge]->(u1), - (u2)-[:alternate_edge]->(u3), - (u3)-[:alternate_edge]->(e), - (u2)-[:bypass_edge]->(e), - (e)-[:alternate_edge]->(u3), - (u3)-[:alternate_edge]->(u2), - (u2)-[:bypass_edge]->(b); --- -(0 rows) - -MATCH p=(u:begin)-[*3..3]->(v:end) RETURN size(p); - size ------- - 7 - 7 -(2 rows) - -MATCH p=(u:begin)-[*]->(v:end) RETURN DISTINCT @-@ p; - ?column? ----------- - 9 - 4 - 6 - 8 - 5 - 7 - 10 - 3 -(8 rows) - -MATCH p=(u:begin)-[*3..3]->(v:end) RETURN relationships(p) ; - relationships -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} -(2 rows) - -MATCH p=(u:begin)-[*3..3]->(v:end) RETURN nodes(p); - nodes ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"{\"id\": 844424930131969, \"label\": \"begin\", \"properties\": {}}","{\"id\": 1407374883553281, \"label\": \"middle\", \"properties\": {}}","{\"id\": 1407374883553281, \"label\": \"middle\", \"properties\": {}}","{\"id\": 1688849860263937, \"label\": \"end\", \"properties\": {}}"} - {"{\"id\": 844424930131969, \"label\": \"begin\", \"properties\": {}}","{\"id\": 1407374883553281, \"label\": \"middle\", \"properties\": {}}","{\"id\": 1407374883553281, \"label\": \"middle\", \"properties\": {}}","{\"id\": 1688849860263937, \"label\": \"end\", \"properties\": {}}"} -(2 rows) - -MATCH p=(u:begin)-[*3..3]->(v:end) WITH nodes(p) as nodes RETURN nodes[1]; -ERROR: operator does not exist: vertex[] -> gtype -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. -MATCH p=(u:begin)-[*3..3]->(v:end) WITH nodes(p) as nodes RETURN nodes[size(nodes) - 1]; -ERROR: function postgraph.size(vertex[]) does not exist -LINE 1: .....3]->(v:end) WITH nodes(p) as nodes RETURN nodes[size(nodes... - ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. --- --- Clean up --- -DROP GRAPH traversal_functions CASCADE; -NOTICE: drop cascades to 9 other objects -DETAIL: drop cascades to table traversal_functions._ag_label_vertex -drop cascades to table traversal_functions._ag_label_edge -drop cascades to table traversal_functions.begin -drop cascades to table traversal_functions.edge -drop cascades to table traversal_functions.middle -drop cascades to table traversal_functions."end" -drop cascades to table traversal_functions.self_loop -drop cascades to table traversal_functions.alternate_edge -drop cascades to table traversal_functions.bypass_edge -NOTICE: graph "traversal_functions" has been dropped - drop_graph ------------- - -(1 row) - --- --- End --- diff --git a/regress/expected/variable_edge_functions.out b/regress/expected/variable_edge_functions.out deleted file mode 100644 index 8a559fc1..00000000 --- a/regress/expected/variable_edge_functions.out +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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. - */ -LOAD 'postgraph'; -SET search_path TO postgraph; -CREATE GRAPH variable_edge_functions; -NOTICE: graph "variable_edge_functions" has been created - create_graph --------------- - -(1 row) - -USE GRAPH variable_edge_functions; - use_graph ------------ - -(1 row) - --- --- Create table to hold the start and end vertices to test the SRF --- --- Create a graph to test -CREATE (b:begin)-[:edge]->(u1:middle)-[:edge]->(u2:middle)-[:edge]->(u3:middle)-[:edge]->(e:end), - (u1)-[:self_loop]->(u1), - (e)-[:self_loop]->(e), - (b)-[:alternate_edge]->(u1), - (u2)-[:alternate_edge]->(u3), - (u3)-[:alternate_edge]->(e), - (u2)-[:bypass_edge]->(e), - (e)-[:alternate_edge]->(u3), - (u3)-[:alternate_edge]->(u2), - (u2)-[:bypass_edge]->(b); --- -(0 rows) - -MATCH (:begin)-[e*3..3]->(:end) RETURN nodes(e); - nodes ---------------------------------------------------------------------------------------------------------------------------------------------------- - {"{\"id\": 1407374883553281, \"label\": \"middle\", \"properties\": {}}","{\"id\": 1407374883553281, \"label\": \"middle\", \"properties\": {}}"} - {"{\"id\": 1407374883553281, \"label\": \"middle\", \"properties\": {}}","{\"id\": 1407374883553281, \"label\": \"middle\", \"properties\": {}}"} -(2 rows) - -MATCH (:begin)-[e*3..3]->(:end) WITH nodes(e) as nodes RETURN nodes[1]; -ERROR: operator does not exist: vertex[] -> gtype -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. -MATCH (:begin)-[e*3..3]->(:end) WITH nodes(e) as nodes RETURN nodes[size(nodes) - 1]; -ERROR: function postgraph.size(vertex[]) does not exist -LINE 1: ...3..3]->(:end) WITH nodes(e) as nodes RETURN nodes[size(nodes... - ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. -MATCH (:begin)-[e*3..3]->(:end) RETURN relationships(e); - relationships -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} -(2 rows) - -MATCH (:begin)-[e*3..3]->(:end) WITH relationships(e) as edges RETURN edges[1]; -ERROR: operator does not exist: edge[] -> gtype -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. -MATCH (:begin)-[e*3..3]->(:end) WITH relationships(e) as edges RETURN edges[size(edges) - 1]; -ERROR: function postgraph.size(edge[]) does not exist -LINE 1: ...:end) WITH relationships(e) as edges RETURN edges[size(edges... - ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. -MATCH (:begin)-[ve*3..3]->(:end) -MATCH ()-[e]->() -RETURN e @> ve, id(e), edges(ve); - ?column? | id | edges -----------+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - f | 1125899906842625 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1125899906842626 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - t | 1125899906842627 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1125899906842628 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1970324836974593 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1970324836974594 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - t | 2251799813685249 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685250 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685251 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685252 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685253 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2533274790395905 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2533274790395906 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1125899906842625 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1125899906842626 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - t | 1125899906842627 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - t | 1125899906842628 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1970324836974593 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1970324836974594 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685249 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685250 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685251 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685252 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685253 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2533274790395905 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2533274790395906 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} -(26 rows) - -MATCH (:begin)-[ve*3..3]->(:end) -MATCH ()-[e]->() -RETURN ve <@ e, id(e), edges(ve); - ?column? | id | edges -----------+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - f | 1125899906842625 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1125899906842626 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - t | 1125899906842627 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1125899906842628 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1970324836974593 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1970324836974594 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - t | 2251799813685249 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685250 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685251 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685252 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685253 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2533274790395905 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2533274790395906 | {"{\"id\": 2251799813685249, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"alternate_edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1125899906842625 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1125899906842626 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - t | 1125899906842627 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - t | 1125899906842628 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1970324836974593 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 1970324836974594 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685249 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685250 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685251 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685252 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2251799813685253 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2533274790395905 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} - f | 2533274790395906 | {"{\"id\": 1125899906842628, \"start_id\": 844424930131969, \"end_id\": 1407374883553281, \"label\": \"edge\", \"properties\": {}}","{\"id\": 1125899906842627, \"start_id\": 1407374883553281, \"end_id\": 1407374883553282, \"label\": \"edge\", \"properties\": {}}","{\"id\": 2533274790395905, \"start_id\": 1407374883553282, \"end_id\": 1688849860263937, \"label\": \"bypass_edge\", \"properties\": {}}"} -(26 rows) - -MATCH (:begin)-[e*]->(:end) RETURN DISTINCT @-@ e; - ?column? ----------- - 9 - 10 - 4 - 7 - 3 - 6 - 5 - 8 -(8 rows) - --- --- Clean up --- -DROP GRAPH variable_edge_functions CASCADE; -NOTICE: drop cascades to 9 other objects -DETAIL: drop cascades to table variable_edge_functions._ag_label_vertex -drop cascades to table variable_edge_functions._ag_label_edge -drop cascades to table variable_edge_functions.begin -drop cascades to table variable_edge_functions.edge -drop cascades to table variable_edge_functions.middle -drop cascades to table variable_edge_functions."end" -drop cascades to table variable_edge_functions.self_loop -drop cascades to table variable_edge_functions.alternate_edge -drop cascades to table variable_edge_functions.bypass_edge -NOTICE: graph "variable_edge_functions" has been dropped - drop_graph ------------- - -(1 row) - --- --- End --- diff --git a/regress/sql/aggregation.sql b/regress/sql/aggregation.sql deleted file mode 100644 index 004af355..00000000 --- a/regress/sql/aggregation.sql +++ /dev/null @@ -1,273 +0,0 @@ - -/* - * Copyright (C) 2023-2024 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -SET extra_float_digits = 0; -LOAD 'postgraph'; -SET search_path TO postgraph; -set timezone TO 'GMT'; - --- --- avg(), sum(), count(), & count(*) --- -CREATE GRAPH college; -USE GRAPH college; - -CREATE (:students {name: 'Jack ', gpa: 3.0, age: 21, zip: 13275}) ; -CREATE (:students {name: 'Jill ', gpa: 3.5, age: 27, zip: 87962}) ; -CREATE (:students {name: 'Jim ', gpa: 3.75, age: 32, zip: 68752}) ; -CREATE (:students {name: 'Rick ', gpa: 2.5, age: 24, zip: '75612 '}) ; -CREATE (:students {name: 'Ann ', gpa: 3.8::numeric, age: 23}) ; -CREATE (:students {name: 'Derek ', gpa: 4.0, age: 19, zip: 65218}) ; -CREATE (:students {name: 'Jessica ', gpa: 3.9::numeric, age: 20}) ; - -MATCH (u) RETURN corr(u.gpa, u.age) ; -MATCH (u) RETURN covar_pop(u.gpa, u.age) ; -MATCH (u) RETURN covar_samp(u.gpa, u.age) ; -MATCH (u) RETURN regr_sxx(u.gpa, u.age) ; -MATCH (u) RETURN regr_syy(u.gpa, u.age) ; -MATCH (u) RETURN regr_sxy(u.gpa, u.age) ; -MATCH (u) RETURN regr_slope(u.gpa, u.age) ; -MATCH (u) RETURN regr_intercept(u.gpa, u.age) ; -MATCH (u) RETURN regr_avgx(u.gpa, u.age) ; -MATCH (u) RETURN regr_avgy(u.gpa, u.age) ; -MATCH (u) RETURN regr_r2(u.gpa, u.age) ; - - -MATCH (u) RETURN avg(u.gpa), sum(u.gpa), sum(u.gpa)/count(u.gpa), count(u.gpa), count(*); - -CREATE (:students {name: 'Dave ', age: 24}); -CREATE (:students {name: 'Mike ', age: 18}); - -MATCH (u) RETURN (u); -MATCH (u) RETURN avg(u.gpa), sum(u.gpa), sum(u.gpa) / count(u.gpa), count(u.gpa), count(*); - --- --- min() & max() --- -MATCH (u) RETURN min(u.gpa), max(u.gpa), count(u.gpa), count(*); -MATCH (u) RETURN min(u.gpa), max(u.gpa), count(u.gpa), count(*) ; -MATCH (u) RETURN min(u.name), max(u.name), count(u.name), count(*); -MATCH (u) RETURN min(u.zip), max(u.zip), count(u.zip), count(*); - --- --- stDev() & stDevP() --- -MATCH (u) RETURN stDev(u.gpa), stDevP(u.gpa); - - --- --- percentileCont() & percentileDisc() --- -MATCH (u) RETURN percentileCont(u.gpa, .55), percentileDisc(u.gpa, .55), percentileCont(u.gpa, .9), percentileDisc(u.gpa, .9); -MATCH (u) RETURN percentileCont(u.gpa, .55); -MATCH (u) RETURN percentileDisc(u.gpa, .55); - --- --- collect() --- -MATCH (u) RETURN collect(u.name), collect(u.age), collect(u.gpa), collect(u.zip); -MATCH (u) RETURN collect(u.gpa), collect(u.gpa); -MATCH (u) RETURN collect(u.zip), collect(u.zip); -MATCH (u) WHERE u.name =~ 'billy ' RETURN collect(u.name); - --- --- DISTINCT inside aggregate functions --- -CREATE (:students {name: 'Sven ', gpa: 3.2, age: 27, zip: 94110}); - -MATCH (u) RETURN (u) ; -MATCH (u) RETURN count(u.zip), count(DISTINCT u.zip); -MATCH (u) RETURN count(u.age), count(DISTINCT u.age); - --- test AUTO GROUP BY for aggregate functions -CREATE GRAPH group_by; -USE GRAPH group_by; - -CREATE (:row {i: 1, j: 2, k:3}); -CREATE (:row {i: 1, j: 2, k:4}); -CREATE (:row {i: 1, j: 3, k:5}); -CREATE (:row {i: 2, j: 3, k:6}); - -MATCH (u:row) RETURN u.i, u.j, u.k; -MATCH (u:row) RETURN u.i, u.j, sum(u.k); - -CREATE (:L {a: 1, b: 2, c:3}); -CREATE (:L {a: 2, b: 3, c:1}); -CREATE (:L {a: 3, b: 1, c:2}); - -/* - * TODO: Get the link from the opencypher website. - */ -MATCH (x:L) RETURN x.a, x.b, x.c, x.a + count(*) + x.b + count(*) + x.c; -MATCH (x:L) RETURN x.a + x.b + x.c, x.a + x.b + x.c + count(*) + count(*) -; - --- with WITH clause -MATCH (x:L) WITH x, count(x) AS cnt RETURN x.a + x.b + x.c + cnt; -MATCH (x:L) WITH x, count(x) AS cnt RETURN x.a + x.b + x.c + cnt + cnt; -MATCH(x:L) WITH x.a + x.b + x.c AS v, count(x) as cnt RETURN v + cnt + cnt; -MATCH (x:L) RETURN x.a, x.a + count(*) + x.b + count(*) + x.c GROUP BY x.a, x.b, x.c; - - --- Why we need explicit grouping -MATCH (x:L) RETURN x.a, x.a + count(*) + x.b + count(*) + x.c; -MATCH (x:L) RETURN x.a + count(*) + x.b + count(*) + x.c; - --- --- Explicit GROUP BY --- -MATCH (x:L) RETURN x.a + count(*) + x.b + count(*) + x.c GROUP BY x.a, x.b, x.c; -MATCH (x:L) RETURN x.a + x.b + x.c + count(*) + count(*) GROUP BY x.a + x.b + x.c; - -MATCH (x:L) RETURN x.a + x.b + x.c + count(*) + count(*); -MATCH (x:L) WITH x.a + count(*) + x.b + count(*) + x.c as cnt GROUP BY x.a, x.b, x.c RETURN cnt ; -MATCH (x:L) WITH x.a + x.b + x.c + count(*) + count(*) as cnt GROUP BY x.a + x.b + x.c RETURN cnt; - --- --- Grouping Keywords --- -MATCH (x) RETURN x.i, x.j, x.k, COUNT(*) GROUP BY ROLLUP (x.i, x.j, x.k); -MATCH (x) RETURN x.i, x.j, x.k, COUNT(*) GROUP BY CUBE (x.i, x.j, x.k); - --- --- HAVING Clause --- -MATCH (x) WITH count(x.i) AS cnt GROUP BY x.i RETURN cnt; -MATCH (x) WITH count(x.i) AS cnt GROUP BY x.i HAVING count(x.i) > 1 RETURN cnt; - --- --- Window Functions --- -MATCH (x) WITH x, row_number() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; -MATCH (x) WITH x, rank() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; -MATCH (x) WITH x, dense_rank() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; -MATCH (x) WITH x, percent_rank() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; -MATCH (x) WITH x, cume_dist() OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num RETURN x, row_num; -MATCH (x) WITH x, lag(x.k) OVER (PARTITION BY COALESCE(x.i, x.a) ORDER BY id(x) ) AS row_num RETURN x, row_num; - - -MATCH (x) -WITH x, lag(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, lead(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, first_value(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, nth_value(x.k, 2) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, ntile(1) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, ntile(2) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, ntile(3) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, last_value(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, row_number() OVER w AS row_num WINDOW w AS (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) -RETURN x, row_num; - -MATCH (x) -WITH x, row_number() OVER w AS row_num WINDOW w AS (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) -RETURN x, row_num; - -MATCH (x) -WITH x, - row_number() OVER ( - PARTITION BY x.i - ORDER BY COALESCE(x.j, x.c) - ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, - row_number() OVER ( - PARTITION BY x.i - ORDER BY COALESCE(x.j, x.c) - ROWS BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING - ) AS row_num -RETURN x, row_num; - -MATCH (x) -WITH x, - row_number() OVER ( - PARTITION BY x.i - ORDER BY COALESCE(x.j, x.c) - ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING - ) AS row_num -RETURN x, row_num; - - - -MATCH (x) RETURN x, last_value(x.k) OVER (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ) AS row_num; -MATCH (x) RETURN x, row_number() OVER w AS row_num WINDOW w AS (PARTITION BY x.i ORDER BY COALESCE(x.j, x.c) ); - --- --- FILTER Clause --- -MATCH (x) RETURN count(*) FILTER (WHERE x.i IS NOT NULL); - --- --- Within Group --- -MATCH (x) RETURN x.i, rank(x.i, x.j) WITHIN GROUP (ORDER BY x.k) GROUP BY x.i; - - --- --- Aggregate Functions for Edge Type --- -CREATE GRAPH edge_aggregates; -USE GRAPH edge_aggregates; - -CREATE ()-[:e]->(); -CREATE ()-[:e]->(); -CREATE ()-[:e]->(); - --- Within Group - -MATCH ()-[e]->() RETURN collect(e); -MATCH ()-[e]->() RETURN collect(e, 2); - -MATCH () RETURN COUNT(*); - --- --- Cleanup --- -DROP GRAPH college CASCADE; -DROP GRAPH edge_aggregates CASCADE; -DROP GRAPH group_by CASCADE; - --- --- End of tests --- diff --git a/regress/sql/cypher_call.sql b/regress/sql/cypher_call.sql deleted file mode 100644 index 0d478c89..00000000 --- a/regress/sql/cypher_call.sql +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -CREATE GRAPH cypher_call; -USE GRAPH cypher_call; - --- --- Create table to hold the start and end vertices to test the SRF --- -CYPHER CALL sin(1) as a RETURN a; - -CYPHER CALL sin(1) as b RETURN b; - -CYPHER WITH [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as lst CALL unnest(lst) as b RETURN b; - -CYPHER WITH [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as lst CALL unnest(lst) as b WHERE b % 2 = 0 RETURN b; - -CYPHER CALL unnest([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) as b RETURN b; - -CYPHER CALL pg_catalog.generate_series(1, 2, 3); - -MATCH (n) CALL this_isnt_a_real_function(1, 2, 3); - -CYPHER CALL { RETURN 1 } RETURN 2; - -CYPHER CALL { RETURN 1 as a } RETURN a; - -CYPHER CALL { CREATE (n) } RETURN n; - -CYPHER CALL { CREATE (n) RETURN n as n } RETURN n; - - --- --- Clean up --- -DROP GRAPH cypher_call CASCADE; - --- --- End --- diff --git a/regress/sql/cypher_delete.sql b/regress/sql/cypher_delete.sql deleted file mode 100644 index 0f6ef071..00000000 --- a/regress/sql/cypher_delete.sql +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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. - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -SELECT create_graph('cypher_delete'); -CREATE GRAPH cypher_delete; -USE GRAPH cypher_delete; - ---Test 1: Delete Vertices -CREATE (:v); -CREATE (:v {i: 0, j: 5, a: 0}); -CREATE (:v {i: 1}); - -MATCH(n) DELETE n RETURN n; -MATCH(n) RETURN n; - ---Test 2: Delete Edges -CREATE (:v)-[:e]->(:v); - ---Should Fail -MATCH(n1)-[e]->(n2) DELETE n1 RETURN n1; -MATCH(n1)-[e]->(n2) DELETE n2 RETURN n2; - -MATCH(n1)-[e]->(n2) DELETE e RETURN e; -MATCH()-[e]->() DELETE e RETURN e; - ---Cleanup -MATCH(n) DELETE n RETURN n; - ---Test 4: DETACH DELECT a vertex -CREATE (:v)-[:e]->(:v); -MATCH(n1)-[e]->(n2) DETACH DELETE n1 RETURN e; - ---Cleanup -MATCH(n) RETURN n; - ---Test 4: DETACH DELECT two vertices tied to the same edge -CREATE (:v)-[:e]->(:v); -MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e; - ---Test 4: DETACH DELECT a vertex -CREATE (:v)-[:e]->(:v); -MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e; - ---Test 5: DETACH DELETE a vertex that points to itself -CREATE (n:v)-[:e]->(n); -MATCH(n1)-[e]->() DETACH DELETE n1 RETURN e; - ---Test 6: DETACH Delete a vertex twice -CREATE (n:v)-[:e]->(n); -MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e; - ---Test 7: Test the SET Clause on DELETED node -CREATE (:v); -CREATE (:v {i: 0, j: 5, a: 0}); -CREATE (:v {i: 1}); - -MATCH(n) DELETE n SET n.lol = 'ftw' RETURN n; -MATCH(n) RETURN n; - ---Test 8: -CREATE (n:v)-[:e]->(:v) CREATE (n)-[:e]->(:v); -MATCH(n1)-[]->() DELETE n1 RETURN n1; - ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; -MATCH(n) RETURN n; - ---Test 9: -CREATE (n:v)-[:e]->(:v); -MATCH(n1)-[e]->() DELETE e, n1 RETURN n1; - ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; -MATCH(n) RETURN n; - ---Test 10: -CREATE (n:v)-[:e]->(:v); -MATCH(n1)-[e]->() DELETE n1, e RETURN n1; - ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; -MATCH(n) RETURN n; - ---Test 11: Delete a vertex twice -CREATE (n:v)-[:e]->(:v), (n)-[:e]->(:v) RETURN n; -MATCH(n1)-[e]->() DETACH DELETE n1 RETURN n1, e; - ---Cleanup -MATCH(n) DELETE n RETURN n; -MATCH(n) RETURN n; - ---Test 12: -CREATE (:v)-[:e]->(:v); -MATCH(n)-[e]->() DETACH DELETE n CREATE (n)-[:e2]->(:v) RETURN e; - ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; -MATCH(n) RETURN n; - ---Test 13: -CREATE (n:v)-[:e]->(n); -MATCH(n)-[e]->(m) DETACH DELETE n CREATE (m)-[:e2]->(:v) RETURN e; - ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; -MATCH(n) RETURN n; - ---Test 14: -CREATE (n:v)-[:e]->(n); -CREATE (:v)-[:e]->(:v); -MATCH(n)-[e]->(m) DETACH DELETE n CREATE (m)-[:e2]->(:v) RETURN e; - ---Cleanup -MATCH(n) DETACH DELETE n RETURN n; -MATCH(n) RETURN n; - ---Test 15: -CREATE (:v); -MATCH(n) SET n.i = 0 DELETE n RETURN n; - ---Cleanup -MATCH(n) RETURN n; - ---Test 16: -CREATE (:v); -MATCH(n) DELETE n SET n.i = 0 RETURN n; - ---Cleanup -MATCH(n) RETURN n; - ---Test 17: -CREATE (n:v)-[:e]->(:v); -MATCH(n)-[e]->(m) DETACH DELETE n SET e.i = 1 RETURN e; - ---Cleanup ---Note: Expect 1 vertex -MATCH(n) DELETE n RETURN n; - ---Test 18: -CREATE (n:v)-[:e]->(:v); -MATCH(n)-[e]->(m) SET e.i = 1 DETACH DELETE n RETURN e; - ---Cleanup -MATCH(n) DELETE n RETURN n; - ---Test 19: -CREATE (n:v); -MATCH (n) DELETE n CREATE (n)-[:e]->(:v) RETURN n; - ---Cleanup -MATCH(n) DELETE n RETURN n; - ---Test 20 Undefined Reference: -MATCH (n) DELETE m RETURN n; - ---Test 21 Prepared Statements -/* -CREATE (v:v); - -PREPARE d AS MATCH (v) DELETE (v) RETURN v; -EXECUTE d; - -CREATE (v:v); -EXECUTE d; - ---Test 22 pl/pgsql Functions -CREATE (v:v); - -CREATE FUNCTION delete_test() -RETURNS TABLE(vertex vertex) -LANGUAGE plpgsql -VOLATILE -AS $BODY$ -BEGIN - RETURN QUERY MATCH (v) DELETE (v) RETURN v; -END -$BODY$; - -SELECT delete_test(); - -CREATE (v:v); -SELECT delete_test(); - --- Clean Up -MATCH(n) DETACH DELETE n RETURN n; -*/ ---Test 23 -CREATE (n:v)-[:e]->(); -CREATE (n:v)-[:e2]->(); - -MATCH p=()-[]->() RETURN p; -MATCH(n)-[e]->(m) DELETE e; -MATCH p=()-[]->() RETURN p; - --- Clean Up -MATCH(n) DELETE n RETURN n; - ---Test 24 -CREATE (n:v)-[:e]->(); -CREATE (n:v)-[:e2]->(); - -MATCH p=()-[]->() RETURN p; -MATCH(n)-[]->() DETACH DELETE n; -MATCH p=()-[]->() RETURN p; - --- Clean Up -MATCH(n) DELETE n RETURN n; - --- --- Clean up --- -DROP FUNCTION delete_test; -SELECT drop_graph('cypher_delete', true); - --- --- End --- diff --git a/regress/sql/cypher_merge.sql b/regress/sql/cypher_merge.sql deleted file mode 100644 index a62d08ec..00000000 --- a/regress/sql/cypher_merge.sql +++ /dev/null @@ -1,479 +0,0 @@ -/* - * 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. - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -CREATE GRAPH cypher_merge; -USE GRAPH cypher_merge; - -/* - * Section 1: MERGE with single vertex - */ -/* - * test 1: Single MERGE Clause, path doesn't exist - */ ---test query -MERGE (n {i: 'Hello Merge'}); - ---validate -MATCH (n) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 2: Single MERGE Clause, path exists - */ ---data setup -CREATE ({i: 'Hello Merge'}); - ---test_query -MERGE ({i: 'Hello Merge'}); - ---validate -MATCH (n) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 3: Prev clause returns no results, no data created - */ ---test query -MATCH (n) MERGE ({i: n.i}); - ---validate -MATCH (n) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 4: Prev clause has results, path exists - */ ---test query -CREATE ({i: 'Hello Merge'}); -MATCH (n) MERGE ({i: n.i}); - ---validate -MATCH (n) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 5: Prev clause has results, path does not exist (differnt property name) - */ ---data setup -CREATE ({i: 'Hello Merge'}); - ---test query -MATCH (n) MERGE ({j: n.i}); - ---validate -MATCH (n) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 6: MERGE with no prev clause, filters correctly, data created - */ --- setup -CREATE ({i: 2}); - ---test query -MERGE (n {i: 1}) RETURN n; - ---validate -MATCH (n) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 7: MERGE with no prev clause, filters correctly, no data created - */ --- setup -CREATE ({i: 1}); -CREATE ({i: 1}); -CREATE ({i: 2}); -CREATE (); - ---test query -MERGE (n {i: 1}) RETURN n; - ---validate -MATCH (n) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - - -/* - * Section 2: MERGE with edges - */ - -/* - * test 8: MERGE creates edge - */ --- setup -CREATE (); - ---test query -MATCH (n) MERGE (n)-[:e]->(:v); - ---validate -MATCH (n)-[e:e]->(m:v) RETURN n, e, m; - ---clean up -MATCH (n) DETACH DELETE n; - - -/* - * test 9: edge already exists - */ --- setup -CREATE ()-[:e]->(); - ---test query -MERGE (n)-[:e]->(:v); - ---validate -MATCH (n)-[e:e]->(m:v) RETURN n, e, m; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 10: edge doesn't exist, using MATCH - */ --- setup -CREATE (); - ---test query -MATCH (n) MERGE (n)-[:e]->(:v); - ---validate created correctly -MATCH (n)-[e:e]->(m:v) RETURN n, e, m; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 11: edge already exists, using MATCH - */ --- setup -CREATE ()-[:e]->(); - ---test query -MATCH (n) MERGE (n)-[:e]->(:v); - ---validate created correctly -MATCH (n)-[e:e]->(m:v) RETURN n, e, m; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 12: Partial Path Exists, creates whole path - */ --- setup -CREATE ()-[:e]->(); - ---test query -MERGE ()-[:e]->()-[:e]->(); - ---validate created correctly ---Returns 3. One for the data setup and 2 for the longer path in MERGE -SELECT count(*) FROM cypher('cypher_merge',; - --- Returns 1, the path created in MERGE -SELECT count(*) FROM cypher('cypher_merge',; - --- 5 vertices total should have been created -SELECT count(*) FROM cypher('cypher_merge',; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 13: edge doesn't exists (differnt label), using MATCH - */ --- setup -CREATE ()-[:e]->(); - ---test query -MATCH (n) MERGE (n)-[:e_new]->(:v); - ---validate created correctly -MATCH (n)-[e]->(m:v) RETURN n, e, m; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 14: edge doesn't exists (different label), without MATCH - */ --- setup -CREATE ()-[:e]->(); - ---test query -MERGE (n)-[:e_new]->(:v); - ---validate created correctly -MATCH (n)-[e]->(m:v) RETURN n, e, m; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * Section 3: MERGE with writing clauses - */ - -/* - * test 15: - */ - ---test query -CREATE () MERGE (n); - ---validate created correctly -MATCH (n) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 16: - */ - ---test query -CREATE (n) WITH n as a MERGE (a)-[:e]->(); - ---validate created correctly -MATCH p=()-[:e]->() RETURN p; - ---clean up -MATCH (n) DETACH DELETE n; - - -/* - * test 17: - * TODO - */ - ---test query -CREATE (n) MERGE (n)-[:e]->(); - ---validate created correctly -MATCH p=()-[:e]->() RETURN p; - ---clean up -MATCH (n) DETACH DELETE n; - - -/* - * test 18: - */ - ---test query -CREATE (n {i : 1}) SET n.i = 2 MERGE ({i: 2}); - ---validate created correctly -MATCH (a) RETURN a; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 19: - */ ---test query -CREATE (n {i : 1}) SET n.i = 2 WITH n as a MERGE ({i: 2}); - ---validate created correctly -MATCH (a) RETURN a; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 20: - */ ---data setup -CREATE (n {i : 1}); - - ---test query -MATCH (n {i : 1}) SET n.i = 2 WITH n as a MERGE ({i: 2}); - ---validate created correctly -MATCH (a) RETURN a; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 21: - */ ---data setup -CREATE (n {i : 1}); - - ---test query -MATCH (n {i : 1}) DELETE n MERGE (n)-[:e]->(); - ---validate, transaction was rolled back because of the error message -MATCH (a) RETURN a; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 22: - * MERGE after MERGE - */ - -CREATE (n:Person {name : 'Rob Reiner', bornIn: 'New York'}); -CREATE (n:Person {name : 'Michael Douglas', bornIn: 'New Jersey'}); -CREATE (n:Person {name : 'Martin Sheen', bornIn: 'Ohio'}); - ---test query -MATCH (person:Person) -MERGE (city:City {name: person.bornIn}) -MERGE (person)-[r:BORN_IN]->(city) -RETURN person.name, person.bornIn, city; - ---validate -MATCH (a) RETURN a; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 23: - */ -MERGE ()-[:e]-(); - ---validate -MATCH p=()-[]->() RETURN p; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 24: - */ -MERGE (a) RETURN a; - ---validate -MATCH (a) RETURN a; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 25: - */ -MERGE p=()-[:e]-() RETURN p; - ---validate -MATCH p=()-[]->() RETURN p; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 26: - */ -MERGE (a)-[:e]-(b) RETURN a; - ---validate -MATCH p=()-[]->() RETURN p; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * test 27: - */ -SELECT * FROM cypher('cypher_merge',; - -MERGE p=()-[:e]-() RETURN p; - - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * Section 4: Error Messages - */ -/* - * test 28: - * Only single paths allowed - */ -MERGE (n), (m) RETURN n, m; - -/* - * test 29: - * Edges cannot reference existing variables - */ -MATCH ()-[e]-() MERGE ()-[e]->(); - -/* - * test 30: - * NULL vertex given to MERGE - */ ---data setup -CREATE (n); - ---test query -MATCH (n) OPTIONAL MATCH (n)-[:e]->(m) MERGE (m); - --- validate only 1 vertex exits -MATCH (n) RETURN n; - --- --- MERGE/SET test - CREATE (n:node {name: 'Jason'}) RETURN n; - MATCH (n:node) RETURN n; - MERGE (n:node {name: 'Jason'}) SET n.name = 'Lisa' RETURN n; - MATCH (n:node) RETURN n; - --- Node doesn't exist, is created, then set - MATCH (n:node) DELETE n; - MATCH (n:node) RETURN n; - MERGE (n:node {name: 'Jason'}) SET n.name = 'Lisa' RETURN n; - MATCH (n:node) RETURN n; --- Multiple SETs - MERGE (n:node {name: 'Lisa'}) SET n.age = 23, n.gender = 'Female' RETURN n; - MATCH (n:node) RETURN n; - MERGE (n:node {name: 'Jason'}) SET n.name = 'Lisa', n.age = 23, n.gender = 'Female' RETURN n; - MATCH (n:node) RETURN n; - ---clean up -MATCH (n) DETACH DELETE n; - -/* - * Clean up graph - */ -DROP GRAPH cypher_merge CASCADE; - diff --git a/regress/sql/cypher_remove.sql b/regress/sql/cypher_remove.sql deleted file mode 100644 index 9acae58a..00000000 --- a/regress/sql/cypher_remove.sql +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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. - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -CREATE GRAPH cypher_remove; -USE GRAPH cypher_remove; - ---test 1 -CREATE (:test_1); -CREATE (:test_1 {i: 0, j: 5, a: 0}); -CREATE (:test_1 {i: 1}); - -MATCH (n:test_1) REMOVE n.i ; -MATCH (n:test_1) RETURN n; - ---test 2 -CREATE (:test_2); -CREATE (:test_2 {i: 0, j: 5, a: 0}); -CREATE (:test_2 {i: 1}); - -MATCH (n:test_2) REMOVE n.j RETURN n; -MATCH (n:test_2) RETURN n; - ---test 3 Validate Paths are updated -CREATE (:test_3 { i : 20 } )-[:test_3_edge {j:20}]->(:test_3 {i:10}); - -MATCH p=(n)-[:test_3_edge]->() REMOVE n.i RETURN p; - ---test 4 Edges -MATCH (n) REMOVE n.i RETURN n; -CREATE (:test_4 { i : 20 } )-[:test_4_edge {j:20}]->(:test_4 {i:10}); - -MATCH ()-[n]->(:test_4) REMOVE n.i RETURN n; -MATCH ()-[n]->(:test_4) RETURN n; - ---test 5 two REMOVE clauses -CREATE (:test_5 {i: 1, j : 2, k : 3}) ; - -MATCH (n:test_5) -REMOVE n.i -REMOVE n.j -RETURN n -; - - -MATCH (n:test_5) -RETURN n -; - ---test 6 Create a loop and see that set can work after create -CREATE (:test_6 {j: 5, y: 99}); - -MATCH (n {j: 5}) -CREATE p=(n)-[e:e {j:34}]->(n) -REMOVE n.y -RETURN n, p -; -MATCH (n:test_6) RETURN n; - - ---test 7 Create a loop and see that set can work after create -CREATE (:test_7)-[e:e {j:34}]->() -REMOVE e.y -RETURN e -; - -MATCH (n:test_7) RETURN n; - - - ---test 8 -MATCH (n:test_7) -MATCH (n)-[e:e {j:34}]->() -REMOVE n.y -RETURN n -; - ---Handle Inheritance -CREATE ( {i : 1 }); -MATCH (n) REMOVE n.i RETURN n; -MATCH (n) RETURN n; - --- prepared statements -/* -CREATE ( {i : 1 }); -PREPARE p_1 AS MATCH (n) REMOVE n.i RETURN n ; -EXECUTE p_1; - -CREATE ( {i : 1 }); -EXECUTE p_1; --- pl/pgsql -CREATE ( {i : 1 }); - -CREATE FUNCTION remove_test() -RETURNS TABLE(vertex vertex) -LANGUAGE plpgsql -VOLATILE -AS $BODY$ -BEGIN - RETURN QUERY MATCH (n) REMOVE n.i RETURN n; -END -$BODY$; - -SELECT remove_test(); - -CREATE ( {i : 1 }); -SELECT remove_test(); - -*/ --- --- Updating Multiple Fields --- -MATCH (n) RETURN n; -MATCH (n) REMOVE n.i, n.j, n.k RETURN n; -MATCH (n) RETURN n; - -CREATE ()-[:edge_multi_property { i: 5, j: 20}]->(); -MATCH ()-[e:edge_multi_property]-() RETURN e; -MATCH ()-[e:edge_multi_property]-() REMOVE e.i, e.j RETURN e; - ---Errors -REMOVE n.i; - -MATCH (n) REMOVE n.i = NULL; - -MATCH (n) REMOVE wrong_var.i; - --- --- Clean up --- -DROP FUNCTION remove_test; -DROP GRAPH cypher_remove CASCADE; - --- --- End --- diff --git a/regress/sql/cypher_set.sql b/regress/sql/cypher_set.sql deleted file mode 100644 index a2e0e8a6..00000000 --- a/regress/sql/cypher_set.sql +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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. - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -CREATE GRAPH cypher_set; -USE GRAPH cypher_set; - -CREATE (:v); -CREATE (:v {i: 0, j: 5, a: 0}); -CREATE (:v {i: 1}); - ---Simple SET test case -MATCH (n) SET n.i = 3; - -MATCH (n) WHERE n.j = 5 SET n.i = NULL RETURN n; -MATCH (n) RETURN n; - -MATCH (n) SET n.i = NULL RETURN n; -MATCH (n) RETURN n; - -MATCH (n) SET n.i = 3 RETURN n; -MATCH (n) RETURN n; - ---Handle Inheritance -CREATE (); -MATCH (n) SET n.i = 3 RETURN n; -MATCH (n) RETURN n; - ---Validate Paths are updated -MATCH (n) CREATE (n)-[:e {j:20}]->(:other_v {k:10}) RETURN n; -MATCH p=(n)-[]->() SET n.i = 50 RETURN p; - ---Edges -MATCH ()-[n]-(:other_v) SET n.i = 3 RETURN n; -MATCH ()-[n]->(:other_v) RETURN n; - - -MATCH (n {j: 5}) SET n.y = 50 SET n.z = 99 RETURN n; - -MATCH (n {j: 5}) RETURN n; - ---Create a loop and see that set can work after create -MATCH (n {j: 5}) CREATE p=(n)-[e:e {j:34}]->(n) SET n.y = 99 RETURN n, p; - ---Create a loop and see that set can work after create -CREATE ()-[e:e {j:34}]->() SET e.y = 99 RETURN e; - -MATCH (n) MATCH (n)-[e:e {j:34}]->() SET n.y = 1 RETURN n; - -MATCH (n) MATCH ()-[e:e {j:34}]->(n) SET n.y = 2 RETURN n; - -MATCH (n)-[]->(n) SET n.y = 99 RETURN n; - -MATCH (n) MATCH (n)-[]->(m) SET n.t = 150 RETURN n; - --- prepared statements -/* -PREPARE p_1 AS MATCH (n) SET n.i = 3 RETURN n ; -EXECUTE p_1; - -EXECUTE p_1; - -PREPARE p_2 AS MATCH (n) SET n.i = $var_name RETURN n ; -EXECUTE p_2('{"var_name": 4}'); - -EXECUTE p_2('{"var_name": 6}'); - -CREATE FUNCTION set_test() -RETURNS TABLE(vertex vertex) -LANGUAGE plpgsql -VOLATILE -AS $BODY$ -BEGIN - RETURN QUERY MATCH (n) SET n.i = 7 RETURN n ; -END -$BODY$; - -SELECT set_test(); - -SELECT set_test(); -*/ --- --- Updating multiple fieds --- -MATCH (n) SET n.i = 3, n.j = 5 RETURN n ; - -MATCH (n)-[m]->(n) SET m.y = n.y RETURN n, m; - ---Errors -SET n.i = NULL; - -MATCH (n) SET wrong_var.i = 3; - -MATCH (n) SET i = 3; - --- --- SET refactor regression tests --- - --- INSERT INTO -/* -CREATE TABLE tbl (result vertex); - - -MATCH (u:vertices) return u ; -MATCH (u:begin)-[:edge]->(v:end) return u, v ; - -INSERT INTO tbl (MATCH (u:vertices) SET u.i = 7 return u ; -INSERT INTO tbl (MATCH (u:vertices) SET u.i = 13 return u ; - -SELECT * FROM tbl; -*/ -CREATE (u:vertices) ; -CREATE (u:begin)-[:edge]->(v:end) ; -MATCH (u:vertices) return u ; - -BEGIN; -MATCH (u:vertices) SET u.i = 1, u.j = 3, u.k = 5 return u ; -MATCH (u:vertices) return u ; - -MATCH (u:vertices) SET u.i = 2, u.j = 4, u.k = 6 return u ; -MATCH (u:vertices) return u ; - -MATCH (u:vertices) SET u.i = 3, u.j = 6, u.k = 9 return u ; -MATCH (u:vertices) return u ; - -MATCH (u:begin)-[:edge]->(v:end) SET u.i = 1, v.i = 2, u.j = 3, v.j = 4 return u, v ; -MATCH (u:begin)-[:edge]->(v:end) return u, v ; - -MATCH (u:begin)-[:edge]->(v:end) SET u.i = 2, v.i = 1, u.j = 4, v.j = 3 return u, v ; -MATCH (u:begin)-[:edge]->(v:end) return u, v ; -END; - -MATCH (u:vertices) return u ; -MATCH (u:begin)-[:edge]->(v:end) return u, v ; - --- test lists -MATCH (n) SET n.i = [3, 'test', [1, 2, 3], {id: 1}, 1.0, 1.0::numeric] RETURN n; - -MATCH (n) RETURN n; - --- test that lists get updated in paths -MATCH p=(u:begin)-[:edge]->(v:end) SET u.i = [1, 2, 3] return u, p ; - -MATCH p=(u:begin)-[:edge]->(v:end) return u, p ; - --- test empty lists -MATCH (n) SET n.i = [] RETURN n; - -MATCH (n) RETURN n; - --- test maps -MATCH (n) SET n.i = {prop1: 3, prop2:'test', prop3: [1, 2, 3], prop4: {id: 1}, prop5: 1.0, prop6:1.0::numeric} RETURN n; - -MATCH (n) RETURN n; - --- test maps in paths -MATCH p=(u:begin)-[:edge]->(v:end) SET u.i = {prop1: 1, prop2: 2, prop3: 3} return u, p ; - -MATCH p=(u:begin)-[:edge]->(v:end) return u, p ; - --- test empty maps -MATCH (n) SET n.i = {} RETURN n; - -MATCH (n) RETURN n; - --- --- Clean up --- -DROP TABLE tbl; -DROP FUNCTION set_test; -DROP GRAPH cypher_set CASCADE; diff --git a/regress/sql/cypher_setop.sql b/regress/sql/cypher_setop.sql deleted file mode 100644 index f8273a32..00000000 --- a/regress/sql/cypher_setop.sql +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2024 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -CREATE GRAPH set_op; - -USE GRAPH set_op; - -CREATE (); - - -MATCH (n) RETURN n UNION MATCH (n) RETURN n; - -MATCH (n) RETURN n UNION ALL MATCH (n) RETURN n; - -MATCH (n) RETURN n UNION RETURN 1; - -MATCH (n) RETURN n UNION RETURN NULL; - -RETURN [1,2,3] UNION RETURN 1; - -RETURN NULL UNION RETURN NULL; - -RETURN NULL UNION ALL RETURN NULL; - -MATCH (n) RETURN n UNION MATCH (n) RETURN n UNION MATCH (n) RETURN n; - -MATCH (n) RETURN n UNION ALL MATCH (n) RETURN n UNION ALL MATCH(n) RETURN n; - -MATCH (n) RETURN n UNION ALL MATCH (n) RETURN n UNION MATCH(n) RETURN n; - -MATCH (n) RETURN n UNION MATCH (n) RETURN n UNION ALL MATCH(n) RETURN n; - -RETURN NULL UNION ALL RETURN NULL UNION ALL RETURN NULL; - -RETURN NULL UNION ALL RETURN NULL UNION RETURN NULL; - -RETURN NULL UNION RETURN NULL UNION ALL RETURN NULL; - -RETURN 1.0::int UNION RETURN 1::float UNION ALL RETURN 2.0::float; - -RETURN 1.0::int UNION RETURN 1.0::float UNION ALL RETURN 1::int; - -RETURN 1.0::float UNION RETURN 1::int UNION RETURN 1::float; - - --- Intersect - -MATCH (n) RETURN n INTERSECT MATCH (m) RETURN m; - -MATCH (n) RETURN n INTERSECT ALL MATCH (m) RETURN m; - - --- Except -MATCH (n) RETURN n EXCEPT MATCH (m) RETURN m; - -MATCH (n) RETURN n EXCEPT ALL MATCH (m) RETURN m; - --- Operator Precedence - -RETURN 2.0::int UNION (RETURN 1::float UNION ALL RETURN 1.0::float); - -(RETURN 2.0::int UNION RETURN 1::float ) UNION ALL RETURN 1.0::float; - - --- Errors -MATCH (n) RETURN n UNION ALL MATCH (m) RETURN n; - -DROP GRAPH set_op; diff --git a/regress/sql/cypher_unwind.sql b/regress/sql/cypher_unwind.sql deleted file mode 100644 index fb43a877..00000000 --- a/regress/sql/cypher_unwind.sql +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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. - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -CREATE GRAPH cypher_unwind; -USE GRAPH cypher_unwind; - -UNWIND [1, 2, 3] AS i RETURN i; - -CREATE ({a: [1, 2, 3]}), ({a: [4, 5, 6]}); - -MATCH (n) WITH n.a AS a UNWIND a AS i RETURN *; - -CYPHER WITH [[1, 2], [3, 4], 5] AS nested -UNWIND nested AS x -UNWIND x AS y -RETURN y; - -CYPHER WITH [[1, 2], [3, 4], 5] AS nested -UNWIND nested AS x -UNWIND x AS y -RETURN x, y; - -CYPHER WITH [1, 2, 3, 4, 5] AS lst -UNWIND lst AS x WHERE x % 2 = 0 -RETURN x; - --- TODO -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -SET a.i = 1 -RETURN a; - --- TODO -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -RETURN a; - -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -RETURN a; - -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -SET a.i = 1 -RETURN a; - -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -WITH a -SET a.i = 1 -RETURN a; - -MATCH (n_1) -WITH collect(n_1) as n -UNWIND n as a -CREATE ({i: a.i}) -RETURN a; - -CREATE ()-[:e {a: [1, 2, 3]}]->(); -CREATE ()-[:e {a: [1, 2, 3]}]->(); - -MATCH t=()-[:e]->() -UNWIND relationships(t) as rel -RETURN rel; - -DROP GRAPH cypher_unwind CASCADE; diff --git a/regress/sql/cypher_vle.sql b/regress/sql/cypher_vle.sql deleted file mode 100644 index 86590b4f..00000000 --- a/regress/sql/cypher_vle.sql +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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. - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -CREATE GRAPH cypher_vle; -USE GRAPH cypher_vle; - --- Create a graph to test -CREATE (b:begin)-[:edge {name: 'main edge'}]->(u1:middle)-[:edge {name: 'main edge'}]->(u2:middle)-[:edge {name: 'main edge'}]->(u3:middle)-[:edge {name: 'main edge'}]->(e:end), - (u1)-[:self_loop {name: 'self loop'}]->(u1), - (e)-[:self_loop {name: 'self loop'}]->(e), - (b)-[:alternate_edge {name: 'alternate edge'}]->(u1), - (u2)-[:alternate_edge {name: 'alternate edge'}]->(u3), - (u3)-[:alternate_edge {name: 'alternate edge'}]->(e), - (u2)-[:bypass_edge {name: 'bypass edge'}]->(e), - (e)-[:alternate_edge {name: 'backup edge'}]->(u3), - (u3)-[:alternate_edge {name: 'backup edge'}]->(u2), - (u2)-[:bypass_edge {name: 'bypass edge'}]->(b) -RETURN b, e; - --- Test the VLE match integration --- Each should find 400 -MATCH (u:begin)-[*]->(v:end) RETURN count(*); -MATCH (u:begin)-[*..]->(v:end) RETURN count(*); -MATCH (u:begin)-[*0..]->(v:end) RETURN count(*); -MATCH (u:begin)-[*1..]->(v:end) RETURN count(*); -MATCH (u:begin)-[*1..200]->(v:end) RETURN count(*); --- Each should find 2 -MATCH (u:begin)<-[*]-(v:end) RETURN count(*); -MATCH (u:begin)<-[*..]-(v:end) RETURN count(*); -MATCH (u:begin)<-[*0..]-(v:end) RETURN count(*); -MATCH (u:begin)<-[*1..]-(v:end) RETURN count(*); -MATCH (u:begin)<-[*1..200]-(v:end) RETURN count(*); --- Each should find 7092 -MATCH (u:begin)-[*]-(v:end) RETURN count(*); -MATCH (u:begin)-[*..]-(v:end) RETURN count(*); -MATCH (u:begin)-[*0..]-(v:end) RETURN count(*); -MATCH (u:begin)-[*1..]-(v:end) RETURN count(*); -MATCH (u:begin)-[*1..200]-(v:end) RETURN count(*); --- Each should find 1 -MATCH (u:begin)-[:edge*]-(v:end) RETURN count(*); -MATCH (u:begin)-[:edge* {name: 'main edge'}]-(v:end) RETURN count(*); -MATCH (u:begin)-[* {name: 'main edge'}]-(v:end) RETURN count(*); --- Each should find 1 -MATCH ()<-[*4..4 {name: 'main edge'}]-() RETURN count(*); -MATCH (u)<-[*4..4 {name: 'main edge'}]-() RETURN count(*); -MATCH ()<-[*4..4 {name: 'main edge'}]-(v) RETURN count(*); --- Each should find 2922 -MATCH ()-[*]->() RETURN count(*); -MATCH (u)-[*]->() RETURN count(*); -MATCH ()-[*]->(v) RETURN count(*); --- Should find 2 -MATCH (u:begin)<-[e*]-(v:end) RETURN e; --- Should find 5 -MATCH p=(:begin)<-[*1..1]-()-[]-() RETURN p ORDER BY p; --- Should find 2922 -MATCH p=()-[*]->(v) RETURN count(*); --- Should find 2 -MATCH p=(u:begin)-[*3..3]->(v:end) RETURN p; --- Should find 12 -MATCH p=(u:begin)-[*3..3]-(v:end) RETURN p; --- Each should find 2 -MATCH p=(u:begin)<-[*]-(v:end) RETURN p; -MATCH p=(u:begin)<-[e*]-(v:end) RETURN p; -MATCH p=(u:begin)<-[e*]-(v:end) RETURN e; -MATCH p=(:begin)<-[*]-()<-[]-(:end) RETURN p; --- Each should return 31 -MATCH ()-[e1]->(v)-[e2]->() RETURN count(*); -MATCH ()-[e1*1..1]->(v)-[e2*1..1]->() RETURN count(*); -MATCH (v)-[e1*1..1]->()-[e2*1..1]->() RETURN count(*); -MATCH ()-[e1]->(v)-[e2*1..1]->() RETURN count(*); -MATCH ()-[e1]->()-[e2*1..1]->() RETURN count(*); -MATCH ()-[e1*1..1]->(v)-[e2]->() RETURN count(*); -MATCH ()-[e1*1..1]->()-[e2]->() RETURN count(*); -MATCH (a)-[e1]->(a)-[e2*1..1]->() RETURN count(*); -MATCH (a) MATCH (a)-[e1*1..1]->(v) RETURN count(*); -MATCH (a) MATCH ()-[e1*1..1]->(a) RETURN count(*); -MATCH (a)-[e*1..1]->() RETURN count(*); - --- Should return 1 path - MATCH p=()<-[e1*]-(:end)-[e2*]->(:begin) RETURN p $$) AS (result traversal); --- Each should return 3 -MATCH (u:begin)-[e*0..1]->(v) RETURN id(u), e, id(v) $$) AS (u gtype, e variable_edge, v gtype); -MATCH p=(u:begin)-[e*0..1]->(v) RETURN p $$) AS (p traversal); --- Each should return 5 -MATCH (u)-[e*0..0]->(v) RETURN id(u), e, id(v) $$) AS (u gtype, e variable_edge, v gtype); -MATCH p=(u)-[e*0..0]->(v) RETURN id(u), p, id(v) $$) AS (u gtype, p traversal, v gtype); --- Each should return 13 and will be the same -MATCH p=()-[*0..0]->()-[]->() RETURN p $$) AS (p traversal); -MATCH p=()-[]->()-[*0..0]->() RETURN p $$) AS (p traversal); - -MATCH (u)-[*]-(v) RETURN count(*); -MATCH (u)-[*0..1]-(v) RETURN count(*); -MATCH (u)-[*..1]-(v) RETURN count(*); -MATCH (u)-[*..5]-(v) RETURN count(*); - -MATCH p=(:begin)<-[ve1*]-(:end), (:end)-[ve2*]->(:begin) RETURN ve1 && ve2; -MATCH p=()<-[ve1:edge*]-(), ()-[ve2:alternate_edge*]->() RETURN ve1 && ve2; -SELECT * FROM ag_label; - --- --- Clean up --- -DROP GRAPH cypher_vle CASCADE; - --- --- End --- diff --git a/regress/sql/load_csv.sql b/regress/sql/load_csv.sql deleted file mode 100644 index 766ef044..00000000 --- a/regress/sql/load_csv.sql +++ /dev/null @@ -1,28 +0,0 @@ - -/* - * Copyright (C) 2023-2025 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -LOAD 'postgraph'; - -CREATE GRAPH load_csv; -USE GRAPH load_csv; - - -CYPHER LOAD CSV '~/postgraph/regress/test.csv' -RETURN *; - -DROP GRAPH load_csv CASCADE; diff --git a/regress/sql/order_by.sql b/regress/sql/order_by.sql deleted file mode 100644 index a78f56c7..00000000 --- a/regress/sql/order_by.sql +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2023-2024 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * Portions Copyright (c) 2020-2023, Apache Software Foundation - * Portions Copyright (c) 2019-2020, Bitnine Global - */ - -LOAD 'postgraph'; - -CREATE GRAPH order_by; -USE GRAPH order_by; - -CREATE (); -CREATE ({i: '1', j: 1}); -CREATE ({i: 1}); -CREATE ({i: 1.0}); -CREATE ({i: 1::numeric}); -CREATE ({i: true}); -CREATE ({i: false}); -CREATE ({i: {key: 'value'}}); -CREATE ({i: [1]}); - -MATCH (u) RETURN u.i ORDER BY u.i; - -MATCH (u) RETURN u.i ORDER BY u.i DESC; - -MATCH (u) RETURN u.i ORDER BY u.i ASC; - -MATCH (x) RETURN x.j ORDER BY x.j NULLS FIRST; - -MATCH (x) RETURN x.j ORDER BY x.j NULLS LAST; - -MATCH (x) RETURN x.i ORDER BY x.i USING <; - -DROP GRAPH order_by CASCADE; \ No newline at end of file diff --git a/regress/sql/postgis.sql b/regress/sql/postgis.sql deleted file mode 100644 index d2984ace..00000000 --- a/regress/sql/postgis.sql +++ /dev/null @@ -1,1113 +0,0 @@ -/* - * PostGraph - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -SET extra_float_digits = 0; -LOAD 'postgraph'; -LOAD 'postgis-3'; -SET search_path TO postgraph, public; -set timezone TO 'GMT'; -CREATE GRAPH postgis; -USE GRAPH postgis; -RETURN 'box(1 2, 5 6)'::box2d; -RETURN 'BOX3D(1 2 3, 4 5 6)'::box3d; -RETURN 'SPHEROID["WGS 84",6378137,298.257223563]'::spheroid; - - -RETURN 'POLYHEDRALSURFACE Z ( - ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), - ((0 0 0, 0 1 0, 0 1 1, 0 0 1, 0 0 0)), - ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), - ((1 1 1, 1 0 1, 0 0 1, 0 1 1, 1 1 1)), - ((1 1 1, 1 0 1, 1 0 0, 1 1 0, 1 1 1)), - ((1 1 1, 1 1 0, 0 1 0, 0 1 1, 1 1 1)) -)'::geometry; - --- --- Box3D --- -WITH u AS (SELECT 'BOX3D(1 2 3, 4 5 6)'::box3d AS g) -SELECT ST_XMin((g)), ST_YMin(g), ST_ZMin(g), ST_XMax(g), ST_YMax(g), ST_ZMax(g)from u; -WITH 'BOX3D(1 2 3, 4 5 6)'::box3d AS g -RETURN ST_XMin((g)), ST_YMin(g), ST_ZMin(g), ST_XMax(g), ST_YMax(g), ST_ZMax(g); --- --- Geometry --- - --- --- I/O Routines --- --- NOTE: The first two queries for each type is the PostGIS Geometry type, used to validate I/O is exactly the same --- - --- Point (2 Dimensional) I/O -SELECT 'POINT( 1 2 )'::geometry; -SELECT ST_AsEWKT('POINT( 1 2 )'::geometry); -RETURN 'POINT( 1 2 )'::geometry; -RETURN ST_AsEWKT('POINT( 1 2 )'::geometry) ; -RETURN ST_AsEWKT('POINT( 1 2 )') ; --- Point (3 Dimensional) I/O -SELECT 'POINT( 1 2 3 )'::geometry; -SELECT ST_AsEWKT('POINT( 1 2 3)'::geometry); -RETURN 'POINT( 1 2 3 )'::geometry; -RETURN ST_AsEWKT('POINT( 1 2 3 )'::geometry) ; -RETURN ST_AsEWKT('POINT( 1 2 3 )') ; --- LineString (2D, 4 Points) I/O -SELECT 'LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)'::geometry; -SELECT ST_AsEWKT('LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)'::geometry); -RETURN 'LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)'::geometry; -RETURN ST_AsEWKT('LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)'::geometry) ; -RETURN ST_AsEWKT('LINESTRING (0 0, 1 1, 2 2, 3 3 , 4 4)') ; --- LineString (3D, 4 Points) I/O -SELECT 'LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3, 4 4 4)'::geometry; -SELECT ST_AsEWKT('LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3 , 4 4 4)'::geometry); -RETURN 'LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3 , 4 4 4)'::geometry; -RETURN ST_AsEWKT('LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3, 4 4 4)'::geometry) ; -RETURN ST_AsEWKT('LINESTRING (0 0 0, 1 1 1, 2 2 2, 3 3 3 , 4 4 4)') ; --- LineString (3D, 2 Points) I/O -SELECT 'LINESTRING (1 2 3, 10 20 30)'::geometry; -SELECT ST_AsEWKT('LINESTRING (1 2 3, 10 20 30)'::geometry); -RETURN 'LINESTRING (1 2 3, 10 20 30)'::geometry; -RETURN ST_AsEWKT('LINESTRING (1 2 3, 10 20 30)'::geometry) ; -RETURN ST_AsEWKT('LINESTRING (1 2 3, 10 20 30)') ; --- Polygon (2D) I/O -SELECT 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry; -SELECT ST_AsEWKT('POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry); -RETURN 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry; -RETURN ST_AsEWKT('POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry) ; -RETURN ST_AsEWKT('POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )') ; --- Polygon (3D) I/O -SELECT 'POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )'::geometry; -SELECT ST_AsEWKT('POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )'::geometry); -RETURN 'POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )'::geometry; -RETURN ST_AsEWKT('POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )'::geometry) ; -RETURN ST_AsEWKT('POLYGON( (0 0 1 , 10 0 1, 10 10 1, 0 10 1, 0 0 1) )') ; --- --- Constructors --- - --- --- ST_MakePoint --- -SELECT ST_MakePoint(1, 0); -SELECT ST_MakePoint(1, 0, 2); -SELECT ST_MakePoint(1, 0, 2, 3); - RETURN ST_MakePoint(1, 0) ; - RETURN ST_MakePoint(1, 0, 2) ; - RETURN ST_MakePoint(1, 0, 2, 3) ; --- --- ST_MakePointM --- -SELECT ST_MakePointM(1, 0, 2); - RETURN ST_MakePointM(1, 0, 2) ; --- --- addbbox --- -SELECT ST_AsEwkt(postgis_addbbox('SRID=4326;POINT(1e+15 1e+15)'::geometry)); -RETURN ST_AsEwkt(postgis_addbbox('SRID=4326;POINT(1e+15 1e+15)'::geometry)) ; - --- --- Comparison Operators --- -WITH p0 AS (SELECT 'POINT(0 0)'::geometry::gtype geom), -p1 AS (SELECT 'POINT(1 1)'::geometry::gtype geom) -SELECT '#4445', - p0.geom < p0.geom, - p0.geom <= p0.geom, - p0.geom = p0.geom, - p0.geom >= p0.geom, - p0.geom > p0.geom, - p0.geom < p1.geom, - p0.geom <= p1.geom, - p0.geom = p1.geom, - p0.geom >= p1.geom, - p0.geom > p1.geom, - p1.geom < p0.geom, - p1.geom <= p0.geom, - p1.geom = p0.geom, - p1.geom >= p0.geom, - p1.geom > p0.geom -FROM p0, p1; - - --- --- 2D Operators --- --- ~= -select 'POINT(1 1)'::geometry ~= 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry ~= 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry ~= 'POINT(1 1 0)'::geometry; -select 'MULTIPOINT(1 1,2 2)'::geometry ~= 'MULTIPOINT(1 1,2 2)'::geometry; -select 'MULTIPOINT(2 2, 1 1)'::geometry ~= 'MULTIPOINT(1 1,2 2)'::geometry; -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry ~= 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -select 'LINESTRING(1 1,2 2)'::geometry ~= 'POINT(1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry ~= 'LINESTRING(2 2, 1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry ~= 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - -RETURN 'POINT(1 1)'::geometry ~= 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry ~= 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry ~= 'POINT(1 1 0)'::geometry; -RETURN 'MULTIPOINT(1 1,2 2)'::geometry ~= 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry ~= 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry ~= 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry ~= 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -RETURN 'LINESTRING(1 1,2 2)'::geometry ~= 'POINT(1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry ~= 'LINESTRING(2 2, 1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry ~= 'LINESTRING(1 1, 2 2, 3 3)'::geometry; --- @ -select 'POINT(1 1)'::geometry @ 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry @ 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry @ 'POINT(1 1 0)'::geometry; -select 'MULTIPOINT(1 1,2 2)'::geometry @ 'MULTIPOINT(1 1,2 2)'::geometry; -select 'MULTIPOINT(2 2, 1 1)'::geometry @ 'MULTIPOINT(1 1,2 2)'::geometry; -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry @ 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -select 'LINESTRING(1 1,2 2)'::geometry @ 'POINT(1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry @ 'LINESTRING(2 2, 1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry @ 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - -RETURN 'POINT(1 1)'::geometry @ 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry @ 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry @ 'POINT(1 1 0)'::geometry; -RETURN 'MULTIPOINT(1 1,2 2)'::geometry @ 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry @ 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry @ 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry @ 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -RETURN 'LINESTRING(1 1,2 2)'::geometry @ 'POINT(1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry @ 'LINESTRING(2 2, 1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry @ 'LINESTRING(1 1, 2 2, 3 3)'::geometry; --- &< -select 'POINT(1 1)'::geometry &< 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry &< 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry &< 'POINT(1 1 0)'::geometry; -select 'MULTIPOINT(1 1,2 2)'::geometry &< 'MULTIPOINT(1 1,2 2)'::geometry; -select 'MULTIPOINT(2 2, 1 1)'::geometry &< 'MULTIPOINT(1 1,2 2)'::geometry; -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &< 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -select 'LINESTRING(1 1,2 2)'::geometry &< 'POINT(1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry &< 'LINESTRING(2 2, 1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry &< 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - -RETURN 'POINT(1 1)'::geometry &< 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry &< 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry &< 'POINT(1 1 0)'::geometry; -RETURN 'MULTIPOINT(1 1,2 2)'::geometry &< 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry &< 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &< 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &< 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -RETURN 'LINESTRING(1 1,2 2)'::geometry &< 'POINT(1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry &< 'LINESTRING(2 2, 1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry &< 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - ---<<| -select 'POINT(1 1)'::geometry <<| 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry <<| 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry <<| 'POINT(1 1 0)'::geometry; -select 'MULTIPOINT(1 1,2 2)'::geometry <<| 'MULTIPOINT(1 1,2 2)'::geometry; -select 'MULTIPOINT(2 2, 1 1)'::geometry <<| 'MULTIPOINT(1 1,2 2)'::geometry; -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry <<| 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -select 'LINESTRING(1 1,2 2)'::geometry <<| 'POINT(1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry <<| 'LINESTRING(2 2, 1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry <<| 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - -RETURN 'POINT(1 1)'::geometry <<| 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry <<| 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry <<| 'POINT(1 1 0)'::geometry; -RETURN 'MULTIPOINT(1 1,2 2)'::geometry <<| 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry <<| 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry <<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry <<| 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -RETURN 'LINESTRING(1 1,2 2)'::geometry <<| 'POINT(1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry <<| 'LINESTRING(2 2, 1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry <<| 'LINESTRING(1 1, 2 2, 3 3)'::geometry; --- &<| -select 'POINT(1 1)'::geometry &<| 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry &<| 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry &<| 'POINT(1 1 0)'::geometry; -select 'MULTIPOINT(1 1,2 2)'::geometry &<| 'MULTIPOINT(1 1,2 2)'::geometry; -select 'MULTIPOINT(2 2, 1 1)'::geometry &<| 'MULTIPOINT(1 1,2 2)'::geometry; -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &<| 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -select 'LINESTRING(1 1,2 2)'::geometry &<| 'POINT(1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry &<| 'LINESTRING(2 2, 1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry &<| 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - -RETURN 'POINT(1 1)'::geometry &<| 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry &<| 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry &<| 'POINT(1 1 0)'::geometry; -RETURN 'MULTIPOINT(1 1,2 2)'::geometry &<| 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry &<| 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &<| 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &<| 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -RETURN 'LINESTRING(1 1,2 2)'::geometry &<| 'POINT(1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry &<| 'LINESTRING(2 2, 1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry &<| 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - --- &> -select 'POINT(1 1)'::geometry &> 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry &> 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry &> 'POINT(1 1 0)'::geometry; -select 'MULTIPOINT(1 1,2 2)'::geometry &> 'MULTIPOINT(1 1,2 2)'::geometry; -select 'MULTIPOINT(2 2, 1 1)'::geometry &> 'MULTIPOINT(1 1,2 2)'::geometry; -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -select 'LINESTRING(1 1,2 2)'::geometry &> 'POINT(1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry &> 'LINESTRING(2 2, 1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry &> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - -RETURN 'POINT(1 1)'::geometry &> 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry &> 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry &> 'POINT(1 1 0)'::geometry; -RETURN 'MULTIPOINT(1 1,2 2)'::geometry &> 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry &> 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry &> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -RETURN 'LINESTRING(1 1,2 2)'::geometry &> 'POINT(1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry &> 'LINESTRING(2 2, 1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry &> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - --- |&> -select 'POINT(1 1)'::geometry |&> 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry |&> 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry |&> 'POINT(1 1 0)'::geometry; -select 'MULTIPOINT(1 1,2 2)'::geometry |&> 'MULTIPOINT(1 1,2 2)'::geometry; -select 'MULTIPOINT(2 2, 1 1)'::geometry |&> 'MULTIPOINT(1 1,2 2)'::geometry; -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |&> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -select 'LINESTRING(1 1,2 2)'::geometry |&> 'POINT(1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry |&> 'LINESTRING(2 2, 1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry |&> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - -RETURN 'POINT(1 1)'::geometry |&> 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry |&> 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry |&> 'POINT(1 1 0)'::geometry; -RETURN 'MULTIPOINT(1 1,2 2)'::geometry |&> 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry |&> 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |&> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |&> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -RETURN 'LINESTRING(1 1,2 2)'::geometry |&> 'POINT(1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry |&> 'LINESTRING(2 2, 1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry |&> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; --- --- |>> --- -select 'POINT(1 1)'::geometry |>> 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry |>> 'POINT(1 1)'::geometry; -select 'POINT(1 1 0)'::geometry |>> 'POINT(1 1 0)'::geometry; -select 'MULTIPOINT(1 1,2 2)'::geometry |>> 'MULTIPOINT(1 1,2 2)'::geometry; -select 'MULTIPOINT(2 2, 1 1)'::geometry |>> 'MULTIPOINT(1 1,2 2)'::geometry; -select 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(4 5 6, 1 2 3)'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -select 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |>> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -select 'LINESTRING(1 1,2 2)'::geometry |>> 'POINT(1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry |>> 'LINESTRING(2 2, 1 1)'::geometry; -select 'LINESTRING(1 1, 2 2)'::geometry |>> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; - -RETURN 'POINT(1 1)'::geometry |>> 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry |>> 'POINT(1 1)'::geometry; -RETURN 'POINT(1 1 0)'::geometry |>> 'POINT(1 1 0)'::geometry; -RETURN 'MULTIPOINT(1 1,2 2)'::geometry |>> 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'MULTIPOINT(2 2, 1 1)'::geometry |>> 'MULTIPOINT(1 1,2 2)'::geometry; -RETURN 'GEOMETRYCOLLECTION(POINT( 1 2 3),POINT(4 5 6))'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(4 5 6, 1 2 3)'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |>> 'GEOMETRYCOLLECTION(POINT( 4 5 6),POINT(1 2 3))'::geometry; -RETURN 'MULTIPOINT(1 2 3, 4 5 6)'::geometry |>> 'GEOMETRYCOLLECTION(MULTIPOINT(1 2 3, 4 5 6))'::geometry; -RETURN 'LINESTRING(1 1,2 2)'::geometry |>> 'POINT(1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry |>> 'LINESTRING(2 2, 1 1)'::geometry; -RETURN 'LINESTRING(1 1, 2 2)'::geometry |>> 'LINESTRING(1 1, 2 2, 3 3)'::geometry; --- --- N-D Operators --- --- nd overlap &&& - - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(3 3 3 5)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(3 3 5 3)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(3 5 3 3)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(5 3 3 3)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(3 3 3 3)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(2 4 2 4)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry &&& 'POINT(4 2 4 2)'::geometry; - --- nd contains ~~ - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(3 3 3 5)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(3 3 5 3)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(3 5 3 3)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(5 3 3 3)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(3 3 3 3)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(2 4 2 4)'::geometry; -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~ 'POINT(4 2 4 2)'::geometry; --- nd within @@ - -RETURN 'POINT(3 3 3 5)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; -RETURN 'POINT(3 3 5 3)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; -RETURN 'POINT(3 5 3 3)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; -RETURN 'POINT(5 3 3 3)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; -RETURN 'POINT(3 3 3 3)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; -RETURN 'POINT(2 4 2 4)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; -RETURN 'POINT(4 2 4 2)'::geometry @@ 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry; --- nd same ~~= - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'POINT(3 3 3 3)'::geometry; - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 2, 4 4 4 5)'::geometry; - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 2, 4 4 5 4)'::geometry; - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 2, 4 5 4 4)'::geometry; - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 2, 5 4 4 4)'::geometry; - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(4 4 4 4, 2 2 2 2)'::geometry; - -RETURN 'LINESTRING(2 2 2 2, 4 4 4 4)'::geometry ~~= 'LINESTRING(2 2 2 4, 2 2 4 2, 2 4 2 2, 4 2 2 2)'::geometry; --- --- Point Cordinates --- - --- --- ST_X --- -SELECT ST_X('POINT (0 0)'::geometry); -RETURN ST_X('POINT (0 0)'::geometry) ; -RETURN X('POINT (0 0)'::geometry) ; -SELECT ST_X('POINTZ (1 2 3)'::geometry); -RETURN ST_X('POINTZ (1 2 3)'::geometry) ; -RETURN X('POINTZ (1 2 3)'::geometry) ; -SELECT ST_X('POINTM (6 7 8)'::geometry); -RETURN ST_X('POINTM (6 7 8)'::geometry) ; -RETURN X('POINTM (6 7 8)'::geometry) ; -SELECT ST_X('POINTZM (10 11 12 13)'::geometry); -RETURN ST_X('POINTZM (10 11 12 13)'::geometry) ; -RETURN X('POINTZM (10 11 12 13)'::geometry) ; -SELECT ST_X('MULTIPOINT ((0 0), (1 1))'::geometry); -RETURN ST_X('MULTIPOINT ((0 0), (1 1))'::geometry) ; -RETURN X('MULTIPOINT ((0 0), (1 1))'::geometry) ; -SELECT ST_X('LINESTRING (0 0, 1 1)'::geometry); -RETURN ST_X('LINESTRING (0 0, 1 1)'::geometry) ; -RETURN X('LINESTRING (0 0, 1 1)'::geometry) ; -SELECT ST_X('GEOMETRYCOLLECTION (POINT(0 0))'::geometry); -RETURN ST_X('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -RETURN X('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -SELECT ST_X('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry); -RETURN ST_X('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -RETURN X('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; --- --- ST_Y --- -SELECT ST_Y('POINT (0 0)'::geometry); -RETURN ST_Y('POINT (0 0)'::geometry) ; -RETURN Y('POINT (0 0)'::geometry) ; -SELECT ST_Y('POINTZ (1 2 3)'::geometry); -RETURN ST_Y('POINTZ (1 2 3)'::geometry) ; -RETURN Y('POINTZ (1 2 3)'::geometry) ; -SELECT ST_Y('POINTM (6 7 8)'::geometry); -RETURN ST_Y('POINTM (6 7 8)'::geometry) ; -RETURN Y('POINTM (6 7 8)'::geometry) ; -SELECT ST_Y('POINTZM (10 11 12 13)'::geometry); -RETURN ST_Y('POINTZM (10 11 12 13)'::geometry) ; -RETURN Y('POINTZM (10 11 12 13)'::geometry) ; -SELECT ST_Y('MULTIPOINT ((0 0), (1 1))'::geometry); -RETURN ST_Y('MULTIPOINT ((0 0), (1 1))'::geometry) ; -RETURN Y('MULTIPOINT ((0 0), (1 1))'::geometry) ; -SELECT ST_Y('LINESTRING (0 0, 1 1)'::geometry); -RETURN ST_Y('LINESTRING (0 0, 1 1)'::geometry) ; -RETURN Y('LINESTRING (0 0, 1 1)'::geometry) ; -SELECT ST_Y('GEOMETRYCOLLECTION (POINT(0 0))'::geometry); -RETURN ST_Y('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -RETURN Y('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -SELECT ST_Y('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry); -RETURN ST_Y('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -RETURN Y('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; --- --- ST_Z --- -SELECT ST_Z('POINT (0 0)'::geometry); -RETURN ST_Z('POINT (0 0)'::geometry) ; -RETURN Z('POINT (0 0)'::geometry) ; -SELECT ST_Z('POINTZ (1 2 3)'::geometry); -RETURN ST_Z('POINTZ (1 2 3)'::geometry) ; -RETURN Z('POINTZ (1 2 3)'::geometry) ; -SELECT ST_Z('POINTM (6 7 8)'::geometry); -RETURN ST_Z('POINTM (6 7 8)'::geometry) ; -RETURN Z('POINTM (6 7 8)'::geometry) ; -SELECT ST_Z('POINTZM (10 11 12 13)'::geometry); -RETURN ST_Z('POINTZM (10 11 12 13)'::geometry) ; -RETURN Z('POINTZM (10 11 12 13)'::geometry) ; -SELECT ST_Z('MULTIPOINT ((0 0), (1 1))'::geometry); -RETURN ST_Z('MULTIPOINT ((0 0), (1 1))'::geometry) ; -RETURN Z('MULTIPOINT ((0 0), (1 1))'::geometry) ; -SELECT ST_Z('LINESTRING (0 0, 1 1)'::geometry); -RETURN ST_Z('LINESTRING (0 0, 1 1)'::geometry) ; -RETURN Z('LINESTRING (0 0, 1 1)'::geometry) ; -SELECT ST_Z('GEOMETRYCOLLECTION (POINT(0 0))'::geometry); -RETURN ST_Z('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -RETURN Z('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -SELECT ST_Z('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry); -RETURN ST_Z('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -RETURN Z('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; --- --- ST_M --- -SELECT ST_M('POINT (0 0)'::geometry); -RETURN ST_M('POINT (0 0)'::geometry) ; -RETURN M('POINT (0 0)'::geometry) ; -SELECT ST_M('POINTZ (1 2 3)'::geometry); -RETURN ST_M('POINTZ (1 2 3)'::geometry) ; -RETURN M('POINTZ (1 2 3)'::geometry) ; -SELECT ST_M('POINTM (6 7 8)'::geometry); -RETURN ST_M('POINTM (6 7 8)'::geometry) ; -RETURN M('POINTM (6 7 8)'::geometry) ; -SELECT ST_M('POINTZM (10 11 12 13)'::geometry); -RETURN ST_M('POINTZM (10 11 12 13)'::geometry) ; -RETURN M('POINTZM (10 11 12 13)'::geometry) ; -SELECT ST_M('MULTIPOINT ((0 0), (1 1))'::geometry); -RETURN ST_M('MULTIPOINT ((0 0), (1 1))'::geometry) ; -RETURN M('MULTIPOINT ((0 0), (1 1))'::geometry) ; -SELECT ST_M('LINESTRING (0 0, 1 1)'::geometry); -RETURN ST_M('LINESTRING (0 0, 1 1)'::geometry) ; -RETURN M('LINESTRING (0 0, 1 1)'::geometry) ; -SELECT ST_M('GEOMETRYCOLLECTION (POINT(0 0))'::geometry); -RETURN ST_M('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -RETURN M('GEOMETRYCOLLECTION (POINT(0 0))'::geometry) ; -SELECT ST_M('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry); -RETURN ST_M('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; -RETURN M('GEOMETRYCOLLECTION (POINT(0 1), LINESTRING(0 0, 1 1))'::geometry) ; --- --- Affines --- --- ST_Scale -select ST_asewkt(ST_Scale('POINT(1 1)'::geometry, 5, 5)); -select ST_asewkt(ST_Scale('POINT(1 1)'::geometry, 3, 2)); -select ST_asewkt(ST_Scale('POINT(10 20 -5)'::geometry, 4, 2, -8)); -select ST_asewkt(ST_Scale('POINT(10 20 -5 3)'::geometry, ST_MakePoint(4, 2, -8))); -select ST_asewkt(ST_Scale('POINT(-2 -1 3 2)'::geometry, ST_MakePointM(-2, 3, 4))); -select ST_asewkt(ST_Scale('POINT(10 20 -5 3)'::geometry, ST_MakePoint(-3, 2, -1, 3))); -select st_astext(st_scale('LINESTRING(1 1, 2 2)'::geometry, 'POINT(2 2)'::geometry, 'POINT(1 1)'::geometry)); - -RETURN ST_asewkt(ST_Scale('POINT(1 1)'::geometry, 5, 5)); -RETURN ST_asewkt(ST_Scale('POINT(1 1)'::geometry, 3, 2)); -RETURN ST_asewkt(ST_Scale('POINT(10 20 -5)'::geometry, 4, 2, -8)); -RETURN ST_asewkt(ST_Scale('POINT(10 20 -5 3)'::geometry, ST_MakePoint(4, 2, -8))); -RETURN ST_asewkt(ST_Scale('POINT(-2 -1 3 2)'::geometry, ST_MakePointM(-2, 3, 4))); -RETURN ST_asewkt(ST_Scale('POINT(10 20 -5 3)'::geometry, ST_MakePoint(-3, 2, -1, 3))); -RETURN ST_asewkt(st_scale('LINESTRING(1 1, 2 2)'::geometry, 'POINT(2 2)'::geometry, 'POINT(1 1)'::geometry)); - - - -RETURN '200', ST_Expand(null::geometry, 1); -RETURN '201', ST_AsText(ST_Expand('LINESTRING (1 2 3, 10 20 30)'::geometry, 1)); -RETURN '202', ST_AsText(ST_Expand('LINESTRINGM (1 2 3, 10 20 30)'::geometry, 1)); -RETURN '203', ST_AsText(ST_Expand('LINESTRING (1 2, 10 20)'::geometry, 3)); -RETURN '204', ST_AsText(ST_Expand('POLYGON EMPTY'::geometry, 4)); -RETURN '205', ST_AsText(ST_Expand('POINT EMPTY'::geometry, 2)); -RETURN '206', ST_AsText(ST_Expand('POINT (2 3)'::geometry, 0)); -RETURN '207', ST_AsText(ST_Expand('LINESTRING (1 2, 3 4)'::geometry, 0)); -RETURN '208', ST_AsText(ST_Expand('POINT (0 0)'::geometry, -1)); -RETURN '209', ST_AsText(ST_Expand('LINESTRING (0 0, 10 10)'::geometry, -4)); -RETURN '210', ST_Expand(null::box3d, 1); -RETURN '211', ST_Expand('BOX3D(-1 3 5, -1 6 8)'::BOX3D, 1); -RETURN '212', ST_Expand(null::box2d, 1); -RETURN '213', ST_Expand('BOX(-2 3, -1 6'::BOX2D, 4); - -RETURN '214', ST_Expand(null::geometry, 1, 1, 1, 1); -RETURN '215', ST_AsText(ST_Expand('LINESTRING (1 2 3, 10 20 30)'::geometry, 1, 4, 2, 7)); - -RETURN '216', ST_AsText(ST_Expand('LINESTRINGM (1 2 3, 10 20 30)'::geometry, 1, 4, 2, 7)); -RETURN '217', ST_AsText(ST_Expand('LINESTRING (1 2, 10 20)'::geometry, 1, 4, 2, 7)); -RETURN '218', ST_AsText(ST_Expand('POLYGON EMPTY'::geometry, 4, 3, 1, 1)); -RETURN '219', ST_AsText(ST_Expand('POINT EMPTY'::geometry, 2, 3, 1, -1)); -RETURN '220', ST_AsText(ST_Expand('POINT (2 3)'::geometry, 0, 4, -2, 8)); -RETURN '221', ST_AsText(ST_Expand('POINT (0 0)'::geometry, -1, -2)); -RETURN '222', ST_Expand(null::box3d, 1, 1, 1); -RETURN '223', ST_Expand('BOX3D(-1 3 5, -1 6 8)'::BOX3D, 1, -1, 7); -RETURN '224', ST_Expand(null::box2d, 1, 1); -RETURN '225', ST_Expand('BOX(-2 3, -1 6'::BOX2D, 4, 2); -RETURN '226', ST_SRID(ST_Expand('SRID=4326;POINT (0 0)'::geometry, 1))=4326; - - - -RETURN '#3069', postgis_getbbox('SRID=0;POINT(1 1)'::geometry); -RETURN '#3069', postgis_getbbox('SRID=0;LINESTRING(0 0, 1 1)'::geometry); -RETURN '#3069', postgis_getbbox('SRID=0;MULTILINESTRING((0 0, 1 1))'::geometry); -RETURN '#3069', postgis_getbbox('SRID=0;MULTIPOINT(1 1)'::geometry); -RETURN '#3069', postgis_getbbox('SRID=0;MULTILINESTRING((0 0,1 1))'::geometry); - --- postgis-users/2006-July/012788.html -RETURN ST_makebox2d('SRID=3;POINT(0 0)', 'SRID=3;POINT(1 1)'); -RETURN ST_makebox2d('POINT(0 0)', 'SRID=3;POINT(1 1)'); - --- --- Measures --- - - --- --- ST_IsPolygonCW --- --- Non-applicable types -SELECT ST_IsPolygonCW('POINT (0 0)'::geometry); -SELECT ST_IsPolygonCW('MULTIPOINT ((0 0), (1 1))'::geometry); -SELECT ST_IsPolygonCW('LINESTRING (1 1, 2 2)'::geometry); -SELECT ST_IsPolygonCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))'::geometry); --- EMPTY handling -SELECT ST_IsPolygonCW('POLYGON EMPTY'::geometry); --- Single polygon, ccw exterior ring only -SELECT ST_IsPolygonCW('POLYGON ((0 0, 1 0, 1 1, 0 0))'::geometry); --- Single polygon, cw exterior ring only -SELECT ST_IsPolygonCW('POLYGON ((0 0, 1 1, 1 0, 0 0))'::geometry); --- Single polygon, ccw exterior ring, cw interior rings -SELECT ST_IsPolygonCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); --- Single polygon, cw exterior ring, ccw interior rings -SELECT ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); --- Single polygon, ccw exerior ring, mixed interior rings -SELECT ST_IsPolygonCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); --- Single polygon, cw exterior ring, mixed interior rings -SELECT ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); --- MultiPolygon, ccw exterior rings only -SELECT ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))'::geometry); --- MultiPolygon, cw exterior rings only -SELECT ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); --- MultiPolygon, mixed exterior rings -SELECT ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - -RETURN ST_IsPolygonCW('POINT (0 0)'::geometry); -RETURN ST_IsPolygonCW('MULTIPOINT ((0 0), (1 1))'::geometry); -RETURN ST_IsPolygonCW('LINESTRING (1 1, 2 2)'::geometry); -RETURN ST_IsPolygonCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))'::geometry); --- EMPTY handling - -RETURN ST_IsPolygonCW('POLYGON EMPTY'::geometry); --- Single polygon, ccw exterior ring only - -RETURN ST_IsPolygonCW('POLYGON ((0 0, 1 0, 1 1, 0 0))'::geometry); --- Single polygon, cw exterior ring only - -RETURN ST_IsPolygonCW('POLYGON ((0 0, 1 1, 1 0, 0 0))'::geometry); --- Single polygon, ccw exterior ring, cw interior rings - -RETURN ST_IsPolygonCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); --- Single polygon, cw exterior ring, ccw interior rings - -RETURN ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); --- Single polygon, ccw exerior ring, mixed interior rings - -RETURN ST_IsPolygonCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); --- Single polygon, cw exterior ring, mixed interior rings - -RETURN ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); --- MultiPolygon, ccw exterior rings only - -RETURN ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))'::geometry); --- MultiPolygon, cw exterior rings only - -RETURN ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); --- MultiPolygon, mixed exterior rings - -RETURN ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); --- --- ST_IsPolygonCCW --- --- Non-applicable types -SELECT ST_IsPolygonCCW('POINT (0 0)'::geometry); -SELECT ST_IsPolygonCCW('MULTIPOINT ((0 0), (1 1))'::geometry); -SELECT ST_IsPolygonCCW('LINESTRING (1 1, 2 2)'::geometry); -SELECT ST_IsPolygonCCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))'::geometry); --- EMPTY handling -SELECT ST_IsPolygonCCW('POLYGON EMPTY'::geometry); --- Single polygon, ccw exterior ring only -SELECT ST_IsPolygonCCW('POLYGON ((0 0, 1 0, 1 1, 0 0))'::geometry); --- Single polygon, cw exterior ring only -SELECT ST_IsPolygonCCW('POLYGON ((0 0, 1 1, 1 0, 0 0))'::geometry); --- Single polygon, ccw exterior ring, cw interior rings -SELECT ST_IsPolygonCCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); --- Single polygon, cw exterior ring, ccw interior rings -SELECT ST_IsPolygonCCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); --- Single polygon, ccw exerior ring, mixed interior rings -SELECT ST_IsPolygonCCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); --- Single polygon, cw exterior ring, mixed interior rings -SELECT ST_IsPolygonCCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); --- MultiPolygon, ccw exterior rings only -SELECT ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))'::geometry); --- MultiPolygon, cw exterior rings only -SELECT ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); --- MultiPolygon, mixed exterior rings -SELECT ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); - -RETURN ST_IsPolygonCCW('POINT (0 0)'::geometry); -RETURN ST_IsPolygonCCW('MULTIPOINT ((0 0), (1 1))'::geometry); -RETURN ST_IsPolygonCCW('LINESTRING (1 1, 2 2)'::geometry); -RETURN ST_IsPolygonCCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))'::geometry); --- EMPTY handling - -RETURN ST_IsPolygonCCW('POLYGON EMPTY'::geometry); --- Single polygon, ccw exterior ring only - -RETURN ST_IsPolygonCCW('POLYGON ((0 0, 1 0, 1 1, 0 0))'::geometry); --- Single polygon, cw exterior ring only - -RETURN ST_IsPolygonCCW('POLYGON ((0 0, 1 1, 1 0, 0 0))'::geometry); --- Single polygon, ccw exterior ring, cw interior rings - -RETURN ST_IsPolygonCCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); --- Single polygon, cw exterior ring, ccw interior rings - -RETURN ST_IsPolygonCCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); --- Single polygon, ccw exerior ring, mixed interior rings - -RETURN ST_IsPolygonCCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))'::geometry); --- Single polygon, cw exterior ring, mixed interior rings - -RETURN ST_IsPolygonCCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))'::geometry); --- MultiPolygon, ccw exterior rings only - -RETURN ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))'::geometry); --- MultiPolygon, cw exterior rings only - -RETURN ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); --- MultiPolygon, mixed exterior rings - -RETURN ST_IsPolygonCCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))'::geometry); --- --- ST_DistanceSpheroid --- -SELECT round(ST_DistanceSpheroid('MULTIPOLYGON(((-10 40,-10 55,-10 70,5 40,-10 40)))'::geometry, - 'MULTIPOINT(20 40,20 55,20 70,35 40,35 55,35 70,50 40,50 55,50 70)', - 'SPHEROID["GRS_1980",6378137,298.257222101]')); -SELECT round(ST_DistanceSpheroid('MULTIPOLYGON(((-10 40,-10 55,-10 70,5 40,-10 40)))'::geometry, - 'MULTIPOINT(20 40,20 55,20 70,35 40,35 55,35 70,50 40,50 55,50 70)')); -RETURN round(ST_DistanceSpheroid('MULTIPOLYGON(((-10 40,-10 55,-10 70,5 40,-10 40)))'::geometry, - 'MULTIPOINT(20 40,20 55,20 70,35 40,35 55,35 70,50 40,50 55,50 70)', - 'SPHEROID["GRS_1980",6378137,298.257222101]')); -RETURN round(ST_DistanceSpheroid('MULTIPOLYGON(((-10 40,-10 55,-10 70,5 40,-10 40)))'::geometry, - 'MULTIPOINT(20 40,20 55,20 70,35 40,35 55,35 70,50 40,50 55,50 70)')); - --- --- Temporal --- - --- --- ST_ClosestPointOfApproach --- --- Converging -select ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'::geometry); -RETURN ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)'::geometry, 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'); --- Following -select ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry); -RETURN ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry); --- Crossing -select ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry); -RETURN ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry); --- Meeting -select ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry); -RETURN ST_ClosestPointOfApproach( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry); --- Disjoint -select ST_ClosestPointOfApproach( 'LINESTRINGM(0 0 0, 0 0 4)', 'LINESTRINGM(0 0 5, 0 0 10)'::geometry); -RETURN ST_ClosestPointOfApproach('LINESTRINGM(0 0 0, 0 0 4)', 'LINESTRINGM(0 0 5, 0 0 10)'::geometry); --- --- ST_DistanceCPA --- --- Converging -select ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'::geometry); -RETURN ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)'::geometry, 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'); --- Following -select ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry); -RETURN ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 10 10 10 10)', 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry); --- Crossing -select ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry); -RETURN ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry); --- Meeting -select ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry); -RETURN ST_DistanceCPA( 'LINESTRINGZM(0 0 0 0, 0 0 0 10)', 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry); --- Disjoint -select ST_DistanceCPA( 'LINESTRINGM(0 0 0, 0 0 4)', 'LINESTRINGM(0 0 5, 0 0 10)'::geometry); -RETURN ST_DistanceCPA('LINESTRINGM(0 0 0, 0 0 4)', 'LINESTRINGM(0 0 5, 0 0 10)'::geometry); --- --- |=| Operator --- --- Converging -select 'LINESTRINGZM(0 0 0 0, 10 10 10 10)' |=| 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'::geometry; -RETURN 'LINESTRINGZM(0 0 0 0, 10 10 10 10)'::geometry |=| 'LINESTRINGZM(0 0 0 1, 10 10 10 10)'; --- Following -select 'LINESTRINGZM(0 0 0 0, 10 10 10 10)' |=| 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry; -RETURN 'LINESTRINGZM(0 0 0 0, 10 10 10 10)' |=| 'LINESTRINGZM(0 0 0 5, 10 10 10 15)'::geometry; --- Crossing -select 'LINESTRINGZM(0 0 0 0, 0 0 0 10)' |=| 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry; -RETURN 'LINESTRINGZM(0 0 0 0, 0 0 0 10)' |=| 'LINESTRINGZM(-30 0 5 4, 10 0 5 6)'::geometry; --- Meeting -select 'LINESTRINGZM(0 0 0 0, 0 0 0 10)' |=| 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry; -RETURN 'LINESTRINGZM(0 0 0 0, 0 0 0 10)' |=| 'LINESTRINGZM(0 5 0 10, 10 0 5 11)'::geometry; --- Disjoint -select 'LINESTRINGM(0 0 0, 0 0 4)' |=| 'LINESTRINGM(0 0 5, 0 0 10)'::geometry; -RETURN 'LINESTRINGM(0 0 0, 0 0 4)' |=| 'LINESTRINGM(0 0 5, 0 0 10)'::geometry; - - --- --- ST_IsValidTrajectory --- -SELECT ST_IsValidTrajectory('POINTM(0 0 0)'::geometry); -SELECT ST_IsValidTrajectory('LINESTRINGZ(0 0 0,1 1 1)'::geometry); -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 0)'::geometry); -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 1,1 1 0)'::geometry); -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1,1 1 2,1 0 1)'::geometry); -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1)'::geometry); -SELECT ST_IsValidTrajectory('LINESTRINGM EMPTY'::geometry); -SELECT ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1,1 1 2)'::geometry); - -RETURN ST_IsValidTrajectory('POINTM(0 0 0)'::geometry); -RETURN ST_IsValidTrajectory('LINESTRINGZ(0 0 0,1 1 1)'::geometry); -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 0)'::geometry); -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 1,1 1 0)'::geometry); -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1,1 1 2,1 0 1)'::geometry); -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1)'::geometry); -RETURN ST_IsValidTrajectory('LINESTRINGM EMPTY'::geometry); -RETURN ST_IsValidTrajectory('LINESTRINGM(0 0 0,1 1 1,1 1 2)'::geometry); - --- --- ST_CPAWithin --- -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 0.0); -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1.0); -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 0.5); -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1); -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2); -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1.9); -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2); -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2.0001); --- temporary disjoint -SELECT ST_CPAWithin( 'LINESTRINGM(0 0 0, 10 0 10)'::geometry ,'LINESTRINGM(10 0 11, 10 10 20)'::geometry, 1e15); -SELECT ST_CPAWithin( 'LINESTRING(0 0 0, 1 0 0)'::geometry ,'LINESTRING(0 0 3 0, 1 0 2 1)'::geometry, 1e16); -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 0.0); -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1.0); -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 0.5); -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1); -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2); -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 1.9); -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2); -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 1 0 1)'::geometry ,'LINESTRINGM(0 0 0, 1 0 1)'::geometry, 2.0001); --- temporary disjoint -RETURN ST_CPAWithin( 'LINESTRINGM(0 0 0, 10 0 10)'::geometry ,'LINESTRINGM(10 0 11, 10 10 20)'::geometry, 1e15); -RETURN ST_CPAWithin( 'LINESTRING(0 0 0, 1 0 0)'::geometry ,'LINESTRING(0 0 3 0, 1 0 2 1)'::geometry, 1e16); --- --- GEOS --- - --- --- ST_Intersection --- -SELECT ST_Intersection('MULTIPOINT ((0 0), (1 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry); -SELECT ST_AsEWKT(ST_Intersection('MULTIPOINT ((0 0), (1 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry)); -RETURN ST_Intersection('MULTIPOINT ((0 0), (1 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry, -1.0); -RETURN ST_AsEWKT(ST_Intersection('MULTIPOINT ((0 0), (1 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry, -1.0)); -RETURN ST_AsEWKT(ST_Intersection('MULTIPOINT ((2 2), (5 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry, -1.0)); -RETURN ST_AsEWKT(ST_Intersection('MULTIPOINT ((2 2), (5 1))'::geometry, 'MULTIPOINT ((0 0), (1 1))'::geometry)); - --- --- Algorithms --- - --- --- ST_Simplify --- -SELECT ST_AsEWKT(ST_Simplify('POLYGON((0 0,1 1,1 3,0 4,-2 3,-1 1,0 0))'::geometry, 1)); -SELECT ST_AsEWKT(ST_Simplify('POLYGON((0 0,1 1,1 3,2 3,2 0,0 0))'::geometry, 1)); - -RETURN ST_AsEWKT(ST_Simplify('POLYGON((0 0,1 1,1 3,0 4,-2 3,-1 1,0 0))', 1)); -RETURN ST_AsEWKT(ST_Simplify('POLYGON((0 0,1 1,1 3,2 3,2 0,0 0))'::geometry, 1)); - -/* - * 3D Functions - */ ---3D Distance functions - - -CYPHER WITH 'POINT(1 1 1)'::geometry as a, 'POINT(3 2 7)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - -CYPHER WITH 'POINT(1 1 1)'::geometry as a, 'LINESTRING(0 0 0, 2 2 2)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - -CYPHER WITH 'POINT(1 1 1)'::geometry as a, 'LINESTRING(5 2 6, -3 -2 4)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - - - -CYPHER WITH 'LINESTRING(1 1 3, 5 7 8)'::geometry as a, 'POINT(1 1 1)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - -CYPHER WITH 'LINESTRING(1 0 5, 11 0 5)'::geometry as a, 'LINESTRING(5 2 0, 5 2 10, 5 0 13)'::geometry as b -RETURN ST_3DDistance(a,b), - ST_3DMaxDistance(a,b)::numeric, - ST_3DDWithin(a,b,5), - ST_3DDFullyWithin(a,b,5), - ST_ASEWKT(ST_3DShortestline(a,b)), - ST_ASEWKT(ST_3DClosestpoint(a,b)), - ST_ASEWKT(ST_3DLongestline(a,b)); - -CYPHER WITH 'LINESTRING(1 1 1 , 2 2 2)'::geometry as a, 'POLYGON((0 0 0, 2 2 2, 3 3 3, 0 0 0))'::geometry as b -RETURN ST_3DDistance(a,b); - - -CYPHER WITH 'LINESTRING(1 1 1 , 2 2 2)'::geometry as a, 'POLYGON((0 0 0, 2 2 2, 3 3 1, 0 0 0))'::geometry as b -RETURN ST_3DDistance(a,b); - - --- 3D mixed dimmentionality #2034 ---closestpoint with 2d as first point and 3d as second - -RETURN st_astext(st_3dclosestpoint('linestring(0 0,1 1,2 0)'::geometry, 'linestring(0 2 3, 3 2 3)'::geometry)); ---closestpoint with 3d as first point and 2d as second - -RETURN st_astext(st_3dclosestpoint('linestring(0 0 1,1 1 2,2 0 3)'::geometry, 'linestring(0 2, 3 2)'::geometry)); ---shortestline with 2d as first point and 3d as second - -RETURN st_astext(st_3dshortestline('linestring(0 0,1 1,2 0)'::geometry, 'linestring(0 2 3, 3 2 3)'::geometry)); ---shortestline with 3d as first point and 2d as second - -RETURN st_astext(st_3dshortestline('linestring(0 0 1,1 1 2,2 0 3)'::geometry, 'linestring(0 2, 3 2)'::geometry)); - ---distance with 2d as first point and 3d as second - -RETURN st_3ddistance('linestring(0 0,1 1,2 0)'::geometry, 'linestring(0 2 3, 3 2 3)'::geometry); ---distance with 3d as first point and 2d as second - -RETURN st_3ddistance('linestring(0 0 1,1 1 2,2 0 3)'::geometry, 'linestring(0 2, 3 2)'::geometry); - -RETURN ST_AsText(ST_3DClosestPoint('POINT(0 0 0)', 'POINT(0 0)')); -RETURN ST_AsText(ST_3DShortestLine('LINESTRING(2 1, 3 0)', 'LINESTRING(0 0 2, 3 3 -4)')); - -/* - * Polyheadral Surface - */ - -- ST_Dimension on 2D: not closed - -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry); -RETURN ST_Dimension('GEOMETRYCOLLECTION(POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0))))'::geometry); --- ST_Dimension on 3D: closed - -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry); -RETURN ST_Dimension('GEOMETRYCOLLECTION(POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0))))'::geometry); --- ST_Dimension on 4D: closed - -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0)))'::geometry); -RETURN ST_Dimension('GEOMETRYCOLLECTION(POLYHEDRALSURFACE(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0))))'::geometry); --- ST_Dimension on 3D: invalid polyedron (a single edge is shared 3 times) - -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,0 1 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry); --- ST_Dimension on 3D: invalid polyedron (redundant point inside each face) - -RETURN ST_Dimension('POLYHEDRALSURFACE(((0 0 0,1 0 0,1 0 0,0 0 0)),((0 0 1,1 0 1,1 0 1,0 0 1)),((0 0 2,1 0 2,1 0 2,0 0 2)),((0 0 3,1 0 3,1 0 3,0 0 3)))'::geometry); --- ST_NumPatches - -RETURN ST_NumPatches('POLYHEDRALSURFACE EMPTY'::geometry); -RETURN ST_NumPatches('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry); -RETURN ST_NumPatches('POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry); --- ST_PatchN - -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE EMPTY'::geometry, 1)); -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry, 1)); -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry, 0)); -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE(((0 0,0 0,0 1,0 0)))'::geometry, 2)); -RETURN ST_AsEWKT(ST_patchN('POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry, 2)); --- PolyedralSurface (TODO) - -RETURN ST_AsText(ST_Reverse('POLYHEDRALSURFACE EMPTY'::geometry)); -RETURN ST_AsText(ST_Reverse('POLYHEDRALSURFACE (((0 0,0 0,0 1,0 0)),((0 0,0 1,1 0,0 0)),((0 0,1 0,0 0,0 0)),((1 0,0 1,0 0,1 0)))'::geometry)); - -RETURN ST_AsText(ST_ShiftLongitude('SRID=4326;POINT(270 0)'::geometry)); -RETURN ST_AsText(ST_ShiftLongitude('SRID=4326;POINT(-90 0)'::geometry)); -RETURN ST_AsText(ST_ShiftLongitude('SRID=4326;LINESTRING(174 12, 182 13)'::geometry)); - --- TODO KNN - - WITH ST_GeomFromText( -'PolyhedralSurface( -((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), -((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), -((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) -)') as a -RETURN ST_Translate(a,100, 450,1000); - - WITH ST_GeomFromText( -'PolyhedralSurface( -((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), -((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )') as a -RETURN ST_Translate(a,100, 450,1000); - --- #68 -- ---RETURN '#68a', ST_AsText(ST_ShiftLongitude(ST_GeomFromText('MULTIPOINT(1 3, 4 5)'))); ---RETURN '#68b', ST_AsText(ST_ShiftLongitude(ST_GeomFromText('CIRCULARSTRING(1 3, 4 5, 6 7)'))); - -CREATE (:j {j: 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'::geometry }) ; -CREATE (:j {j: 'PolyhedralSurface( -((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), -((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), -((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) -)'::geometry }) ; -SET enable_mergejoin = ON; -SET enable_hashjoin = ON; -SET enable_nestloop = ON; -SET enable_seqscan = false; -SET enable_sort = false; -CREATE INDEX ON postgis.j USING gist((properties->'"j"') gist_geometry_ops_nd); -\d+ postgis.j - -EXPLAIN (costs off) - - MATCH (a:j) - WHERE a.j &&& 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'::geometry -RETURN a; - - MATCH (a:j) - WHERE a.j &&& 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'::geometry -RETURN a; -EXPLAIN (costs off) - - MATCH (a:j) -RETURN a ORDER BY a.j <<->> 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'; - - MATCH (a:j) -RETURN a ORDER BY a.j <<->> 'PolyhedralSurface(((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)) )'; --- --- 2D Gist Indices --- -CREATE (:i {i: 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry }); -CREATE (:i {i: 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry }) ; -CREATE (:i {i: 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry }) ; -SELECT create_vlabel('postgis', 'i'); -CREATE INDEX ON postgis.i USING gist ((properties->'"i"') gist_geometry_ops_2d); -CREATE (:i {i: 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry }); -/* - - - - - - - MATCH (i:i) - WHERE i.i << 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry -RETURN i; -EXPLAIN - - MATCH (i:i) - WHERE i.i << 'POLYGON( (0 0, 10 0, 10 10, 0 10, 0 0) )'::geometry -RETURN i; -*/ - - - - - - - - --- --- Typecasting --- - --- --- To Geometry --- -RETURN topoint('(1, 2)')::geometry ; -RETURN topath('(1, 2), (3, 4)')::geometry ; -RETURN topolygon('(1,1), (2,2), (3, 3), (4, 4)')::geometry ; -SELECT 'POLYGON((0 0,1 1,1 3,2 3,2 0,0 0))'::geometry::gtype; - -RETURN topoint('POINT(1 1)'::geometry); - -RETURN 'POINT(1 1)'::geometry; --- --- From Box3D --- --- gtype Box3D to box3d -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)'); --- gtype string to box3d -RETURN 'BOX3D(1 2 3, 4 5 6)' ; --- gtype Box3D to gtype box2d -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)')::box2d ; --- gtype Box3D to gtype geometry -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)')::geometry; --- gtype Box3D to geometry -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)'); --- --- From Box2D --- --- gtype Box2d to Box2d -RETURN toBox2D('box(1 2, 5 6)') ; --- gtype string to Box2d -RETURN 'box(1 2, 5 6)' ; --- gtype Box2d to geometry -RETURN toBox2D('box(1 2, 5 6)') ; --- gtype Box3D to box2d -RETURN toBox3D('BOX3D(1 2 3, 4 5 6)') ; - -DROP GRAPH postgis CASCADE; \ No newline at end of file diff --git a/regress/sql/variable_edge_functions.sql b/regress/sql/variable_edge_functions.sql deleted file mode 100644 index 6a88a128..00000000 --- a/regress/sql/variable_edge_functions.sql +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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. - */ - -LOAD 'postgraph'; -SET search_path TO postgraph; - -CREATE GRAPH variable_edge_functions; -USE GRAPH variable_edge_functions; --- --- Create table to hold the start and end vertices to test the SRF --- - --- Create a graph to test -CREATE (b:begin)-[:edge]->(u1:middle)-[:edge]->(u2:middle)-[:edge]->(u3:middle)-[:edge]->(e:end), - (u1)-[:self_loop]->(u1), - (e)-[:self_loop]->(e), - (b)-[:alternate_edge]->(u1), - (u2)-[:alternate_edge]->(u3), - (u3)-[:alternate_edge]->(e), - (u2)-[:bypass_edge]->(e), - (e)-[:alternate_edge]->(u3), - (u3)-[:alternate_edge]->(u2), - (u2)-[:bypass_edge]->(b); - - -MATCH (:begin)-[e*3..3]->(:end) RETURN nodes(e); -MATCH (:begin)-[e*3..3]->(:end) WITH nodes(e) as nodes RETURN nodes[1]; -MATCH (:begin)-[e*3..3]->(:end) WITH nodes(e) as nodes RETURN nodes[size(nodes) - 1]; - - -MATCH (:begin)-[e*3..3]->(:end) RETURN relationships(e); -MATCH (:begin)-[e*3..3]->(:end) WITH relationships(e) as edges RETURN edges[1]; -MATCH (:begin)-[e*3..3]->(:end) WITH relationships(e) as edges RETURN edges[size(edges) - 1]; - - -MATCH (:begin)-[ve*3..3]->(:end) -MATCH ()-[e]->() -RETURN e @> ve, id(e), edges(ve); - -MATCH (:begin)-[ve*3..3]->(:end) -MATCH ()-[e]->() -RETURN ve <@ e, id(e), edges(ve); - -MATCH (:begin)-[e*]->(:end) RETURN DISTINCT @-@ e; - --- --- Clean up --- -DROP GRAPH variable_edge_functions CASCADE; - --- --- End --- diff --git a/sql/postgraph-gtype.sql.in b/sql/postgraph-gtype.sql.in index 95fcd613..3268d464 100644 --- a/sql/postgraph-gtype.sql.in +++ b/sql/postgraph-gtype.sql.in @@ -341,30 +341,7 @@ AS OPERATOR 5 >=, FUNCTION 1 gtype_btree_cmp(gtype, gtype); --- --- gtype - ivfflat Operator Classes --- -CREATE OPERATOR CLASS gtype_l2_ops -DEFAULT FOR TYPE gtype -USING ivfflat AS -OPERATOR 1 <-> (gtype, gtype) FOR ORDER BY gtype_ops_btree, -FUNCTION 1 l2_squared_distance(gtype, gtype), -FUNCTION 3 l2_distance(gtype, gtype); - -CREATE OPERATOR CLASS gtype_ip_ops -FOR TYPE gtype USING ivfflat AS -OPERATOR 1 <#> (gtype, gtype) FOR ORDER BY gtype_ops_btree, -FUNCTION 1 negative_inner_product(gtype, gtype), -FUNCTION 3 spherical_distance(gtype, gtype), -FUNCTION 4 norm(gtype); - -CREATE OPERATOR CLASS gtype_cosine_ops -FOR TYPE gtype USING ivfflat AS -OPERATOR 1 <=> (gtype, gtype) FOR ORDER BY gtype_ops_btree, -FUNCTION 1 negative_inner_product(gtype, gtype), -FUNCTION 2 norm(gtype), -FUNCTION 3 spherical_distance(gtype, gtype), -FUNCTION 4 norm(gtype); + -- diff --git a/sql/postgraph.sql.in b/sql/postgraph.sql.in index 12aa61ea..bb384405 100644 --- a/sql/postgraph.sql.in +++ b/sql/postgraph.sql.in @@ -164,15 +164,6 @@ RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; -CREATE FUNCTION create_ivfflat_l2_ops_index(graph_name name, label_name name, property_name name, dimensions int, lists int) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION create_ivfflat_ip_ops_index(graph_name name, label_name name, property_name name, dimensions int, lists int) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; CREATE FUNCTION create_unique_properties_constraint(graph_name name, label_name name) RETURNS void @@ -411,17 +402,6 @@ AS 'MODULE_PATHNAME'; CREATE TYPE variable_edge (INPUT = variable_edge_in, OUTPUT = variable_edge_out, LIKE = jsonb); --- --- Access Methods --- - -CREATE FUNCTION ivfflathandler(internal) RETURNS index_am_handler AS 'MODULE_PATHNAME' LANGUAGE C; - -CREATE ACCESS METHOD ivfflat TYPE INDEX HANDLER ivfflathandler; - -COMMENT ON ACCESS METHOD ivfflat IS 'ivfflat index access method'; - - -- -- MATCH edge uniqueness -- @@ -531,35 +511,6 @@ PARALLEL SAFE AS 'MODULE_PATHNAME', 'edge_unnest'; -CREATE FUNCTION cypher_load_csv (gtype) -RETURNS SETOF gtype -LANGUAGE C -STABLE -RETURNS NULL ON NULL INPUT -PARALLEL UNSAFE -AS 'MODULE_PATHNAME', 'cypher_load_csv'; - -CREATE FUNCTION vle (IN gtype, IN vertex, IN vertex, IN gtype, IN gtype, - IN gtype, IN gtype, IN gtype, OUT edges variable_edge) -RETURNS SETOF variable_edge -LANGUAGE C -STABLE -CALLED ON NULL INPUT -PARALLEL UNSAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_vle'; - -CREATE FUNCTION match_vles(variable_edge, variable_edge) -RETURNS boolean -LANGUAGE C -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR !!= (FUNCTION = match_vles, LEFTARG = variable_edge, RIGHTARG = variable_edge); - - CREATE FUNCTION throw_error(gtype) RETURNS gtype LANGUAGE C diff --git a/src/backend/access/ivfflat/ivfbuild.c b/src/backend/access/ivfflat/ivfbuild.c deleted file mode 100644 index 63313705..00000000 --- a/src/backend/access/ivfflat/ivfbuild.c +++ /dev/null @@ -1,1075 +0,0 @@ -#include "postgres.h" - -#include - -#include "access/parallel.h" -#include "access/xact.h" -#include "catalog/index.h" -#include "catalog/pg_operator_d.h" -#include "catalog/pg_type_d.h" -#include "access/ivfflat.h" -#include "miscadmin.h" -#include "storage/bufmgr.h" -#include "utils/memutils.h" -#include "tcop/tcopprot.h" - -#if PG_VERSION_NUM >= 140000 -#include "utils/backend_progress.h" -#elif PG_VERSION_NUM >= 120000 -#include "pgstat.h" -#endif - -#if PG_VERSION_NUM >= 120000 -#include "access/tableam.h" -#include "commands/progress.h" -#else -#define PROGRESS_CREATEIDX_SUBPHASE 0 -#define PROGRESS_CREATEIDX_TUPLES_TOTAL 0 -#define PROGRESS_CREATEIDX_TUPLES_DONE 0 -#endif - -#if PG_VERSION_NUM >= 130000 -#define CALLBACK_ITEM_POINTER ItemPointer tid -#else -#define CALLBACK_ITEM_POINTER HeapTuple hup -#endif - -#if PG_VERSION_NUM >= 120000 -#define UpdateProgress(index, val) pgstat_progress_update_param(index, val) -#else -#define UpdateProgress(index, val) ((void)val) -#endif - -#if PG_VERSION_NUM >= 140000 -#include "utils/backend_status.h" -#include "utils/wait_event.h" -#endif - -#if PG_VERSION_NUM >= 120000 -#include "access/table.h" -#include "optimizer/optimizer.h" -#else -#include "access/heapam.h" -#include "optimizer/planner.h" -#include "pgstat.h" -#endif - -#define PARALLEL_KEY_IVFFLAT_SHARED UINT64CONST(0xA000000000000001) -#define PARALLEL_KEY_TUPLESORT UINT64CONST(0xA000000000000002) -#define PARALLEL_KEY_IVFFLAT_CENTERS UINT64CONST(0xA000000000000003) -#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000004) - -/* - * Add sample - */ -static void -AddSample(Datum *values, IvfflatBuildState * buildstate) -{ - VectorArray samples = buildstate->samples; - int targsamples = samples->maxlen; - - // Detoast once for all calls - Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); - - /* - * Normalize with KMEANS_NORM_PROC since spherical distance function - * expects unit vectors - */ - if (buildstate->kmeansnormprocinfo != NULL) - { - if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec)) - return; - } - - if (samples->length < targsamples) - { - VectorArraySet(samples, samples->length, DatumGetVector(value)); - samples->length++; - } - else - { - if (buildstate->rowstoskip < 0) - buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, samples->length, targsamples); - - if (buildstate->rowstoskip <= 0) - { -#if PG_VERSION_NUM >= 150000 - int k = (int) (targsamples * sampler_random_fract(&buildstate->rstate.randstate)); -#else - int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate)); -#endif - - Assert(k >= 0 && k < targsamples); - VectorArraySet(samples, k, DatumGetVector(value)); - } - - buildstate->rowstoskip -= 1; - } -} - -/* - * Callback for sampling - */ -static void -SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values, - bool *isnull, bool tupleIsAlive, void *state) -{ - IvfflatBuildState *buildstate = (IvfflatBuildState *) state; - MemoryContext oldCtx; - - // Skip nulls - if (isnull[0]) - return; - - // Use memory context since detoast can allocate - oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); - - // Add sample - AddSample(values, state); - - // Reset memory context - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(buildstate->tmpCtx); -} - -/* - * Sample rows with same logic as ANALYZE - */ -static void -SampleRows(IvfflatBuildState * buildstate) -{ - int targsamples = buildstate->samples->maxlen; - BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap); - - buildstate->rowstoskip = -1; - - BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, RandomInt()); - - reservoir_init_selection_state(&buildstate->rstate, targsamples); - while (BlockSampler_HasMore(&buildstate->bs)) - { - BlockNumber targblock = BlockSampler_Next(&buildstate->bs); - -#if PG_VERSION_NUM >= 120000 - table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, - false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL); -#else - IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo, - false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL); -#endif - } -} - -/* - * Add tuple to sort - */ -static void -AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState * buildstate) -{ - double distance; - double minDistance = DBL_MAX; - int closestCenter = 0; - VectorArray centers = buildstate->centers; - TupleTableSlot *slot = buildstate->slot; - - // Detoast once for all calls - Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); - - // Normalize if needed - if (buildstate->normprocinfo != NULL) - { - if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec)) - return; - } - - // Find the list that minimizes the distance - for (int i = 0; i < centers->length; i++) - { - distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, value, PointerGetDatum(VectorArrayGet(centers, i)))); - - if (distance < minDistance) - { - minDistance = distance; - closestCenter = i; - } - } - -#ifdef IVFFLAT_KMEANS_DEBUG - buildstate->inertia += minDistance; - buildstate->listSums[closestCenter] += minDistance; - buildstate->listCounts[closestCenter]++; -#endif - - // Create a virtual tuple - ExecClearTuple(slot); - slot->tts_values[0] = Int32GetDatum(closestCenter); - slot->tts_isnull[0] = false; - slot->tts_values[1] = PointerGetDatum(tid); - slot->tts_isnull[1] = false; - slot->tts_values[2] = value; - slot->tts_isnull[2] = false; - ExecStoreVirtualTuple(slot); - - /* - * Add tuple to sort - * - * tuplesort_puttupleslot comment: Input data is always copied; the caller - * need not save it. - */ - tuplesort_puttupleslot(buildstate->sortstate, slot); - - buildstate->indtuples++; -} - -/* - * Callback for table_index_build_scan - */ -static void -BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values, bool *isnull, bool tupleIsAlive, void *state) -{ - IvfflatBuildState *buildstate = (IvfflatBuildState *) state; - MemoryContext oldCtx; - -#if PG_VERSION_NUM < 130000 - ItemPointer tid = &hup->t_self; -#endif - - // Skip nulls - if (isnull[0]) - return; - - // Use memory context since detoast can allocate - oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); - - // Add tuple to sort - AddTupleToSort(index, tid, values, buildstate); - - // Reset memory context - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(buildstate->tmpCtx); -} - -/* - * Get index tuple from sort state - */ -static inline void -GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot, IndexTuple *itup, int *list) -{ - Datum value; - bool isnull; - - if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL)) { - *list = DatumGetInt32(slot_getattr(slot, 1, &isnull)); - value = slot_getattr(slot, 3, &isnull); - - // Form the index tuple - *itup = index_form_tuple(tupdesc, &value, &isnull); - (*itup)->t_tid = *((ItemPointer) DatumGetPointer(slot_getattr(slot, 2, &isnull))); - } - else { - *list = -1; - } -} - -/* - * Create initial entry pages - */ -static void -InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum) -{ - int list; - IndexTuple itup = NULL; // silence compiler warning - int64 inserted = 0; - -#if PG_VERSION_NUM >= 120000 - TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple); -#else - TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc); -#endif - TupleDesc tupdesc = RelationGetDescr(index); - - UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD); - - UpdateProgress(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples); - - GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list); - - for (int i = 0; i < buildstate->centers->length; i++) { - Buffer buf; - Page page; - GenericXLogState *state; - BlockNumber startPage; - BlockNumber insertPage; - - // Can take a while, so ensure we can interrupt - // Needs to be called when no buffer locks are held - CHECK_FOR_INTERRUPTS(); - - buf = IvfflatNewBuffer(index, forkNum); - IvfflatInitRegisterPage(index, &buf, &page, &state); - - startPage = BufferGetBlockNumber(buf); - - // Get all tuples for list - while (list == i) { - // Check for free space - Size itemsz = MAXALIGN(IndexTupleSize(itup)); - - if (PageGetFreeSpace(page) < itemsz) - IvfflatAppendPage(index, &buf, &page, &state, forkNum); - - // Add the item - if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - pfree(itup); - - UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted); - - GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list); - } - - insertPage = BufferGetBlockNumber(buf); - - IvfflatCommitBuffer(buf, state); - - // Set the start and insert pages - IvfflatUpdateList(index, buildstate->listInfo[i], insertPage, InvalidBlockNumber, startPage, forkNum); - } -} - -/* - * Initialize the build state - */ -static void -InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo) -{ - buildstate->heap = heap; - buildstate->index = index; - buildstate->indexInfo = indexInfo; - - buildstate->lists = IvfflatGetLists(index); - - buildstate->dimensions = IvfflatGetDimensions(index); - - // Require column to have dimensions to be indexed - if (buildstate->dimensions <= 0) - elog(ERROR, "column does not have dimensions, %i", buildstate->dimensions); - - if (buildstate->dimensions > IVFFLAT_MAX_DIM) - elog(ERROR, "column cannot have more than %d dimensions for ivfflat index", IVFFLAT_MAX_DIM); - - buildstate->reltuples = 0; - buildstate->indtuples = 0; - - // Get support functions - buildstate->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC); - buildstate->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); - buildstate->kmeansnormprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); - buildstate->collation = index->rd_indcollation[0]; - - // Require more than one dimension for spherical k-means - if (buildstate->kmeansnormprocinfo != NULL && buildstate->dimensions == 1) - elog(ERROR, "dimensions must be greater than one for this opclass"); - - // Create tuple description for sorting -#if PG_VERSION_NUM >= 120000 - buildstate->tupdesc = CreateTemplateTupleDesc(3); -#else - buildstate->tupdesc = CreateTemplateTupleDesc(3, false); -#endif - TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0); - TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0); - TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0); - -#if PG_VERSION_NUM >= 120000 - buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual); -#else - buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc); -#endif - - buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions); - buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists); - - // Reuse for each tuple - buildstate->normvec = gtype_value_to_gtype(InitVectorGType(buildstate->dimensions)); - - buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, "Ivfflat build temporary context", ALLOCSET_DEFAULT_SIZES); - -#ifdef IVFFLAT_KMEANS_DEBUG - buildstate->inertia = 0; - buildstate->listSums = palloc0(sizeof(double) * buildstate->lists); - buildstate->listCounts = palloc0(sizeof(int) * buildstate->lists); -#endif - - buildstate->ivfleader = NULL; -} - -/* - * Free resources - */ -static void -FreeBuildState(IvfflatBuildState * buildstate) -{ - VectorArrayFree(buildstate->centers); - pfree(buildstate->listInfo); - pfree(buildstate->normvec); - -#ifdef IVFFLAT_KMEANS_DEBUG - pfree(buildstate->listSums); - pfree(buildstate->listCounts); -#endif - - MemoryContextDelete(buildstate->tmpCtx); -} - -/* - * Compute centers - */ -static void -ComputeCenters(IvfflatBuildState * buildstate) -{ - int numSamples; - - UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS); - - // Target 50 samples per list, with at least 10000 samples - // The number of samples has a large effect on index build time - numSamples = buildstate->lists * 50; - if (numSamples < 10000) - numSamples = 10000; - - // Skip samples for unlogged table - if (buildstate->heap == NULL) - numSamples = 1; - - // Sample rows - // TODO Ensure within maintenance_work_mem - buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions); - if (buildstate->heap != NULL) { - SampleRows(buildstate); - - if (buildstate->samples->length < buildstate->lists) { - ereport(NOTICE, (errmsg("ivfflat index created with little data"), - errdetail("This will cause low recall."), - errhint("Drop the index until the table has more data."))); - } - } - - // Calculate centers - IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers)); - - // Free samples before we allocate more memory - VectorArrayFree(buildstate->samples); -} - -/* - * Create the metapage - */ -static void -CreateMetaPage(Relation index, int dimensions, int lists, ForkNumber forkNum) -{ - Buffer buf; - Page page; - GenericXLogState *state; - IvfflatMetaPage metap; - - buf = IvfflatNewBuffer(index, forkNum); - IvfflatInitRegisterPage(index, &buf, &page, &state); - - // Set metapage data - metap = IvfflatPageGetMeta(page); - metap->magicNumber = IVFFLAT_MAGIC_NUMBER; - metap->version = IVFFLAT_VERSION; - metap->dimensions = dimensions; - metap->lists = lists; - ((PageHeader) page)->pd_lower = ((char *) metap + sizeof(IvfflatMetaPageData)) - (char *) page; - - IvfflatCommitBuffer(buf, state); -} - -/* - * Create list pages - */ -static void -CreateListPages(Relation index, VectorArray centers, int dimensions, int lists, ForkNumber forkNum, ListInfo * *listInfo) { - Buffer buf; - Page page; - GenericXLogState *state; - OffsetNumber offno; - Size itemsz; - IvfflatList list; - - itemsz = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions)); - list = palloc(itemsz); - - buf = IvfflatNewBuffer(index, forkNum); - IvfflatInitRegisterPage(index, &buf, &page, &state); - - for (int i = 0; i < lists; i++) { - // Load list - list->startPage = InvalidBlockNumber; - list->insertPage = InvalidBlockNumber; - memcpy(&list->center, VectorArrayGet(centers, i), VECTOR_SIZE(dimensions)); - - // Ensure free space - if (PageGetFreeSpace(page) < itemsz) - IvfflatAppendPage(index, &buf, &page, &state, forkNum); - - // Add the item - offno = PageAddItem(page, (Item) list, itemsz, InvalidOffsetNumber, false, false); - if (offno == InvalidOffsetNumber) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - // Save location info - (*listInfo)[i].blkno = BufferGetBlockNumber(buf); - (*listInfo)[i].offno = offno; - } - - IvfflatCommitBuffer(buf, state); - - pfree(list); -} - -/* - * Print k-means metrics - */ -#ifdef IVFFLAT_KMEANS_DEBUG -static void -PrintKmeansMetrics(IvfflatBuildState * buildstate) { - elog(INFO, "inertia: %.3e", buildstate->inertia); - - // Calculate Davies-Bouldin index - if (buildstate->lists > 1 && !buildstate->ivfleader) { - double db = 0.0; - - // Calculate average distance - for (int i = 0; i < buildstate->lists; i++) - { - if (buildstate->listCounts[i] > 0) - buildstate->listSums[i] /= buildstate->listCounts[i]; - } - - for (int i = 0; i < buildstate->lists; i++) - { - double max = 0.0; - double distance; - - for (int j = 0; j < buildstate->lists; j++) - { - if (j == i) - continue; - - distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, PointerGetDatum(VectorArrayGet(buildstate->centers, i)), PointerGetDatum(VectorArrayGet(buildstate->centers, j)))); - distance = (buildstate->listSums[i] + buildstate->listSums[j]) / distance; - - if (distance > max) - max = distance; - } - db += max; - } - db /= buildstate->lists; - elog(INFO, "davies-bouldin: %.3f", db); - } -} -#endif - -/* - * Within leader, wait for end of heap scan - */ -static double -ParallelHeapScan(IvfflatBuildState * buildstate) -{ - IvfflatShared *ivfshared = buildstate->ivfleader->ivfshared; - int nparticipanttuplesorts; - double reltuples; - - nparticipanttuplesorts = buildstate->ivfleader->nparticipanttuplesorts; - for (;;) { - SpinLockAcquire(&ivfshared->mutex); - if (ivfshared->nparticipantsdone == nparticipanttuplesorts) { - buildstate->indtuples = ivfshared->indtuples; - reltuples = ivfshared->reltuples; -#ifdef IVFFLAT_KMEANS_DEBUG - buildstate->inertia = ivfshared->inertia; -#endif - SpinLockRelease(&ivfshared->mutex); - break; - } - SpinLockRelease(&ivfshared->mutex); - - ConditionVariableSleep(&ivfshared->workersdonecv, WAIT_EVENT_PARALLEL_CREATE_INDEX_SCAN); - } - - ConditionVariableCancelSleep(); - - return reltuples; -} - -/* - * Perform a worker's portion of a parallel sort - */ -static void -IvfflatParallelScanAndSort(IvfflatSpool * ivfspool, IvfflatShared * ivfshared, Sharedsort *sharedsort, gtype * ivfcenters, int sortmem, bool progress) { - SortCoordinate coordinate; - IvfflatBuildState buildstate; -#if PG_VERSION_NUM >= 120000 - TableScanDesc scan; -#else - HeapScanDesc scan; -#endif - double reltuples; - IndexInfo *indexInfo; - - // Sort options, which must match AssignTuples - AttrNumber attNums[] = {1}; - Oid sortOperators[] = {Int4LessOperator}; - Oid sortCollations[] = {InvalidOid}; - bool nullsFirstFlags[] = {false}; - - // Initialize local tuplesort coordination state - coordinate = palloc0(sizeof(SortCoordinateData)); - coordinate->isWorker = true; - coordinate->nParticipants = -1; - coordinate->sharedsort = sharedsort; - - // Join parallel scan - indexInfo = BuildIndexInfo(ivfspool->index); - indexInfo->ii_Concurrent = ivfshared->isconcurrent; - InitBuildState(&buildstate, ivfspool->heap, ivfspool->index, indexInfo); - memcpy(buildstate.centers->items, ivfcenters, VECTOR_SIZE(buildstate.centers->dim) * buildstate.centers->maxlen); - buildstate.centers->length = buildstate.centers->maxlen; - ivfspool->sortstate = tuplesort_begin_heap(buildstate.tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, sortmem, coordinate, false); - buildstate.sortstate = ivfspool->sortstate; -#if PG_VERSION_NUM >= 120000 - scan = table_beginscan_parallel(ivfspool->heap, ParallelTableScanFromIvfflatShared(ivfshared)); - reltuples = table_index_build_scan(ivfspool->heap, ivfspool->index, indexInfo, true, progress, BuildCallback, (void *) &buildstate, scan); -#else - scan = heap_beginscan_parallel(ivfspool->heap, &ivfshared->heapdesc); - reltuples = IndexBuildHeapScan(ivfspool->heap, ivfspool->index, indexInfo, true, BuildCallback, (void *) &buildstate, scan); -#endif - - // Execute this worker's part of the sort - tuplesort_performsort(ivfspool->sortstate); - - // Record statistics - SpinLockAcquire(&ivfshared->mutex); - ivfshared->nparticipantsdone++; - ivfshared->reltuples += reltuples; - ivfshared->indtuples += buildstate.indtuples; -#ifdef IVFFLAT_KMEANS_DEBUG - ivfshared->inertia += buildstate.inertia; -#endif - SpinLockRelease(&ivfshared->mutex); - - // Log statistics - if (progress) - ereport(DEBUG1, (errmsg("leader processed " INT64_FORMAT " tuples", (int64) reltuples))); - else - ereport(DEBUG1, (errmsg("worker processed " INT64_FORMAT " tuples", (int64) reltuples))); - - // Notify leader - ConditionVariableSignal(&ivfshared->workersdonecv); - - // We can end tuplesorts immediately - tuplesort_end(ivfspool->sortstate); - - FreeBuildState(&buildstate); -} - -/* - * Perform work within a launched parallel process - */ -void -IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc) -{ - char *sharedquery; - IvfflatSpool *ivfspool; - IvfflatShared *ivfshared; - Sharedsort *sharedsort; - Vector *ivfcenters; - Relation heapRel; - Relation indexRel; - LOCKMODE heapLockmode; - LOCKMODE indexLockmode; - int sortmem; - - // Set debug_query_string for individual workers first - sharedquery = shm_toc_lookup(toc, PARALLEL_KEY_QUERY_TEXT, true); - debug_query_string = sharedquery; - - // Report the query string from leader - pgstat_report_activity(STATE_RUNNING, debug_query_string); - - // Look up shared state - ivfshared = shm_toc_lookup(toc, PARALLEL_KEY_IVFFLAT_SHARED, false); - - // Open relations using lock modes known to be obtained by index.c - if (!ivfshared->isconcurrent) { - heapLockmode = ShareLock; - indexLockmode = AccessExclusiveLock; - } else { - heapLockmode = ShareUpdateExclusiveLock; - indexLockmode = RowExclusiveLock; - } - - // Open relations within worker -#if PG_VERSION_NUM >= 120000 - heapRel = table_open(ivfshared->heaprelid, heapLockmode); -#else - heapRel = heap_open(ivfshared->heaprelid, heapLockmode); -#endif - indexRel = index_open(ivfshared->indexrelid, indexLockmode); - - // Initialize worker's own spool - ivfspool = (IvfflatSpool *) palloc0(sizeof(IvfflatSpool)); - ivfspool->heap = heapRel; - ivfspool->index = indexRel; - - // Look up shared state private to tuplesort.c - sharedsort = shm_toc_lookup(toc, PARALLEL_KEY_TUPLESORT, false); - tuplesort_attach_shared(sharedsort, seg); - - ivfcenters = shm_toc_lookup(toc, PARALLEL_KEY_IVFFLAT_CENTERS, false); - - // Perform sorting - sortmem = maintenance_work_mem / ivfshared->scantuplesortstates; - IvfflatParallelScanAndSort(ivfspool, ivfshared, sharedsort, ivfcenters, sortmem, false); - - // Close relations within worker - index_close(indexRel, indexLockmode); -#if PG_VERSION_NUM >= 120000 - table_close(heapRel, heapLockmode); -#else - heap_close(heapRel, heapLockmode); -#endif -} - -/* - * End parallel build - */ -static void -IvfflatEndParallel(IvfflatLeader *ivfleader) { - // Shutdown worker processes - WaitForParallelWorkersToFinish(ivfleader->pcxt); - - // Free last reference to MVCC snapshot, if one was used - if (IsMVCCSnapshot(ivfleader->snapshot)) - UnregisterSnapshot(ivfleader->snapshot); - DestroyParallelContext(ivfleader->pcxt); - ExitParallelMode(); -} - -/* - * Return size of shared memory required for parallel index build - */ -static Size -ParallelEstimateShared(Relation heap, Snapshot snapshot) -{ -#if PG_VERSION_NUM >= 120000 - return add_size(BUFFERALIGN(sizeof(IvfflatShared)), table_parallelscan_estimate(heap, snapshot)); -#else - if (!IsMVCCSnapshot(snapshot)) - { - Assert(snapshot == SnapshotAny); - return sizeof(IvfflatShared); - } - - return add_size(offsetof(IvfflatShared, heapdesc) + - offsetof(ParallelHeapScanDescData, phs_snapshot_data), - EstimateSnapshotSpace(snapshot)); -#endif -} - -/* - * Within leader, participate as a parallel worker - */ -static void -IvfflatLeaderParticipateAsWorker(IvfflatBuildState * buildstate) -{ - IvfflatLeader *ivfleader = buildstate->ivfleader; - IvfflatSpool *leaderworker; - int sortmem; - - // Allocate memory and initialize private spool - leaderworker = (IvfflatSpool *) palloc0(sizeof(IvfflatSpool)); - leaderworker->heap = buildstate->heap; - leaderworker->index = buildstate->index; - - // Perform work common to all participants - sortmem = maintenance_work_mem / ivfleader->nparticipanttuplesorts; - IvfflatParallelScanAndSort(leaderworker, ivfleader->ivfshared, ivfleader->sharedsort, ivfleader->ivfcenters, sortmem, true); -} - -/* - * Begin parallel build - */ -static void -IvfflatBeginParallel(IvfflatBuildState * buildstate, bool isconcurrent, int request) -{ - ParallelContext *pcxt; - int scantuplesortstates; - Snapshot snapshot; - Size estivfshared; - Size estsort; - Size estcenters; - IvfflatShared *ivfshared; - Sharedsort *sharedsort; - gtype *ivfcenters; - IvfflatLeader *ivfleader = (IvfflatLeader *) palloc0(sizeof(IvfflatLeader)); - bool leaderparticipates = true; - int querylen; - -#ifdef DISABLE_LEADER_PARTICIPATION - leaderparticipates = false; -#endif - - // Enter parallel mode and create context - EnterParallelMode(); - Assert(request > 0); -#if PG_VERSION_NUM >= 120000 - pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request); -#else - pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request, true); -#endif - - scantuplesortstates = leaderparticipates ? request + 1 : request; - - // Get snapshot for table scan - if (!isconcurrent) - snapshot = SnapshotAny; - else - snapshot = RegisterSnapshot(GetTransactionSnapshot()); - - // Estimate size of workspaces - estivfshared = ParallelEstimateShared(buildstate->heap, snapshot); - shm_toc_estimate_chunk(&pcxt->estimator, estivfshared); - estsort = tuplesort_estimate_shared(scantuplesortstates); - shm_toc_estimate_chunk(&pcxt->estimator, estsort); - estcenters = VECTOR_SIZE(buildstate->dimensions) * buildstate->lists; - shm_toc_estimate_chunk(&pcxt->estimator, estcenters); - shm_toc_estimate_keys(&pcxt->estimator, 3); - - // Finally, estimate PARALLEL_KEY_QUERY_TEXT space - if (debug_query_string) - { - querylen = strlen(debug_query_string); - shm_toc_estimate_chunk(&pcxt->estimator, querylen + 1); - shm_toc_estimate_keys(&pcxt->estimator, 1); - } - else - querylen = 0; // keep compiler quiet - - // Everyone's had a chance to ask for space, so now create the DSM - InitializeParallelDSM(pcxt); - - // If no DSM segment was available, back out (do serial build) - if (pcxt->seg == NULL) - { - if (IsMVCCSnapshot(snapshot)) - UnregisterSnapshot(snapshot); - DestroyParallelContext(pcxt); - ExitParallelMode(); - return; - } - - // Store shared build state, for which we reserved space - ivfshared = (IvfflatShared *) shm_toc_allocate(pcxt->toc, estivfshared); - // Initialize immutable state - ivfshared->heaprelid = RelationGetRelid(buildstate->heap); - ivfshared->indexrelid = RelationGetRelid(buildstate->index); - ivfshared->isconcurrent = isconcurrent; - ivfshared->scantuplesortstates = scantuplesortstates; - ConditionVariableInit(&ivfshared->workersdonecv); - SpinLockInit(&ivfshared->mutex); - // Initialize mutable state - ivfshared->nparticipantsdone = 0; - ivfshared->reltuples = 0; - ivfshared->indtuples = 0; -#ifdef IVFFLAT_KMEANS_DEBUG - ivfshared->inertia = 0; -#endif -#if PG_VERSION_NUM >= 120000 - table_parallelscan_initialize(buildstate->heap, ParallelTableScanFromIvfflatShared(ivfshared), snapshot); -#else - heap_parallelscan_initialize(&ivfshared->heapdesc, buildstate->heap, snapshot); -#endif - - // Store shared tuplesort-private state, for which we reserved space - sharedsort = (Sharedsort *) shm_toc_allocate(pcxt->toc, estsort); - tuplesort_initialize_shared(sharedsort, scantuplesortstates, - pcxt->seg); - - ivfcenters = (gtype *) shm_toc_allocate(pcxt->toc, estcenters); - memcpy(ivfcenters, buildstate->centers->items, estcenters); - - shm_toc_insert(pcxt->toc, PARALLEL_KEY_IVFFLAT_SHARED, ivfshared); - shm_toc_insert(pcxt->toc, PARALLEL_KEY_TUPLESORT, sharedsort); - shm_toc_insert(pcxt->toc, PARALLEL_KEY_IVFFLAT_CENTERS, ivfcenters); - - // Store query string for workers - if (debug_query_string) - { - char *sharedquery; - - sharedquery = (char *) shm_toc_allocate(pcxt->toc, querylen + 1); - memcpy(sharedquery, debug_query_string, querylen + 1); - shm_toc_insert(pcxt->toc, PARALLEL_KEY_QUERY_TEXT, sharedquery); - } - - // Launch workers, saving status for leader/caller - LaunchParallelWorkers(pcxt); - ivfleader->pcxt = pcxt; - ivfleader->nparticipanttuplesorts = pcxt->nworkers_launched; - if (leaderparticipates) - ivfleader->nparticipanttuplesorts++; - ivfleader->ivfshared = ivfshared; - ivfleader->sharedsort = sharedsort; - ivfleader->snapshot = snapshot; - ivfleader->ivfcenters = ivfcenters; - - // If no workers were successfully launched, back out (do serial build) - if (pcxt->nworkers_launched == 0) - { - IvfflatEndParallel(ivfleader); - return; - } - - // Log participants - ereport(DEBUG1, (errmsg("using %d parallel workers", pcxt->nworkers_launched))); - - // Save leader state now that it's clear build will be parallel - buildstate->ivfleader = ivfleader; - - // Join heap scan ourselves - if (leaderparticipates) - IvfflatLeaderParticipateAsWorker(buildstate); - - // Wait for all launched workers - WaitForParallelWorkersToAttach(pcxt); -} - -/* - * Scan table for tuples to index - */ -static void -AssignTuples(IvfflatBuildState * buildstate) -{ - int parallel_workers = 0; - SortCoordinate coordinate = NULL; - - // Sort options, which must match IvfflatParallelScanAndSort - AttrNumber attNums[] = {1}; - Oid sortOperators[] = {Int4LessOperator}; - Oid sortCollations[] = {InvalidOid}; - bool nullsFirstFlags[] = {false}; - - UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_ASSIGN); - - // Calculate parallel workers - if (buildstate->heap != NULL) - parallel_workers = plan_create_index_workers(RelationGetRelid(buildstate->heap), RelationGetRelid(buildstate->index)); - - // Attempt to launch parallel worker scan when required - if (parallel_workers > 0) - IvfflatBeginParallel(buildstate, buildstate->indexInfo->ii_Concurrent, parallel_workers); - - // Set up coordination state if at least one worker launched - if (buildstate->ivfleader) - { - coordinate = (SortCoordinate) palloc0(sizeof(SortCoordinateData)); - coordinate->isWorker = false; - coordinate->nParticipants = buildstate->ivfleader->nparticipanttuplesorts; - coordinate->sharedsort = buildstate->ivfleader->sharedsort; - } - - // Begin serial/leader tuplesort - buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, coordinate, false); - - // Add tuples to sort - if (buildstate->heap != NULL) - { - if (buildstate->ivfleader) - buildstate->reltuples = ParallelHeapScan(buildstate); - else - { -#if PG_VERSION_NUM >= 120000 - buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, true, true, BuildCallback, (void *) buildstate, NULL); -#else - buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo, true, BuildCallback, (void *) buildstate, NULL); -#endif - } - -#ifdef IVFFLAT_KMEANS_DEBUG - PrintKmeansMetrics(buildstate); -#endif - } -} - -/* - * Create entry pages - */ -static void -CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum) -{ - // Assign - IvfflatBench("assign tuples", AssignTuples(buildstate)); - - // Sort - IvfflatBench("sort tuples", tuplesort_performsort(buildstate->sortstate)); - - // Load - IvfflatBench("load tuples", InsertTuples(buildstate->index, buildstate, forkNum)); - - // End sort - tuplesort_end(buildstate->sortstate); - - // End parallel build - if (buildstate->ivfleader) - IvfflatEndParallel(buildstate->ivfleader); -} - -/* - * Build the index - */ -static void -BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo, IvfflatBuildState * buildstate, ForkNumber forkNum) { - InitBuildState(buildstate, heap, index, indexInfo); - - ComputeCenters(buildstate); - - // Create pages - CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum); - CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo); - CreateEntryPages(buildstate, forkNum); - - FreeBuildState(buildstate); -} - -/* - * Build the index for a logged table - */ -IndexBuildResult * -ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo) { - IndexBuildResult *result; - IvfflatBuildState buildstate; - - BuildIndex(heap, index, indexInfo, &buildstate, MAIN_FORKNUM); - - result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); - result->heap_tuples = buildstate.reltuples; - result->index_tuples = buildstate.indtuples; - - return result; -} - -/* - * Build the index for an unlogged table - */ -void -ivfflatbuildempty(Relation index) { - IndexInfo *indexInfo = BuildIndexInfo(index); - IvfflatBuildState buildstate; - - BuildIndex(NULL, index, indexInfo, &buildstate, INIT_FORKNUM); -} diff --git a/src/backend/access/ivfflat/ivfflat.c b/src/backend/access/ivfflat/ivfflat.c deleted file mode 100644 index 1fb92569..00000000 --- a/src/backend/access/ivfflat/ivfflat.c +++ /dev/null @@ -1,246 +0,0 @@ -#include "postgres.h" - -#include - -#include "access/amapi.h" -#include "commands/vacuum.h" -#include "access/ivfflat.h" -#include "utils/guc.h" -#include "utils/selfuncs.h" -#include "utils/spccache.h" -#include "utils/vector.h" - -#if PG_VERSION_NUM >= 120000 -#include "commands/progress.h" -#endif - -int ivfflat_probes; -static relopt_kind ivfflat_relopt_kind; - -/* - * Initialize index options and variables - */ -void -IvfflatInit(void) -{ - ivfflat_relopt_kind = add_reloption_kind(); - add_int_reloption(ivfflat_relopt_kind, "lists", "Number of inverted lists", IVFFLAT_DEFAULT_LISTS, 1, IVFFLAT_MAX_LISTS, AccessExclusiveLock); - - - add_int_reloption(ivfflat_relopt_kind, "dimensions", "Number of dimensions in the index", IVFFLAT_DEFAULT_ELEMENTS, -1, VECTOR_MAX_DIM, AccessExclusiveLock); - - - DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes", "Valid range is 1..lists.", &ivfflat_probes, 1, 1, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL); -} - -/* - * Get the name of index build phase - */ -#if PG_VERSION_NUM >= 120000 -static char * -ivfflatbuildphasename(int64 phasenum) -{ - switch (phasenum) - { - case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE: - return "initializing"; - case PROGRESS_IVFFLAT_PHASE_KMEANS: - return "performing k-means"; - case PROGRESS_IVFFLAT_PHASE_ASSIGN: - return "assigning tuples"; - case PROGRESS_IVFFLAT_PHASE_LOAD: - return "loading tuples"; - default: - return NULL; - } -} -#endif - -/* - * Estimate the cost of an index scan - */ -static void -ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, - Cost *indexStartupCost, Cost *indexTotalCost, - Selectivity *indexSelectivity, double *indexCorrelation, - double *indexPages) -{ - GenericCosts costs; - int lists; - double ratio; - double spc_seq_page_cost; - Relation indexRel; -#if PG_VERSION_NUM < 120000 - List *qinfos; -#endif - - // Never use index without order - if (path->indexorderbys == NULL) - { - *indexStartupCost = DBL_MAX; - *indexTotalCost = DBL_MAX; - *indexSelectivity = 0; - *indexCorrelation = 0; - *indexPages = 0; - return; - } - - MemSet(&costs, 0, sizeof(costs)); - - indexRel = index_open(path->indexinfo->indexoid, NoLock); - lists = IvfflatGetLists(indexRel); - index_close(indexRel, NoLock); - - // Get the ratio of lists that we need to visit - ratio = ((double) ivfflat_probes) / lists; - if (ratio > 1.0) - ratio = 1.0; - - /* - * This gives us the subset of tuples to visit. This value is passed into - * the generic cost estimator to determine the number of pages to visit - * during the index scan. - */ - costs.numIndexTuples = path->indexinfo->tuples * ratio; - -#if PG_VERSION_NUM >= 120000 - genericcostestimate(root, path, loop_count, &costs); -#else - qinfos = deconstruct_indexquals(path); - genericcostestimate(root, path, loop_count, qinfos, &costs); -#endif - - get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost); - - // Adjust cost if needed since TOAST not included in seq scan cost - if (costs.numIndexPages > path->indexinfo->rel->pages && ratio < 0.5) - { - // Change all page cost from random to sequential - costs.indexTotalCost -= costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost); - - // Remove cost of extra pages - costs.indexTotalCost -= (costs.numIndexPages - path->indexinfo->rel->pages) * spc_seq_page_cost; - } - else - { - // Change some page cost from random to sequential - costs.indexTotalCost -= 0.5 * costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost); - } - - /* - * If the list selectivity is lower than what is returned from the generic - * cost estimator, use that. - */ - if (ratio < costs.indexSelectivity) - costs.indexSelectivity = ratio; - - // Use total cost since most work happens before first tuple is returned - *indexStartupCost = costs.indexTotalCost; - *indexTotalCost = costs.indexTotalCost; - *indexSelectivity = costs.indexSelectivity; - *indexCorrelation = costs.indexCorrelation; - *indexPages = costs.numIndexPages; -} - -/* - * Parse and validate the reloptions - */ -static bytea * -ivfflatoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"lists", RELOPT_TYPE_INT, offsetof(IvfflatOptions, lists)}, - {"dimensions", RELOPT_TYPE_INT, offsetof(IvfflatOptions, dimensions)}, - }; - -#if PG_VERSION_NUM >= 130000 - return (bytea *) build_reloptions(reloptions, validate, ivfflat_relopt_kind, 2 * sizeof(IvfflatOptions), tab, 2 * lengthof(tab)); -#else - relopt_value *options; - int numoptions; - IvfflatOptions *rdopts; - - options = parseRelOptions(reloptions, validate, ivfflat_relopt_kind, &numoptions); - rdopts = allocateReloptStruct(sizeof(IvfflatOptions), options, numoptions); - fillRelOptions((void *) rdopts, sizeof(IvfflatOptions), options, numoptions, validate, tab, lengthof(tab)); - - return (bytea *) rdopts; -#endif -} - -/* - * Validate catalog entries for the specified operator class - */ -static bool -ivfflatvalidate(Oid opclassoid) -{ - return true; -} - -/* - * Define index handler - * - * See https://www.postgresql.org/docs/current/index-api.html - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(ivfflathandler); -Datum -ivfflathandler(PG_FUNCTION_ARGS) -{ - IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); - - amroutine->amstrategies = 0; - amroutine->amsupport = 4; -#if PG_VERSION_NUM >= 130000 - amroutine->amoptsprocnum = 0; -#endif - amroutine->amcanorder = false; - amroutine->amcanorderbyop = true; - amroutine->amcanbackward = false; // can change direction mid-scan - amroutine->amcanunique = false; - amroutine->amcanmulticol = false; - amroutine->amoptionalkey = true; - amroutine->amsearcharray = false; - amroutine->amsearchnulls = false; - amroutine->amstorage = false; - amroutine->amclusterable = false; - amroutine->ampredlocks = false; - amroutine->amcanparallel = false; - amroutine->amcaninclude = false; -#if PG_VERSION_NUM >= 130000 - amroutine->amusemaintenanceworkmem = false; // not used during VACUUM - amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL; -#endif - amroutine->amkeytype = InvalidOid; - - // Interface functions - amroutine->ambuild = ivfflatbuild; - amroutine->ambuildempty = ivfflatbuildempty; - amroutine->aminsert = ivfflatinsert; - amroutine->ambulkdelete = ivfflatbulkdelete; - amroutine->amvacuumcleanup = ivfflatvacuumcleanup; - amroutine->amcanreturn = NULL; // tuple not included in heapsort - amroutine->amcostestimate = ivfflatcostestimate; - amroutine->amoptions = ivfflatoptions; - amroutine->amproperty = NULL; // TODO AMPROP_DISTANCE_ORDERABLE -#if PG_VERSION_NUM >= 120000 - amroutine->ambuildphasename = ivfflatbuildphasename; -#endif - amroutine->amvalidate = ivfflatvalidate; -#if PG_VERSION_NUM >= 140000 - amroutine->amadjustmembers = NULL; -#endif - amroutine->ambeginscan = ivfflatbeginscan; - amroutine->amrescan = ivfflatrescan; - amroutine->amgettuple = ivfflatgettuple; - amroutine->amgetbitmap = NULL; - amroutine->amendscan = ivfflatendscan; - amroutine->ammarkpos = NULL; - amroutine->amrestrpos = NULL; - - // Interface functions to support parallel index scans - amroutine->amestimateparallelscan = NULL; - amroutine->aminitparallelscan = NULL; - amroutine->amparallelrescan = NULL; - - PG_RETURN_POINTER(amroutine); -} diff --git a/src/backend/access/ivfflat/ivfinsert.c b/src/backend/access/ivfflat/ivfinsert.c deleted file mode 100644 index 48a4646a..00000000 --- a/src/backend/access/ivfflat/ivfinsert.c +++ /dev/null @@ -1,192 +0,0 @@ -#include "postgres.h" - -#include - -#include "access/ivfflat.h" -#include "storage/bufmgr.h" -#include "storage/lmgr.h" -#include "utils/memutils.h" - -/* - * Find the list that minimizes the distance function - */ -static void -FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo * listInfo) { - Buffer cbuf; - Page cpage; - IvfflatList list; - double distance; - double minDistance = DBL_MAX; - BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO; - FmgrInfo *procinfo; - Oid collation; - OffsetNumber offno; - OffsetNumber maxoffno; - - // Avoid compiler warning - listInfo->blkno = nextblkno; - listInfo->offno = FirstOffsetNumber; - - procinfo = index_getprocinfo(rel, 1, IVFFLAT_DISTANCE_PROC); - collation = rel->rd_indcollation[0]; - - // Search all list pages - while (BlockNumberIsValid(nextblkno)) - { - cbuf = ReadBuffer(rel, nextblkno); - LockBuffer(cbuf, BUFFER_LOCK_SHARE); - cpage = BufferGetPage(cbuf); - maxoffno = PageGetMaxOffsetNumber(cpage); - - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) { - list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno)); - distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, values[0], PointerGetDatum(&list->center))); - - if (distance < minDistance || !BlockNumberIsValid(*insertPage)) { - *insertPage = list->insertPage; - listInfo->blkno = nextblkno; - listInfo->offno = offno; - minDistance = distance; - } - } - - nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno; - - UnlockReleaseBuffer(cbuf); - } -} - -/* - * Insert a tuple into the index - */ -static void -InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel) { - IndexTuple itup; - Datum value; - FmgrInfo *normprocinfo; - Buffer buf; - Page page; - GenericXLogState *state; - Size itemsz; - BlockNumber insertPage = InvalidBlockNumber; - ListInfo listInfo; - BlockNumber originalInsertPage; - - // Detoast once for all calls - value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); - - // Normalize if needed - normprocinfo = IvfflatOptionalProcInfo(rel, IVFFLAT_NORM_PROC); - if (normprocinfo != NULL) { - if (!IvfflatNormValue(normprocinfo, rel->rd_indcollation[0], &value, NULL)) - return; - } - - // Find the insert page - sets the page and list info - FindInsertPage(rel, values, &insertPage, &listInfo); - Assert(BlockNumberIsValid(insertPage)); - originalInsertPage = insertPage; - - // Form tuple - itup = index_form_tuple(RelationGetDescr(rel), &value, isnull); - itup->t_tid = *heap_tid; - - // Get tuple size - itemsz = MAXALIGN(IndexTupleSize(itup)); - Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData))); - - // Find a page to insert the item - for (;;) { - buf = ReadBuffer(rel, insertPage); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - - state = GenericXLogStart(rel); - page = GenericXLogRegisterBuffer(state, buf, 0); - - if (PageGetFreeSpace(page) >= itemsz) - break; - - insertPage = IvfflatPageGetOpaque(page)->nextblkno; - - if (BlockNumberIsValid(insertPage)) { - // Move to next page - GenericXLogAbort(state); - UnlockReleaseBuffer(buf); - } else { - Buffer newbuf; - Page newpage; - - // Add a new page - LockRelationForExtension(rel, ExclusiveLock); - newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM); - UnlockRelationForExtension(rel, ExclusiveLock); - - // Init new page - newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE); - IvfflatInitPage(newbuf, newpage); - - // Update insert page - insertPage = BufferGetBlockNumber(newbuf); - - // Update previous buffer - IvfflatPageGetOpaque(page)->nextblkno = insertPage; - - // Commit - MarkBufferDirty(newbuf); - MarkBufferDirty(buf); - GenericXLogFinish(state); - - // Unlock previous buffer - UnlockReleaseBuffer(buf); - - // Prepare new buffer - state = GenericXLogStart(rel); - buf = newbuf; - page = GenericXLogRegisterBuffer(state, buf, 0); - break; - } - } - - // Add to next offset - if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(rel)); - - IvfflatCommitBuffer(buf, state); - - // Update the insert page - if (insertPage != originalInsertPage) - IvfflatUpdateList(rel, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM); -} - -/* - * Insert a tuple into the index - */ -bool -ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique -#if PG_VERSION_NUM >= 140000 - , bool indexUnchanged -#endif - , IndexInfo *indexInfo) { - MemoryContext oldCtx; - MemoryContext insertCtx; - - // Skip nulls - if (isnull[0]) - return false; - - /* - * Use memory context since detoast, IvfflatNormValue, and - * index_form_tuple can allocate - */ - insertCtx = AllocSetContextCreate(CurrentMemoryContext, "Ivfflat insert temporary context", ALLOCSET_DEFAULT_SIZES); - oldCtx = MemoryContextSwitchTo(insertCtx); - - // Insert tuple - InsertTuple(index, values, isnull, heap_tid, heap); - - // Delete memory context - MemoryContextSwitchTo(oldCtx); - MemoryContextDelete(insertCtx); - - return false; -} diff --git a/src/backend/access/ivfflat/ivfkmeans.c b/src/backend/access/ivfflat/ivfkmeans.c deleted file mode 100644 index 6c791fe3..00000000 --- a/src/backend/access/ivfflat/ivfkmeans.c +++ /dev/null @@ -1,512 +0,0 @@ -#include "postgres.h" - -#include -#include - -#include "access/ivfflat.h" -#include "miscadmin.h" - -/* - * Initialize with kmeans++ - * - * https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf - */ -static void -InitCenters(Relation index, VectorArray samples, VectorArray centers, float8 *lowerBound) -{ - FmgrInfo *procinfo; - Oid collation; - int64 j; - double distance; - double sum; - double choice; - gtype *vec; - float8 *weight = palloc(samples->length * sizeof(float8)); - int numCenters = centers->maxlen; - int numSamples = samples->length; - - procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC); - collation = index->rd_indcollation[0]; - - // Choose an initial center uniformly at random - VectorArraySet(centers, 0, VectorArrayGet(samples, RandomInt() % samples->length)); - centers->length++; - - for (j = 0; j < numSamples; j++) - weight[j] = DBL_MAX; - - for (int i = 0; i < numCenters; i++) { - CHECK_FOR_INTERRUPTS(); - - sum = 0.0; - - for (j = 0; j < numSamples; j++) { - vec = VectorArrayGet(samples, j); - - // Only need to compute distance for new center - // TODO Use triangle inequality to reduce distance calculations - distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, i)))); - - // Set lower bound - lowerBound[j * numCenters + i] = distance; - - // Use distance squared for weighted probability distribution - distance *= distance; - - if (distance < weight[j]) - weight[j] = distance; - - sum += weight[j]; - } - - // Only compute lower bound on last iteration - if (i + 1 == numCenters) - break; - - // Choose new center using weighted probability distribution. - choice = sum * RandomDouble(); - for (j = 0; j < numSamples - 1; j++) { - choice -= weight[j]; - if (choice <= 0) - break; - } - - VectorArraySet(centers, i + 1, VectorArrayGet(samples, j)); - centers->length++; - } - - pfree(weight); -} - -/* - * Apply norm to vector - */ -static inline void -ApplyNorm(FmgrInfo *normprocinfo, Oid collation, gtype * vec) { - double norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(vec))); - - // TODO Handle zero norm - if (norm > 0) { - for (int i = 0; i < AGT_ROOT_COUNT(vec); i++) - *((float8 *)(&vec->root.children[1 + (i * sizeof(float8))])) /= norm; - } -} - -/* - * Compare vectors - */ -static int -CompareVectors(const void *a, const void *b) { - return gtype_vector_cmp((Vector *) a, (Vector *) b); -} - -/* - * Quick approach if we have little data - */ -static void -QuickCenters(Relation index, VectorArray samples, VectorArray centers) { - gtype *vec; - int dimensions = centers->dim; - Oid collation = index->rd_indcollation[0]; - FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); - - // Copy existing vectors while avoiding duplicates - if (samples->length > 0) { - qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors); - for (int i = 0; i < samples->length; i++) { - vec = VectorArrayGet(samples, i); - - if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0) { - VectorArraySet(centers, centers->length, vec); - centers->length++; - } - } - } - - // Fill remaining with random data - while (centers->length < centers->maxlen) { - vec = VectorArrayGet(centers, centers->length); - - SET_VARSIZE(vec, VECTOR_SIZE(dimensions)); - vec->root.header = dimensions | GT_FEXTENDED_COMPOSITE; - vec->root.children[0] = GT_HEADER_VECTOR; - - for (int j = 0; j < dimensions; j++) { - *((float8 *)(&vec->root.children[1 + (j * sizeof(float8))])) = RandomDouble(); - } - - // Normalize if needed (only needed for random centers) - if (normprocinfo != NULL) - ApplyNorm(normprocinfo, collation, vec); - - centers->length++; - } -} - -/* - * Use Elkan for performance. This requires distance function to satisfy triangle inequality. - * - * We use L2 distance for L2 (not L2 squared like index scan) - * and angular distance for inner product and cosine distance - * - * https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf - */ -static void -ElkanKmeans(Relation index, VectorArray samples, VectorArray centers) -{ - FmgrInfo *procinfo; - FmgrInfo *normprocinfo; - Oid collation; - gtype *vec; - gtype *newCenter; - int iteration; - int64 j; - int64 k; - int dimensions = centers->dim; - int numCenters = centers->maxlen; - int numSamples = samples->length; - VectorArray newCenters; - int *centerCounts; - int *closestCenters; - float8 *lowerBound; - float8 *upperBound; - float8 *s; - float8 *halfcdist; - float8 *newcdist; - int changes; - double minDistance; - int closestCenter; - double distance; - bool rj; - bool rjreset; - double dxcx; - double dxc; - - // Calculate allocation sizes - Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim); - Size centersSize = VECTOR_ARRAY_SIZE(centers->maxlen, centers->dim); - Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, dimensions); - Size centerCountsSize = sizeof(int) * numCenters; - Size closestCentersSize = sizeof(int) * numSamples; - Size lowerBoundSize = sizeof(float8) * numSamples * numCenters; - Size upperBoundSize = sizeof(float8) * numSamples; - Size sSize = sizeof(float8) * numCenters; - Size halfcdistSize = sizeof(float8) * numCenters * numCenters; - Size newcdistSize = sizeof(float8) * numCenters; - - // Calculate total size - Size totalSize = samplesSize + centersSize + newCentersSize + centerCountsSize + closestCentersSize + lowerBoundSize + upperBoundSize + sSize + halfcdistSize + newcdistSize; - - // Check memory requirements - // Add one to error message to ceil - if (totalSize > (Size) maintenance_work_mem * 1024L) - ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("memory required is %zu MB, maintenance_work_mem is %d MB", totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024))); - - // Ensure indexing does not overflow - if (numCenters * numCenters > INT_MAX) - elog(ERROR, "Indexing overflow detected. Please report a bug."); - - // Set support functions - procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC); - normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); - collation = index->rd_indcollation[0]; - - // Allocate space - // Use float instead of double to save memory - centerCounts = palloc(centerCountsSize); - closestCenters = palloc(closestCentersSize); - lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE); - upperBound = palloc(upperBoundSize); - s = palloc(sSize); - halfcdist = palloc_extended(halfcdistSize, MCXT_ALLOC_HUGE); - newcdist = palloc(newcdistSize); - - newCenters = VectorArrayInit(numCenters, dimensions); - for (j = 0; j < numCenters; j++) { - vec = VectorArrayGet(newCenters, j); - SET_VARSIZE(vec, VECTOR_SIZE(dimensions)); - vec->root.header = dimensions | GT_FEXTENDED_COMPOSITE; - vec->root.children[0] = GT_HEADER_VECTOR; - } - - // Pick initial centers - InitCenters(index, samples, centers, lowerBound); - - // Assign each x to its closest initial center c(x) = argmin d(x,c) - for (j = 0; j < numSamples; j++) - { - minDistance = DBL_MAX; - closestCenter = 0; - - // Find closest center - for (k = 0; k < numCenters; k++) { - // TODO Use Lemma 1 in k-means++ initialization - distance = lowerBound[j * numCenters + k]; - - if (distance < minDistance) { - minDistance = distance; - closestCenter = k; - } - } - - upperBound[j] = minDistance; - closestCenters[j] = closestCenter; - } - - // Give 500 iterations to converge - for (iteration = 0; iteration < 500; iteration++) { - // Can take a while, so ensure we can interrupt - CHECK_FOR_INTERRUPTS(); - - changes = 0; - - // Step 1: For all centers, compute distance - for (j = 0; j < numCenters; j++) - { - vec = VectorArrayGet(centers, j); - - for (k = j + 1; k < numCenters; k++) - { - distance = 0.5 * DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k)))); - halfcdist[j * numCenters + k] = distance; - halfcdist[k * numCenters + j] = distance; - } - } - - // For all centers c, compute s(c) - for (j = 0; j < numCenters; j++) - { - minDistance = DBL_MAX; - - for (k = 0; k < numCenters; k++) - { - if (j == k) - continue; - - distance = halfcdist[j * numCenters + k]; - if (distance < minDistance) - minDistance = distance; - } - - s[j] = minDistance; - } - - rjreset = iteration != 0; - - for (j = 0; j < numSamples; j++) - { - // Step 2: Identify all points x such that u(x) <= s(c(x)) - if (upperBound[j] <= s[closestCenters[j]]) - continue; - - rj = rjreset; - - for (k = 0; k < numCenters; k++) - { - // Step 3: For all remaining points x and centers c - if (k == closestCenters[j]) - continue; - - if (upperBound[j] <= lowerBound[j * numCenters + k]) - continue; - - if (upperBound[j] <= halfcdist[closestCenters[j] * numCenters + k]) - continue; - - vec = VectorArrayGet(samples, j); - - // Step 3a - if (rj) - { - dxcx = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, closestCenters[j])))); - - // d(x,c(x)) computed, which is a form of d(x,c) - lowerBound[j * numCenters + closestCenters[j]] = dxcx; - upperBound[j] = dxcx; - - rj = false; - } - else - dxcx = upperBound[j]; - - // Step 3b - if (dxcx > lowerBound[j * numCenters + k] || dxcx > halfcdist[closestCenters[j] * numCenters + k]) - { - dxc = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k)))); - - // d(x,c) calculated - lowerBound[j * numCenters + k] = dxc; - - if (dxc < dxcx) - { - closestCenters[j] = k; - - // c(x) changed - upperBound[j] = dxc; - - changes++; - } - - } - } - } - - // Step 4: For each center c, let m(c) be mean of all points assigned - for (j = 0; j < numCenters; j++) - { - vec = VectorArrayGet(newCenters, j); - for (k = 0; k < dimensions; k++) - *((float8 *)&vec->root.children[1 + (k * sizeof(float8))]) = 0.0; - - centerCounts[j] = 0; - } - - for (j = 0; j < numSamples; j++) - { - vec = VectorArrayGet(samples, j); - closestCenter = closestCenters[j]; - - // Increment sum and count of closest center - newCenter = VectorArrayGet(newCenters, closestCenter); - for (k = 0; k < dimensions; k++) - *((float8 *)&newCenter->root.children[1 + (k * sizeof(float8))]) += *((float8 *)(&vec->root.children[1 + (k * sizeof(float8))])); - - centerCounts[closestCenter] += 1; - } - - for (j = 0; j < numCenters; j++) - { - vec = VectorArrayGet(newCenters, j); - - if (centerCounts[j] > 0) - { - // Double avoids overflow, but requires more memory - // TODO Update bounds - for (k = 0; k < dimensions; k++) - { - if (isinf((double)vec->root.children[1 + (k * sizeof(float8))])) - *((float8 *)(&vec->root.children[1 + (k * sizeof(float8))])) = *((float8 *)&vec->root.children[1 + (k * sizeof(float8))]) > 0 ? FLT_MAX : -FLT_MAX; - } - - for (k = 0; k < dimensions; k++) - *((float8 *)&vec->root.children[1 + (k * sizeof(float8))]) /= centerCounts[j]; - } - else - { - // TODO Handle empty centers properly - for (k = 0; k < dimensions; k++) - *((float8 *)&vec->root.children[1 + (k * sizeof(float8))]) = RandomDouble(); - } - - // Normalize if needed - if (normprocinfo != NULL) - ApplyNorm(normprocinfo, collation, vec); - } - - // Step 5 - for (j = 0; j < numCenters; j++) - newcdist[j] = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(VectorArrayGet(centers, j)), PointerGetDatum(VectorArrayGet(newCenters, j)))); - - for (j = 0; j < numSamples; j++) - { - for (k = 0; k < numCenters; k++) - { - distance = lowerBound[j * numCenters + k] - newcdist[k]; - - if (distance < 0) - distance = 0; - - lowerBound[j * numCenters + k] = distance; - } - } - - // Step 6 - // We reset r(x) before Step 3 in the next iteration - for (j = 0; j < numSamples; j++) - upperBound[j] += newcdist[closestCenters[j]]; - - // Step 7 - for (j = 0; j < numCenters; j++) - memcpy(VectorArrayGet(centers, j), VectorArrayGet(newCenters, j), VECTOR_SIZE(dimensions)); - - if (changes == 0 && iteration != 0) - break; - } - - VectorArrayFree(newCenters); - pfree(centerCounts); - pfree(closestCenters); - pfree(lowerBound); - pfree(upperBound); - pfree(s); - pfree(halfcdist); - pfree(newcdist); -} - -/* - * Detect issues with centers - */ -static void -CheckCenters(Relation index, VectorArray centers) -{ - FmgrInfo *normprocinfo; - Oid collation; - gtype *vec; - double norm; - - if (centers->length != centers->maxlen) - elog(ERROR, "Not enough centers. Please report a bug."); - - // Ensure no NaN or infinite values - for (int i = 0; i < centers->length; i++) - { - vec = VectorArrayGet(centers, i); - - for (int j = 0; j < AGT_ROOT_COUNT(vec); j++) { - if (isnan((double) vec->root.children[1 + (j * sizeof(float8))])) - elog(ERROR, "NaN detected. Please report a bug."); - - if (isinf((double) vec->root.children[1 + (j * sizeof(float8))])) - elog(ERROR, "Infinite value detected. Please report a bug."); - } - } - - // Ensure no duplicate centers - // Fine to sort in-place - qsort(centers->items, centers->length, VECTOR_SIZE(centers->dim), CompareVectors); - for (int i = 1; i < centers->length; i++) - { - if (CompareVectors(VectorArrayGet(centers, i), VectorArrayGet(centers, i - 1)) == 0) - elog(ERROR, "Duplicate centers detected. Please report a bug."); - } - - // Ensure no zero vectors for cosine distance - // Check NORM_PROC instead of KMEANS_NORM_PROC - normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); - if (normprocinfo != NULL) - { - collation = index->rd_indcollation[0]; - - for (int i = 0; i < centers->length; i++) - { - norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(VectorArrayGet(centers, i)))); - if (norm == 0) - elog(ERROR, "Zero norm detected. Please report a bug."); - } - } -} - -/* - * Perform naive k-means centering - * We use spherical k-means for inner product and cosine - */ -void -IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers) -{ - if (samples->length <= centers->maxlen) - QuickCenters(index, samples, centers); - else - ElkanKmeans(index, samples, centers); - - CheckCenters(index, centers); -} diff --git a/src/backend/access/ivfflat/ivfscan.c b/src/backend/access/ivfflat/ivfscan.c deleted file mode 100644 index edca8c90..00000000 --- a/src/backend/access/ivfflat/ivfscan.c +++ /dev/null @@ -1,363 +0,0 @@ -#include "postgres.h" - -#include - -#include "access/relscan.h" -#include "catalog/pg_operator_d.h" -#include "catalog/pg_type_d.h" -#include "access/ivfflat.h" -#include "miscadmin.h" -#include "pgstat.h" -#include "storage/bufmgr.h" - -/* - * Compare list distances - */ -static int -CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg) { - if (((const IvfflatScanList *) a)->distance > ((const IvfflatScanList *) b)->distance) - return 1; - - if (((const IvfflatScanList *) a)->distance < ((const IvfflatScanList *) b)->distance) - return -1; - - return 0; -} - -/* - * Get lists and sort by distance - */ -static void -GetScanLists(IndexScanDesc scan, Datum value) { - Buffer cbuf; - Page cpage; - IvfflatList list; - OffsetNumber offno; - OffsetNumber maxoffno; - BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO; - int listCount = 0; - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - double distance; - IvfflatScanList *scanlist; - double maxDistance = DBL_MAX; - - // Search all list pages - while (BlockNumberIsValid(nextblkno)) { - cbuf = ReadBuffer(scan->indexRelation, nextblkno); - LockBuffer(cbuf, BUFFER_LOCK_SHARE); - cpage = BufferGetPage(cbuf); - - maxoffno = PageGetMaxOffsetNumber(cpage); - - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) { - list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno)); - - // Use procinfo from the index instead of scan key for performance - distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value)); - - if (listCount < so->probes) { - scanlist = &so->lists[listCount]; - scanlist->startPage = list->startPage; - scanlist->distance = distance; - listCount++; - - // Add to heap - pairingheap_add(so->listQueue, &scanlist->ph_node); - - // Calculate max distance - if (listCount == so->probes) - maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance; - } else if (distance < maxDistance) { - // Remove - scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue); - - // Reuse - scanlist->startPage = list->startPage; - scanlist->distance = distance; - pairingheap_add(so->listQueue, &scanlist->ph_node); - - // Update max distance - maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance; - } - } - - nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno; - - UnlockReleaseBuffer(cbuf); - } -} - -/* - * Get items - */ -static void -GetScanItems(IndexScanDesc scan, Datum value) { - IvfflatScanOpaque so = (IvfflatScanOpaque)scan->opaque; - Buffer buf; - Page page; - IndexTuple itup; - BlockNumber searchPage; - OffsetNumber offno; - OffsetNumber maxoffno; - Datum datum; - bool isnull; - TupleDesc tupdesc = RelationGetDescr(scan->indexRelation); - double tuples = 0; - -#if PG_VERSION_NUM >= 120000 - TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual); -#else - TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc); -#endif - - /* - * Reuse same set of shared buffers for scan - * - * See postgres/src/backend/storage/buffer/README for description - */ - BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD); - - // Search closest probes lists - while (!pairingheap_is_empty(so->listQueue)) { - searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage; - - // Search all entry pages for list - while (BlockNumberIsValid(searchPage)) { - buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - maxoffno = PageGetMaxOffsetNumber(page); - - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) { - itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno)); - datum = index_getattr(itup, 1, tupdesc, &isnull); - - /* - * Add virtual tuple - * - * Use procinfo from the index instead of scan key for - * performance - */ - ExecClearTuple(slot); - slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value); - slot->tts_isnull[0] = false; - slot->tts_values[1] = PointerGetDatum(&itup->t_tid); - slot->tts_isnull[1] = false; - slot->tts_values[2] = Int32GetDatum((int) searchPage); - slot->tts_isnull[2] = false; - ExecStoreVirtualTuple(slot); - - tuplesort_puttupleslot(so->sortstate, slot); - - tuples++; - } - - searchPage = IvfflatPageGetOpaque(page)->nextblkno; - - UnlockReleaseBuffer(buf); - } - } - - FreeAccessStrategy(bas); - - if (tuples < 100) - ereport(DEBUG1, (errmsg("index scan found few tuples"), errdetail("Index may have been created with little data."), errhint("Recreate the index and possibly decrease lists."))); - - tuplesort_performsort(so->sortstate); -} - -/* - * Get dimensions from metapage - */ -static int -GetDimensions(Relation index) -{ - Buffer buf; - Page page; - IvfflatMetaPage metap; - int dimensions; - - buf = ReadBuffer(index, IVFFLAT_METAPAGE_BLKNO); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - metap = IvfflatPageGetMeta(page); - - dimensions = metap->dimensions; - - UnlockReleaseBuffer(buf); - - return dimensions; -} - -/* - * Prepare for an index scan - */ -IndexScanDesc -ivfflatbeginscan(Relation index, int nkeys, int norderbys) { - IndexScanDesc scan; - IvfflatScanOpaque so; - int lists; - AttrNumber attNums[] = {1}; - Oid sortOperators[] = {Float8LessOperator}; - Oid sortCollations[] = {InvalidOid}; - bool nullsFirstFlags[] = {false}; - int probes = ivfflat_probes; - - scan = RelationGetIndexScan(index, nkeys, norderbys); - lists = IvfflatGetLists(scan->indexRelation); - - if (probes > lists) - probes = lists; - - so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList)); - so->buf = InvalidBuffer; - so->first = true; - so->probes = probes; - - // Set support functions - so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC); - so->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); - so->collation = index->rd_indcollation[0]; - - // Create tuple description for sorting -#if PG_VERSION_NUM >= 120000 - so->tupdesc = CreateTemplateTupleDesc(3); -#else - so->tupdesc = CreateTemplateTupleDesc(3, false); -#endif - TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0); - TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0); - TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0); - - // Prep sort - so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false); - -#if PG_VERSION_NUM >= 120000 - so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple); -#else - so->slot = MakeSingleTupleTableSlot(so->tupdesc); -#endif - - so->listQueue = pairingheap_allocate(CompareLists, scan); - - scan->opaque = so; - - return scan; -} - -/* - * Start or restart an index scan - */ -void -ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys) -{ - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - -#if PG_VERSION_NUM >= 130000 - if (!so->first) - tuplesort_reset(so->sortstate); -#endif - - so->first = true; - pairingheap_reset(so->listQueue); - - if (keys && scan->numberOfKeys > 0) - memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData)); - - if (orderbys && scan->numberOfOrderBys > 0) - memmove(scan->orderByData, orderbys, scan->numberOfOrderBys * sizeof(ScanKeyData)); -} - -/* - * Fetch the next tuple in the given scan - */ -bool -ivfflatgettuple(IndexScanDesc scan, ScanDirection dir) -{ - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - - /* - * Index can be used to scan backward, but Postgres doesn't support - * backward scan on operators - */ - Assert(ScanDirectionIsForward(dir)); - - if (so->first) { - Datum value; - - // Count index scan for stats - pgstat_count_index_scan(scan->indexRelation); - - // Safety check - if (scan->orderByData == NULL) - elog(ERROR, "cannot scan ivfflat index without order"); - - if (scan->orderByData->sk_flags & SK_ISNULL) { - value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation))); - } else { - value = scan->orderByData->sk_argument; - - // Value should not be compressed or toasted - Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value))); - Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value))); - - // Fine if normalization fails - if (so->normprocinfo != NULL) - IvfflatNormValue(so->normprocinfo, so->collation, &value, NULL); - } - - IvfflatBench("GetScanLists", GetScanLists(scan, value)); - IvfflatBench("GetScanItems", GetScanItems(scan, value)); - so->first = false; - - // Clean up if we allocated a new value - if (value != scan->orderByData->sk_argument) - pfree(DatumGetPointer(value)); - } - - if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL)) { - ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull)); - BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull)); - -#if PG_VERSION_NUM >= 120000 - scan->xs_heaptid = *tid; -#else - scan->xs_ctup.t_self = *tid; -#endif - - if (BufferIsValid(so->buf)) - ReleaseBuffer(so->buf); - - /* - * An index scan must maintain a pin on the index page holding the - * item last returned by amgettuple - * - * https://www.postgresql.org/docs/current/index-locking.html - */ - so->buf = ReadBuffer(scan->indexRelation, indexblkno); - - scan->xs_recheckorderby = false; - return true; - } - - return false; -} - -/* - * End a scan and release resources - */ -void -ivfflatendscan(IndexScanDesc scan) -{ - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - - // Release pin - if (BufferIsValid(so->buf)) - ReleaseBuffer(so->buf); - - pairingheap_free(so->listQueue); - tuplesort_end(so->sortstate); - - pfree(so); - scan->opaque = NULL; -} diff --git a/src/backend/access/ivfflat/ivfutils.c b/src/backend/access/ivfflat/ivfutils.c deleted file mode 100644 index b6129f4a..00000000 --- a/src/backend/access/ivfflat/ivfutils.c +++ /dev/null @@ -1,523 +0,0 @@ -#include "postgres.h" - -#include "tcop/utility.h" -#include "nodes/makefuncs.h" -#include "nodes/value.h" - -#include "access/ivfflat.h" -#include "catalog/ag_graph.h" -#include "catalog/ag_label.h" -#include "storage/bufmgr.h" -#include "utils/vector.h" -#include "utils/gtype.h" - -/* makeRoleSpec - * Create a RoleSpec with the given type - */ -static RoleSpec * -makeRoleSpec(RoleSpecType type, int location) -{ - RoleSpec *spec = makeNode(RoleSpec); - - spec->roletype = type; - spec->location = location; - - return spec; -} - -static Node * -makeTypeCast(Node *arg, TypeName *typename, int location) -{ - TypeCast *n = makeNode(TypeCast); - n->arg = arg; - n->typeName = typename; - n->location = location; - - return (Node *) n; -} - - -static Node * -makeStringConst(char *str, int location) -{ - A_Const *n = makeNode(A_Const); - - n->val.type = T_String; - n->val.val.str = str; - n->location = location; - - return (Node *)n; -} - -static Node * -makeStringConstCast(char *str, int location, TypeName *typename) -{ - Node *s = makeStringConst(str, location); - - return makeTypeCast(s, typename, -1); -} - -static char *make_property_alias_for_index(char *var_name) { - char *str = palloc0(strlen(var_name) + 4); - - str[0] = '"'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 1] = var_name[i]; - - str[i + 1] = '"'; - - return str; -} - -PG_FUNCTION_INFO_V1(create_ivfflat_ip_ops_index); -Datum create_ivfflat_ip_ops_index(PG_FUNCTION_ARGS) -{ - char *graph; - Name graph_name; - char *graph_name_str; - Oid graph_oid; - List *parent; - - RangeVar *rv; - - char *label; - Name label_name; - char *label_name_str; - - char *property; - Name property_name; - char *property_name_str; - - - if (PG_ARGISNULL(0)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("graph name must not be NULL"))); - - if (PG_ARGISNULL(1)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("label name must not be NULL"))); - - if (PG_ARGISNULL(2)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("property name must not be NULL"))); - - if (PG_ARGISNULL(3)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("dims must not be NULL"))); - - if (PG_ARGISNULL(4)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("lists must not be NULL"))); - - - graph_name = PG_GETARG_NAME(0); - label_name = PG_GETARG_NAME(1); - property_name = PG_GETARG_NAME(2); - - graph_name_str = NameStr(*graph_name); - label_name_str = NameStr(*label_name); - property_name_str = NameStr(*property_name); - - // Check if graph does not exist - if (!graph_exists(graph_name_str)) - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist.", graph_name_str))); - - graph_oid = get_graph_oid(graph_name_str); - - // Check if label with the input name already exists - if (!label_exists(label_name_str, graph_oid)) - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("label \"%s\" does not exist", label_name_str))); - - //Create the default label tables - graph = graph_name->data; - label = label_name->data; - property = property_name->data; - PlannedStmt *wrapper; - - DefElem *dims = makeDefElem("dimensions", makeInteger(PG_GETARG_INT32(3)), -1); - DefElem *lst = makeDefElem("lists", makeInteger(PG_GETARG_INT32(4)), -1); - - ColumnRef *c = makeNode(ColumnRef); - c->fields = list_make1(makeString("properties")); - c->location = -1; - - IndexElem *idx_elem = makeNode(IndexElem); - idx_elem->name = NULL; - idx_elem->expr = makeSimpleA_Expr(AEXPR_OP, "->", c, makeStringConstCast(make_property_alias_for_index(property), -1, makeTypeName("gtype")), -1); - idx_elem->indexcolname = NULL; - idx_elem->collation = NIL; - idx_elem->opclass = list_make1(makeString("gtype_ip_ops")); - idx_elem->opclassopts = NIL; - idx_elem->ordering = SORTBY_NULLS_DEFAULT; - idx_elem->nulls_ordering = SORTBY_DEFAULT; - - IndexStmt *idx = makeNode(IndexStmt); - idx->unique = false; - idx->concurrent = false; - idx->idxname = NULL; - idx->relation = makeRangeVar(graph_name, label_name, -1); - idx->accessMethod = "ivfflat"; - idx->indexParams = list_make1(idx_elem); - idx->indexIncludingParams = NULL; - idx->options = list_make2(dims, lst); - idx->tableSpace = NULL; - idx->whereClause = NULL; - idx->excludeOpNames = NIL; - idx->idxcomment = NULL; - idx->indexOid = InvalidOid; - idx->oldNode = InvalidOid; - idx->oldCreateSubid = InvalidSubTransactionId; - idx->oldFirstRelfilenodeSubid = InvalidSubTransactionId; - idx->primary = false; - idx->isconstraint = false; - idx->deferrable = false; - idx->initdeferred = false; - idx->transformed = false; - idx->if_not_exists = false; - idx->reset_default_tblspc = false; - - wrapper = makeNode(PlannedStmt); - wrapper->commandType = CMD_UTILITY; - wrapper->canSetTag = false; - wrapper->utilityStmt = (Node *)idx; - wrapper->stmt_location = -1; - wrapper->stmt_len = 0; - - - ProcessUtility(wrapper, "(generated ALTER TABLE ADD CONSTRAINT command)", false, PROCESS_UTILITY_SUBCOMMAND, NULL, NULL, None_Receiver, NULL); - - PG_RETURN_VOID(); -} - - -PG_FUNCTION_INFO_V1(create_ivfflat_l2_ops_index); -Datum create_ivfflat_l2_ops_index(PG_FUNCTION_ARGS) -{ - char *graph; - Name graph_name; - char *graph_name_str; - Oid graph_oid; - List *parent; - - RangeVar *rv; - - char *label; - Name label_name; - char *label_name_str; - - char *property; - Name property_name; - char *property_name_str; - - - if (PG_ARGISNULL(0)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("graph name must not be NULL"))); - - if (PG_ARGISNULL(1)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("label name must not be NULL"))); - - if (PG_ARGISNULL(2)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("property name must not be NULL"))); - - if (PG_ARGISNULL(3)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("dims must not be NULL"))); - - if (PG_ARGISNULL(4)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("lists must not be NULL"))); - - - graph_name = PG_GETARG_NAME(0); - label_name = PG_GETARG_NAME(1); - property_name = PG_GETARG_NAME(2); - - graph_name_str = NameStr(*graph_name); - label_name_str = NameStr(*label_name); - property_name_str = NameStr(*property_name); - - // Check if graph does not exist - if (!graph_exists(graph_name_str)) - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist.", graph_name_str))); - - graph_oid = get_graph_oid(graph_name_str); - - // Check if label with the input name already exists - if (!label_exists(label_name_str, graph_oid)) - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("label \"%s\" does not exist", label_name_str))); - - //Create the default label tables - graph = graph_name->data; - label = label_name->data; - property = property_name->data; - PlannedStmt *wrapper; - - DefElem *dims = makeDefElem("dimensions", makeInteger(PG_GETARG_INT32(3)), -1); - DefElem *lst = makeDefElem("lists", makeInteger(PG_GETARG_INT32(4)), -1); - - ColumnRef *c = makeNode(ColumnRef); - c->fields = list_make1(makeString("properties")); - c->location = -1; - - IndexElem *idx_elem = makeNode(IndexElem); - idx_elem->name = NULL; - idx_elem->expr = makeSimpleA_Expr(AEXPR_OP, "->", c, makeStringConstCast(make_property_alias_for_index(property), -1, makeTypeName("gtype")), -1); - idx_elem->indexcolname = NULL; - idx_elem->collation = NIL; - idx_elem->opclass = list_make1(makeString("gtype_l2_ops")); - idx_elem->opclassopts = NIL; - idx_elem->ordering = SORTBY_NULLS_DEFAULT; - idx_elem->nulls_ordering = SORTBY_DEFAULT; - - IndexStmt *idx = makeNode(IndexStmt); - idx->unique = false; - idx->concurrent = false; - idx->idxname = NULL; - idx->relation = makeRangeVar(graph_name, label_name, -1); - idx->accessMethod = "ivfflat"; - idx->indexParams = list_make1(idx_elem); - idx->indexIncludingParams = NULL; - idx->options = list_make2(dims, lst); - idx->tableSpace = NULL; - idx->whereClause = NULL; - idx->excludeOpNames = NIL; - idx->idxcomment = NULL; - idx->indexOid = InvalidOid; - idx->oldNode = InvalidOid; - idx->oldCreateSubid = InvalidSubTransactionId; - idx->oldFirstRelfilenodeSubid = InvalidSubTransactionId; - idx->primary = false; - idx->isconstraint = false; - idx->deferrable = false; - idx->initdeferred = false; - idx->transformed = false; - idx->if_not_exists = false; - idx->reset_default_tblspc = false; - - wrapper = makeNode(PlannedStmt); - wrapper->commandType = CMD_UTILITY; - wrapper->canSetTag = false; - wrapper->utilityStmt = (Node *)idx; - wrapper->stmt_location = -1; - wrapper->stmt_len = 0; - - - ProcessUtility(wrapper, "(generated ALTER TABLE ADD CONSTRAINT command)", false, PROCESS_UTILITY_SUBCOMMAND, NULL, NULL, None_Receiver, NULL); - - PG_RETURN_VOID(); -} - - - -/* - * Allocate a vector array - */ -VectorArray -VectorArrayInit(int maxlen, int dimensions) { - VectorArray res = palloc0(sizeof(VectorArrayData)); - - res->length = 0; - res->maxlen = maxlen; - res->dim = dimensions; - int gtype_size = VECTOR_SIZE(dimensions) * maxlen; - - res->items = palloc_extended(gtype_size * 2, MCXT_ALLOC_ZERO | MCXT_ALLOC_HUGE); - - for (int i = 0; i < dimensions; i++) { - gtype *vec = VectorArrayGet(res, i); - - SET_VARSIZE(vec, VECTOR_SIZE(dimensions)); - vec->root.header = dimensions | GT_FEXTENDED_COMPOSITE; - vec->root.children[0] = GT_HEADER_VECTOR; - } - - return res; -} - -/* - * Free a vector array - */ -void -VectorArrayFree(VectorArray arr) -{ - pfree(arr->items); - pfree(arr); -} - -/* - * Get the number of lists in the index - */ -int -IvfflatGetLists(Relation index) { - IvfflatOptions *opts = (IvfflatOptions *) index->rd_options; - - if (opts) - return opts->lists; - - return IVFFLAT_DEFAULT_LISTS; -} - -int -IvfflatGetDimensions(Relation index) -{ - IvfflatOptions *opts = (IvfflatOptions *) index->rd_options; - - if (opts) - return opts->dimensions; - - return IVFFLAT_DEFAULT_ELEMENTS; -} - - -/* - * Get proc - */ -FmgrInfo * -IvfflatOptionalProcInfo(Relation rel, uint16 procnum) { - if (!OidIsValid(index_getprocid(rel, 1, procnum))) - return NULL; - - return index_getprocinfo(rel, 1, procnum); -} - -/* - * Divide by the norm - * - * Returns false if value should not be indexed - * - * The caller needs to free the pointer stored in value - * if it's different than the original value - */ -bool -IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, gtype *result) { - double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); - - if (norm > 0) { - gtype *v = DatumGetVector(*value); - - if (result == NULL) - result = gtype_value_to_gtype(InitVectorGType(AGT_ROOT_COUNT(v))); - - for (int i = 0; i < AGT_ROOT_COUNT(v); i++) - result->root.children[1 + (i * sizeof(float8))] = v->root.children[1 + (i * sizeof(float8))] / norm; - - *value = PointerGetDatum(result); - - return true; - } - - return false; -} - -/* - * New buffer - */ -Buffer -IvfflatNewBuffer(Relation index, ForkNumber forkNum) -{ - Buffer buf = ReadBufferExtended(index, forkNum, P_NEW, RBM_NORMAL, NULL); - - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - return buf; -} - -/* - * Init page - */ -void -IvfflatInitPage(Buffer buf, Page page) -{ - PageInit(page, BufferGetPageSize(buf), sizeof(IvfflatPageOpaqueData)); - IvfflatPageGetOpaque(page)->nextblkno = InvalidBlockNumber; - IvfflatPageGetOpaque(page)->page_id = IVFFLAT_PAGE_ID; -} - -/* - * Init and register page - */ -void -IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state) -{ - *state = GenericXLogStart(index); - *page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE); - IvfflatInitPage(*buf, *page); -} - -/* - * Commit buffer - */ -void -IvfflatCommitBuffer(Buffer buf, GenericXLogState *state) -{ - MarkBufferDirty(buf); - GenericXLogFinish(state); - UnlockReleaseBuffer(buf); -} - -/* - * Add a new page - * - * The order is very important!! - */ -void -IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum) { - // Get new buffer - Buffer newbuf = IvfflatNewBuffer(index, forkNum); - Page newpage = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE); - - // Update the previous buffer - IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf); - - // Init new page - IvfflatInitPage(newbuf, newpage); - - // Commit - MarkBufferDirty(*buf); - MarkBufferDirty(newbuf); - GenericXLogFinish(*state); - - // Unlock - UnlockReleaseBuffer(*buf); - - *state = GenericXLogStart(index); - *page = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE); - *buf = newbuf; -} - -/* - * Update the start or insert page of a list - */ -void -IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum) { - Buffer buf; - Page page; - GenericXLogState *state; - IvfflatList list; - bool changed = false; - - buf = ReadBufferExtended(index, forkNum, listInfo.blkno, RBM_NORMAL, NULL); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - list = (IvfflatList) PageGetItem(page, PageGetItemId(page, listInfo.offno)); - - if (BlockNumberIsValid(insertPage) && insertPage != list->insertPage) - { - // Skip update if insert page is lower than original insert page - // This is needed to prevent insert from overwriting vacuum - if (!BlockNumberIsValid(originalInsertPage) || insertPage >= originalInsertPage) - { - list->insertPage = insertPage; - changed = true; - } - } - - if (BlockNumberIsValid(startPage) && startPage != list->startPage) - { - list->startPage = startPage; - changed = true; - } - - // Only commit if changed - if (changed) { - IvfflatCommitBuffer(buf, state); - } else { - GenericXLogAbort(state); - UnlockReleaseBuffer(buf); - } -} diff --git a/src/backend/access/ivfflat/ivfvacuum.c b/src/backend/access/ivfflat/ivfvacuum.c deleted file mode 100644 index a86e4774..00000000 --- a/src/backend/access/ivfflat/ivfvacuum.c +++ /dev/null @@ -1,144 +0,0 @@ -#include "postgres.h" - -#include "commands/vacuum.h" -#include "access/ivfflat.h" -#include "storage/bufmgr.h" - -/* - * Bulk delete tuples from the index - */ -IndexBulkDeleteResult * -ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state) { - Relation index = info->index; - BlockNumber blkno = IVFFLAT_HEAD_BLKNO; - BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD); - - if (stats == NULL) - stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); - - // Iterate over list pages - while (BlockNumberIsValid(blkno)) { - Buffer cbuf; - Page cpage; - OffsetNumber coffno; - OffsetNumber cmaxoffno; - BlockNumber startPages[MaxOffsetNumber]; - ListInfo listInfo; - - cbuf = ReadBuffer(index, blkno); - LockBuffer(cbuf, BUFFER_LOCK_SHARE); - cpage = BufferGetPage(cbuf); - - cmaxoffno = PageGetMaxOffsetNumber(cpage); - - // Iterate over lists - for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno)) { - IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, coffno)); - - startPages[coffno - FirstOffsetNumber] = list->startPage; - } - - listInfo.blkno = blkno; - blkno = IvfflatPageGetOpaque(cpage)->nextblkno; - - UnlockReleaseBuffer(cbuf); - - for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno)) { - BlockNumber searchPage = startPages[coffno - FirstOffsetNumber]; - BlockNumber insertPage = InvalidBlockNumber; - - // Iterate over entry pages - while (BlockNumberIsValid(searchPage)) { - Buffer buf; - Page page; - GenericXLogState *state; - OffsetNumber offno; - OffsetNumber maxoffno; - OffsetNumber deletable[MaxOffsetNumber]; - int ndeletable; - - vacuum_delay_point(); - - buf = ReadBufferExtended(index, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas); - - /* - * ambulkdelete cannot delete entries from pages that are - * pinned by other backends - * - * https://www.postgresql.org/docs/current/index-locking.html - */ - LockBufferForCleanup(buf); - - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - - maxoffno = PageGetMaxOffsetNumber(page); - ndeletable = 0; - - // Find deleted tuples - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) { - IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno)); - ItemPointer htup = &(itup->t_tid); - - if (callback(htup, callback_state)) { - deletable[ndeletable++] = offno; - stats->tuples_removed++; - } else - stats->num_index_tuples++; - } - - // Set to first free page - // Must be set before searchPage is updated - if (!BlockNumberIsValid(insertPage) && ndeletable > 0) - insertPage = searchPage; - - searchPage = IvfflatPageGetOpaque(page)->nextblkno; - - if (ndeletable > 0) { - // Delete tuples - PageIndexMultiDelete(page, deletable, ndeletable); - MarkBufferDirty(buf); - GenericXLogFinish(state); - } else - GenericXLogAbort(state); - - UnlockReleaseBuffer(buf); - } - - /* - * Update after all tuples deleted. - * - * We don't add or delete items from lists pages, so offset won't - * change. - */ - if (BlockNumberIsValid(insertPage)) { - listInfo.offno = coffno; - IvfflatUpdateList(index, listInfo, insertPage, InvalidBlockNumber, InvalidBlockNumber, MAIN_FORKNUM); - } - } - } - - FreeAccessStrategy(bas); - - return stats; -} - -/* - * Clean up after a VACUUM operation - */ -IndexBulkDeleteResult * -ivfflatvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) { - Relation rel = info->index; - - if (info->analyze_only) - return stats; - - // stats is NULL if ambulkdelete not called - // OK to return NULL if index not changed - if (stats == NULL) - return NULL; - - stats->num_pages = RelationGetNumberOfBlocks(rel); - - return stats; -} diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index 82a4afa4..4a0e9381 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -42,10 +42,15 @@ #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "commands/label_commands.h" -#include "executor/cypher_utils.h" #include "utils/ag_cache.h" #include "utils/graphid.h" + + + +static ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, + char *label_name); + PG_FUNCTION_INFO_V1(ltree_in); PG_FUNCTION_INFO_V1(ltree_addltree); @@ -87,15 +92,7 @@ void insert_label(const char *label_name, Oid graph_oid, int32 label_id, values[Anum_ag_label_relation - 1] = ObjectIdGetDatum(label_relation); nulls[Anum_ag_label_relation - 1] = false; -/* - if (ltree == NULL) { - values[Anum_ag_label_label_path - 1] = NULL; - nulls[Anum_ag_label_label_path - 1] = false; - } else { - values[Anum_ag_label_label_path - 1] = DirectFunctionCall1(ltree_in, CStringGetDatum(ltree)); - nulls[Anum_ag_label_label_path - 1] = true; - }*/ - + if (strcmp(label_name, "_ag_label_vertex") == 0 || strcmp(label_name, "_ag_label_edge") == 0) { values[5] = DirectFunctionCall1(ltree_in, CStringGetDatum(label_name)); nulls[5] = false; @@ -277,6 +274,47 @@ RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, return makeRangeVar(graph_name, relname, 2); } +#include "parser/parse_node.h" +/* + * Given the graph name and the label name, create a ResultRelInfo for the table + * those to variables represent. Open the Indices too. + */ +ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, + char *label_name) +{ + RangeVar *rv; + Relation label_relation; + ResultRelInfo *resultRelInfo; + + ParseState *pstate = make_parsestate(NULL); + + resultRelInfo = palloc(sizeof(ResultRelInfo)); + + if (strlen(label_name) == 0) + { + rv = makeRangeVar(graph_name, AG_DEFAULT_LABEL_VERTEX, -1); + } + else + { + rv = makeRangeVar(graph_name, label_name, -1); + } + + label_relation = parserOpenTable(pstate, rv, RowExclusiveLock); + + // initialize the resultRelInfo + InitResultRelInfo(resultRelInfo, label_relation, + list_length(estate->es_range_table), NULL, + estate->es_instrument); + + // open the parse state + ExecOpenIndices(resultRelInfo, false); + + free_parsestate(pstate); + + return resultRelInfo; +} + + /* * Retrieves a list of all the names of a graph. * @@ -334,7 +372,12 @@ List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid) table_endscan(scan_desc); - destroy_entity_result_rel_info(resultRelInfo); + // close the indices + ExecCloseIndices(resultRelInfo); + + // close the rel + table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); + table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); return labels; diff --git a/src/backend/commands/graph_commands.c b/src/backend/commands/graph_commands.c index 762c63de..51ecf997 100644 --- a/src/backend/commands/graph_commands.c +++ b/src/backend/commands/graph_commands.c @@ -171,7 +171,6 @@ Datum use_graph(PG_FUNCTION_ARGS) CommandCounterIncrement(); update_session_graph_oid(get_graph_oid(graph_name_str)); - //PopActiveSnapshot(); PG_RETURN_VOID(); } diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index e6b02afd..16521496 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -32,24 +32,56 @@ #include "catalog/ag_label.h" #include "executor/cypher_executor.h" -#include "executor/cypher_utils.h" #include "nodes/cypher_nodes.h" #include "utils/gtype.h" #include "utils/graphid.h" #include "utils/vertex.h" #include "utils/edge.h" +#include "utils/ag_cache.h" #include "utils/traversal.h" +/* + * When executing the children of the CREATE, SET, REMOVE, and + * DELETE clasues, we need to alter the command id in the estate + * and the snapshot. That way we can hide the modified tuples from + * the sub clauses that should not know what their parent clauses are + * doing. + */ +#define Increment_Estate_CommandId(estate) \ + estate->es_output_cid++; \ + estate->es_snapshot->curcid++; + +#define Decrement_Estate_CommandId(estate) \ + estate->es_output_cid--; \ + estate->es_snapshot->curcid--; + +typedef struct cypher_create_custom_scan_state +{ + CustomScanState css; + CustomScan *cs; + List *pattern; + List *path_values; + uint32 flags; + TupleTableSlot *slot; + Oid graph_oid; +} cypher_create_custom_scan_state; + + + +static HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, + TupleTableSlot *elemTupleSlot, + EState *estate); +static bool entity_exists(EState *estate, Oid graph_oid, graphid id); static void begin_cypher_create(CustomScanState *node, EState *estate, int eflags); static TupleTableSlot *exec_cypher_create(CustomScanState *node); static void end_cypher_create(CustomScanState *node); static void rescan_cypher_create(CustomScanState *node); -static void create_edge_1(cypher_create_custom_scan_state *css, +static void insert_edge(cypher_create_custom_scan_state *css, cypher_target_node *node, Datum prev_vertex_id, ListCell *next, List *list); -static Datum create_vertex_1(cypher_create_custom_scan_state *css, +static Datum insert_vertex(cypher_create_custom_scan_state *css, cypher_target_node *node, ListCell *next, List *list); static void process_pattern(cypher_create_custom_scan_state *css); @@ -181,7 +213,7 @@ static void process_pattern(cypher_create_custom_scan_state *css) * Create the first vertex. The create_vertex function will * create the rest of the path, if necessary. */ - create_vertex_1(css, lfirst(lc), lnext(path->target_nodes, lc), path->target_nodes); + insert_vertex(css, lfirst(lc), lnext(path->target_nodes, lc), path->target_nodes); /* * If this path is a variable, take the list that was accumulated @@ -197,7 +229,6 @@ static void process_pattern(cypher_create_custom_scan_state *css) ps = css->css.ss.ps.lefttree; scantuple = ps->ps_ExprContext->ecxt_scantuple; - //result = make_path(css->path_values); result = create_traversal(css->path_values); scantuple->tts_values[path->path_attr_num - 1] = result; scantuple->tts_isnull[path->path_attr_num - 1] = false; @@ -329,7 +360,7 @@ Node *create_cypher_create_plan_state(CustomScan *cscan) /* * Create the edge entity. */ -static void create_edge_1(cypher_create_custom_scan_state *css, +static void insert_edge(cypher_create_custom_scan_state *css, cypher_target_node *node, Datum prev_vertex_id, ListCell *next, List *list) { @@ -352,7 +383,7 @@ static void create_edge_1(cypher_create_custom_scan_state *css, * next vertex's id. */ css->path_values = NIL; - next_vertex_id = create_vertex_1(css, lfirst(next), lnext(list, next), list); + next_vertex_id = insert_vertex(css, lfirst(next), lnext(list, next), list); /* * Set the start and end vertex ids @@ -439,7 +470,7 @@ static void create_edge_1(cypher_create_custom_scan_state *css, * Creates the vertex entity, returns the vertex's id in case the caller is * the create_edge function. */ -static Datum create_vertex_1(cypher_create_custom_scan_state *css, +static Datum insert_vertex(cypher_create_custom_scan_state *css, cypher_target_node *node, ListCell *next, List *list) { bool isNull; @@ -565,8 +596,88 @@ static Datum create_vertex_1(cypher_create_custom_scan_state *css, // If the path continues, create the next edge, passing the vertex's id. if (next != NULL) - create_edge_1(css, lfirst(next), id, lnext(list, next), list); + insert_edge(css, lfirst(next), id, lnext(list, next), list); return id; } + +/* + * Insert the edge/vertex tuple into the table and indices. Check that the + * table's constraints have not been violated. + * + * This function uses the passed cid for updates. + */ +HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, + TupleTableSlot *elemTupleSlot, + EState *estate) +{ + HeapTuple tuple = NULL; + + ExecStoreVirtualTuple(elemTupleSlot); + tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); + + /* Check the constraints of the tuple */ + tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); + if (resultRelInfo->ri_RelationDesc->rd_att->constr != NULL) + { + ExecConstraints(resultRelInfo, elemTupleSlot, estate); + } + + // Insert the tuple normally + table_tuple_insert(resultRelInfo->ri_RelationDesc, elemTupleSlot, + GetCurrentCommandId(true), 0, NULL); + + // Insert index entries for the tuple + if (resultRelInfo->ri_NumIndices > 0) + { + ExecInsertIndexTuples(resultRelInfo, elemTupleSlot, estate, false, false, NULL, NIL); + } + + return tuple; +} + + +/* + * Find out if the entity still exists. This is for 'implicit' deletion + * of an entity. + */ +bool entity_exists(EState *estate, Oid graph_oid, graphid id) +{ + label_cache_data *label; + ScanKeyData scan_keys[1]; + TableScanDesc scan_desc; + HeapTuple tuple; + Relation rel; + bool result = true; + + /* + * Extract the label id from the graph id and get the table name + * the entity is part of. + */ + label = search_label_graph_oid_cache(graph_oid, GET_LABEL_ID(id)); + + // Setup the scan key to be the graphid + ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, + F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); + + rel = table_open(label->relation, RowExclusiveLock); + scan_desc = table_beginscan(rel, estate->es_snapshot, 1, scan_keys); + + tuple = heap_getnext(scan_desc, ForwardScanDirection); + + /* + * If a single tuple was returned, the tuple is still valid, otherwise' + * set to false. + */ + if (!HeapTupleIsValid(tuple)) + { + result = false; + } + + table_endscan(scan_desc); + table_close(rel, RowExclusiveLock); + + return result; +} + diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c deleted file mode 100644 index 459bf95d..00000000 --- a/src/backend/executor/cypher_delete.c +++ /dev/null @@ -1,483 +0,0 @@ -/* - * 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. - */ - -#include "postgres.h" - -#include "access/heapam.h" -#include "access/htup_details.h" -#include "access/multixact.h" -#include "access/table.h" -#include "access/xact.h" -#include "executor/tuptable.h" -#include "nodes/execnodes.h" -#include "nodes/extensible.h" -#include "nodes/nodes.h" -#include "nodes/plannodes.h" -#include "parser/parsetree.h" -#include "storage/bufmgr.h" -#include "utils/rel.h" - -#include "catalog/ag_label.h" -#include "executor/cypher_executor.h" -#include "executor/cypher_utils.h" -#include "nodes/cypher_nodes.h" -#include "utils/gtype.h" -#include "utils/graphid.h" -#include "utils/vertex.h" -#include "utils/edge.h" - -static void begin_cypher_delete(CustomScanState *node, EState *estate, int eflags); -static TupleTableSlot *exec_cypher_delete(CustomScanState *node); -static void end_cypher_delete(CustomScanState *node); -static void rescan_cypher_delete(CustomScanState *node); - -static void process_delete_list(CustomScanState *node); - -static void find_connected_edges(CustomScanState *node, char *graph_name, - List *labels, char *var_name, graphid id, - bool detach_delete); -static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, - HeapTuple tuple); - -const CustomExecMethods cypher_delete_exec_methods = {DELETE_SCAN_STATE_NAME, - begin_cypher_delete, - exec_cypher_delete, - end_cypher_delete, - rescan_cypher_delete, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL}; - -/* - * Initialization at the beginning of execution. Setup the child node, - * setup its scan tuple slot and projection infp, expression context, - * collect metadata about visible edges, and alter the commandid for - * the transaction. - */ -static void begin_cypher_delete(CustomScanState *node, EState *estate, - int eflags) -{ - cypher_delete_custom_scan_state *css = - (cypher_delete_custom_scan_state *)node; - Plan *subplan; - - Assert(list_length(css->cs->custom_plans) == 1); - - // setup child - subplan = linitial(css->cs->custom_plans); - node->ss.ps.lefttree = ExecInitNode(subplan, estate, eflags); - - // setup expr context - ExecAssignExprContext(estate, &node->ss.ps); - - // setup scan tuple slot and projection info - ExecInitScanTupleSlot(estate, &node->ss, ExecGetResultType(node->ss.ps.lefttree), &TTSOpsHeapTuple); - - if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) - { - TupleDesc tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; - - ExecAssignProjectionInfo(&node->ss.ps, tupdesc); - } - - /* - * Get all the labels that are visible to this delete clause at this point - * in the transaction. To be used later when the delete clause finds - * vertices. - */ - css->edge_labels = get_all_edge_labels_per_graph(estate, css->delete_data->graph_oid); - - /* - * Postgres does not assign the es_output_cid in queries that do - * not write to disk, ie: SELECT commands. We need the command id - * for our clauses, and we may need to initialize it. We cannot use - * GetCurrentCommandId because there may be other cypher clauses - * that have modified the command id. - */ - if (estate->es_output_cid == 0) - estate->es_output_cid = estate->es_snapshot->curcid; - - Increment_Estate_CommandId(estate); -} - -/* - * Called once per tuple. If this is a terminal DELETE clause - * process everyone of its child tuple, otherwise process the - * next tuple. - */ -static TupleTableSlot *exec_cypher_delete(CustomScanState *node) -{ - cypher_delete_custom_scan_state *css = - (cypher_delete_custom_scan_state *)node; - EState *estate = css->css.ss.ps.state; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *slot; - - if (CYPHER_CLAUSE_IS_TERMINAL(css->flags)) - { - /* - * If the DELETE clause was the final cypher clause written - * then we aren't returning anything from this result node. - * So the exec_cypher_delete function will only be called once. - * Therefore we will process all tuples from the subtree at once. - */ - while(true) - { - //Process the subtree first - Decrement_Estate_CommandId(estate) - slot = ExecProcNode(node->ss.ps.lefttree); - Increment_Estate_CommandId(estate) - - if (TupIsNull(slot)) - break; - - // setup the scantuple that the process_delete_list needs - econtext->ecxt_scantuple = - node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - - process_delete_list(node); - } - - return NULL; - } - else - { - //Process the subtree first - Decrement_Estate_CommandId(estate) - slot = ExecProcNode(node->ss.ps.lefttree); - Increment_Estate_CommandId(estate) - - if (TupIsNull(slot)) - return NULL; - - // setup the scantuple that the process_delete_list needs - econtext->ecxt_scantuple = - node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - - process_delete_list(node); - - econtext->ecxt_scantuple = - ExecProject(node->ss.ps.lefttree->ps_ProjInfo); - - return ExecProject(node->ss.ps.ps_ProjInfo); - } -} - -/* - * Called at the end of execution. Tell its child to - * end its execution. - */ -static void end_cypher_delete(CustomScanState *node) -{ - ExecEndNode(node->ss.ps.lefttree); -} - -/* - * Used for rewinding the scan state and reprocessing the results. - * - * XXX: This is not currently supported. We need to find out - * when this function will be called and determine a process - * for allowing the Delete clause to run multiple times without - * redundant edits to the database. - */ -static void rescan_cypher_delete(CustomScanState *node) -{ - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cypher DELETE clause cannot be rescaned"), - errhint("its unsafe to use joins in a query with a Cypher DELETE clause"))); -} - -/* - * Create the CustomScanState from the CustomScan and pass - * necessary metadata. - */ -Node *create_cypher_delete_plan_state(CustomScan *cscan) -{ - cypher_delete_custom_scan_state *cypher_css = - palloc0(sizeof(cypher_delete_custom_scan_state)); - cypher_delete_information *delete_data; - char *serialized_data; - Const *c; - - cypher_css->cs = cscan; - - // get the serialized data structure from the Const and deserialize it. - c = linitial(cscan->custom_private); - serialized_data = (char *)c->constvalue; - delete_data = stringToNode(serialized_data); - - Assert(is_ag_node(delete_data, cypher_delete_information)); - - cypher_css->delete_data = delete_data; - cypher_css->flags = delete_data->flags; - cypher_css->graph_oid = delete_data->graph_oid; - cypher_css->css.ss.ps.type = T_CustomScanState; - cypher_css->css.methods = &cypher_delete_exec_methods; - - return (Node *)cypher_css; -} - -/* - * Try and delete the entity that is describe by the HeapTuple in the table - * described by the resultRelInfo. - */ -static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, HeapTuple tuple) { - ResultRelInfo **saved_resultRelsInfo; - LockTupleMode lockmode; - TM_FailureData hufd; - TM_Result lock_result; - TM_Result delete_result; - Buffer buffer; - - // Find the physical tuple, this variable is coming from - saved_resultRelsInfo = estate->es_result_relations; - estate->es_result_relations = &resultRelInfo; - - lockmode = ExecUpdateLockMode(estate, resultRelInfo); - - lock_result = heap_lock_tuple(resultRelInfo->ri_RelationDesc, tuple, GetCurrentCommandId(false), lockmode, - LockWaitBlock, false, &buffer, &hufd); - - /* - * It is possible the entity may have already been deleted. If the tuple - * can be deleted, the lock result will be HeapTupleMayBeUpdated. If the - * tuple was already deleted by this DELETE clause, the result would be - * TM_SelfModified, if the result was deleted by a previous delete - * clause, the result will TM_Invisible. Throw an error if any - * other result was returned. - */ - if (lock_result == TM_Ok) - { - delete_result = heap_delete(resultRelInfo->ri_RelationDesc, &tuple->t_self, GetCurrentCommandId(true), - estate->es_crosscheck_snapshot, true, &hufd, false); - - /* - * Unlike locking, the heap_delete either succeeded - * HeapTupleMayBeUpdate, or it failed and returned any other result. - */ - switch (delete_result) - { - case TM_Ok: - break; - case TM_SelfModified: - ereport( - ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("deleting the same entity more than once cannot happen"))); - /* ereport never gets here */ - break; - case TM_Updated: - ereport( - ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - /* ereport never gets here */ - break; - default: - elog(ERROR, "Entity failed to be update"); - /* elog never gets here */ - break; - } - /* increment the command counter */ - CommandCounterIncrement(); - } - else if (lock_result != TM_Invisible && lock_result != TM_SelfModified) - { - ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("Entity could not be locked for updating"))); - - } - - ReleaseBuffer(buffer); - - estate->es_result_relations = saved_resultRelsInfo; -} - -/* - * After the delete's subtress has been processed, we then go through the list - * of variables to be deleted. - */ -static void process_delete_list(CustomScanState *node) { - cypher_delete_custom_scan_state *css = (cypher_delete_custom_scan_state *)node; - ListCell *lc; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - EState *estate = node->ss.ps.state; - char *graph_name = css->delete_data->graph_name; - - foreach(lc, css->delete_data->delete_items) - { - cypher_delete_item *item; - gtype_value *original_entity_value, *id, *label; - ScanKeyData scan_keys[1]; - TableScanDesc scan_desc; - ResultRelInfo *resultRelInfo; - HeapTuple heap_tuple; - Value *pos; - int entity_position; - graphid gid = 0; - - item = lfirst(lc); - - pos = item->entity_position; - entity_position = pos->val.ival; - - if (scanTupleSlot->tts_isnull[entity_position - 1]) - continue; - - TupleDesc tupleDescriptor = scanTupleSlot->tts_tupleDescriptor; - if (tupleDescriptor->attrs[entity_position -1].atttypid == EDGEOID) { - edge *e = DATUM_GET_EDGE(scanTupleSlot->tts_values[entity_position - 1]); - - gid = *((int64 *)(&e->children[0])); - char *label = extract_edge_label(e); - - resultRelInfo = create_entity_result_rel_info(estate, css->delete_data->graph_name, label); - - ScanKeyInit(&scan_keys[0], Anum_ag_label_edge_table_id, - BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(gid)); - - } else if (tupleDescriptor->attrs[entity_position -1].atttypid == VERTEXOID) { - vertex *v = DATUM_GET_VERTEX(scanTupleSlot->tts_values[entity_position - 1]); - - gid = EXTRACT_VERTEX_ID(v); - char *label = extract_vertex_label(v); - - resultRelInfo = create_entity_result_rel_info(estate, css->delete_data->graph_name, label); - - - ScanKeyInit(&scan_keys[0], Anum_ag_label_vertex_table_id, - BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(gid)); - } else { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("DELETE clause can only delete vertices and edges"))); - } - - scan_desc = table_beginscan(resultRelInfo->ri_RelationDesc, estate->es_snapshot, 1, scan_keys); - - heap_tuple = heap_getnext(scan_desc, ForwardScanDirection); - - if (!HeapTupleIsValid(heap_tuple)) - { - table_endscan(scan_desc); - destroy_entity_result_rel_info(resultRelInfo); - - continue; - } - - if (tupleDescriptor->attrs[entity_position -1].atttypid == VERTEXOID) - find_connected_edges(node, graph_name, css->edge_labels, item->var_name, gid, css->delete_data->detach); - - delete_entity(estate, resultRelInfo, heap_tuple); - - table_endscan(scan_desc); - destroy_entity_result_rel_info(resultRelInfo); - } -} - -/* - * Find the edges connected to the given node. If there is any edges either - * delete them or throw an error, depending on the detach delete option. - */ -static void find_connected_edges(CustomScanState *node, char *graph_name, - List *labels, char *var_name, graphid id, - bool detach_delete) -{ - cypher_delete_custom_scan_state *css = - (cypher_delete_custom_scan_state *)node; - EState *estate = css->css.ss.ps.state; - ListCell *lc; - - Increment_Estate_CommandId(estate); - - /* - * We need to scan through all the edges to see if this vertex has - * any edges attached to it. - * - * XXX: If we implement an on-disc graph storage system. Such as - * an adjacency matrix, the performace of this check can be massively - * improved. However, right now we have to scan every edge to see if - * one has this vertex as a start or end vertex. - */ - foreach(lc, labels) - { - char *label_name = lfirst(lc); - ResultRelInfo *resultRelInfo; - TableScanDesc scan_desc; - HeapTuple tuple; - TupleTableSlot *slot; - - resultRelInfo = create_entity_result_rel_info(estate, - graph_name, label_name); - - scan_desc = table_beginscan(resultRelInfo->ri_RelationDesc, - estate->es_snapshot, 0, NULL); - - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - - // scan the table - while(true) - { - graphid startid, endid; - bool isNull; - - tuple = heap_getnext(scan_desc, ForwardScanDirection); - - // no more tuples to process, break and scan the next label. - if (!HeapTupleIsValid(tuple)) - break; - - ExecStoreHeapTuple(tuple, slot, false); - - startid = GRAPHID_GET_DATUM(slot_getattr(slot, Anum_ag_label_edge_table_start_id, &isNull)); - endid = GRAPHID_GET_DATUM(slot_getattr(slot, Anum_ag_label_edge_table_end_id, &isNull)); - - if (id == startid || id == endid) - { - /* - * We have found an edge that uses the vertex. Either delete the - * edge or throw an error. Depending on whether the DETACH - * option was specified in the query. - */ - if (detach_delete) - delete_entity(estate, resultRelInfo, tuple); - else - ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("Cannot delete vertex %s, because it still has edges attached. " - "To delete this vertex, you must first delete the attached edges.", - var_name))); - } - } - - table_endscan(scan_desc); - destroy_entity_result_rel_info(resultRelInfo); - } - - Decrement_Estate_CommandId(estate); -} diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c deleted file mode 100644 index 6933dcc5..00000000 --- a/src/backend/executor/cypher_merge.c +++ /dev/null @@ -1,927 +0,0 @@ -/* - * 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. - */ - -#include "postgres.h" - -#include "access/htup_details.h" -#include "access/table.h" -#include "access/xact.h" -#include "executor/tuptable.h" -#include "nodes/execnodes.h" -#include "nodes/extensible.h" -#include "nodes/nodes.h" -#include "nodes/plannodes.h" -#include "utils/rel.h" - -#include "catalog/ag_label.h" -#include "executor/cypher_executor.h" -#include "executor/cypher_utils.h" -#include "nodes/cypher_nodes.h" -#include "utils/gtype.h" -#include "utils/graphid.h" -#include "utils/vertex.h" -#include "utils/edge.h" -#include "utils/traversal.h" - -static void begin_cypher_merge(CustomScanState *node, EState *estate, - int eflags); -static TupleTableSlot *exec_cypher_merge(CustomScanState *node); -static void end_cypher_merge(CustomScanState *node); -static void rescan_cypher_merge(CustomScanState *node); -static Datum merge_vertex(cypher_merge_custom_scan_state *css, - cypher_target_node *node, ListCell *next, List* list); -static void merge_edge(cypher_merge_custom_scan_state *css, - cypher_target_node *node, Datum prev_vertex_id, - ListCell *next, List *list); -static void process_simple_merge(CustomScanState *node); -static bool check_path(cypher_merge_custom_scan_state *css, - TupleTableSlot *slot); -static void process_path(cypher_merge_custom_scan_state *css); -static void mark_tts_isnull(TupleTableSlot *slot); - -const CustomExecMethods cypher_merge_exec_methods = {MERGE_SCAN_STATE_NAME, - begin_cypher_merge, - exec_cypher_merge, - end_cypher_merge, - rescan_cypher_merge, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL}; - -static void -append_to_buffer(StringInfo buffer, const char *data, int len) { - int offset = reserve_from_buffer(buffer, len); - memcpy(buffer->data + offset, data, len); -} - - -static Datum create_traversal(List *entities) { - ListCell *lc; - StringInfoData buffer; - initStringInfo(&buffer); - - // header - reserve_from_buffer(&buffer, VARHDRSZ); - - // length - reserve_from_buffer(&buffer, sizeof(pentry)); - - foreach(lc, entities) { - Datum d = lfirst(lc); - append_to_buffer(&buffer, d, VARSIZE_ANY(d)); - } - - traversal *p = (traversal *)buffer.data; - - p->children[0] = list_length(entities); - SET_VARSIZE(p, buffer.len); - - return TRAVERSAL_GET_DATUM(p); -} - - -/* - * Initializes the MERGE Execution Node at the begginning of the execution - * phase. - */ -static void begin_cypher_merge(CustomScanState *node, EState *estate, int eflags) { - cypher_merge_custom_scan_state *css = - (cypher_merge_custom_scan_state *)node; - ListCell *lc; - Plan *subplan; - - Assert(list_length(css->cs->custom_plans) == 1); - - // initialize the subplan - subplan = linitial(css->cs->custom_plans); - node->ss.ps.lefttree = ExecInitNode(subplan, estate, eflags); - - ExecAssignExprContext(estate, &node->ss.ps); - - ExecInitScanTupleSlot(estate, &node->ss, - ExecGetResultType(node->ss.ps.lefttree), - &TTSOpsVirtual); - - /* - * When MERGE is not the last clause in a cypher query. Setup projection - * information to pass to the parent execution node. - */ - if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) - { - TupleDesc tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; - - ExecAssignProjectionInfo(&node->ss.ps, tupdesc); - } - - /* - * For each vertex and edge in the path, setup the information - * needed if we need to create them. - */ - foreach(lc, css->path->target_nodes) - { - cypher_target_node *cypher_node = - (cypher_target_node *)lfirst(lc); - Relation rel; - - /* - * This entity is refrences an entity that is already declared. Either - * by a previous clause or an entity earlier in the MERGE path. In both - * cases, this target_entry will not create data, only reference data - * that already exists. - */ - if (!CYPHER_TARGET_NODE_INSERT_ENTITY(cypher_node->flags)) - continue; - - // Open relation and aquire a row exclusive lock. - rel = table_open(cypher_node->relid, RowExclusiveLock); - - // Initialize resultRelInfo for the vertex - cypher_node->resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, rel, - list_length(estate->es_range_table), NULL, - estate->es_instrument); - - // Open all indexes for the relation - ExecOpenIndices(cypher_node->resultRelInfo, false); - - // Setup the relation's tuple slot - cypher_node->elemTupleSlot = ExecInitExtraTupleSlot( - estate, - RelationGetDescr(cypher_node->resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - - if (cypher_node->id_expr != NULL) - cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); - - if (cypher_node->prop_expr != NULL) - cypher_node->prop_expr_state = ExecInitExpr(cypher_node->prop_expr, (PlanState *)node); - } - - /* - * Postgres does not assign the es_output_cid in queries that do - * not write to disk, ie: SELECT commands. We need the command id - * for our clauses, and we may need to initialize it. We cannot use - * GetCurrentCommandId because there may be other cypher clauses - * that have modified the command id. - */ - if (estate->es_output_cid == 0) - estate->es_output_cid = estate->es_snapshot->curcid; - - /* store the currentCommandId for this instance */ - css->base_currentCommandId = GetCurrentCommandId(false); - - Increment_Estate_CommandId(estate); -} - -/* - * Checks the subtree to see if the lateral join representing the MERGE path - * found results. Returns true if the path does not exist and must be created, - * false otherwise. - */ -static bool check_path(cypher_merge_custom_scan_state *css, - TupleTableSlot *slot) -{ - cypher_create_path *path = css->path; - ListCell *lc; - - foreach(lc, path->target_nodes) - { - cypher_target_node *node = lfirst(lc); - - /* - * If target_node as a valid attribute number and is a node not - * declared in a previous clause, check the tuple position in the - * slot. If the slot is null, the path was not found. The rules - * state that if one part of the path does not exists, the whold - * path must be created. - */ - if (node->tuple_position != InvalidAttrNumber || ((node->flags & CYPHER_TARGET_NODE_MERGE_EXISTS) == 0)) - { - /* - * Attribute number is 1 indexed and tts_values is 0 indexed, - * offset by 1. - */ - if (slot->tts_isnull[node->tuple_position - 1]) - { - return true; - } - } - - } - - return false; -} - -static void process_path(cypher_merge_custom_scan_state *css) -{ - cypher_create_path *path = css->path; - - ListCell *lc = list_head(path->target_nodes); - - /* - * Create the first vertex. The create_vertex function will - * create the rest of the path, if necessary. - */ - merge_vertex(css, lfirst(lc), lnext(path->target_nodes, lc), path->target_nodes); - - - /* - * If this path is a variable, take the list that was accumulated - * in the vertex/edge creation, create a path datum, and add to the - * scantuple slot. - */ - if (path->path_attr_num != InvalidAttrNumber) - { - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *scantuple = econtext->ecxt_scantuple; - Datum result; - - //result = make_path(css->path_values); - result = create_traversal(css->path_values); - scantuple->tts_values[path->path_attr_num - 1] = result; - scantuple->tts_isnull[path->path_attr_num - 1] = false; - } -} - -/* - * Function that handles the case where MERGE is the only clause in the query. - */ -static void process_simple_merge(CustomScanState *node) -{ - cypher_merge_custom_scan_state *css = - (cypher_merge_custom_scan_state *)node; - EState *estate = css->css.ss.ps.state; - TupleTableSlot *slot; - - /*Process the subtree first */ - Decrement_Estate_CommandId(estate) - slot = ExecProcNode(node->ss.ps.lefttree); - Increment_Estate_CommandId(estate) - - if (TupIsNull(slot)) - { - ExprContext *econtext = node->ss.ps.ps_ExprContext; - - /* setup the scantuple that the process_path needs */ - econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ResultTupleSlot; - - process_path(css); - } -} - -/* - * Iterate through the TupleTableSlot's tts_values and marks the isnull field - * with true. - */ -static void mark_tts_isnull(TupleTableSlot *slot) -{ - int numberOfAttributes = slot->tts_tupleDescriptor->natts; - int i; - - for (i = 0; i < numberOfAttributes; i++) - { - Datum val; - - val = slot->tts_values[i]; - - if (val == (Datum)NULL) - { - slot->tts_isnull[i] = true; - } - } -} - -/* - * Function that is called mid-execution. This function will call - * its subtree in the execution tree, and depending on the results - * create the new path, and depending on the the context of the MERGE - * within the query pass data to the parent execution node. - * - * Returns a TupleTableSlot with the next tuple to it parent or - * Returns NULL when MERGE has no more tuples to emit. - */ -static TupleTableSlot *exec_cypher_merge(CustomScanState *node) -{ - cypher_merge_custom_scan_state *css = - (cypher_merge_custom_scan_state *)node; - EState *estate = css->css.ss.ps.state; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *slot; - bool terminal = CYPHER_CLAUSE_IS_TERMINAL(css->flags); - - /* - * There are three cases that dictate the flow of the execution logic. - * - * 1. MERGE is not the first clause in the cypher query. - * 2. MERGE is the first clause and there are no following clauses. - * 3. MERGE is the first clause and there are following clauses. - * CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE - */ - if (CYPHER_CLAUSE_HAS_PREVIOUS_CLAUSE(css->flags)) - { - /* - * Case 1: MERGE is not the first clause in the cypher query. - * - * For this case, we need to process all tuples give to us by the - * previous clause. When we receive a tuple from the previous clause: - * check to see if the left lateral join found the pattern already. If - * it did, we don't need to create the pattern. If the lateral join did - * not find the whole path, create the whole path. - * - * If this is a terminal clause, process all tuples. If not, pass the - * tuple to up the execution tree. - */ - do - { - /*Process the subtree first */ - Decrement_Estate_CommandId(estate) - slot = ExecProcNode(node->ss.ps.lefttree); - Increment_Estate_CommandId(estate) - - /* - * We are done processing the subtree, mark as terminal - * so the function returns NULL. - */ - if (TupIsNull(slot)) - { - terminal = true; - break; - } - - /* setup the scantuple that the process_path needs */ - econtext->ecxt_scantuple = - node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - - if (check_path(css, econtext->ecxt_scantuple)) - { - process_path(css); - } - - } while (terminal); - - /* if this was a terminal MERGE just return NULL */ - if (terminal) - { - return NULL; - } - - //return ExecProject(node->ss.ps.ps_ProjInfo); - econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); - return ExecProject(node->ss.ps.ps_ProjInfo); - - } - else if (terminal) - { - /* - * Case 2: MERGE is the first clause and there are no following clauses - * - * For case 2, check to see if we found the pattern, if not create it. - * Return NULL in either cases, because no rows are expected. - */ - process_simple_merge(node); - - /* - * Case 2 always returns NULL the first time exec_cypher_merge is - * called. - */ - return NULL; - } - else - { - /* - * Case 3: MERGE is the first clause and there are following clauses. - * - * Case three has two subcases: - * - * 1. The already path exists. - * 2. The path does not exist. - */ - - /* - * Part of Case 2. - * - * If created_new_path is marked as true. The path did not exist and - * MERGE created it. We have already passed that information up the - * execution tree, and now we tell MERGE's parents in the execution - * tree there is no more tuples to pass. - */ - if (css->created_new_path) - { - /* - * If the created_new_path is set to true. Then MERGE should not - * have found a path, because this should only be set to true if - * merge found sub-case 1 - */ - Assert(css->found_a_path == false); - - return NULL; - } - - /* - * Process the subtree. The subtree will only consist of the MERGE - * path. - */ - Decrement_Estate_CommandId(estate) - slot = ExecProcNode(node->ss.ps.lefttree); - Increment_Estate_CommandId(estate) - - if (!TupIsNull(slot)) - { - /* - * Part of Sub-Case 1. - * - * If we found a path, mark the found_a_path flag to true and - * pass the tuple to the next execution tree. When the path - * exists, we don't need to create/modify anything. - */ - css->found_a_path = true; - - econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); - return ExecProject(node->ss.ps.ps_ProjInfo); - } - else if (TupIsNull(slot) && css->found_a_path) - { - /* - * Part of Sub-Case 2. - * - * MERGE found the path(s) that already exists and we are done passing - * all the found path(s) up the execution tree. - */ - return NULL; - } - else - { - /* - * Part of Sub-Case 1. - * - * MERGE could not find the path in memory and the sub-node in the - * execution tree returned NULL. We need to create the path and - * pass the tuple to the next execution node. The subtrees will - * begin its cleanup process when there are no more tuples found. - * So we will need to create a TupleTableSlot and populate with the - * information from the newly created path that the query needs. - */ - ExprContext *econtext = node->ss.ps.ps_ExprContext; - SubqueryScanState *sss = (SubqueryScanState *)node->ss.ps.lefttree; - - /* - * Our child execution node is always a subquery. If not there - * is an issue. - */ - Assert(IsA(sss, SubqueryScanState)); - - /* - * found_a_path should only be set to true if MERGE is following - * sub-case 2. - */ - Assert(css->found_a_path == false); - - /* - * This block of sub-case 1 should only be exectuted once. To - * create the single path if the path does not exist. If we find - * ourselves here again, the internal state of the MERGE execution - * node was incorrectly altered. - */ - Assert(css->created_new_path == false); - - /* - * Postgres cleared the child tuple table slot, we need to remake - * it. - */ - ExecInitScanTupleSlot(estate, &sss->ss, - ExecGetResultType(sss->subplan), - &TTSOpsVirtual); - - /* setup the scantuple that the process_path needs */ - econtext->ecxt_scantuple = sss->ss.ss_ScanTupleSlot; - - // create the path - process_path(css); - - // mark the create_new_path flag to true. - css->created_new_path = true; - - /* - * find the tts_values that process_path did not populate and - * mark as null. - */ - mark_tts_isnull(econtext->ecxt_scantuple); - - // store the heap tuble - ExecStoreVirtualTuple(econtext->ecxt_scantuple); - - /* - * make the subquery's projection scan slot be the tuple table we - * created and run the projection logic. - */ - sss->ss.ps.ps_ProjInfo->pi_exprContext->ecxt_scantuple = - econtext->ecxt_scantuple; - - // assign this to be our scantuple - econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); - - /* - * run the merge's projection logic and pass to its parent - * execution node - */ - return ExecProject(node->ss.ps.ps_ProjInfo); - } - } -} - - -/* - * Function called at the end of the execution phase to cleanup - * MERGE. - */ -static void end_cypher_merge(CustomScanState *node) -{ - cypher_merge_custom_scan_state *css = - (cypher_merge_custom_scan_state *)node; - cypher_create_path *path = css->path; - ListCell *lc; - - // increment the command counter - CommandCounterIncrement(); - - ExecEndNode(node->ss.ps.lefttree); - - foreach (lc, path->target_nodes) - { - cypher_target_node *cypher_node = - (cypher_target_node *)lfirst(lc); - - if (!CYPHER_TARGET_NODE_INSERT_ENTITY(cypher_node->flags)) - continue; - - // close all indices for the node - ExecCloseIndices(cypher_node->resultRelInfo); - - // close the relation itself - table_close(cypher_node->resultRelInfo->ri_RelationDesc, - RowExclusiveLock); - } -} - -/* - * Rescan is mostly used by join execution nodes, and several others. - * Since we are creating data here its not safe to rescan the node. Throw - * an error and try to help the uer understand what went wrong. - */ -static void rescan_cypher_merge(CustomScanState *node) -{ - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cypher merge clause cannot be rescaned"), - errhint("its unsafe to use joins in a query with a Cypher MERGE clause"))); -} - -/* - * Extracts the metadata information that MERGE needs from the - * merge_custom_scan node and creates the cypher_merge_custom_scan_state - * for the execution phase. - */ -Node *create_cypher_merge_plan_state(CustomScan *cscan) -{ - cypher_merge_custom_scan_state *cypher_css = - palloc0(sizeof(cypher_merge_custom_scan_state)); - cypher_merge_information *merge_information; - char *serialized_data; - Const *c; - - cypher_css->cs = cscan; - - // get the serialized data structure from the Const and deserialize it. - c = linitial(cscan->custom_private); - serialized_data = (char *)c->constvalue; - merge_information = stringToNode(serialized_data); - - Assert(is_ag_node(merge_information, cypher_merge_information)); - - cypher_css->merge_information = merge_information; - cypher_css->flags = merge_information->flags; - cypher_css->merge_function_attr = merge_information->merge_function_attr; - cypher_css->path = merge_information->path; - cypher_css->created_new_path = false; - cypher_css->found_a_path = false; - cypher_css->graph_oid = merge_information->graph_oid; - - cypher_css->css.ss.ps.type = T_CustomScanState; - cypher_css->css.methods = &cypher_merge_exec_methods; - - return (Node *)cypher_css; -} - -/* - * Creates the vertex entity, returns the vertex's id in case the caller is - * the create_edge function. - */ -static Datum merge_vertex(cypher_merge_custom_scan_state *css, - cypher_target_node *node, ListCell *next, List *list) -{ - bool isNull; - Datum id; - EState *estate = css->css.ss.ps.state; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - ResultRelInfo *resultRelInfo = node->resultRelInfo; - TupleTableSlot *elemTupleSlot = node->elemTupleSlot; - TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - - Assert(node->type == LABEL_KIND_VERTEX); - - /* - * Vertices in a path might already exists. If they do get the id - * to pass to the edges before and after it. Otherwise, insert the - * new vertex into it's table and then pass the id along. - */ - if (CYPHER_TARGET_NODE_INSERT_ENTITY(node->flags)) - { - ResultRelInfo **old_estate_es_result_relations_info = NULL; - Datum prop; - /* - * Set estate's result relation to the vertex's result - * relation. - * - * Note: This obliterates what was their previously - */ - - /* save the old result relation info */ - old_estate_es_result_relations_info = estate->es_result_relations; - - estate->es_result_relations = &resultRelInfo; - - ExecClearTuple(elemTupleSlot); - - /* get the next graphid for this vertex */ - id = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - elemTupleSlot->tts_values[vertex_tuple_id] = id; - elemTupleSlot->tts_isnull[vertex_tuple_id] = isNull; - - /* get the properties for this vertex */ - prop = ExecEvalExpr(node->prop_expr_state, econtext, &isNull); - elemTupleSlot->tts_values[vertex_tuple_properties] = prop; - elemTupleSlot->tts_isnull[vertex_tuple_properties] = isNull; - - /* - * Insert the new vertex. - * - * Depending on the currentCommandId, we need to do this one of two - * different ways - - * - * 1) If they are equal, the currentCommandId hasn't been used for an - * update, or it hasn't been incremented after being used. In either - * case, we need to use the current one and then increment it so that - * the following commands will have visibility of this update. Note, - * it isn't our job to update the currentCommandId first and then do - * this check. - * - * 2) If they are not equal, the currentCommandId has been used and/or - * updated. In this case, we can't use it. Otherwise our update won't - * be visible to anything that follows, until the currentCommandId is - * updated again. Remember, visibility is, greater than but not equal - * to, the currentCommandID used for the update. So, in this case we - * need to use the original currentCommandId when begin_cypher_merge - * was initiated as everything under this instance of merge needs to - * be based off of that initial currentCommandId. This allows the - * following command to see the updates generated by this instance of - * merge. - */ - if (css->base_currentCommandId == GetCurrentCommandId(false)) - { - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - - /* - * Increment the currentCommandId since we processed an update. We - * don't want to do this outside of this block because we don't want - * to inadvertently or unnecessarily update the commandCounterId of - * another command. - */ - CommandCounterIncrement(); - } - else - { - insert_entity_tuple_cid(resultRelInfo, elemTupleSlot, estate, - css->base_currentCommandId); - } - - /* restore the old result relation info */ - estate->es_result_relations = old_estate_es_result_relations_info; - - /* - * When the vertex is used by clauses higher in the execution tree - * we need to create a vertex datum. When the vertex is a variable, - * add to the scantuple slot. When the vertex is part of a path - * variable, add to the list. - */ - if (CYPHER_TARGET_NODE_OUTPUT(node->flags)) - { - Datum result; - - /* make the vertex gtype */ - result = VERTEX_GET_DATUM(create_vertex(id, css->graph_oid, DATUM_GET_GTYPE_P(prop))); - - /* append to the path list */ - if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) - css->path_values = lappend(css->path_values, DatumGetPointer(result)); - - /* - * Put the vertex in the correct spot in the scantuple, so parent - * execution nodes can reference the newly created variable. - */ - if (CYPHER_TARGET_NODE_IS_VARIABLE(node->flags)) { - scanTupleSlot->tts_values[node->tuple_position - 1] = result; - scanTupleSlot->tts_isnull[node->tuple_position - 1] = false; - } - } - } - else - { - TupleTableSlot *scantuple; - PlanState *ps; - - ps = css->css.ss.ps.lefttree; - scantuple = ps->ps_ExprContext->ecxt_scantuple; - if (scantuple->tts_isnull[node->tuple_position - 1]) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Existing variable %s cannot be NULL in MERGE clause", node->variable_name))); - - vertex *v = DATUM_GET_VERTEX(scantuple->tts_values[node->tuple_position - 1]); - id = GRAPHID_GET_DATUM(*((int64 *)(&v->children[0]))); - - /* - * Its possible the variable has already been deleted. There are two - * ways this can happen. One is the query explicitly deleted the - * variable, the is_deleted flag will catch that. However, it is - * possible the user deleted the vertex using another variable name. We - * need to scan the table to find the vertex's current status relative - * to this CREATE clause. If the variable was initially created in this - * clause, we can skip this check, because the transaction system - * guarantees that nothing can happen to that tuple, as far as we are - * concerned with at this time. - */ - if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) - { - if (!entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id))) - { - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("vertex assigned to variable %s was deleted", - node->variable_name))); - } - } - - /* - * Add the Datum to the list of entities for creating the path variable - */ - if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) - { - Datum vertex = scanTupleSlot->tts_values[node->tuple_position - 1]; - css->path_values = lappend(css->path_values, DatumGetPointer(vertex)); - } - } - - /* If the path continues, create the next edge, passing the vertex's id. */ - if (next != NULL) - { - merge_edge(css, lfirst(next), id, lnext(list, next), list); - } - - return id; -} - -/* - * Create the edge entity. - */ -static void merge_edge(cypher_merge_custom_scan_state *css, - cypher_target_node *node, Datum prev_vertex_id, - ListCell *next, List *list) -{ - bool isNull; - EState *estate = css->css.ss.ps.state; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - ResultRelInfo *resultRelInfo = node->resultRelInfo; - ResultRelInfo **old_estate_es_result_relations_info = NULL; - TupleTableSlot *elemTupleSlot = node->elemTupleSlot; - Datum id; - Datum start_id, end_id, next_vertex_id; - List *prev_path = css->path_values; - Datum prop; - - Assert(node->type == LABEL_KIND_EDGE); - Assert(lfirst(next) != NULL); - - /* - * Create the next vertex before creating the edge. We need the - * next vertex's id. - */ - css->path_values = NIL; - next_vertex_id = merge_vertex(css, lfirst(next), lnext(list, next), list); - - /* - * Set the start and end vertex ids - */ - if (node->dir == CYPHER_REL_DIR_RIGHT || node->dir == CYPHER_REL_DIR_NONE) - { - // create pattern (prev_vertex)-[edge]->(next_vertex) - start_id = prev_vertex_id; - end_id = next_vertex_id; - } - else if (node->dir == CYPHER_REL_DIR_LEFT) - { - // create pattern (prev_vertex)<-[edge]-(next_vertex) - start_id = next_vertex_id; - end_id = prev_vertex_id; - } - else - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("edge direction must be specified in a MERGE clause"))); - } - - /* - * Set estate's result relation to the vertex's result - * relation. - * - * Note: This obliterates what was their previously - */ - - /* save the old result relation info */ - old_estate_es_result_relations_info = estate->es_result_relations; - - estate->es_result_relations = &resultRelInfo; - - ExecClearTuple(elemTupleSlot); - - // Graph Id for the edge - id = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - elemTupleSlot->tts_values[edge_tuple_id] = id; - elemTupleSlot->tts_isnull[edge_tuple_id] = isNull; - - // Graph id for the starting vertex - elemTupleSlot->tts_values[edge_tuple_start_id] = start_id; - elemTupleSlot->tts_isnull[edge_tuple_start_id] = false; - - // Graph id for the ending vertex - elemTupleSlot->tts_values[edge_tuple_end_id] = end_id; - elemTupleSlot->tts_isnull[edge_tuple_end_id] = false; - - // Edge's properties map - prop = ExecEvalExpr(node->prop_expr_state, econtext, &isNull); - elemTupleSlot->tts_values[edge_tuple_properties] = prop; - elemTupleSlot->tts_isnull[edge_tuple_properties] = isNull; - - // Insert the new edge - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - - /* restore the old result relation info */ - estate->es_result_relations = old_estate_es_result_relations_info; - - /* - * When the edge is used by clauses higher in the execution tree - * we need to create an edge datum. When the edge is a variable, - * add to the scantuple slot. When the edge is part of a path - * variable, add to the list. - */ - if (CYPHER_TARGET_NODE_OUTPUT(node->flags)) - { - Datum result; - - //result = make_edge( id, start_id, end_id, CStringGetDatum(node->label_name), prop); - result = EDGE_GET_DATUM(create_edge(id, start_id, end_id, css->graph_oid, GTYPE_P_GET_DATUM(prop))); - - // add the Datum to the list of entities for creating the path variable - if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) { - prev_path = lappend(prev_path, DatumGetPointer(result)); - css->path_values = list_concat(prev_path, css->path_values); - } - - // Add the entity to the TupleTableSlot if necessary - if (CYPHER_TARGET_NODE_IS_VARIABLE(node->flags)) { - TupleTableSlot *scantuple = econtext->ecxt_scantuple; - - scantuple->tts_values[node->tuple_position - 1] = result; - scantuple->tts_isnull[node->tuple_position - 1] = false; - } - } -} diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c deleted file mode 100644 index e0f5dca9..00000000 --- a/src/backend/executor/cypher_set.c +++ /dev/null @@ -1,640 +0,0 @@ -/* - * 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. - */ - -#include "postgres.h" - -#include "access/heapam.h" -#include "access/htup_details.h" -#include "access/xact.h" -#include "executor/tuptable.h" -#include "nodes/execnodes.h" -#include "nodes/extensible.h" -#include "nodes/nodes.h" -#include "nodes/plannodes.h" -#include "rewrite/rewriteHandler.h" -#include "storage/bufmgr.h" -#include "utils/rel.h" - -#include "executor/cypher_executor.h" -#include "executor/cypher_utils.h" -#include "nodes/cypher_nodes.h" -#include "utils/gtype.h" -#include "utils/graphid.h" -#include "utils/vertex.h" -#include "utils/edge.h" -#include "catalog/ag_label.h" - - -static void begin_cypher_set(CustomScanState *node, EState *estate, int eflags); -static TupleTableSlot *exec_cypher_set(CustomScanState *node); -static void end_cypher_set(CustomScanState *node); -static void rescan_cypher_set(CustomScanState *node); - -static void process_update_list(CustomScanState *node); -static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, - EState *estate, HeapTuple old_tuple); - -const CustomExecMethods cypher_set_exec_methods = {SET_SCAN_STATE_NAME, - begin_cypher_set, - exec_cypher_set, - end_cypher_set, - rescan_cypher_set, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL}; - -static void begin_cypher_set(CustomScanState *node, EState *estate, int eflags) { - cypher_set_custom_scan_state *css = - (cypher_set_custom_scan_state *)node; - Plan *subplan; - - Assert(list_length(css->cs->custom_plans) == 1); - - subplan = linitial(css->cs->custom_plans); - node->ss.ps.lefttree = ExecInitNode(subplan, estate, eflags); - - ExecAssignExprContext(estate, &node->ss.ps); - - ExecInitScanTupleSlot(estate, &node->ss, ExecGetResultType(node->ss.ps.lefttree), &TTSOpsHeapTuple); - - if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) { - TupleDesc tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; - - ExecAssignProjectionInfo(&node->ss.ps, tupdesc); - } - - /* - * Postgres does not assign the es_output_cid in queries that do - * not write to disk, ie: SELECT commands. We need the command id - * for our clauses, and we may need to initialize it. We cannot use - * GetCurrentCommandId because there may be other cypher clauses - * that have modified the command id. - */ - if (estate->es_output_cid == 0) - estate->es_output_cid = estate->es_snapshot->curcid; - - Increment_Estate_CommandId(estate); -} - -static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, - EState *estate, HeapTuple old_tuple) { - HeapTuple tuple = NULL; - LockTupleMode lockmode; - TM_FailureData hufd; - TM_Result lock_result; - Buffer buffer; - bool update_indexes; - TM_Result result; - - ResultRelInfo **saved_resultRelsInfo = estate->es_result_relations; - estate->es_result_relations = &resultRelInfo; - - lockmode = ExecUpdateLockMode(estate, resultRelInfo); - - lock_result = heap_lock_tuple(resultRelInfo->ri_RelationDesc, old_tuple, - GetCurrentCommandId(false), lockmode, - LockWaitBlock, false, &buffer, &hufd); - - if (lock_result == TM_Ok) { - ExecOpenIndices(resultRelInfo, false); - ExecStoreVirtualTuple(elemTupleSlot); - tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); - tuple->t_self = old_tuple->t_self; - - // Check the constraints of the tuple - tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); - if (resultRelInfo->ri_RelationDesc->rd_att->constr != NULL) - ExecConstraints(resultRelInfo, elemTupleSlot, estate); - - result = table_tuple_update(resultRelInfo->ri_RelationDesc, - &tuple->t_self, elemTupleSlot, - GetCurrentCommandId(true), - //estate->es_output_cid, - estate->es_snapshot,// NULL, - estate->es_crosscheck_snapshot, - true /* wait for commit */ , - &hufd, &lockmode, &update_indexes); - - if (result == TM_SelfModified) - { - if (hufd.cmax != estate->es_output_cid) - { - ereport(ERROR, - (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION), - errmsg("tuple to be updated was already modified"))); - } - - ExecCloseIndices(resultRelInfo); - estate->es_result_relations = saved_resultRelsInfo; - - return tuple; - } - - if (result != TM_Ok) - ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("Entity failed to be updated: %i", result))); - - // Insert index entries for the tuple - if (resultRelInfo->ri_NumIndices > 0 && update_indexes) - ExecInsertIndexTuples(resultRelInfo, elemTupleSlot, estate, false, false, NULL, NIL); - - ExecCloseIndices(resultRelInfo); - } - else if (lock_result == TM_SelfModified) - { - if (hufd.cmax != estate->es_output_cid) - { - ereport(ERROR, - (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION), - errmsg("tuple to be updated was already modified"))); - } - } - else - { - ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("Entity failed to be updated: %i", lock_result))); - } - - ReleaseBuffer(buffer); - - estate->es_result_relations = saved_resultRelsInfo; - - return tuple; -} - -/* - * When the CREATE clause is the last cypher clause, consume all input from the - * previous clause(s) in the first call of exec_cypher_create. - */ -static void process_all_tuples(CustomScanState *node) -{ - cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; - TupleTableSlot *slot; - EState *estate = css->css.ss.ps.state; - - do - { - process_update_list(node); - Decrement_Estate_CommandId(estate) - slot = ExecProcNode(node->ss.ps.lefttree); - Increment_Estate_CommandId(estate) - } while (!TupIsNull(slot)); -} - -/* - * Checks the path to see if the entities contained within - * have the same graphid and the updated_id field. Returns - * true if yes, false otherwise. - */ -static bool check_path(gtype_value *path, graphid updated_id) -{ - int i; - -/* for (i = 0; i < path->val.array.num_elems; i++) - { - gtype_value *elem = &path->val.array.elems[i]; - - gtype_value *id = GET_GTYPE_VALUE_OBJECT_VALUE(elem, "id"); - - if (updated_id == id->val.int_value) - { - return true; - } - } -*/ - return false; -} - -/* XXX: Update to new datatype - * Construct a new gtype path with the entity with updated_id - * replacing all of its intances in path with updated_entity - */ -static gtype_value *replace_entity_in_path(gtype_value *path, graphid updated_id, gtype *updated_entity) { - gtype_iterator *it; - gtype_iterator_token tok = WGT_DONE; - gtype_parse_state *parse_state = NULL; - gtype_value *r; - gtype_value *parsed_gtype_value = NULL; - gtype *prop_gtype; - int i; - - r = palloc(sizeof(gtype_value)); - - prop_gtype = gtype_value_to_gtype(path); - it = gtype_iterator_init(&prop_gtype->root); - tok = gtype_iterator_next(&it, r, true); - - parsed_gtype_value = push_gtype_value(&parse_state, tok, - tok < WGT_BEGIN_ARRAY ? r : NULL); - - // Iterate through the path, replace entities as necessary. - for (i = 0; i < path->val.array.num_elems; i++) - { - gtype_value *id, *elem; - - elem = &path->val.array.elems[i]; - - // extract the id field - id = NULL;//GET_GTYPE_VALUE_OBJECT_VALUE(elem, "id"); - - /* - * Either replace or keep the entity in the new path, depending on the id - * check. - */ - if (updated_id == id->val.int_value) - { - parsed_gtype_value = push_gtype_value(&parse_state, WGT_ELEM, - get_ith_gtype_value_from_container(&updated_entity->root, 0)); - } - else - { - parsed_gtype_value = push_gtype_value(&parse_state, WGT_ELEM, - elem); - } - } - - parsed_gtype_value = push_gtype_value(&parse_state, WGT_END_ARRAY, NULL); - //parsed_gtype_value->type = AGTV_PATH; - - return parsed_gtype_value; -} - -/* - * When a vertex or edge is updated, we need to update the vertex - * or edge if it is contained within a path. Scan through scanTupleSlot - * to find all paths and check if they need to be updated. - */ -static void update_all_paths(CustomScanState *node, graphid id, - gtype *updated_entity) -{ - cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - int i; - - for (i = 0; i < scanTupleSlot->tts_tupleDescriptor->natts; i++) - { - gtype *original_entity; - gtype_value *original_entity_value; - - // skip nulls - if (scanTupleSlot->tts_tupleDescriptor->attrs[i].atttypid != GTYPEOID) - { - continue; - } - - // skip non gtype values - if (scanTupleSlot->tts_isnull[i]) - { - continue; - } - - original_entity = DATUM_GET_GTYPE_P(scanTupleSlot->tts_values[i]); - - // if the value is not a scalar type, its not a path - if (!GTYPE_CONTAINER_IS_SCALAR(&original_entity->root)) - { - continue; - } - - original_entity_value = get_ith_gtype_value_from_container(&original_entity->root, 0); - - // we found a path - if (original_entity_value->type == 1)// AGTV_PATH) - { - // check if the path contains the entity. - if (check_path(original_entity_value, id)) - { - // the path does contain the entity replace with the new entity. - gtype_value *new_path = replace_entity_in_path(original_entity_value, id, updated_entity); - - scanTupleSlot->tts_values[i] = GTYPE_P_GET_DATUM(gtype_value_to_gtype(new_path)); - } - } - } -} - -TupleTableSlot *populate_vertex_tts_1(TupleTableSlot *elemTupleSlot, graphid id, gtype_value *properties) -{ - bool properties_isnull; - - if (id == NULL) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("vertex id field cannot be NULL"))); - } - - properties_isnull = properties == NULL; - - elemTupleSlot->tts_values[vertex_tuple_id] = GRAPHID_GET_DATUM(id); - elemTupleSlot->tts_isnull[vertex_tuple_id] = false; - - elemTupleSlot->tts_values[vertex_tuple_properties] = - GTYPE_P_GET_DATUM(gtype_value_to_gtype(properties)); - elemTupleSlot->tts_isnull[vertex_tuple_properties] = properties_isnull; - - return elemTupleSlot; -} - -TupleTableSlot *populate_edge_tts_1(TupleTableSlot *elemTupleSlot, graphid id, graphid startid, - graphid endid, gtype_value *properties) -{ - bool properties_isnull; - - properties_isnull = properties == NULL; - - elemTupleSlot->tts_values[edge_tuple_id] = GRAPHID_GET_DATUM(id); - elemTupleSlot->tts_isnull[edge_tuple_id] = false; - - elemTupleSlot->tts_values[edge_tuple_start_id] = GRAPHID_GET_DATUM(startid); - elemTupleSlot->tts_isnull[edge_tuple_start_id] = false; - - elemTupleSlot->tts_values[edge_tuple_end_id] = GRAPHID_GET_DATUM(endid); - elemTupleSlot->tts_isnull[edge_tuple_end_id] = false; - - elemTupleSlot->tts_values[edge_tuple_properties] = - GTYPE_P_GET_DATUM(gtype_value_to_gtype(properties)); - elemTupleSlot->tts_isnull[edge_tuple_properties] = properties_isnull; - - return elemTupleSlot; -} - - -static void process_update_list(CustomScanState *node) -{ - cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - ListCell *lc; - EState *estate = css->css.ss.ps.state; - int *luindex = NULL; - int lidx = 0; - - /* allocate an array to hold the last update index of each 'entity' */ - luindex = palloc0(sizeof(int) * scanTupleSlot->tts_nvalid); - /* - * Iterate through the SET items list and store the loop index of each - * 'entity' update. As there is only one entry for each entity, this will - * have the effect of overwriting the previous loop index stored - if this - * 'entity' is used more than once. This will create an array of the last - * loop index for the update of that particular 'entity'. This will allow us - * to correctly update an 'entity' after all other previous updates to that - * 'entity' have been done. - */ - foreach (lc, css->set_list->set_items) - { - cypher_update_item *update_item = NULL; - - update_item = (cypher_update_item *)lfirst(lc); - luindex[update_item->entity_position - 1] = lidx; - - /* increment the loop index */ - lidx++; - } - - /* reset loop index */ - lidx = 0; - - /* iterate through SET set items */ - foreach (lc, css->set_list->set_items) - { - gtype_value *altered_properties; - gtype_value *original_properties; - graphid id; - gtype *new_property_value; - TupleTableSlot *slot; - ResultRelInfo *resultRelInfo; - ScanKeyData scan_keys[1]; - TableScanDesc scan_desc; - bool remove_property; - char *label_name; - cypher_update_item *update_item; - Datum new_entity; - HeapTuple heap_tuple; - char *clause_name = css->set_list->clause_name; - int cid; - - update_item = (cypher_update_item *)lfirst(lc); - - /* - * If the entity is null, we can skip this update. this will be - * possible when the OPTIONAL MATCH clause is implemented. - */ - if (scanTupleSlot->tts_isnull[update_item->entity_position - 1]) - continue; - - if (update_item->remove_item) - remove_property = true; - else - remove_property = scanTupleSlot->tts_isnull[update_item->prop_position - 1]; - - /* - * If we need to remove the property, set the value to NULL. Otherwise - * fetch the evaluated expression from the tuble slot. - */ - if (remove_property) - new_property_value = NULL; - else - new_property_value = DATUM_GET_GTYPE_P(scanTupleSlot->tts_values[update_item->prop_position - 1]); - - - if (scanTupleSlot->tts_tupleDescriptor->attrs[update_item->entity_position -1].atttypid == VERTEXOID) { - - vertex *v = DATUM_GET_VERTEX(scanTupleSlot->tts_values[update_item->entity_position - 1]); - - id = *((int64 *)(&v->children[0])); - - label_name = extract_vertex_label(v); - - original_properties = extract_vertex_properties(v); - - /* - * Alter the properties Agtype value to contain or remove the updated - * property. - */ - altered_properties = alter_property_value(original_properties, update_item->prop_name, - new_property_value, remove_property); - - resultRelInfo = create_entity_result_rel_info(estate, css->set_list->graph_name, label_name); - - slot = ExecInitExtraTupleSlot(estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), &TTSOpsHeapTuple); - - new_entity = VERTEX_GET_DATUM(create_vertex(id, css->graph_oid, gtype_value_to_gtype(altered_properties))); - - slot = populate_vertex_tts_1(slot, id, altered_properties); - } - else if (scanTupleSlot->tts_tupleDescriptor->attrs[update_item->entity_position -1].atttypid == EDGEOID) { - edge *v = DATUM_GET_EDGE(scanTupleSlot->tts_values[update_item->entity_position - 1]); - - id = *((int64 *)(&v->children[0])); - graphid startid = *((int64 *)(&v->children[2])); - graphid endid = *((int64 *)(&v->children[4])); - - label_name = extract_edge_label(v); - - original_properties = extract_edge_properties(v); - - altered_properties = alter_property_value(original_properties, update_item->prop_name, - new_property_value, remove_property); - - resultRelInfo = create_entity_result_rel_info(estate, css->set_list->graph_name, label_name); - - slot = ExecInitExtraTupleSlot(estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), &TTSOpsHeapTuple); - - new_entity = EDGE_GET_DATUM(create_edge(id, startid, endid, css->graph_oid, gtype_value_to_gtype(altered_properties))); - - slot = populate_edge_tts_1(slot, id, startid, endid, altered_properties); - - } - else - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("%s clause can only update vertex and edges", clause_name))); - } - - scanTupleSlot->tts_values[update_item->entity_position - 1] = new_entity; - - //update_all_paths(node, - // id->val.int_value, DATUM_GET_GTYPE_P(new_entity)); - - cid = estate->es_snapshot->curcid; - estate->es_snapshot->curcid = GetCurrentCommandId(false); - - if (luindex[update_item->entity_position - 1] == lidx) - { - ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_GRAPHIDEQ, - GRAPHID_GET_DATUM(id)); - - scan_desc = table_beginscan(resultRelInfo->ri_RelationDesc, - estate->es_snapshot, 1, scan_keys); - - heap_tuple = heap_getnext(scan_desc, ForwardScanDirection); - - if (HeapTupleIsValid(heap_tuple)) - heap_tuple = update_entity_tuple(resultRelInfo, slot, estate, heap_tuple); - - table_endscan(scan_desc); - } - - estate->es_snapshot->curcid = cid; - /* close relation */ - ExecCloseIndices(resultRelInfo); - table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); - - /* increment loop index */ - lidx++; - } - /* free our lookup array */ - pfree(luindex); -} - -static TupleTableSlot *exec_cypher_set(CustomScanState *node) -{ - cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; - ResultRelInfo **saved_resultRelsInfo; - EState *estate = css->css.ss.ps.state; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *slot; - - saved_resultRelsInfo = estate->es_result_relations; - - //Process the subtree first - Decrement_Estate_CommandId(estate); - slot = ExecProcNode(node->ss.ps.lefttree); - Increment_Estate_CommandId(estate); - - if (TupIsNull(slot)) - { - return NULL; - } - - econtext->ecxt_scantuple = - node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - - if (CYPHER_CLAUSE_IS_TERMINAL(css->flags)) - { - estate->es_result_relations = saved_resultRelsInfo; - - process_all_tuples(node); - - /* increment the command counter to reflect the updates */ - CommandCounterIncrement(); - - return NULL; - } - - process_update_list(node); - - /* increment the command counter to reflect the updates */ - CommandCounterIncrement(); - - estate->es_result_relations = saved_resultRelsInfo; - - econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); - - return ExecProject(node->ss.ps.ps_ProjInfo); -} - -static void end_cypher_set(CustomScanState *node) -{ - ExecEndNode(node->ss.ps.lefttree); -} - -static void rescan_cypher_set(CustomScanState *node) -{ - cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; - char *clause_name = css->set_list->clause_name; - - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cypher %s clause cannot be rescaned", - clause_name), - errhint("its unsafe to use joins in a query with a Cypher %s clause", clause_name))); -} - -Node *create_cypher_set_plan_state(CustomScan *cscan) -{ - cypher_set_custom_scan_state *cypher_css = palloc0(sizeof(cypher_set_custom_scan_state)); - cypher_update_information *set_list; - char *serialized_data; - Const *c; - - cypher_css->cs = cscan; - - // get the serialized data structure from the Const and deserialize it. - c = linitial(cscan->custom_private); - serialized_data = (char *)c->constvalue; - set_list = stringToNode(serialized_data); - - Assert(is_ag_node(set_list, cypher_update_information)); - - cypher_css->set_list = set_list; - cypher_css->flags = set_list->flags; - cypher_css->graph_oid = set_list->graph_oid; - - cypher_css->css.ss.ps.type = T_CustomScanState; - cypher_css->css.methods = &cypher_set_exec_methods; - - return (Node *)cypher_css; -} diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c deleted file mode 100644 index d071818c..00000000 --- a/src/backend/executor/cypher_utils.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * For PostgreSQL Database Management System: - * (formerly known as Postgres, then as Postgres95) - * - * Portions Copyright (c) 1996-2010, The PostgreSQL Global Development Group - * - * Portions Copyright (c) 1994, The Regents of the University of California - * - * Permission to use, copy, modify, and distribute this software and its documentation for any purpose, - * without fee, and without a written agreement is hereby granted, provided that the above copyright notice - * and this paragraph and the following two paragraphs appear in all copies. - * - * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, - * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, - * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY - * OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - * - * THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA - * HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - */ - -#include "postgres.h" - -#include "access/htup_details.h" -#include "access/sysattr.h" -#include "access/xact.h" -#include "access/heapam.h" -#include "access/multixact.h" -#include "access/xact.h" -#include "nodes/extensible.h" -#include "nodes/makefuncs.h" -#include "nodes/nodes.h" -#include "nodes/nodeFuncs.h" -#include "nodes/plannodes.h" -#include "parser/parsetree.h" -#include "parser/parse_relation.h" -#include "storage/procarray.h" -#include "utils/rel.h" - -#include "catalog/ag_label.h" -#include "commands/label_commands.h" -#include "executor/cypher_executor.h" -#include "executor/cypher_utils.h" -#include "utils/gtype.h" -#include "utils/ag_cache.h" -#include "utils/gtype.h" -#include "utils/graphid.h" - -/* - * Given the graph name and the label name, create a ResultRelInfo for the table - * those to variables represent. Open the Indices too. - */ -ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, - char *label_name) -{ - RangeVar *rv; - Relation label_relation; - ResultRelInfo *resultRelInfo; - - ParseState *pstate = make_parsestate(NULL); - - resultRelInfo = palloc(sizeof(ResultRelInfo)); - - if (strlen(label_name) == 0) - { - rv = makeRangeVar(graph_name, AG_DEFAULT_LABEL_VERTEX, -1); - } - else - { - rv = makeRangeVar(graph_name, label_name, -1); - } - - label_relation = parserOpenTable(pstate, rv, RowExclusiveLock); - - // initialize the resultRelInfo - InitResultRelInfo(resultRelInfo, label_relation, - list_length(estate->es_range_table), NULL, - estate->es_instrument); - - // open the parse state - ExecOpenIndices(resultRelInfo, false); - - free_parsestate(pstate); - - return resultRelInfo; -} - -// close the result_rel_info and close all the indices -void destroy_entity_result_rel_info(ResultRelInfo *result_rel_info) -{ - // close the indices - ExecCloseIndices(result_rel_info); - - // close the rel - table_close(result_rel_info->ri_RelationDesc, RowExclusiveLock); -} - -TupleTableSlot *populate_vertex_tts( - TupleTableSlot *elemTupleSlot, gtype_value *id, gtype_value *properties) -{ - bool properties_isnull; - - if (id == NULL) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("vertex id field cannot be NULL"))); - } - - properties_isnull = properties == NULL; - - elemTupleSlot->tts_values[vertex_tuple_id] = GRAPHID_GET_DATUM(id->val.int_value); - elemTupleSlot->tts_isnull[vertex_tuple_id] = false; - - elemTupleSlot->tts_values[vertex_tuple_properties] = - GTYPE_P_GET_DATUM(gtype_value_to_gtype(properties)); - elemTupleSlot->tts_isnull[vertex_tuple_properties] = properties_isnull; - - return elemTupleSlot; -} - -TupleTableSlot *populate_edge_tts( - TupleTableSlot *elemTupleSlot, gtype_value *id, gtype_value *startid, - gtype_value *endid, gtype_value *properties) -{ - bool properties_isnull; - - if (id == NULL) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("edge id field cannot be NULL"))); - } - if (startid == NULL) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("edge start_id field cannot be NULL"))); - } - - if (endid == NULL) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("edge end_id field cannot be NULL"))); - } - - properties_isnull = properties == NULL; - - elemTupleSlot->tts_values[edge_tuple_id] = - GRAPHID_GET_DATUM(id->val.int_value); - elemTupleSlot->tts_isnull[edge_tuple_id] = false; - - elemTupleSlot->tts_values[edge_tuple_start_id] = - GRAPHID_GET_DATUM(startid->val.int_value); - elemTupleSlot->tts_isnull[edge_tuple_start_id] = false; - - elemTupleSlot->tts_values[edge_tuple_end_id] = - GRAPHID_GET_DATUM(endid->val.int_value); - elemTupleSlot->tts_isnull[edge_tuple_end_id] = false; - - elemTupleSlot->tts_values[edge_tuple_properties] = - GTYPE_P_GET_DATUM(gtype_value_to_gtype(properties)); - elemTupleSlot->tts_isnull[edge_tuple_properties] = properties_isnull; - - return elemTupleSlot; -} - - -/* - * Find out if the entity still exists. This is for 'implicit' deletion - * of an entity. - */ -bool entity_exists(EState *estate, Oid graph_oid, graphid id) -{ - label_cache_data *label; - ScanKeyData scan_keys[1]; - TableScanDesc scan_desc; - HeapTuple tuple; - Relation rel; - bool result = true; - - /* - * Extract the label id from the graph id and get the table name - * the entity is part of. - */ - label = search_label_graph_oid_cache(graph_oid, GET_LABEL_ID(id)); - - // Setup the scan key to be the graphid - ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, - F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); - - rel = table_open(label->relation, RowExclusiveLock); - scan_desc = table_beginscan(rel, estate->es_snapshot, 1, scan_keys); - - tuple = heap_getnext(scan_desc, ForwardScanDirection); - - /* - * If a single tuple was returned, the tuple is still valid, otherwise' - * set to false. - */ - if (!HeapTupleIsValid(tuple)) - { - result = false; - } - - table_endscan(scan_desc); - table_close(rel, RowExclusiveLock); - - return result; -} - -/* - * Insert the edge/vertex tuple into the table and indices. Check that the - * table's constraints have not been violated. - * - * This function defaults to, and flags for update, the currentCommandId. If - * you need to pass a specific cid and avoid using the currentCommandId, use - * insert_entity_tuple_cid instead. - */ -HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, - TupleTableSlot *elemTupleSlot, - EState *estate) -{ - return insert_entity_tuple_cid(resultRelInfo, elemTupleSlot, estate, - GetCurrentCommandId(true)); -} - -/* - * Insert the edge/vertex tuple into the table and indices. Check that the - * table's constraints have not been violated. - * - * This function uses the passed cid for updates. - */ -HeapTuple insert_entity_tuple_cid(ResultRelInfo *resultRelInfo, - TupleTableSlot *elemTupleSlot, - EState *estate, CommandId cid) -{ - HeapTuple tuple = NULL; - - ExecStoreVirtualTuple(elemTupleSlot); - tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); - - /* Check the constraints of the tuple */ - tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); - if (resultRelInfo->ri_RelationDesc->rd_att->constr != NULL) - { - ExecConstraints(resultRelInfo, elemTupleSlot, estate); - } - - // Insert the tuple normally - table_tuple_insert(resultRelInfo->ri_RelationDesc, elemTupleSlot, - GetCurrentCommandId(true), 0, NULL); - - // Insert index entries for the tuple - if (resultRelInfo->ri_NumIndices > 0) - { - ExecInsertIndexTuples(resultRelInfo, elemTupleSlot, estate, false, false, NULL, NIL); - } - - return tuple; -} diff --git a/src/backend/optimizer/cypher_createplan.c b/src/backend/optimizer/cypher_createplan.c index c6480d15..32486464 100644 --- a/src/backend/optimizer/cypher_createplan.c +++ b/src/backend/optimizer/cypher_createplan.c @@ -29,12 +29,7 @@ const CustomScanMethods cypher_create_plan_methods = { "Cypher Create", create_cypher_create_plan_state}; -const CustomScanMethods cypher_set_plan_methods = { - "Cypher Set", create_cypher_set_plan_state}; -const CustomScanMethods cypher_delete_plan_methods = { - "Cypher Delete", create_cypher_delete_plan_state}; -const CustomScanMethods cypher_merge_plan_methods = { - "Cypher Merge", create_cypher_merge_plan_state}; + Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, @@ -77,168 +72,3 @@ Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, return (Plan *)cs; } - -Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans) -{ - CustomScan *cs; - Plan *subplan = linitial(custom_plans); - - cs = makeNode(CustomScan); - - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; - - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; - - cs->scan.plan.plan_node_id = 0; // Set later in set_plan_refs - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - cs->custom_private = best_path->custom_private; - cs->custom_scan_tlist = subplan->targetlist; - - cs->custom_relids = NULL; - cs->methods = &cypher_set_plan_methods; - - return (Plan *)cs; -} - -/* - * Coverts the Scan node representing the delete clause - * to the delete Plan node - */ -Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans) -{ - CustomScan *cs; - Plan *subplan = linitial(custom_plans); - - cs = makeNode(CustomScan); - - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; - - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; - - cs->scan.plan.plan_node_id = 0; // Set later in set_plan_refs - /* - * the scan list of the delete node, used for its ScanTupleSlot used - * by its parent in the execution phase. - */ - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - /* - * We do not want Postgres to assume we are scanning a table, postgres' - * optimizer will make assumptions about our targetlist that are false - */ - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - // child plan nodes are here, Postgres processed them for us. - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - // transfer delete metadata needed by the delete clause. - cs->custom_private = best_path->custom_private; - /* - * the scan list of the delete node's children, used for ScanTupleSlot - * in execution. - */ - cs->custom_scan_tlist = subplan->targetlist; - - cs->custom_relids = NULL; - cs->methods = &cypher_delete_plan_methods; - - return (Plan *)cs; -} - -/* - * Coverts the Scan node representing the delete clause - * to the merge Plan node - */ -Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans) -{ - CustomScan *cs; - Plan *subplan = linitial(custom_plans); - - cs = makeNode(CustomScan); - - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; - - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; - - cs->scan.plan.plan_node_id = 0; // Set later in set_plan_refs - /* - * the scan list of the delete node, used for its ScanTupleSlot used - * by its parent in the execution phase. - */ - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - /* - * We do not want Postgres to assume we are scanning a table, postgres' - * optimizer will make assumptions about our targetlist that are false - */ - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - // child plan nodes are here, Postgres processed them for us. - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - // transfer delete metadata needed by the delete clause. - cs->custom_private = best_path->custom_private; - /* - * the scan list of the merge node's children, used for ScanTupleSlot - * in execution. - */ - cs->custom_scan_tlist = subplan->targetlist; - - cs->custom_relids = NULL; - cs->methods = &cypher_merge_plan_methods; - - return (Plan *)cs; -} diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index cdd0b063..3bab7a6e 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -28,12 +28,7 @@ const CustomPathMethods cypher_create_path_methods = { CREATE_PATH_NAME, plan_cypher_create_path, NULL}; -const CustomPathMethods cypher_set_path_methods = { - SET_PATH_NAME, plan_cypher_set_path, NULL}; -const CustomPathMethods cypher_delete_path_methods = { - DELETE_PATH_NAME, plan_cypher_delete_path, NULL}; -const CustomPathMethods cypher_merge_path_methods = { - MERGE_PATH_NAME, plan_cypher_merge_path, NULL}; + CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) @@ -70,125 +65,3 @@ CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, return cp; } - -CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, - List *custom_private) -{ - CustomPath *cp; - - cp = makeNode(CustomPath); - - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - // Do not allow parallel methods - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->path.rows = 0; // Basic SET will not return rows - cp->path.startup_cost = 0; // Basic SET will not fetch any pages - cp->path.total_cost = 0; - - // No output ordering for basic SET - cp->path.pathkeys = NULL; - - // Disable all custom flags for now - cp->flags = 0; - - cp->custom_paths = rel->pathlist; - cp->custom_private = custom_private; - cp->methods = &cypher_set_path_methods; - - return cp; -} - -/* - * Creates a Delete Path. Makes the original path a child of the new - * path. We leave it to the caller to replace the pathlist of the rel. - */ -CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, - List *custom_private) -{ - CustomPath *cp; - - cp = makeNode(CustomPath); - - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - // Do not allow parallel methods - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->path.rows = 0; - cp->path.startup_cost = 0; - cp->path.total_cost = 0; - - // No output ordering for basic SET - cp->path.pathkeys = NULL; - - // Disable all custom flags for now - cp->flags = 0; - - // Make the original paths the children of the new path - cp->custom_paths = rel->pathlist; - // Store the metadata Delete will need in the execution phase. - cp->custom_private = custom_private; - // Tells Postgres how to turn this path to the correct CustomScan - cp->methods = &cypher_delete_path_methods; - - return cp; -} - -/* - * Creates a Delete Path. Makes the original path a child of the new - * path. We leave it to the caller to replace the pathlist of the rel. - */ -CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, - List *custom_private) -{ - CustomPath *cp; - - cp = makeNode(CustomPath); - - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - // Do not allow parallel methods - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->path.rows = 0; - cp->path.startup_cost = 0; - cp->path.total_cost = 0; - - // No output ordering for basic SET - cp->path.pathkeys = NULL; - - // Disable all custom flags for now - cp->flags = 0; - - // Make the original paths the children of the new path - cp->custom_paths = rel->pathlist; - // Store the metadata Delete will need in the execution phase. - cp->custom_private = custom_private; - // Tells Postgres how to turn this path to the correct CustomScan - cp->methods = &cypher_merge_path_methods; - - return cp; -} diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index 80d91612..b52db827 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -44,12 +44,6 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte); static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); -static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); -static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); -static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); void set_rel_pathlist_init(void) { @@ -73,17 +67,6 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, case CYPHER_CLAUSE_CREATE: handle_cypher_create_clause(root, rel, rti, rte); break; - case CYPHER_CLAUSE_SET: - handle_cypher_set_clause(root, rel, rti, rte); - break; - case CYPHER_CLAUSE_DELETE: - handle_cypher_delete_clause(root, rel, rti, rte); - break; - case CYPHER_CLAUSE_MERGE: - handle_cypher_merge_clause(root, rel, rti, rte); - break; - case CYPHER_CLAUSE_NONE: - break; default: ereport(ERROR, (errmsg_internal("invalid cypher_clause_kind"))); } @@ -118,45 +101,10 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte) if (is_oid_ag_func(fe->funcid, CREATE_CLAUSE_FUNCTION_NAME)) return CYPHER_CLAUSE_CREATE; - if (is_oid_ag_func(fe->funcid, SET_CLAUSE_FUNCTION_NAME)) - return CYPHER_CLAUSE_SET; - if (is_oid_ag_func(fe->funcid, DELETE_CLAUSE_FUNCTION_NAME)) - return CYPHER_CLAUSE_DELETE; - if (is_oid_ag_func(fe->funcid, MERGE_CLAUSE_FUNCTION_NAME)) - return CYPHER_CLAUSE_MERGE; else return CYPHER_CLAUSE_NONE; } -// replace all possible paths with our CustomPath -static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) -{ - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; - - // Add the pattern to the CustomPath - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - // pass the const that holds the data structure to the path. - custom_private = fe->args; - - cp = create_cypher_delete_path(root, rel, custom_private); - - // Discard any pre-existing paths - rel->pathlist = NIL; - rel->partial_pathlist = NIL; - - add_path(rel, (Path *)cp); -} - -/* - * Take the paths possible for the RelOptInfo that represents our - * _cypher_delete_clause function replace them with our delete clause - * path. The original paths will be children to the new delete path. - */ static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { @@ -180,51 +128,3 @@ static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, // Add the new path to the rel. add_path(rel, (Path *)cp); } - -// replace all possible paths with our CustomPath -static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) -{ - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; - - // Add the pattern to the CustomPath - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - // pass the const that holds the data structure to the path. - custom_private = fe->args; - - cp = create_cypher_set_path(root, rel, custom_private); - - // Discard any pre-existing paths - rel->pathlist = NIL; - rel->partial_pathlist = NIL; - - add_path(rel, (Path *)cp); -} - -// replace all possible paths with our CustomPath -static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) -{ - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; - - // Add the pattern to the CustomPath - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - // pass the const that holds the data structure to the path. - custom_private = fe->args; - - cp = create_cypher_merge_path(root, rel, custom_private); - - // Discard any pre-existing paths - rel->pathlist = NIL; - rel->partial_pathlist = NIL; - - add_path(rel, (Path *)cp); -} diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 13a9b379..c2c1837c 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -50,87 +50,24 @@ #include "utils/traversal.h" #include "utils/variable_edge.h" -#define INCLUDE_NODE_IN_JOIN_TREE(path, node) \ - (path->var_name || node->name || node->props) + typedef Query *(*transform_method)(cypher_parsestate *cpstate, cypher_clause *clause); // projection -static Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause); + static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, ParseExprKind expr_kind); static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, ParseExprKind expr_kind, const char *construct_name); -static Query *transform_cypher_with(cypher_parsestate *cpstate, cypher_clause *clause); static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause); // match clause static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause); -static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause); -static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, cypher_path *path); static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where); -static List *transform_match_path(cypher_parsestate *cpstate, Query *query, cypher_path *path); -static Expr *transform_cypher_edge(cypher_parsestate *cpstate, cypher_relationship *rel, List **target_list); -static Expr *transform_cypher_node(cypher_parsestate *cpstate, cypher_node *node, List **target_list, bool output_node); -static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); -static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); -static Node *make_qual(cypher_parsestate *cpstate, transform_entity *entity, char *name); -static TargetEntry * transform_match_create_path_variable(cypher_parsestate *cpstate, cypher_path *path, List *entities); -static List *make_path_join_quals(cypher_parsestate *cpstate, List *entities); -static List *make_directed_edge_join_conditions( cypher_parsestate *cpstate, transform_entity *prev_entity, transform_entity *next_entity, Node *prev_qual, Node *next_qual, char *prev_node_label, char *next_node_label); -static List *join_to_entity(cypher_parsestate *cpstate, transform_entity *entity, Node *qual, enum transform_entity_join_side side); -static List *make_join_condition_for_edge(cypher_parsestate *cpstate, transform_entity *prev_edge, transform_entity *prev_node, transform_entity *entity, transform_entity *next_node, transform_entity *next_edge); -static List *make_edge_quals(cypher_parsestate *cpstate, transform_entity *edge, enum transform_entity_join_side side); -static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, Node *id_field, char *label); -static Node *create_property_constraints(cypher_parsestate *cpstate, transform_entity *entity, Node *property_constraints); -static TargetEntry *findTarget(List *targetList, char *resname); -static transform_entity *transform_VLE_edge_entity(cypher_parsestate *cpstate, cypher_relationship *rel, Query *query, FuncCall *func); -static ParseNamespaceItem *add_vle_to_query(cypher_parsestate *cpstate, Node *n); +static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause); + // create clause static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause); -static List *transform_cypher_create_pattern(cypher_parsestate *cpstate, Query *query, List *pattern); -static cypher_create_path * transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, cypher_path *cp); -static cypher_target_node * transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, cypher_node *node); static cypher_target_node * -transform_create_cypher_new_node(cypher_parsestate *cpstate, List **target_list, cypher_node *node); -static cypher_target_node *transform_create_cypher_existing_node( cypher_parsestate *cpstate, List **target_list, bool declared_in_current_clause, cypher_node *node); -static cypher_target_node * -transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, cypher_relationship *edge); -static Expr *cypher_create_properties(cypher_parsestate *cpstate, cypher_target_node *rel, Relation label_relation, Node *props, enum transform_entity_type type); -static Expr *add_volatile_wrapper(Expr *node); -static Expr *add_volatile_edge_wrapper(Expr *node); -static Expr *add_volatile_vertex_wrapper(Expr *node); -static Expr *add_volatile_vle_edge_wrapper(Expr *node); -static bool variable_exists(cypher_parsestate *cpstate, char *name); -static int get_target_entry_resno(cypher_parsestate *cpstate, List *target_list, char *name); -static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, bool first_rte); -static Query *transform_cypher_sub_pattern(cypher_parsestate *cpstate, cypher_clause *clause); -// set and remove clause -static Query *transform_cypher_set(cypher_parsestate *cpstate, cypher_clause *clause); -static cypher_update_information *transform_cypher_set_item_list(cypher_parsestate *cpstate, List *set_item_list, Query *query); -static cypher_update_information *transform_cypher_remove_item_list(cypher_parsestate *cpstate, List *remove_item_list, Query *query); -// delete -static Query *transform_cypher_delete(cypher_parsestate *cpstate, cypher_clause *clause); -static List *transform_cypher_delete_item_list(cypher_parsestate *cpstate, List *delete_item_list, Query *query); -//set operators -static Query *transform_cypher_union(cypher_parsestate *cpstate, cypher_clause *clause); -static Node * transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause, bool isTopLevel, List **targetlist); -Query *cypher_parse_sub_analyze_union(cypher_clause *clause, cypher_parsestate *cpstate, CommonTableExpr *parentCTE, bool locked_from_parent, bool resolve_unknowns); static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, ParseNamespaceItem *r_pnsi, List **res_colnames, List **res_colvars); -// unwind -static Query *transform_cypher_unwind(cypher_parsestate *cpstate, cypher_clause *clause); -// merge -static Query *transform_cypher_merge(cypher_parsestate *cpstate, cypher_clause *clause); -static cypher_create_path *transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, cypher_clause *isolated_merge_clause); -static cypher_create_path *transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, cypher_path *path); -static cypher_target_node *transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, cypher_relationship *edge); -static cypher_target_node *transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, cypher_node *node); -static Node *transform_clause_for_join(cypher_parsestate *cpstate, cypher_clause *clause, RangeTblEntry **rte, ParseNamespaceItem **nsitem, Alias* alias); -static cypher_clause *convert_merge_to_match(cypher_merge *merge); -static void transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate, List *target_list, cypher_create_path *path); -static TargetEntry *placeholder_vertex(cypher_parsestate *cpstate, char *name); -static TargetEntry *placeholder_edge(cypher_parsestate *cpstate, char *name); -static TargetEntry *placeholder_traversal(cypher_parsestate *cpstate, char *name); -// call -static Query *transform_cypher_call(cypher_parsestate *cpstate, cypher_clause *clause); -// load csv -static Query *transform_cypher_load_csv(cypher_parsestate *cpstate, cypher_clause *clause); + // transform #define PREV_CYPHER_CLAUSE_ALIAS "_" #define CYPHER_OPT_RIGHT_ALIAS "_R" @@ -139,33 +76,14 @@ static Query *transform_cypher_load_csv(cypher_parsestate *cpstate, cypher_claus prev_clause, NULL, add_rte_to_query) static ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, Alias *alias, bool add_rte_to_query); static Query *analyze_cypher_clause(transform_method transform, cypher_clause *clause, cypher_parsestate *parent_cpstate); -static List *transform_group_clause(cypher_parsestate *cpstate, List *grouplist, List **groupingSets, List **targetlist, List *sortClause, ParseExprKind exprKind); -static Node *flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets); -static Index transform_group_clause_expr(List **flatresult, Bitmapset *seen_local, cypher_parsestate *cpstate, Node *gexpr, List **targetlist, List *sortClause, ParseExprKind exprKind, bool toplevel); -static List *add_target_to_group_list(cypher_parsestate *cpstate, TargetEntry *tle, List *grouplist, List *targetlist, int location); static void advance_transform_entities_to_next_clause(List *entities); static ParseNamespaceItem *get_namespace_item(ParseState *pstate, RangeTblEntry *rte); static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); -static Expr *add_volatile_wrapper(Expr *node); -static FuncExpr *make_clause_func_expr(char *function_name, Node *clause_information); -static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, RangeFunction *r); -static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace); static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); - -static Node * -transform_cypher_union_tree_label(cypher_parsestate *cpstate, bool isTopLevel, List **targetlist, cypher_label_tree_node *label_tree_node); -// utils XXX: Should be moved into its own file -RangeFunction *make_range_function(FuncCall *func, Alias *alias, bool is_lateral, bool ordinality, bool is_rows_from); -Alias *make_alias(char *name, List *colnames); -static char *make_property_alias(char *var_name); -static char *make_id_alias(char *var_name); -static char *make_startid_alias(char *var_name); -static char *make_endid_alias(char *var_name); List * transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); -static ParseNamespaceItem * -transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, cypher_clause *clause, Alias *alias, bool add_rte_to_query, Query *query); + /* * transform a cypher_clause */ @@ -179,30 +97,14 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause if (n->op == SETOP_NONE) result = transform_cypher_return(cpstate, clause); - else if (n->op == SETOP_UNION || n->op == SETOP_INTERSECT || n->op == SETOP_EXCEPT) - result = transform_cypher_union(cpstate, clause); else ereport(ERROR, (errmsg_internal("unexpected Node for cypher_return"))); } else if (is_ag_node(self, cypher_with)) { result = transform_cypher_with(cpstate, clause); - } else if (is_ag_node(self, cypher_call)) { - result = transform_cypher_call(cpstate, clause); } else if (is_ag_node(self, cypher_match)) { result = transform_cypher_match(cpstate, clause); } else if (is_ag_node(self, cypher_create)) { result = transform_cypher_create(cpstate, clause); - } else if (is_ag_node(self, cypher_set)) { - result = transform_cypher_set(cpstate, clause); - } else if (is_ag_node(self, cypher_delete)) { - result = transform_cypher_delete(cpstate, clause); - } else if (is_ag_node(self, cypher_merge)) { - result = transform_cypher_merge(cpstate, clause); - } else if (is_ag_node(self, cypher_sub_pattern)) { - result = transform_cypher_sub_pattern(cpstate, clause); - } else if (is_ag_node(self, cypher_unwind)) { - result = transform_cypher_unwind(cpstate, clause); - } else if (is_ag_node(self, cypher_load_csv)) { - result = transform_cypher_load_csv(cpstate, clause); } else { ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); } @@ -212,5525 +114,621 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause return result; } - -static A_Expr * -makeSimpleCypherA_Expr(A_Expr_Kind kind, char *name, - Node *lexpr, Node *rexpr, int location) -{ - A_Expr *a = makeNode(A_Expr); - - a->kind = kind; - a->name = list_make2(makeString("postgraph"), makeString((char *) name)); - a->lexpr = lexpr; - a->rexpr = rexpr; - a->location = location; - return a; +static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause) { + ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); } -/* - * Transform the UNION operator/clause. Creates a cypher_union - * node and the necessary information needed in the execution - * phase - */ -static cypher_clause *make_cypher_clause(List *stmt) { - cypher_clause *clause; - ListCell *lc; - - /* - * Since the first clause in stmt is the innermost subquery, the order of - * the clauses is inverted. - */ - clause = NULL; - foreach (lc, stmt) { - cypher_clause *next; - - next = palloc0(sizeof(*next)); - next->next = NULL; - next->self = lfirst(lc); - next->prev = clause; - - if (clause != NULL) - clause->next = next; - clause = next; - } - return clause; -} -static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace); -static ParseNamespaceItem *add_srf_to_query(cypher_parsestate *cpstate, Node *n, char *var_name) { - RangeTblEntry *rte = NULL; - RangeTblRef *rtr; - List *namespace = NULL; - int rtindex; +Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause) { ParseState *pstate = (ParseState *)cpstate; + cypher_return *self = (cypher_return *)clause->self; + Query *query; + List *groupClause = NIL; - Alias *alias = make_alias(var_name, NIL); - RangeFunction *rf = make_range_function(n, alias, true, false, false); + query = makeNode(Query); + query->commandType = CMD_SELECT; + pstate->p_windowdefs = self->window_clause; - rtr = transform_srf_function(cpstate, rf, &rte, &rtindex, &namespace); - Assert(rtr != NULL); + if (clause->prev) + transform_prev_cypher_clause(cpstate, clause->prev, true); - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + query->targetList = transform_cypher_item_list(cpstate, self->items, &groupClause, EXPR_KIND_SELECT_TARGET); - setNamespaceLateralState(namespace, true, true); + markTargetListOrigins(pstate, query->targetList); - pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); - pstate->p_namespace = list_concat(pstate->p_namespace, namespace); + Expr *expr = NULL; + if (self->where != NULL) + expr = transform_cypher_expr(cpstate, self->where, EXPR_KIND_WHERE); - setNamespaceLateralState(pstate->p_namespace, true, true); + // ORDER BY + query->sortClause = transform_cypher_order_by(cpstate, self->order_by, &query->targetList, EXPR_KIND_ORDER_BY); - return lfirst(list_head(namespace)); -} + if (self->real_group_clause != NIL) + query->groupClause = transform_group_clause(cpstate, self->real_group_clause, &query->groupingSets, + &query->targetList, + query->sortClause, EXPR_KIND_GROUP_BY); + else if (groupClause != NIL) // auto GROUP BY + query->groupClause = transform_group_clause(cpstate, groupClause, &query->groupingSets, &query->targetList, + query->sortClause, EXPR_KIND_GROUP_BY); + else + query->groupClause = NULL; -static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace) { - ParseState *pstate = (ParseState *)cpstate; - - Assert(IsA(n, RangeFunction)); - - if (IsA(n, RangeFunction)) { - RangeTblRef *rtr; - RangeTblEntry *rte; - ParseNamespaceItem *nsitem; - int rtindex; - - nsitem = transform_RangeFunction(cpstate, (RangeFunction *) n); - rte = nsitem->p_rte; - rtindex = list_length(pstate->p_rtable); - Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); - *top_rte = rte; - *top_rti = rtindex; - *namespace = list_make1(nsitem); - rtr = makeNode(RangeTblRef); - rtr->rtindex = rtindex; - return (Node *) rtr; + if (self->having) { + query->havingQual = transform_cypher_expr(cpstate, self->having, EXPR_KIND_HAVING); + } + // DISTINCT + if (self->distinct) { + query->distinctClause = transformDistinctClause(pstate, &query->targetList, query->sortClause, false); + query->hasDistinctOn = false; + } else { + query->distinctClause = NIL; + query->hasDistinctOn = false; } - - return NULL; -} - -static Query *transform_cypher_load_csv(cypher_parsestate *cpstate, cypher_clause *clause) { - cypher_parsestate *child_cpstate = make_cypher_parsestate(cpstate); - ParseState *pstate = (ParseState *) child_cpstate; - cypher_load_csv *load = clause->self; - TargetEntry *te; - ParseNamespaceItem *pnsi; - FuncCall *fc = makeFuncCall(list_make2(makeString("postgraph"), makeString("cypher_load_csv")), - list_make1(makeString(load->file)), - COERCE_SQL_SYNTAX, - -1); - - Query *query = makeNode(Query); - query->commandType = CMD_SELECT; - - /*if (clause->prev) { - int rtindex; - - pnsi = transform_prev_cypher_clause(child_cpstate, clause->prev, true); - rtindex = list_length(pstate->p_rtable); - Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate - query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1); - }*/ - - Expr *expr = NULL; - //if (!call->where) - { - expr = transform_cypher_expr(child_cpstate, fc, EXPR_KIND_SELECT_TARGET); - //te = makeTargetEntry((Expr *) expr, (AttrNumber) pstate->p_next_resno++, call->alias, false); - te = makeTargetEntry((Expr *) expr, (AttrNumber) pstate->p_next_resno++, "test", false); - - expr = NULL; - query->targetList = lappend(query->targetList, te); - } /*else { - pnsi = add_srf_to_query(child_cpstate, call->func, call->alias); - Node *var = scanNSItemForColumn(pstate, pnsi, 0, call->alias, -1); - te = makeTargetEntry((Expr *) var, (AttrNumber) pstate->p_next_resno++, call->alias, false); - query->targetList = lappend(query->targetList, te); - - transform_entity *entity = make_transform_entity(child_cpstate, ENT_FUNC_CALL, NULL, (Node *)var, call->alias); - child_cpstate->entities = lappend(child_cpstate->entities, entity); + // SKIP and LIMIT + query->limitOffset = transform_cypher_limit(cpstate, self->skip, EXPR_KIND_OFFSET, "SKIP"); + query->limitCount = transform_cypher_limit(cpstate, self->limit, EXPR_KIND_LIMIT, "LIMIT"); - expr = transform_cypher_expr(child_cpstate, call->where, EXPR_KIND_WHERE); - }*/ + if (pstate->p_windowdefs != NIL) { + //ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("window functions are not done"))); + query->windowClause = transform_window_definitions(pstate, pstate->p_windowdefs, &query->targetList); + } query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, expr); + query->hasWindowFuncs = pstate->p_hasWindowFuncs; query->hasTargetSRFs = pstate->p_hasTargetSRFs; + query->hasSubLinks = pstate->p_hasSubLinks; + query->hasAggs = pstate->p_hasAggs; assign_query_collations(pstate, query); - free_cypher_parsestate(child_cpstate); + // this must be done after collations, for reliable comparison of exprs + if (pstate->p_hasAggs || query->groupClause || query->groupingSets || query->havingQual) + parse_check_aggregates(pstate, query); + return query; } -static Query *transform_cypher_call(cypher_parsestate *cpstate, cypher_clause *clause) { - cypher_parsestate *child_cpstate = make_cypher_parsestate(cpstate); - ParseState *pstate = (ParseState *) child_cpstate; - cypher_call *call = clause->self; - TargetEntry *te; - ParseNamespaceItem *pnsi; +// see findTargetlistEntrySQL99() +static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, + ParseExprKind expr_kind) { + Node *expr; + ListCell *lt; + TargetEntry *te; - Query *query = makeNode(Query); - query->commandType = CMD_SELECT; - + expr = transform_cypher_expr(cpstate, node, expr_kind); - if (call->cck == CCK_CYPHER_SUBQUERY) { - cypher_clause *call_clause = make_cypher_clause(call->cypher); + foreach (lt, *target_list) { + Node *te_expr; - //ereport(ERROR, (errmsg_internal("Call only supports set-returning functions at this time"))); - Node *self = call_clause->self; + te = lfirst(lt); + te_expr = strip_implicit_coercions((Node *)te->expr); - // examine the type of clause and call the transform logic for it - if (!is_ag_node(self, cypher_return)) - ereport(ERROR, (errmsg_internal("Call Cypher Subquery must end with a RETURN"))); + if (equal(expr, te_expr)) + return te; + } - if (clause->prev) { -ereport(ERROR, (errmsg_internal("Call only supports set-returning functions at this time"))); - /* RangeTblEntry *rte; - int rtindex; - ParseNamespaceItem *pnsi; + te = transform_cypher_item(cpstate, node, expr, expr_kind, NULL, true); - pnsi = transform_prev_cypher_clause(child_cpstate, clause->prev, true); - rte = pnsi->p_rte; - rtindex = list_length(pstate->p_rtable); - Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate + *target_list = lappend(*target_list, te); - pnsi = get_namespace_item(pstate, rte); - query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1); - transform_match_pattern(child_cpstate, query, self->pattern, where); - } else if () { - RangeTblEntry *rte = transform_cypher_match_clause(cpstate, clause); + return te; +} - query->targetList = make_target_list_from_join(pstate, rte); - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - */ - } else { - return transform_cypher_clause(child_cpstate, call_clause); - } +// see transformSortClause() +List *transform_cypher_order_by(cypher_parsestate *cpstate, List *sort_items, List **target_list, + ParseExprKind expr_kind) { + ParseState *pstate = (ParseState *)cpstate; + List *sort_list = NIL; + ListCell *li; - markTargetListOrigins(pstate, query->targetList); + foreach (li, sort_items) { + SortBy *sort_by = lfirst(li); + TargetEntry *te; - query->hasSubLinks = pstate->p_hasSubLinks; - query->hasWindowFuncs = pstate->p_hasWindowFuncs; - query->hasTargetSRFs = pstate->p_hasTargetSRFs; - query->hasAggs = pstate->p_hasAggs; + te = find_target_list_entry(cpstate, sort_by->node, target_list, expr_kind); - assign_query_collations(pstate, query); + sort_list = addTargetToSortList(pstate, te, sort_list, *target_list, sort_by); + } - return query; + return sort_list; +} - } else { - if (call->cck != CCK_FUNCTION) - ereport(ERROR, (errmsg_internal("Call only supports set-returning functions at this time"))); - if (clause->prev) { - int rtindex; +// see transformLimitClause() +static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, ParseExprKind expr_kind, + const char *construct_name) { + ParseState *pstate = (ParseState *)cpstate; + Node *qual; - pnsi = transform_prev_cypher_clause(child_cpstate, clause->prev, true); - rtindex = list_length(pstate->p_rtable); - Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate - query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1); - } + if (!node) + return NULL; - Expr *expr = NULL; - if (!call->where) { - expr = transform_cypher_expr(child_cpstate, call->func, EXPR_KIND_SELECT_TARGET); - te = makeTargetEntry((Expr *) expr, (AttrNumber) pstate->p_next_resno++, call->alias, false); + qual = transform_cypher_expr(cpstate, node, expr_kind); - expr = NULL; - query->targetList = lappend(query->targetList, te); - } else { - pnsi = add_srf_to_query(child_cpstate, call->func, call->alias); - Node *var = scanNSItemForColumn(pstate, pnsi, 0, call->alias, -1); - te = makeTargetEntry((Expr *) var, (AttrNumber) pstate->p_next_resno++, call->alias, false); - query->targetList = lappend(query->targetList, te); + qual = coerce_to_specific_type(pstate, qual, INT8OID, construct_name); - transform_entity *entity = make_transform_entity(child_cpstate, ENT_FUNC_CALL, NULL, (Node *)var, call->alias); - child_cpstate->entities = lappend(child_cpstate->entities, entity); + // LIMIT can't refer to any variables of the current query. + if (contain_vars_of_level(qual, 0)) + ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("argument of %s must not contain variables", construct_name), + parser_errposition(pstate, locate_var_of_level(qual, 0)))); + return qual; +} - expr = transform_cypher_expr(child_cpstate, call->where, EXPR_KIND_WHERE); - } +Query *transform_cypher_with(cypher_parsestate *cpstate, cypher_clause *clause) { + cypher_with *self = (cypher_with *)clause->self; + cypher_return *return_clause; + cypher_clause *wrapper; - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, expr); - query->hasTargetSRFs = pstate->p_hasTargetSRFs; + // WITH clause is basically RETURN clause with optional WHERE subclause + return_clause = make_ag_node(cypher_return); + return_clause->distinct = self->distinct; + return_clause->items = self->items; + return_clause->real_group_clause = self->real_group_clause; + return_clause->having = self->having; + return_clause->window_clause = self->window_clause; + return_clause->order_by = self->order_by; + return_clause->skip = self->skip; + return_clause->limit = self->limit; + return_clause->where = self->where; - assign_query_collations(pstate, query); + wrapper = palloc(sizeof(*wrapper)); + wrapper->self = (Node *)return_clause; + wrapper->prev = clause->prev; - free_cypher_parsestate(child_cpstate); - return query; - } + return transform_cypher_clause_with_where(cpstate, transform_cypher_return, wrapper); } -/* - * transform_cypher_union - - * transforms a union tree, derived from postgresql's - * transformSetOperationStmt. A lot of the general logic is similar, - * with adjustments made for AGE. - * - * A union tree is just a return, but with UNION structure to it. - * We must transform each leaf SELECT and build up a top-level Query - * that contains the leaf SELECTs as subqueries in its rangetable. - * The tree of unions is converted into the setOperations field of - * the top-level Query. - */ -static Query *transform_cypher_union(cypher_parsestate *cpstate, cypher_clause *clause) { +static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, transform_method transform, + cypher_clause *clause) +{ ParseState *pstate = (ParseState *)cpstate; - Query *qry = makeNode(Query); - int leftmostRTI; - Query *leftmostQuery; - SetOperationStmt *cypher_union_statement; - Node *skip = NULL; // equivalent to postgres limitOffset - Node *limit = NULL; // equivalent to postgres limitCount - List *order_by = NIL; - Node *node; - cypher_return *self = (cypher_return *)clause->self; - ListCell *left_tlist, *lct, *lcm, *lcc; - List *targetvars, *targetnames, *sv_namespace; - int sv_rtable_length; - int tllen; - ParseNamespaceItem *nsitem; - ParseNamespaceColumn *sortnscolumns; - int sortcolindex; - - qry->commandType = CMD_SELECT; + Query *query; + cypher_match *self = (cypher_match *)clause->self; + Node *where = self->where; - /* - * Union is a node that should never have a previous node because - * of where it is used in the parse logic. The query parts around it - * are children located in larg or rarg. Something went wrong if the - * previous clause field is not null. - */ - if (clause->prev) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Union is a parent node, there are no previous"), - parser_errposition(&cpstate->pstate, 0))); + if (where) { + int rtindex; + ParseNamespaceItem *pnsi; - order_by = self->order_by; - skip = self->skip; - limit = self->limit; + query = makeNode(Query); + query->commandType = CMD_SELECT; - self->order_by = NIL; - self->skip = NULL; - self->limit = NULL; + pnsi = transform_cypher_clause_as_subquery(cpstate, transform, clause, NULL, true); + Assert(pnsi != NULL); + rtindex = list_length(pstate->p_rtable); + Assert(rtindex == 1); // rte is the only RangeTblEntry in pstate - // Recursively transform the components of the tree. - cypher_union_statement = (SetOperationStmt *) transform_cypher_union_tree(cpstate, clause, true, NULL); + query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1); - Assert(cypher_union_statement); - qry->setOperations = (Node *) cypher_union_statement; + markTargetListOrigins(pstate, query->targetList); - /* - * Re-find leftmost return (now it's a sub-query in rangetable) - */ - node = cypher_union_statement->larg; - while (node && IsA(node, SetOperationStmt)) - node = ((SetOperationStmt *) node)->larg; - - Assert(node && IsA(node, RangeTblRef)); - leftmostRTI = ((RangeTblRef *) node)->rtindex; - leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery; - Assert(leftmostQuery != NULL); + query->rtable = pstate->p_rtable; + query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - /* - * Generate dummy targetlist for outer query using column names of - * leftmost return and common datatypes/collations of topmost set - * operation. Also make lists of the dummy vars and their names for use - * in parsing ORDER BY. - * - * Note: we use leftmostRTI as the varno of the dummy variables. It - * shouldn't matter too much which RT index they have, as long as they - * have one that corresponds to a real RT entry; else funny things may - * happen when the tree is mashed by rule rewriting. - */ - qry->targetList = NIL; - targetvars = NIL; - targetnames = NIL; - sortnscolumns = (ParseNamespaceColumn *) - palloc0(list_length(cypher_union_statement->colTypes) * sizeof(ParseNamespaceColumn)); - sortcolindex = 0; - - forfour(lct, cypher_union_statement->colTypes, lcm, cypher_union_statement->colTypmods, - lcc, cypher_union_statement->colCollations, left_tlist, leftmostQuery->targetList) { - Oid colType = lfirst_oid(lct); - int32 colTypmod = lfirst_int(lcm); - Oid colCollation = lfirst_oid(lcc); - TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist); - char *colName; - TargetEntry *tle; - Var *var; - - Assert(!lefttle->resjunk); - colName = pstrdup(lefttle->resname); - var = makeVar(leftmostRTI, lefttle->resno, colType, colTypmod, colCollation, 0); - var->location = exprLocation((Node *) lefttle->expr); - tle = makeTargetEntry((Expr *) var, (AttrNumber) pstate->p_next_resno++, colName, false); - qry->targetList = lappend(qry->targetList, tle); - targetvars = lappend(targetvars, var); - targetnames = lappend(targetnames, makeString(colName)); - sortnscolumns[sortcolindex].p_varno = leftmostRTI; - sortnscolumns[sortcolindex].p_varattno = lefttle->resno; - sortnscolumns[sortcolindex].p_vartype = colType; - sortnscolumns[sortcolindex].p_vartypmod = colTypmod; - sortnscolumns[sortcolindex].p_varcollid = colCollation; - sortnscolumns[sortcolindex].p_varnosyn = leftmostRTI; - sortnscolumns[sortcolindex].p_varattnosyn = lefttle->resno; - sortcolindex++; + assign_query_collations(pstate, query); + } else { + query = transform(cpstate, clause); } - /* - * As a first step towards supporting sort clauses that are expressions - * using the output columns, generate a namespace entry that makes the - * output columns visible. A Join RTE node is handy for this, since we - * can easily control the Vars generated upon matches. - * - * Note: we don't yet do anything useful with such cases, but at least - * "ORDER BY upper(foo)" will draw the right error message rather than - * "foo not found". - */ - sv_rtable_length = list_length(pstate->p_rtable); - - nsitem = addRangeTableEntryForJoin(pstate, targetnames, sortnscolumns, JOIN_INNER, 0, targetvars, NIL, NIL, NULL, NULL, false); - - sv_namespace = pstate->p_namespace; - pstate->p_namespace = NIL; + query->hasSubLinks = pstate->p_hasSubLinks; + query->hasTargetSRFs = pstate->p_hasTargetSRFs; + query->hasAggs = pstate->p_hasAggs; - // add jrte to column namespace only - addNSItemToQuery(pstate, nsitem, false, false, true); + return query; +} - tllen = list_length(qry->targetList); +static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause) { + return transform_cypher_clause_with_where(cpstate, transform_cypher_match_pattern, clause); +} - qry->sortClause = transformSortClause(pstate, order_by, &qry->targetList, EXPR_KIND_ORDER_BY, false); +/* + * Transform the clause into a subquery. This subquery will be used + * in a join so setup the namespace item and the created the rtr + * for the join to use. + */ +static Node *transform_clause_for_join(cypher_parsestate *cpstate, cypher_clause *clause, + RangeTblEntry **rte, ParseNamespaceItem **nsitem, Alias* alias) { + RangeTblRef *rtr; - // restore namespace, remove jrte from rtable - pstate->p_namespace = sv_namespace; - pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length); + *nsitem = transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, clause, alias, false); + *rte = (*nsitem)->p_rte; - if (tllen != list_length(qry->targetList)) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid UNION ORDER BY clause"), - errdetail("Only result column names can be used, not expressions or functions."), - parser_errposition(pstate, exprLocation(list_nth(qry->targetList, tllen))))); + rtr = makeNode(RangeTblRef); + rtr->rtindex = (*nsitem)->p_rtindex; - qry->limitOffset = transform_cypher_limit(cpstate, skip, EXPR_KIND_OFFSET, "OFFSET"); - qry->limitCount = transform_cypher_limit(cpstate, limit, EXPR_KIND_LIMIT, "LIMIT"); - - qry->rtable = pstate->p_rtable; - qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); - qry->hasAggs = pstate->p_hasAggs; - - assign_query_collations(pstate, qry); - - // this must be done after collations, for reliable comparison of exprs - if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual) - parse_check_aggregates(pstate, qry); - - return qry; - -} - -/* - * transform_cypher_union_tree - * Recursively transform leaves and internal nodes of a set-op tree, - * derived from postgresql's transformSetOperationTree. A lot of - * the general logic is similar, with adjustments made for AGE. - * - * In addition to returning the transformed node, if targetlist isn't NULL - * then we return a list of its non-resjunk TargetEntry nodes. For a leaf - * set-op node these are the actual targetlist entries; otherwise they are - * dummy entries created to carry the type, typmod, collation, and location - * (for error messages) of each output column of the set-op node. This info - * is needed only during the internal recursion of this function, so outside - * callers pass NULL for targetlist. Note: the reason for passing the - * actual targetlist entries of a leaf node is so that upper levels can - * replace UNKNOWN Consts with properly-coerced constants. - */ -static Node * -transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause, bool isTopLevel, List **targetlist) { - bool isLeaf; - - ParseState *pstate = (ParseState *)cpstate; - cypher_return *cmp; - ParseNamespaceItem *pnsi; - - // Guard against queue overflow due to overly complex set-expressions - check_stack_depth(); - - if (IsA(clause, List)) - clause = make_cypher_clause((List *)clause); - - if (is_ag_node(clause->self, cypher_return)) - cmp = (cypher_return *) clause->self; - else - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Cypher found an unsupported node"), - parser_errposition(pstate, 0))); - - if (cmp->op == SETOP_NONE) { - Assert(cmp->larg == NULL && cmp->rarg == NULL); - isLeaf = true; - } else if (cmp->op == SETOP_UNION | cmp->op == SETOP_EXCEPT | cmp->op == SETOP_INTERSECT) { - Assert(cmp->larg != NULL && cmp->rarg != NULL); - if (cmp->order_by || cmp->limit || cmp->skip) - isLeaf = true; - else - isLeaf = false; - } else { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Cypher found an unsupported SETOP"), - parser_errposition(pstate, 0))); - } - - if (isLeaf) { - //process leaf return - Query *returnQuery; - char returnName[32]; - RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY; - RangeTblRef *rtr; - ListCell *tl; - - /* - * Transform SelectStmt into a Query. - * - * This works the same as RETURN transformation normally would, except - * that we prevent resolving unknown-type outputs as TEXT. This does - * not change the subquery's semantics since if the column type - * matters semantically, it would have been resolved to something else - * anyway. Doing this lets us resolve such outputs using - * select_common_type(), below. - * - * Note: previously transformed sub-queries don't affect the parsing - * of this sub-query, because they are not in the toplevel pstate's - * namespace list. - */ - - /* - * Convert the List * that the grammar gave us to a cypher_clause. - * cypher_analyze doesn't do this because the cypher_union clause - * is hiding it. - */ - - returnQuery = cypher_parse_sub_analyze_union((cypher_clause *) clause, cpstate, NULL, false, false); - /* - * Check for bogus references to Vars on the current query level (but - * upper-level references are okay). Normally this can't happen - * because the namespace will be empty, but it could happen if we are - * inside a rule. - */ - if (pstate->p_namespace && contain_vars_of_level((Node *) returnQuery, 1)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("UNION member statement cannot refer to other relations of same query level"), - parser_errposition(pstate, locate_var_of_level((Node *) returnQuery, 1)))); - - /* - * Extract a list of the non-junk TLEs for upper-level processing. - */ - if (targetlist) { - *targetlist = NIL; - foreach(tl, returnQuery->targetList) { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - *targetlist = lappend(*targetlist, tle); - } - } - - /* - * Make the leaf query be a subquery in the top-level rangetable. - */ - snprintf(returnName, sizeof(returnName), "*SELECT* %d ", list_length(pstate->p_rtable) + 1); - pnsi = addRangeTableEntryForSubquery(pstate, returnQuery, makeAlias(returnName, NIL), false, false); - rte = pnsi->p_rte; - rtr = makeNode(RangeTblRef); - // assume new rte is at end - rtr->rtindex = list_length(pstate->p_rtable); - Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable)); - return (Node *) rtr; - } - else //is not a leaf - { - // Process an internal node (set operation node) - SetOperationStmt *op = makeNode(SetOperationStmt); - List *ltargetlist; - List *rtargetlist; - ListCell *ltl; - ListCell *rtl; - cypher_return *self = (cypher_return *) clause->self; - const char *context; - - context = "UNION"; - - op->op = self->op; - op->all = self->all_or_distinct; - - /* - * Recursively transform the left child node. - */ - op->larg = transform_cypher_union_tree(cpstate, (cypher_clause *) self->larg, false, <argetlist); - - /* - * If we find ourselves processing a recursive CTE here something - * went horribly wrong. That is an SQL contruct with no parallel in - * cypher. - */ - if (isTopLevel && pstate->p_parent_cte && pstate->p_parent_cte->cterecursive) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Cypher does not support recursive CTEs"), - parser_errposition(pstate, 0))); - - /* - * Recursively transform the right child node. - */ - op->rarg = transform_cypher_union_tree(cpstate, (cypher_clause *) self->rarg, false, &rtargetlist); - - /* - * Verify that the two children have the same number of non-junk - * columns, and determine the types of the merged output columns. - */ - if (list_length(ltargetlist) != list_length(rtargetlist)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("each %s query must have the same number of columns", context), - parser_errposition(pstate, exprLocation((Node *) rtargetlist)))); - - if (targetlist) - *targetlist = NIL; - - op->colTypes = NIL; - op->colTypmods = NIL; - op->colCollations = NIL; - op->groupClauses = NIL; - - forboth(ltl, ltargetlist, rtl, rtargetlist) { - TargetEntry *ltle = (TargetEntry *) lfirst(ltl); - TargetEntry *rtle = (TargetEntry *) lfirst(rtl); - Node *lcolnode = (Node *) ltle->expr; - Node *rcolnode = (Node *) rtle->expr; - Oid lcoltype = exprType(lcolnode); - Oid rcoltype = exprType(rcolnode); - int32 lcoltypmod = exprTypmod(lcolnode); - int32 rcoltypmod = exprTypmod(rcolnode); - Node *bestexpr; - int bestlocation; - Oid rescoltype; - int32 rescoltypmod; - Oid rescolcoll; - - // select common type, same as CASE et al - rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode), context, &bestexpr); - bestlocation = exprLocation(bestexpr); - // if same type and same typmod, use typmod; else default - if (lcoltype == rcoltype && lcoltypmod == rcoltypmod) - rescoltypmod = lcoltypmod; - else - rescoltypmod = -1; - - /* - * Verify the coercions are actually possible. If not, we'd fail - * later anyway, but we want to fail now while we have sufficient - * context to produce an error cursor position. - * - * For all non-UNKNOWN-type cases, we verify coercibility but we - * don't modify the child's expression, for fear of changing the - * child query's semantics. - * - * If a child expression is an UNKNOWN-type Const or Param, we - * want to replace it with the coerced expression. This can only - * happen when the child is a leaf set-op node. It's safe to - * replace the expression because if the child query's semantics - * depended on the type of this output column, it'd have already - * coerced the UNKNOWN to something else. We want to do this - * because (a) we want to verify that a Const is valid for the - * target type, or resolve the actual type of an UNKNOWN Param, - * and (b) we want to avoid unnecessary discrepancies between the - * output type of the child query and the resolved target type. - * Such a discrepancy would disable optimization in the planner. - * - * If it's some other UNKNOWN-type node, eg a Var, we do nothing - * (knowing that coerce_to_common_type would fail). The planner - * is sometimes able to fold an UNKNOWN Var to a constant before - * it has to coerce the type, so failing now would just break - * cases that might work. - */ - if (lcoltype != UNKNOWNOID) - lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype, context); - else if (IsA(lcolnode, Const) || IsA(lcolnode, Param)) - ltle->expr = (Expr *) coerce_to_common_type(pstate, lcolnode, rescoltype, context); - - if (rcoltype != UNKNOWNOID) - rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype, context); - else if (IsA(rcolnode, Const) || IsA(rcolnode, Param)) - rtle->expr = (Expr *) coerce_to_common_type(pstate, rcolnode, rescoltype, context); - - /* - * Select common collation. A common collation is required for - * all set operators except UNION ALL; see SQL:2008 7.13 Syntax Rule 15c. (If we fail to identify a common - * collation for a UNION ALL column, the curCollations element - * will be set to InvalidOid, which may result in a runtime error - * if something at a higher query level wants to use the column's - * collation.) - */ - rescolcoll = select_common_collation(pstate, list_make2(lcolnode, rcolnode), - (op->op == SETOP_UNION && op->all)); - - // emit results - op->colTypes = lappend_oid(op->colTypes, rescoltype); - op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); - op->colCollations = lappend_oid(op->colCollations, rescolcoll); - - /* - * For all cases except UNION ALL, identify the grouping operators - * (and, if available, sorting operators) that will be used to - * eliminate duplicates. - */ - if (op->op != SETOP_UNION || !op->all) { - SortGroupClause *grpcl = makeNode(SortGroupClause); - Oid sortop; - Oid eqop; - bool hashable = false; - ParseCallbackState pcbstate; - - /* - * determine the eqop and optional sortop - * - * NOTE: for UNION, we set hashable to false and pass a NULL to - * isHashable in get_sort_group_operators to prevent a logic error - * where UNION fails to exclude duplicate results. - * - */ - setup_parser_errposition_callback(&pcbstate, pstate, bestlocation); - get_sort_group_operators(rescoltype, false, true, false, &sortop, &eqop, NULL, NULL); - cancel_parser_errposition_callback(&pcbstate); - - // we don't have a tlist yet, so can't assign sortgrouprefs - grpcl->tleSortGroupRef = 0; - grpcl->eqop = eqop; - grpcl->sortop = sortop; - grpcl->nulls_first = false; // OK with or without sortop - grpcl->hashable = hashable; - - op->groupClauses = lappend(op->groupClauses, grpcl); - } - - /* - * Construct a dummy tlist entry to return. We use a SetToDefault - * node for the expression, since it carries exactly the fields - * needed, but any other expression node type would do as well. - */ - if (targetlist) { - SetToDefault *rescolnode = makeNode(SetToDefault); - TargetEntry *restle; - - rescolnode->typeId = rescoltype; - rescolnode->typeMod = rescoltypmod; - rescolnode->collation = rescolcoll; - rescolnode->location = bestlocation; - restle = makeTargetEntry((Expr *) rescolnode, 0, NULL, false); - *targetlist = lappend(*targetlist, restle); - } - } - - return (Node *)op; - }//end else (is not leaf) -} - - -static Query *transform_cypher_match_union_label(cypher_parsestate *cpstate, cypher_label_tree_node *label_tree_node) { - ParseState *pstate = (ParseState *)cpstate; - int leftmostRTI; - Query *leftmostQuery; - SetOperationStmt *cypher_union_statement; - Node *node; - ListCell *left_tlist, *lct, *lcm, *lcc; - List *targetvars, *targetnames, *sv_namespace; - int sv_rtable_length; - int tllen; - ParseNamespaceItem *nsitem; - ParseNamespaceColumn *sortnscolumns; - int sortcolindex; - - Query *qry = makeNode(Query); - qry->commandType = CMD_SELECT; - - qry->setOperations = transform_cypher_union_tree_label(cpstate, true, NULL, label_tree_node); - - /* - * Re-find leftmost return (now it's a sub-query in rangetable) - */ - node = ((SetOperationStmt *) qry->setOperations)->larg; - while (node && IsA(node, SetOperationStmt)) - node = ((SetOperationStmt *) qry->setOperations)->larg; - - Assert(node && IsA(node, RangeTblRef)); - leftmostRTI = ((RangeTblRef *) node)->rtindex; - leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery; - Assert(leftmostQuery != NULL); - - /* - * Generate dummy targetlist for outer query using column names of - * leftmost return and common datatypes/collations of topmost set - * operation. Also make lists of the dummy vars and their names for use - * in parsing ORDER BY. - * - * Note: we use leftmostRTI as the varno of the dummy variables. It - * shouldn't matter too much which RT index they have, as long as they - * have one that corresponds to a real RT entry; else funny things may - * happen when the tree is mashed by rule rewriting. - */ - qry->targetList = NIL; - targetvars = NIL; - targetnames = NIL; - sortnscolumns = (ParseNamespaceColumn *) - palloc0(list_length(cypher_union_statement->colTypes) * sizeof(ParseNamespaceColumn)); - sortcolindex = 0; - - forfour(lct, cypher_union_statement->colTypes, lcm, cypher_union_statement->colTypmods, - lcc, cypher_union_statement->colCollations, left_tlist, leftmostQuery->targetList) { - Oid colType = lfirst_oid(lct); - int32 colTypmod = lfirst_int(lcm); - Oid colCollation = lfirst_oid(lcc); - TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist); - char *colName; - TargetEntry *tle; - Var *var; - - Assert(!lefttle->resjunk); - colName = pstrdup(lefttle->resname); - var = makeVar(leftmostRTI, lefttle->resno, colType, colTypmod, colCollation, 0); - var->location = exprLocation((Node *) lefttle->expr); - tle = makeTargetEntry((Expr *) var, (AttrNumber) pstate->p_next_resno++, colName, false); - qry->targetList = lappend(qry->targetList, tle); - targetvars = lappend(targetvars, var); - targetnames = lappend(targetnames, makeString(colName)); - sortnscolumns[sortcolindex].p_varno = leftmostRTI; - sortnscolumns[sortcolindex].p_varattno = lefttle->resno; - sortnscolumns[sortcolindex].p_vartype = colType; - sortnscolumns[sortcolindex].p_vartypmod = colTypmod; - sortnscolumns[sortcolindex].p_varcollid = colCollation; - sortnscolumns[sortcolindex].p_varnosyn = leftmostRTI; - sortnscolumns[sortcolindex].p_varattnosyn = lefttle->resno; - sortcolindex++; - } - - /* - * As a first step towards supporting sort clauses that are expressions - * using the output columns, generate a namespace entry that makes the - * output columns visible. A Join RTE node is handy for this, since we - * can easily control the Vars generated upon matches. - * - * Note: we don't yet do anything useful with such cases, but at least - * "ORDER BY upper(foo)" will draw the right error message rather than - * "foo not found". - */ - sv_rtable_length = list_length(pstate->p_rtable); - - nsitem = addRangeTableEntryForJoin(pstate, targetnames, sortnscolumns, JOIN_INNER, 0, targetvars, NIL, NIL, NULL, NULL, false); - - sv_namespace = pstate->p_namespace; - pstate->p_namespace = NIL; - - // add jrte to column namespace only - addNSItemToQuery(pstate, nsitem, false, false, true); - - tllen = list_length(qry->targetList); - - - // restore namespace, remove jrte from rtable - pstate->p_namespace = sv_namespace; - pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length); - - if (tllen != list_length(qry->targetList)) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid UNION ORDER BY clause"), - errdetail("Only result column names can be used, not expressions or functions."), - parser_errposition(pstate, exprLocation(list_nth(qry->targetList, tllen))))); - - qry->rtable = pstate->p_rtable; - qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); - qry->hasAggs = pstate->p_hasAggs; - - assign_query_collations(pstate, qry); - - // this must be done after collations, for reliable comparison of exprs - if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual) - parse_check_aggregates(pstate, qry); - - return qry; - -} - -Query *cypher_parse_sub_analyze_union_label(cypher_parsestate *cpstate, char *schema_name, - char *var_name, char *rel_name) { - cypher_parsestate *state = make_cypher_parsestate(cpstate); - ParseState *pstate = state; - - Query *query = makeNode(Query); - query->commandType = CMD_SELECT; - - //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); - RangeVar *label_range_var = makeRangeVar(schema_name, rel_name, -1); - Node *alias = makeAlias(var_name, NIL); - - ParseNamespaceItem *pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); - addNSItemToQuery(pstate, pnsi, true, true, true); - - // make target entry and add it - query->targetList = - lappend(query->targetList, - makeTargetEntry( - (Expr *)make_vertex_expr(cpstate, pnsi), - ++pstate->p_next_resno, - var_name, - false)); - - // id - query->targetList = - lappend(query->targetList, - makeTargetEntry( - scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1), - ++pstate->p_next_resno, - make_id_alias(var_name), - false)); - - //properties - query->targetList = - lappend(query->targetList, - makeTargetEntry( - scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1), - ++pstate->p_next_resno, - make_property_alias(var_name), - false)); - - markTargetListOrigins(pstate, query->targetList); - - query->hasSubLinks = pstate->p_hasSubLinks; - query->hasWindowFuncs = pstate->p_hasWindowFuncs; - query->hasTargetSRFs = pstate->p_hasTargetSRFs; - query->hasAggs = pstate->p_hasAggs; - - assign_query_collations(pstate, query); - - free_cypher_parsestate(state); - - return query; -} - - -static Node * -transform_cypher_union_tree_label(cypher_parsestate *cpstate, bool isTopLevel, List **targetlist, cypher_label_tree_node *label_tree_node) { - ParseState *pstate = (ParseState *)cpstate; - cypher_return *cmp; - ParseNamespaceItem *pnsi; - - // Guard against queue overflow due to overly complex set-expressions - check_stack_depth(); - if (label_tree_node->larg) { - //process leaf return - Query *returnQuery; - char returnName[32]; - RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY; - RangeTblRef *rtr; - ListCell *tl; - returnQuery = cypher_parse_sub_analyze_union_label(cpstate, cpstate->graph_name, label_tree_node->label_name, label_tree_node->label_name); - - - /* - * Extract a list of the non-junk TLEs for upper-level processing. - */ - if (targetlist) { - *targetlist = NIL; - foreach(tl, returnQuery->targetList) { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - *targetlist = lappend(*targetlist, tle); - } - } - - /* - * Make the leaf query be a subquery in the top-level rangetable. - */ - snprintf(returnName, sizeof(returnName), "*SELECT* %d ", list_length(pstate->p_rtable) + 1); - pnsi = addRangeTableEntryForSubquery(pstate, returnQuery, makeAlias(returnName, NIL), false, false); - rte = pnsi->p_rte; - rtr = makeNode(RangeTblRef); - // assume new rte is at end - rtr->rtindex = list_length(pstate->p_rtable); - Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable)); - return (Node *) rtr; - } - else //is not a leaf - { - // Process an internal node (set operation node) - SetOperationStmt *op = makeNode(SetOperationStmt); - List *ltargetlist; - List *rtargetlist; - ListCell *ltl; - ListCell *rtl; - const char *context; - - context = "UNION"; - - op->op = SETOP_UNION; - op->all = true; - - op->larg = transform_cypher_union_tree_label(cpstate, false, <argetlist, label_tree_node->larg); - op->rarg = transform_cypher_union_tree_label(cpstate, false, &rtargetlist, label_tree_node->rarg); - - /* - * Verify that the two children have the same number of non-junk - * columns, and determine the types of the merged output columns. - */ - if (list_length(ltargetlist) != list_length(rtargetlist)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("each %s query must have the same number of columns", context), - parser_errposition(pstate, exprLocation((Node *) rtargetlist)))); - - if (targetlist) - *targetlist = NIL; - - op->colTypes = NIL; - op->colTypmods = NIL; - op->colCollations = NIL; - op->groupClauses = NIL; - - forboth(ltl, ltargetlist, rtl, rtargetlist) { - TargetEntry *ltle = (TargetEntry *) lfirst(ltl); - TargetEntry *rtle = (TargetEntry *) lfirst(rtl); - Node *lcolnode = (Node *) ltle->expr; - Node *rcolnode = (Node *) rtle->expr; - Oid lcoltype = exprType(lcolnode); - Oid rcoltype = exprType(rcolnode); - int32 lcoltypmod = exprTypmod(lcolnode); - int32 rcoltypmod = exprTypmod(rcolnode); - Node *bestexpr; - int bestlocation; - Oid rescoltype; - int32 rescoltypmod; - Oid rescolcoll; - - // select common type, same as CASE et al - rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode), context, &bestexpr); - bestlocation = exprLocation(bestexpr); - // if same type and same typmod, use typmod; else default - if (lcoltype == rcoltype && lcoltypmod == rcoltypmod) - rescoltypmod = lcoltypmod; - else - rescoltypmod = -1; - - if (lcoltype != UNKNOWNOID) - lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype, context); - else if (IsA(lcolnode, Const) || IsA(lcolnode, Param)) - ltle->expr = (Expr *) coerce_to_common_type(pstate, lcolnode, rescoltype, context); - - if (rcoltype != UNKNOWNOID) - rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype, context); - else if (IsA(rcolnode, Const) || IsA(rcolnode, Param)) - rtle->expr = (Expr *) coerce_to_common_type(pstate, rcolnode, rescoltype, context); - - rescolcoll = select_common_collation(pstate, list_make2(lcolnode, rcolnode), true); - - // emit results - op->colTypes = lappend_oid(op->colTypes, rescoltype); - op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); - op->colCollations = lappend_oid(op->colCollations, rescolcoll); - - /* - * Construct a dummy tlist entry to return. We use a SetToDefault - * node for the expression, since it carries exactly the fields - * needed, but any other expression node type would do as well. - */ - if (targetlist) { - SetToDefault *rescolnode = makeNode(SetToDefault); - TargetEntry *restle; - - rescolnode->typeId = rescoltype; - rescolnode->typeMod = rescoltypmod; - rescolnode->collation = rescolcoll; - rescolnode->location = bestlocation; - restle = makeTargetEntry((Expr *) rescolnode, 0, NULL, false); - *targetlist = lappend(*targetlist, restle); - } - } - - return (Node *)op; - }//end else (is not leaf) -} - - -static Query *transform_cypher_delete(cypher_parsestate *cpstate, cypher_clause *clause) { - ParseState *pstate = (ParseState *)cpstate; - cypher_delete *self = (cypher_delete *)clause->self; - Query *query; - TargetEntry *tle; - FuncExpr *func_expr; - - cypher_delete_information *delete_data; - - delete_data = make_ag_node(cypher_delete_information); - - query = makeNode(Query); - query->commandType = CMD_SELECT; - query->targetList = NIL; - - Const *null_const = makeNullConst(GTYPEOID, -1, InvalidOid); - tle = makeTargetEntry((Expr *)null_const, pstate->p_next_resno++, "_null", false); - query->targetList = lappend(query->targetList, tle); - - if (!clause->prev) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("DELETE cannot be the first clause in a Cypher query"), - parser_errposition(pstate, self->location))); - else - handle_prev_clause(cpstate, query, clause->prev, true); - - delete_data->delete_items = transform_cypher_delete_item_list(cpstate, self->exprs, query); - delete_data->graph_name = cpstate->graph_name; - delete_data->graph_oid = cpstate->graph_oid; - delete_data->detach = self->detach; - - if (!clause->next) - delete_data->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; - - func_expr = make_clause_func_expr(DELETE_CLAUSE_FUNCTION_NAME, (Node *)delete_data); - - // Create the target entry - tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, "_delete_clause", false); - query->targetList = lappend(query->targetList, tle); - - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - - - if (clause->next) - return query; - - { - Query *topquery; - cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); - topquery = makeNode(Query); - topquery->commandType = CMD_SELECT; - topquery->targetList = NIL; - - ParseState *pstate = (ParseState *) cpstate; - int rtindex; - - ParseNamespaceItem *pnsi = transform_cypher_clause_as_subquery_2(new_cpstate, clause, NULL, false, query); - topquery->rtable = new_cpstate->pstate.p_rtable; - topquery->jointree = makeFromExpr(new_cpstate->pstate.p_joinlist, NULL); - - return topquery; - } -} -static Node *make_bool_const(bool b, int location) -{ - cypher_bool_const *n; - - n = make_ag_node(cypher_bool_const); - n->boolean = b; - n->location = location; - - return (Node *)n; -} - -/* - * transform_cypher_unwind - * It contains logic to convert the form of an array into a row. Here, we - * are simply calling `unnest` function, and the actual transformation - * is handled by `unnest` function. - */ -static Query *transform_cypher_unwind(cypher_parsestate *cpstate, cypher_clause *clause) { - cypher_unwind *self = (cypher_unwind *) clause->self; - int target_syntax_loc; - Query *query; - Node *expr; - FuncCall *unwind; - ParseExprKind old_expr_kind; - Node *funcexpr; - TargetEntry *te; - ParseNamespaceItem *pnsi; - cypher_parsestate *child_cpstate = make_cypher_parsestate(cpstate); - ParseState *pstate = (ParseState *) child_cpstate; - - query = makeNode(Query); - query->commandType = CMD_SELECT; - - if (clause->prev) { - int rtindex; - - pnsi = transform_prev_cypher_clause(child_cpstate, clause->prev, true); - rtindex = list_length(pstate->p_rtable); - Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate - query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1); - } - - target_syntax_loc = exprLocation((const Node *) self->target); - - if (findTarget(query->targetList, self->target->name) != NULL) - ereport(ERROR, (errcode(ERRCODE_DUPLICATE_ALIAS), - errmsg("duplicate variable \"%s\"", self->target->name), - parser_errposition((ParseState *) child_cpstate, target_syntax_loc))); - - expr = transform_cypher_expr(child_cpstate, self->target->val, EXPR_KIND_SELECT_TARGET); - - unwind = makeFuncCall(list_make2(makeString(CATALOG_SCHEMA), makeString("unnest")), list_make2(self->target->val, make_bool_const(true, -1)), COERCE_SQL_SYNTAX, -1); - pnsi = add_srf_to_query(child_cpstate, unwind, self->target->name); - Node *var = scanNSItemForColumn(pstate, pnsi, 0, self->target->name, -1); - te = makeTargetEntry((Expr *) var, (AttrNumber) pstate->p_next_resno++, self->target->name, false); - query->targetList = lappend(query->targetList, te); - - transform_entity *entity = make_transform_entity(child_cpstate, ENT_FUNC_CALL, NULL, (Node *)var, self->target->name); - child_cpstate->entities = lappend(child_cpstate->entities, entity); - - if (self->where) - expr = transform_cypher_expr(child_cpstate, self->where, EXPR_KIND_WHERE); - - /* - old_expr_kind = pstate->p_expr_kind; - pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET; - funcexpr = ParseFuncOrColumn(pstate, unwind->funcname, list_make2(expr, makeBoolConst(true, false)), - pstate->p_last_srf, unwind, false, target_syntax_loc); - - pstate->p_expr_kind = old_expr_kind; - - te = makeTargetEntry((Expr *) funcexpr, (AttrNumber) pstate->p_next_resno++, res->name, false); - - expr = transform_cypher_expr(child_cpstate, call->where, EXPR_KIND_WHERE); - - transform_entity *entity = make_transform_entity(child_cpstate, ENT_FUNC_CALL, NULL, (Node *)var, call->alias); - child_cpstate->entities = lappend(child_cpstate->entities, entity); - - query->targetList = lappend(query->targetList, te); - */ - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, expr); - query->hasTargetSRFs = pstate->p_hasTargetSRFs; - - assign_query_collations(pstate, query); - - free_cypher_parsestate(child_cpstate); - return query; -} - -/* - * Iterate through the list of items to delete and extract the variable name. - * Then find the resno that the variable name belongs to. - */ -static List *transform_cypher_delete_item_list(cypher_parsestate *cpstate, List *delete_item_list, Query *query) { - ParseState *pstate = (ParseState *)cpstate; - List *items = NIL; - ListCell *lc; - - foreach(lc, delete_item_list) { - Node *expr = lfirst(lc); - ColumnRef *col; - Value *val, *pos; - int resno; - - cypher_delete_item *item = make_ag_node(cypher_delete_item); - - if (!IsA(expr, ColumnRef)) - ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); - - col = (ColumnRef *)expr; - - if (list_length(col->fields) != 1) - ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); - - val = linitial(col->fields); - - if (!IsA(val, String)) - ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); - - resno = get_target_entry_resno(cpstate, query->targetList, val->val.str); - - if (resno == -1) - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("undefined reference to variable %s in DELETE clause", val->val.str), - parser_errposition(pstate, col->location))); - - pos = makeInteger(resno); - - item->var_name = val->val.str; - item->entity_position = pos; - - items = lappend(items, item); - } - - return items; -} - -static Query *transform_cypher_set(cypher_parsestate *cpstate, cypher_clause *clause) { - ParseState *pstate = (ParseState *)cpstate; - cypher_set *self = (cypher_set *)clause->self; - Query *query; - cypher_update_information *set_items_target_list; - TargetEntry *tle; - FuncExpr *func_expr; - char *clause_name; - int rtindex; - - query = makeNode(Query); - query->commandType = CMD_SELECT; - query->targetList = NIL; - Const *null_const = makeNullConst(GTYPEOID, -1, InvalidOid); - tle = makeTargetEntry((Expr *)null_const, pstate->p_next_resno++, "_null", false); - query->targetList = lappend(query->targetList, tle); - - if (self->is_remove == true) - clause_name = UPDATE_CLAUSE_REMOVE; - else - clause_name = UPDATE_CLAUSE_SET; - - if (!clause->prev) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("%s cannot be the first clause in a Cypher query", clause_name), - parser_errposition(pstate, self->location))); - else - handle_prev_clause(cpstate, query, clause->prev, true); - - if (self->is_remove == true) - set_items_target_list = transform_cypher_remove_item_list(cpstate, self->items, query); - else - set_items_target_list = transform_cypher_set_item_list(cpstate, self->items, query); - - set_items_target_list->clause_name = clause_name; - set_items_target_list->graph_name = cpstate->graph_name; - set_items_target_list->graph_oid = cpstate->graph_oid; - - if (!clause->next) - set_items_target_list->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; - - func_expr = make_clause_func_expr(SET_CLAUSE_FUNCTION_NAME, (Node *)set_items_target_list); - - // Create the target entry - tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, "_set_clause", false); - query->targetList = lappend(query->targetList, tle); - - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - - if (clause->next) - return query; - - { - Query *topquery; - cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); - topquery = makeNode(Query); - topquery->commandType = CMD_SELECT; - topquery->targetList = NIL; - - ParseState *pstate = (ParseState *) cpstate; - int rtindex; - - ParseNamespaceItem *pnsi = transform_cypher_clause_as_subquery_2(new_cpstate, clause, NULL, false, query); - topquery->rtable = new_cpstate->pstate.p_rtable; - topquery->jointree = makeFromExpr(new_cpstate->pstate.p_joinlist, NULL); - - return topquery; - } -} - -cypher_update_information *transform_cypher_remove_item_list(cypher_parsestate *cpstate, List *remove_item_list, Query *query) { - ParseState *pstate = (ParseState *)cpstate; - ListCell *li; - cypher_update_information *info = make_ag_node(cypher_update_information); - - info->set_items = NIL; - info->flags = 0; - - foreach (li, remove_item_list) { - cypher_set_item *set_item = lfirst(li); - cypher_update_item *item; - ColumnRef *ref; - char *variable_name, *property_name; - Value *property_node, *variable_node; - - item = make_ag_node(cypher_update_item); - item->remove_item = true; - - ColumnRef *set_item_prop = set_item->prop; - if (!IsA(set_item_prop, ColumnRef)) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("REMOVE clause must be in the format: REMOVE variable.property_name"), - parser_errposition(pstate, set_item->location))); - - ref = (ColumnRef *)set_item_prop; - variable_node = linitial(ref->fields); - - - variable_name = variable_node->val.str; - - item->var_name = variable_name; - item->entity_position = get_target_entry_resno(cpstate, query->targetList, variable_name); - - if (item->entity_position == -1) - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("undefined reference to variable %s in REMOVE clause", variable_name), - parser_errposition(pstate, set_item->location))); - - // extract property name - if (list_length(ref->fields) != 2) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("REMOVE clause must be in the format: REMOVE variable.property_name"), - parser_errposition(pstate, set_item->location))); - - property_node = lsecond(ref->fields); - - if (!IsA(property_node, String)) - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("REMOVE clause expects a property name"), - parser_errposition(pstate, set_item->location))); - - property_name = property_node->val.str; - item->prop_name = property_name; - - info->set_items = lappend(info->set_items, item); - } - - return info; -} - - -cypher_update_information *transform_cypher_set_item_list(cypher_parsestate *cpstate, List *set_item_list, Query *query) { - ParseState *pstate = (ParseState *)cpstate; - ListCell *li; - cypher_update_information *info = make_ag_node(cypher_update_information); - - info->set_items = NIL; - info->flags = 0; - - foreach (li, set_item_list) { - cypher_set_item *set_item = lfirst(li); - TargetEntry *target_item; - cypher_update_item *item; - ColumnRef *ref; - A_Indirection *ind; - char *variable_name, *property_name; - Value *property_node, *variable_node; - - if (set_item->is_add) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("SET clause does not yet support adding propereties from maps"), - parser_errposition(pstate, set_item->location))); - - if (!IsA(set_item->prop, ColumnRef)) - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("SET clause expects a variable name"), - parser_errposition(pstate, set_item->location))); - - ColumnRef *set_item_prop = set_item->prop; - - //ind = (A_Indirection *)set_item->prop; - item = make_ag_node(cypher_update_item); - item->remove_item = false; - - // extract variable name - ref = (ColumnRef *)set_item_prop; - - variable_node = linitial(ref->fields); - if (!IsA(variable_node, String)) - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("SET clause expects a variable name"), - parser_errposition(pstate, set_item->location))); - - variable_name = variable_node->val.str; - item->var_name = variable_name; - item->entity_position = get_target_entry_resno(cpstate, query->targetList, variable_name); - - if (item->entity_position == -1) - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("undefined reference to variable %s in SET clause", variable_name), - parser_errposition(pstate, set_item->location))); - - // extract property name - if (list_length(ref->fields) != 2) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("SET clause doesnt not support updating maps or lists in a property"), - parser_errposition(pstate, set_item->location))); - - property_node = lsecond(ref->fields); - if (!IsA(property_node, String)) - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("SET clause expects a property name"), - parser_errposition(pstate, set_item->location))); - - property_name = property_node->val.str; - item->prop_name = property_name; - - // create target entry for the new property value - item->prop_position = (AttrNumber)pstate->p_next_resno; - cpstate->default_alias_num++; - target_item = transform_cypher_item(cpstate, set_item->expr, NULL, EXPR_KIND_SELECT_TARGET, - get_next_default_alias(cpstate), false); - target_item->expr = add_volatile_wrapper(target_item->expr); - - query->targetList = lappend(query->targetList, target_item); - info->set_items = lappend(info->set_items, item); - } - - return info; -} - -/*------------------------------------------------------------------------- - * Flatten out parenthesized sublists in grouping lists, and some cases - * of nested grouping sets. - * - * Inside a grouping set (ROLLUP, CUBE, or GROUPING SETS), we expect the - * content to be nested no more than 2 deep: i.e. ROLLUP((a,b),(c,d)) is - * ok, but ROLLUP((a,(b,c)),d) is flattened to ((a,b,c),d), which we then - * (later) normalize to ((a,b,c),(d)). - * - * CUBE or ROLLUP can be nested inside GROUPING SETS (but not the reverse), - * and we leave that alone if we find it. But if we see GROUPING SETS inside - * GROUPING SETS, we can flatten and normalize as follows: - * GROUPING SETS (a, (b,c), GROUPING SETS ((c,d),(e)), (f,g)) - * becomes - * GROUPING SETS ((a), (b,c), (c,d), (e), (f,g)) - * - * This is per the spec's syntax transformations, but these are the only such - * transformations we do in parse analysis, so that queries retain the - * originally specified grouping set syntax for CUBE and ROLLUP as much as - * possible when deparsed. (Full expansion of the result into a list of - * grouping sets is left to the planner.) - * - * When we're done, the resulting list should contain only these possible - * elements: - * - an expression - * - a CUBE or ROLLUP with a list of expressions nested 2 deep - * - a GROUPING SET containing any of: - * - expression lists - * - empty grouping sets - * - CUBE or ROLLUP nodes with lists nested 2 deep - * The return is a new list, but doesn't deep-copy the old nodes except for - * GroupingSet nodes. - * - * As a side effect, flag whether the list has any GroupingSet nodes. - *------------------------------------------------------------------------- - */ -static Node *flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets) { - // just in case of pathological input - check_stack_depth(); - - if (expr == (Node *) NIL) - return (Node *) NIL; - - switch (expr->type) - { - case T_RowExpr: - { - RowExpr *r = (RowExpr *) expr; - - if (r->row_format == COERCE_IMPLICIT_CAST) - return flatten_grouping_sets((Node *) r->args, false, NULL); - break; - } - case T_GroupingSet: - { - GroupingSet *gset = (GroupingSet *) expr; - ListCell *l2; - List *result_set = NIL; - - if (hasGroupingSets) - *hasGroupingSets = true; - - /* - * at the top level, we skip over all empty grouping sets; the - * caller can supply the canonical GROUP BY () if nothing is - * left. - */ - if (toplevel && gset->kind == GROUPING_SET_EMPTY) - return (Node *) NIL; - - foreach(l2, gset->content) { - Node *n1 = lfirst(l2); - Node *n2 = flatten_grouping_sets(n1, false, NULL); - - if (IsA(n1, GroupingSet) && ((GroupingSet *) n1)->kind == GROUPING_SET_SETS) - result_set = list_concat(result_set, (List *) n2); - else - result_set = lappend(result_set, n2); - } - - /* - * At top level, keep the grouping set node; but if we're in a - * nested grouping set, then we need to concat the flattened - * result into the outer list if it's simply nested. - */ - - if (toplevel || (gset->kind != GROUPING_SET_SETS)) - return (Node *) makeGroupingSet(gset->kind, result_set, gset->location); - else - return (Node *) result_set; - - break; - } - case T_List: - { - List *result = NIL; - ListCell *l; - - foreach(l, (List *) expr) { - Node *n = NULL; - - n = flatten_grouping_sets(lfirst(l), toplevel, hasGroupingSets); - - if (n != (Node *) NIL) { - if (IsA(n, List)) - result = list_concat(result, (List *) n); - else - result = lappend(result, n); - } - } - return (Node *) result; - } - default: - break; - } - return expr; -} - -// from PG's addTargetToGroupList -static List *add_target_to_group_list(cypher_parsestate *cpstate, TargetEntry *tle, List *grouplist, List *targetlist, int location) { - ParseState *pstate = (ParseState *)cpstate; - Oid restype = exprType((Node *) tle->expr); - - // if tlist item is an UNKNOWN literal, change it to TEXT - if (restype == UNKNOWNOID) { - tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, restype, GTYPEOID, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); - restype = GTYPEOID; - } - - // avoid making duplicate grouplist entries - if (!targetIsInSortList(tle, InvalidOid, grouplist)) { - SortGroupClause *grpcl = makeNode(SortGroupClause); - Oid sortop; - Oid eqop; - bool hashable; - ParseCallbackState pcbstate; - - // determine the eqop and optional sortop - setup_parser_errposition_callback(&pcbstate, pstate, location); - get_sort_group_operators(restype, false, true, false, &sortop, &eqop, NULL, &hashable); - cancel_parser_errposition_callback(&pcbstate); - - grpcl->tleSortGroupRef = assignSortGroupRef(tle, targetlist); - grpcl->eqop = eqop; - grpcl->sortop = sortop; - grpcl->nulls_first = false; // OK with or without sortop - grpcl->hashable = hashable; - - grouplist = lappend(grouplist, grpcl); - } - - return grouplist; -} - -// from PG's transformGroupClauseExpr -static Index transform_group_clause_expr(List **flatresult, Bitmapset *seen_local, cypher_parsestate *cpstate, Node *gexpr, List **targetlist, List *sortClause, ParseExprKind exprKind, bool toplevel) { - TargetEntry *tle = NULL; - bool found = false; - - tle = find_target_list_entry(cpstate, gexpr, targetlist, exprKind); - - if (tle->ressortgroupref > 0) { - ListCell *sl; - - /* - * Eliminate duplicates (GROUP BY x, x) but only at local level. - * (Duplicates in grouping sets can affect the number of returned - * rows, so can't be dropped indiscriminately.) - * - * Since we don't care about anything except the sortgroupref, we can - * use a bitmapset rather than scanning lists. - */ - if (bms_is_member(tle->ressortgroupref, seen_local)) - return 0; - - /* - * If we're already in the flat clause list, we don't need to consider - * adding ourselves again. - */ - found = targetIsInSortList(tle, InvalidOid, *flatresult); - if (found) - return tle->ressortgroupref; - - /* - * If the GROUP BY tlist entry also appears in ORDER BY, copy operator - * info from the (first) matching ORDER BY item. This means that if - * you write something like "GROUP BY foo ORDER BY foo USING <<<", the - * GROUP BY operation silently takes on the equality semantics implied - * by the ORDER BY. There are two reasons to do this: it improves the - * odds that we can implement both GROUP BY and ORDER BY with a single - * sort step, and it allows the user to choose the equality semantics - * used by GROUP BY, should she be working with a datatype that has - * more than one equality operator. - * - * If we're in a grouping set, though, we force our requested ordering - * to be NULLS LAST, because if we have any hope of using a sorted agg - * for the job, we're going to be tacking on generated NULL values - * after the corresponding groups. If the user demands nulls first, - * another sort step is going to be inevitable, but that's the - * planner's problem. - */ - foreach(sl, sortClause) { - SortGroupClause *sc = (SortGroupClause *) lfirst(sl); - - if (sc->tleSortGroupRef == tle->ressortgroupref) { - SortGroupClause *grpc = copyObject(sc); - - if (!toplevel) - grpc->nulls_first = false; - *flatresult = lappend(*flatresult, grpc); - found = true; - break; - } - } - } - - /* - * If no match in ORDER BY, just add it to the result using default - * sort/group semantics. - */ - if (!found) - *flatresult = add_target_to_group_list(cpstate, tle, *flatresult, *targetlist, exprLocation(gexpr)); - - // _something_ must have assigned us a sortgroupref by now... - - return tle->ressortgroupref; -} - -/* - * Transform a list of expressions within a GROUP BY clause or grouping set. - * - * The list of expressions belongs to a single clause within which duplicates - * can be safely eliminated. - * - * Returns an integer list of ressortgroupref values. - * - * flatresult reference to flat list of SortGroupClause nodes - * pstate ParseState - * list nodes to transform - * targetlist reference to TargetEntry list - * sortClause ORDER BY clause (SortGroupClause nodes) - * exprKind expression kind - * useSQL99 SQL99 rather than SQL92 syntax - * toplevel false if within any grouping set - */ -static List * transformGroupClauseList(List **flatresult, ParseState *pstate, List *list, List **targetlist, - List *sortClause, ParseExprKind exprKind, bool useSQL99, bool toplevel) { - Bitmapset *seen_local = NULL; - List *result = NIL; - ListCell *gl; - - foreach(gl, list) - { - Node *gexpr = (Node *) lfirst(gl); - - Index ref = transform_group_clause_expr(flatresult, seen_local, pstate, gexpr, targetlist, sortClause, exprKind, toplevel); - - if (ref > 0) { - seen_local = bms_add_member(seen_local, ref); - result = lappend_int(result, ref); - } - } - - return result; -} - -/* - * Transform a grouping set and (recursively) its content. - * - * The grouping set might be a GROUPING SETS node with other grouping sets - * inside it, but SETS within SETS have already been flattened out before - * reaching here. - * - * Returns the transformed node, which now contains SIMPLE nodes with lists - * of ressortgrouprefs rather than expressions. - * - * flatresult reference to flat list of SortGroupClause nodes - * pstate ParseState - * gset grouping set to transform - * targetlist reference to TargetEntry list - * sortClause ORDER BY clause (SortGroupClause nodes) - * exprKind expression kind - * useSQL99 SQL99 rather than SQL92 syntax - * toplevel false if within any grouping set - */ -static Node * -transformGroupingSet(List **flatresult, ParseState *pstate, GroupingSet *gset, List **targetlist, List *sortClause, - ParseExprKind exprKind, bool useSQL99, bool toplevel) { - ListCell *gl; - List *content = NIL; - - Assert(toplevel || gset->kind != GROUPING_SET_SETS); - - foreach(gl, gset->content) - { - Node *n = lfirst(gl); - - if (IsA(n, List)) - { - List *l = transformGroupClauseList(flatresult, pstate, (List *) n, targetlist, sortClause, - exprKind, useSQL99, false); - - content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE, l, exprLocation(n))); - } - else if (IsA(n, GroupingSet)) - { - GroupingSet *gset2 = (GroupingSet *) lfirst(gl); - - content = lappend(content, transformGroupingSet(flatresult, pstate, gset2, targetlist, sortClause, - exprKind, useSQL99, false)); - } - else - { - Index ref = transform_group_clause_expr(flatresult, NULL, pstate, n, targetlist, sortClause, - exprKind, false); - - content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE, list_make1_int(ref), exprLocation(n))); - } - } - - /* Arbitrarily cap the size of CUBE, which has exponential growth */ - if (gset->kind == GROUPING_SET_CUBE) - { - if (list_length(content) > 12) - ereport(ERROR, (errcode(ERRCODE_TOO_MANY_COLUMNS), - errmsg("CUBE is limited to 12 elements"), - parser_errposition(pstate, gset->location))); - } - - return (Node *) makeGroupingSet(gset->kind, content, gset->location); -} - -static List * transform_group_clause(cypher_parsestate *cpstate, List *grouplist, List **groupingSets, - List **targetlist, List *sortClause, ParseExprKind exprKind) { - ParseState *pstate = (ParseState *)cpstate; - List *result = NIL; - List *flat_grouplist; - List *gsets = NIL; - ListCell *gl; - bool hasGroupingSets = false; - Bitmapset *seen_local = NULL; - - /* - * Recursively flatten implicit RowExprs. (Technically this is only needed - * for GROUP BY, per the syntax rules for grouping sets, but we do it - * anyway.) - */ - flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist, true, &hasGroupingSets); - - /* - * If the list is now empty, but hasGroupingSets is true, it's because we - * elided redundant empty grouping sets. Restore a single empty grouping - * set to leave a canonical form: GROUP BY () - */ - - if (flat_grouplist == NIL && hasGroupingSets) - flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY, NIL, exprLocation((Node *) grouplist))); - - - foreach(gl, flat_grouplist) { - Node *gexpr = (Node *) lfirst(gl); - - if (IsA(gexpr, GroupingSet)) { - GroupingSet *gset = (GroupingSet *) gexpr; - - switch (gset->kind) - { - case GROUPING_SET_EMPTY: - gsets = lappend(gsets, gset); - break; - case GROUPING_SET_SIMPLE: - /* can't happen */ - Assert(false); - break; - case GROUPING_SET_SETS: - case GROUPING_SET_CUBE: - case GROUPING_SET_ROLLUP: - gsets = lappend(gsets, - transformGroupingSet(&result, pstate, gset, targetlist, sortClause, exprKind, true, true)); - break; - } - - } else { - Index ref = transform_group_clause_expr(&result, seen_local, cpstate, gexpr, targetlist, - sortClause, exprKind, true); - if (ref > 0) { - seen_local = bms_add_member(seen_local, ref); - if (hasGroupingSets) { - seen_local = bms_add_member(seen_local, ref); - if (hasGroupingSets) - gsets = lappend(gsets, - makeGroupingSet(GROUPING_SET_SIMPLE, list_make1_int(ref), exprLocation(gexpr))); - - } - } - } - } - - - if (groupingSets) - *groupingSets = gsets; - - return result; -} - -static Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause) { - ParseState *pstate = (ParseState *)cpstate; - cypher_return *self = (cypher_return *)clause->self; - Query *query; - List *groupClause = NIL; - - query = makeNode(Query); - query->commandType = CMD_SELECT; - - pstate->p_windowdefs = self->window_clause; - - if (clause->prev) - transform_prev_cypher_clause(cpstate, clause->prev, true); - - query->targetList = transform_cypher_item_list(cpstate, self->items, &groupClause, EXPR_KIND_SELECT_TARGET); - - markTargetListOrigins(pstate, query->targetList); - - Expr *expr = NULL; - if (self->where != NULL) - expr = transform_cypher_expr(cpstate, self->where, EXPR_KIND_WHERE); - - // ORDER BY - query->sortClause = transform_cypher_order_by(cpstate, self->order_by, &query->targetList, EXPR_KIND_ORDER_BY); - - if (self->real_group_clause != NIL) - query->groupClause = transform_group_clause(cpstate, self->real_group_clause, &query->groupingSets, - &query->targetList, - query->sortClause, EXPR_KIND_GROUP_BY); - else if (groupClause != NIL) // auto GROUP BY - query->groupClause = transform_group_clause(cpstate, groupClause, &query->groupingSets, &query->targetList, - query->sortClause, EXPR_KIND_GROUP_BY); - else - query->groupClause = NULL; - - if (self->having) { - query->havingQual = transform_cypher_expr(cpstate, self->having, EXPR_KIND_HAVING); - } - // DISTINCT - if (self->distinct) { - query->distinctClause = transformDistinctClause(pstate, &query->targetList, query->sortClause, false); - query->hasDistinctOn = false; - } else { - query->distinctClause = NIL; - query->hasDistinctOn = false; - } - - // SKIP and LIMIT - query->limitOffset = transform_cypher_limit(cpstate, self->skip, EXPR_KIND_OFFSET, "SKIP"); - query->limitCount = transform_cypher_limit(cpstate, self->limit, EXPR_KIND_LIMIT, "LIMIT"); - - if (pstate->p_windowdefs != NIL) { - //ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("window functions are not done"))); - query->windowClause = transform_window_definitions(pstate, pstate->p_windowdefs, &query->targetList); - } - - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, expr); - query->hasWindowFuncs = pstate->p_hasWindowFuncs; - query->hasTargetSRFs = pstate->p_hasTargetSRFs; - query->hasSubLinks = pstate->p_hasSubLinks; - query->hasAggs = pstate->p_hasAggs; - - assign_query_collations(pstate, query); - - // this must be done after collations, for reliable comparison of exprs - if (pstate->p_hasAggs || query->groupClause || query->groupingSets || query->havingQual) - parse_check_aggregates(pstate, query); - - return query; -} - -/* - * findWindowClause - * Find the named WindowClause in the list, or return NULL if not there - */ -static WindowClause * -findWindowClause(List *wclist, const char *name) -{ - ListCell *l; - - foreach(l, wclist) - { - WindowClause *wc = (WindowClause *) lfirst(l); - - if (wc->name && strcmp(wc->name, name) == 0) - return wc; - } - - return NULL; -} - - -/* - * checkExprIsVarFree - * Check that given expr has no Vars of the current query level - * (aggregates and window functions should have been rejected already). - * - * This is used to check expressions that have to have a consistent value - * across all rows of the query, such as a LIMIT. Arguably it should reject - * volatile functions, too, but we don't do that --- whatever value the - * function gives on first execution is what you get. - * - * constructName does not affect the semantics, but is used in error messages - */ -static void -checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName) -{ - if (contain_vars_of_level(n, 0)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("argument of %s must not contain variables", constructName), - parser_errposition(pstate, locate_var_of_level(n, 0)))); - } -} - - -/* - * transformFrameOffset - * Process a window frame offset expression - * - * In RANGE mode, rangeopfamily is the sort opfamily for the input ORDER BY - * column, and rangeopcintype is the input data type the sort operator is - * registered with. We expect the in_range function to be registered with - * that same type. (In binary-compatible cases, it might be different from - * the input column's actual type, so we can't use that for the lookups.) - * We'll return the OID of the in_range function to *inRangeFunc. - */ -static Node * -transform_frame_offset(ParseState *pstate, int frameOptions, - Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc, - Node *clause) -{ - const char *constructName = NULL; - Node *node; - - *inRangeFunc = InvalidOid; /* default result */ - - /* Quick exit if no offset expression */ - if (clause == NULL) - return NULL; - - if (frameOptions & FRAMEOPTION_ROWS) - { - /* Transform the raw expression tree */ - node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_ROWS); - - /* - * Like LIMIT clause, simply coerce to int8 - */ - constructName = "ROWS"; - node = coerce_to_specific_type(pstate, node, INT8OID, constructName); - } - else if (frameOptions & FRAMEOPTION_RANGE) - { - /* - * We must look up the in_range support function that's to be used, - * possibly choosing one of several, and coerce the "offset" value to - * the appropriate input type. - */ - Oid nodeType; - Oid preferredType; - int nfuncs = 0; - int nmatches = 0; - Oid selectedType = InvalidOid; - Oid selectedFunc = InvalidOid; - CatCList *proclist; - int i; - - /* Transform the raw expression tree */ - node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE); - nodeType = exprType(node); - - /* - * If there are multiple candidates, we'll prefer the one that exactly - * matches nodeType; or if nodeType is as yet unknown, prefer the one - * that exactly matches the sort column type. (The second rule is - * like what we do for "known_type operator unknown".) - */ - preferredType = (nodeType != UNKNOWNOID) ? nodeType : rangeopcintype; - - /* Find the in_range support functions applicable to this case */ - proclist = SearchSysCacheList2(AMPROCNUM, - ObjectIdGetDatum(rangeopfamily), - ObjectIdGetDatum(rangeopcintype)); - for (i = 0; i < proclist->n_members; i++) - { - HeapTuple proctup = &proclist->members[i]->tuple; - Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); - - /* The search will find all support proc types; ignore others */ - if (procform->amprocnum != BTINRANGE_PROC) - continue; - nfuncs++; - - /* Ignore function if given value can't be coerced to that type */ - if (!can_coerce_type(1, &nodeType, &procform->amprocrighttype, - COERCION_IMPLICIT)) - continue; - nmatches++; - - /* Remember preferred match, or any match if didn't find that */ - if (selectedType != preferredType) - { - selectedType = procform->amprocrighttype; - selectedFunc = procform->amproc; - } - } - ReleaseCatCacheList(proclist); - - /* - * Throw error if needed. It seems worth taking the trouble to - * distinguish "no support at all" from "you didn't match any - * available offset type". - */ - if (nfuncs == 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s", - format_type_be(rangeopcintype)), - parser_errposition(pstate, exprLocation(node)))); - if (nmatches == 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s and offset type %s", - format_type_be(rangeopcintype), - format_type_be(nodeType)), - errhint("Cast the offset value to an appropriate type."), - parser_errposition(pstate, exprLocation(node)))); - if (nmatches != 1 && selectedType != preferredType) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RANGE with offset PRECEDING/FOLLOWING has multiple interpretations for column type %s and offset type %s", - format_type_be(rangeopcintype), - format_type_be(nodeType)), - errhint("Cast the offset value to the exact intended type."), - parser_errposition(pstate, exprLocation(node)))); - - /* OK, coerce the offset to the right type */ - constructName = "RANGE"; - node = coerce_to_specific_type(pstate, node, - selectedType, constructName); - *inRangeFunc = selectedFunc; - } - else if (frameOptions & FRAMEOPTION_GROUPS) - { - /* Transform the raw expression tree */ - node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_GROUPS); - - /* - * Like LIMIT clause, simply coerce to int8 - */ - constructName = "GROUPS"; - node = coerce_to_specific_type(pstate, node, INT8OID, constructName); - } - else - { - Assert(false); - node = NULL; - } - - /* Disallow variables in frame offsets */ - checkExprIsVarFree(pstate, node, constructName); - - return node; -} - -/* - * transformWindowDefinitions - - * transform window definitions (WindowDef to WindowClause) - */ -List * -transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist) -{ - List *result = NIL; - Index winref = 0; - ListCell *lc; - - foreach(lc, windowdefs) - { - WindowDef *windef = (WindowDef *) lfirst(lc); - WindowClause *refwc = NULL; - List *partitionClause; - List *orderClause; - Oid rangeopfamily = InvalidOid; - Oid rangeopcintype = InvalidOid; - WindowClause *wc; - - winref++; - - /* - * Check for duplicate window names. - */ - if (windef->name && findWindowClause(result, windef->name) != NULL) - ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("window \"%s\" is already defined", windef->name), - parser_errposition(pstate, windef->location))); - - /* - * If it references a previous window, look that up. - */ - if (windef->refname) - { - refwc = findWindowClause(result, windef->refname); - if (refwc == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("window \"%s\" does not exist", - windef->refname), - parser_errposition(pstate, windef->location))); - } - - /* - * Transform PARTITION and ORDER specs, if any. These are treated - * almost exactly like top-level GROUP BY and ORDER BY clauses, - * including the special handling of nondefault operator semantics. - */ - orderClause = transform_cypher_order_by(pstate, windef->orderClause, targetlist, EXPR_KIND_ORDER_BY); - partitionClause = transform_group_clause(pstate, windef->partitionClause, NULL, targetlist, - orderClause, EXPR_KIND_GROUP_BY); - - /* - * And prepare the new WindowClause. - */ - wc = makeNode(WindowClause); - wc->name = windef->name; - wc->refname = windef->refname; - - /* - * Per spec, a windowdef that references a previous one copies the - * previous partition clause (and mustn't specify its own). It can - * specify its own ordering clause, but only if the previous one had - * none. It always specifies its own frame clause, and the previous - * one must not have a frame clause. Yeah, it's bizarre that each of - * these cases works differently, but SQL:2008 says so; see 7.11 - * syntax rule 10 and general rule 1. The frame - * clause rule is especially bizarre because it makes "OVER foo" - * different from "OVER (foo)", and requires the latter to throw an - * error if foo has a nondefault frame clause. Well, ours not to - * reason why, but we do go out of our way to throw a useful error - * message for such cases. - */ - if (refwc) - { - if (partitionClause) - ereport(ERROR, - (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot override PARTITION BY clause of window \"%s\"", windef->refname), - parser_errposition(pstate, windef->location))); - wc->partitionClause = copyObject(refwc->partitionClause); - } - else - wc->partitionClause = partitionClause; - if (refwc) { - if (orderClause && refwc->orderClause) - ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot override ORDER BY clause of window \"%s\"", windef->refname), - parser_errposition(pstate, windef->location))); - if (orderClause) { - wc->orderClause = orderClause; - wc->copiedOrder = false; - } else { - wc->orderClause = copyObject(refwc->orderClause); - wc->copiedOrder = true; - } - } else { - wc->orderClause = orderClause; - wc->copiedOrder = false; - } - if (refwc && refwc->frameOptions != FRAMEOPTION_DEFAULTS) { - /* - * Use this message if this is a WINDOW clause, or if it's an OVER - * clause that includes ORDER BY or framing clauses. (We already - * rejected PARTITION BY above, so no need to check that.) - */ - if (windef->name || orderClause || windef->frameOptions != FRAMEOPTION_DEFAULTS) - ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot copy window \"%s\" because it has a frame clause", windef->refname), - parser_errposition(pstate, windef->location))); - /* Else this clause is just OVER (foo), so say this: */ - ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot copy window \"%s\" because it has a frame clause", windef->refname), - errhint("Omit the parentheses in this OVER clause."), - parser_errposition(pstate, windef->location))); - } - wc->frameOptions = windef->frameOptions; - - /* - * RANGE offset PRECEDING/FOLLOWING requires exactly one ORDER BY - * column; check that and get its sort opfamily info. - */ - if ((wc->frameOptions & FRAMEOPTION_RANGE) && - (wc->frameOptions & (FRAMEOPTION_START_OFFSET | FRAMEOPTION_END_OFFSET))) - { - SortGroupClause *sortcl; - Node *sortkey; - int16 rangestrategy; - - if (list_length(wc->orderClause) != 1) - ereport(ERROR, - (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column"), - parser_errposition(pstate, windef->location))); - sortcl = castNode(SortGroupClause, linitial(wc->orderClause)); - sortkey = get_sortgroupclause_expr(sortcl, *targetlist); - /* Find the sort operator in pg_amop */ - if (!get_ordering_op_properties(sortcl->sortop, - &rangeopfamily, - &rangeopcintype, - &rangestrategy)) - elog(ERROR, "operator %u is not a valid ordering operator", - sortcl->sortop); - /* Record properties of sort ordering */ - wc->inRangeColl = exprCollation(sortkey); - wc->inRangeAsc = (rangestrategy == BTLessStrategyNumber); - wc->inRangeNullsFirst = sortcl->nulls_first; - } - - /* Per spec, GROUPS mode requires an ORDER BY clause */ - if (wc->frameOptions & FRAMEOPTION_GROUPS) - { - if (wc->orderClause == NIL) - ereport(ERROR, - (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("GROUPS mode requires an ORDER BY clause"), - parser_errposition(pstate, windef->location))); - } - - /* Process frame offset expressions */ - wc->startOffset = transform_frame_offset(pstate, wc->frameOptions, - rangeopfamily, rangeopcintype, - &wc->startInRangeFunc, - windef->startOffset); - wc->endOffset = transform_frame_offset(pstate, wc->frameOptions, - rangeopfamily, rangeopcintype, - &wc->endInRangeFunc, - windef->endOffset); - wc->winref = winref; - - result = lappend(result, wc); - } - - return result; -} - - -// see transformSortClause() -List *transform_cypher_order_by(cypher_parsestate *cpstate, List *sort_items, List **target_list, - ParseExprKind expr_kind) { - ParseState *pstate = (ParseState *)cpstate; - List *sort_list = NIL; - ListCell *li; - - foreach (li, sort_items) { - SortBy *sort_by = lfirst(li); - TargetEntry *te; - - te = find_target_list_entry(cpstate, sort_by->node, target_list, expr_kind); - - sort_list = addTargetToSortList(pstate, te, sort_list, *target_list, sort_by); - } - - return sort_list; -} - -// see findTargetlistEntrySQL99() -static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, - ParseExprKind expr_kind) { - Node *expr; - ListCell *lt; - TargetEntry *te; - - expr = transform_cypher_expr(cpstate, node, expr_kind); - - foreach (lt, *target_list) { - Node *te_expr; - - te = lfirst(lt); - te_expr = strip_implicit_coercions((Node *)te->expr); - - if (equal(expr, te_expr)) - return te; - } - - te = transform_cypher_item(cpstate, node, expr, expr_kind, NULL, true); - - *target_list = lappend(*target_list, te); - - return te; -} - -// see transformLimitClause() -static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, ParseExprKind expr_kind, - const char *construct_name) { - ParseState *pstate = (ParseState *)cpstate; - Node *qual; - - if (!node) - return NULL; - - qual = transform_cypher_expr(cpstate, node, expr_kind); - - qual = coerce_to_specific_type(pstate, qual, INT8OID, construct_name); - - // LIMIT can't refer to any variables of the current query. - if (contain_vars_of_level(qual, 0)) - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("argument of %s must not contain variables", construct_name), - parser_errposition(pstate, locate_var_of_level(qual, 0)))); - - return qual; -} - -static Query *transform_cypher_with(cypher_parsestate *cpstate, cypher_clause *clause) { - cypher_with *self = (cypher_with *)clause->self; - cypher_return *return_clause; - cypher_clause *wrapper; - - // WITH clause is basically RETURN clause with optional WHERE subclause - return_clause = make_ag_node(cypher_return); - return_clause->distinct = self->distinct; - return_clause->items = self->items; - return_clause->real_group_clause = self->real_group_clause; - return_clause->having = self->having; - return_clause->window_clause = self->window_clause; - return_clause->order_by = self->order_by; - return_clause->skip = self->skip; - return_clause->limit = self->limit; - return_clause->where = self->where; - - wrapper = palloc(sizeof(*wrapper)); - wrapper->self = (Node *)return_clause; - wrapper->prev = clause->prev; - - return transform_cypher_clause_with_where(cpstate, transform_cypher_return, wrapper); -} - -static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, transform_method transform, - cypher_clause *clause) -{ - ParseState *pstate = (ParseState *)cpstate; - Query *query; - cypher_match *self = (cypher_match *)clause->self; - Node *where = self->where; - - if (where) { - int rtindex; - ParseNamespaceItem *pnsi; - - query = makeNode(Query); - query->commandType = CMD_SELECT; - - pnsi = transform_cypher_clause_as_subquery(cpstate, transform, clause, NULL, true); - Assert(pnsi != NULL); - rtindex = list_length(pstate->p_rtable); - Assert(rtindex == 1); // rte is the only RangeTblEntry in pstate - - query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1); - - markTargetListOrigins(pstate, query->targetList); - - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - - assign_query_collations(pstate, query); - } else { - query = transform(cpstate, clause); - } - - query->hasSubLinks = pstate->p_hasSubLinks; - query->hasTargetSRFs = pstate->p_hasTargetSRFs; - query->hasAggs = pstate->p_hasAggs; - - return query; -} - -static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause) { - return transform_cypher_clause_with_where(cpstate, transform_cypher_match_pattern, clause); -} - -/* - * Transform the clause into a subquery. This subquery will be used - * in a join so setup the namespace item and the created the rtr - * for the join to use. - */ -static Node *transform_clause_for_join(cypher_parsestate *cpstate, cypher_clause *clause, - RangeTblEntry **rte, ParseNamespaceItem **nsitem, Alias* alias) { - RangeTblRef *rtr; - - *nsitem = transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, clause, alias, false); - *rte = (*nsitem)->p_rte; - - rtr = makeNode(RangeTblRef); - rtr->rtindex = (*nsitem)->p_rtindex; - - return (Node *) rtr; -} - -/* - * For cases where we need to join two subqueries together (OPTIONAL MATCH and - * MERGE) we need to take the columns available in each rte and merge them - * together. The l_rte has precedence when there is a conflict, because that - * means that the pattern create in the current clause is referencing a - * variable declared in a previous clause (the l_rte). The output is the - * res_colnames and res_colvars that are passed in. - */ -static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, - ParseNamespaceItem *r_pnsi, List **res_colnames, List **res_colvars) { - List *l_colnames, *l_colvars; - List *r_colnames, *r_colvars; - ListCell *r_lname, *r_lvar; - List *colnames = NIL; - List *colvars = NIL; - - expandRTE(l_pnsi->p_rte, l_pnsi->p_rtindex, 0, -1, false, &l_colnames, &l_colvars); - expandRTE(r_pnsi->p_rte, r_pnsi->p_rtindex, 0, -1, false, &r_colnames, &r_colvars); - - // add in all colnames and colvars from the l_rte. - *res_colnames = list_concat(*res_colnames, l_colnames); - *res_colvars = list_concat(*res_colvars, l_colvars); - - // find new columns and if they are a var, pass them in. - forboth(r_lname, r_colnames, r_lvar, r_colvars) { - char *r_colname = strVal(lfirst(r_lname)); - ListCell *lname; - ListCell *lvar; - Var *var = NULL; - - forboth(lname, *res_colnames, lvar, *res_colvars) { - char *colname = strVal(lfirst(lname)); - - if (strcmp(r_colname, colname) == 0) { - var = lfirst(lvar); - break; - } - } - - if (var == NULL) { - colnames = lappend(colnames, lfirst(r_lname)); - colvars = lappend(colvars, lfirst(r_lvar)); - } - } - - *res_colnames = list_concat(*res_colnames, colnames); - *res_colvars = list_concat(*res_colvars, colvars); -} - -/* - * transform_cypher_match_clause - * Transform the previous clauses and OPTIONAL MATCH clauses to be LATERAL LEFT JOIN - * transform_cypher_match_clause to construct a result value. - */ -static RangeTblEntry *transform_cypher_match_clause(cypher_parsestate *cpstate, cypher_clause *clause) { - cypher_clause *prevclause; - RangeTblEntry *l_rte, *r_rte; - ParseNamespaceItem *l_nsitem, *r_nsitem; - ParseState *pstate = (ParseState *) cpstate; - JoinExpr* j = makeNode(JoinExpr); - List *res_colnames = NIL, *res_colvars = NIL; - Alias *l_alias, *r_alias; - ParseNamespaceItem *jnsitem; - int i = 0; - - j->jointype = JOIN_INNER; - - l_alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL); - - j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, &l_nsitem, l_alias); - pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); - - /* - * Remove the previous clause so when the transform_clause_for_join function - * transforms the OPTIONAL MATCH, the previous clause will not be transformed - * again. - */ - prevclause = clause->prev; - clause->prev = NULL; - - //set the lateral flag to true - pstate->p_lateral_active = true; - - j->rarg = transform_clause_for_join(cpstate, clause, &r_rte, &r_nsitem, r_alias); - - // we are done transform the lateral left join - pstate->p_lateral_active = false; - - /* - * We are done with the previous clause in the transform phase, but - * reattach the previous clause for semantics. - */ - clause->prev = prevclause; - - pstate->p_namespace = NIL; - - // get the colnames and colvars from the rtes - get_res_cols(pstate, l_nsitem, r_nsitem, &res_colnames, &res_colvars); - - jnsitem = addRangeTableEntryForJoin(pstate, res_colnames, NULL, j->jointype, 0, res_colvars, NIL, - NIL, j->alias, NULL, false); - - j->rtindex = jnsitem->p_rtindex; - - for (i = list_length(pstate->p_joinexprs) + 1; i < j->rtindex; i++) - pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL); - pstate->p_joinexprs = lappend(pstate->p_joinexprs, j); - Assert(list_length(pstate->p_joinexprs) == j->rtindex); - - pstate->p_joinlist = lappend(pstate->p_joinlist, j); - - // add jrte to column namespace only - addNSItemToQuery(pstate, jnsitem, false, false, true); - - return jnsitem->p_rte; -} - - -/* - * transform_cypher_optional_match_clause - * Transform the previous clauses and OPTIONAL MATCH clauses to be LATERAL LEFT JOIN - * transform_cypher_optional_match_clause to construct a result value. - */ -static RangeTblEntry *transform_cypher_optional_match_clause(cypher_parsestate *cpstate, cypher_clause *clause) { - cypher_clause *prevclause; - RangeTblEntry *l_rte, *r_rte; - ParseNamespaceItem *l_nsitem, *r_nsitem; - ParseState *pstate = (ParseState *) cpstate; - JoinExpr* j = makeNode(JoinExpr); - List *res_colnames = NIL, *res_colvars = NIL; - Alias *l_alias, *r_alias; - ParseNamespaceItem *jnsitem; - int i = 0; - - j->jointype = JOIN_LEFT; - - l_alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL); - - j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, &l_nsitem, l_alias); - pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); - - /* - * Remove the previous clause so when the transform_clause_for_join function - * transforms the OPTIONAL MATCH, the previous clause will not be transformed - * again. - */ - prevclause = clause->prev; - clause->prev = NULL; - - //set the lateral flag to true - pstate->p_lateral_active = true; - - j->rarg = transform_clause_for_join(cpstate, clause, &r_rte, &r_nsitem, r_alias); - - // we are done transform the lateral left join - pstate->p_lateral_active = false; - - /* - * We are done with the previous clause in the transform phase, but - * reattach the previous clause for semantics. - */ - clause->prev = prevclause; - - pstate->p_namespace = NIL; - - // get the colnames and colvars from the rtes - get_res_cols(pstate, l_nsitem, r_nsitem, &res_colnames, &res_colvars); - - jnsitem = addRangeTableEntryForJoin(pstate, res_colnames, NULL, j->jointype, 0, res_colvars, NIL, NIL, j->alias, NULL, false); - - j->rtindex = jnsitem->p_rtindex; - - for (i = list_length(pstate->p_joinexprs) + 1; i < j->rtindex; i++) - pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL); - pstate->p_joinexprs = lappend(pstate->p_joinexprs, j); - Assert(list_length(pstate->p_joinexprs) == j->rtindex); - - pstate->p_joinlist = lappend(pstate->p_joinlist, j); - - // add jrte to column namespace only - addNSItemToQuery(pstate, jnsitem, false, false, true); - - return jnsitem->p_rte; -} - -static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause) { - ParseState *pstate = (ParseState *)cpstate; - cypher_match *self = (cypher_match *)clause->self; - Query *query; - Node *where = self->where; - Node *order_by = self->order_by; - self->order_by = NULL; - query = makeNode(Query); - query->commandType = CMD_SELECT; - - // If there is no previous clause, transform to a general MATCH clause. - if (self->optional == true && clause->prev != NULL) { - RangeTblEntry *rte = transform_cypher_optional_match_clause(cpstate, clause); - - query->targetList = make_target_list_from_join(pstate, rte); - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - } else if (clause->prev && (self->order_by)) { - RangeTblEntry *rte = transform_cypher_match_clause(cpstate, clause); - - query->targetList = make_target_list_from_join(pstate, rte); - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - } else if (clause->prev) { - RangeTblEntry *rte; - int rtindex; - ParseNamespaceItem *pnsi; - - pnsi = transform_prev_cypher_clause(cpstate, clause->prev, true); - rte = pnsi->p_rte; - rtindex = list_length(pstate->p_rtable); - Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate - - /* - * add all the target entries in rte to the current target list to pass - * all the variables that are introduced in the previous clause to the - * next clause - */ - pnsi = get_namespace_item(pstate, rte); - query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1); - transform_match_pattern(cpstate, query, self->pattern, where); - } else { - transform_match_pattern(cpstate, query, self->pattern, where); - } - - // ORDER BY - query->sortClause = transform_cypher_order_by(cpstate, order_by, &query->targetList, EXPR_KIND_ORDER_BY); - - markTargetListOrigins(pstate, query->targetList); - - query->hasSubLinks = pstate->p_hasSubLinks; - query->hasWindowFuncs = pstate->p_hasWindowFuncs; - query->hasTargetSRFs = pstate->p_hasTargetSRFs; - query->hasAggs = pstate->p_hasAggs; - - assign_query_collations(pstate, query); - - return query; -} - -static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte) { - List *targetlist = NIL; - ListCell *lt; - ListCell *ln; - - AssertArg(rte->rtekind == RTE_JOIN); - - forboth(lt, rte->joinaliasvars, ln, rte->eref->colnames) { - Var *varnode = lfirst(lt); - char *resname = strVal(lfirst(ln)); - TargetEntry *tmp; - - tmp = makeTargetEntry((Expr *) varnode, (AttrNumber) pstate->p_next_resno++, pstrdup(resname), false); - targetlist = lappend(targetlist, tmp); - } - - return targetlist; -} - -/* - * Transform a cypher sub pattern. This is put here because it is a sub clause. - * This works in tandem with transform_Sublink in cypher_expr.c - */ -static Query *transform_cypher_sub_pattern(cypher_parsestate *cpstate, cypher_clause *clause) { - cypher_clause *c = NULL; - Query *qry; - cypher_sub_pattern *subpat = (cypher_sub_pattern*)clause->self; - cypher_parsestate *child_parse_state = make_cypher_parsestate(cpstate); - - // Since the first clause in stmt is the innermost subquery, the order of the clauses is inverted. - ListCell *lc; - foreach (lc, subpat->pattern) { - cypher_clause *next; - - next = palloc(sizeof(*next)); - next->next = NULL; - next->self = lfirst(lc); - next->prev = c; - - if (c != NULL) - c->next = next; - c = next; - } - - //ParseExprKind old_expr_kind = pstate->p_expr_kind; - child_parse_state->entities = list_concat(NIL, cpstate->entities); - child_parse_state->pstate.p_expr_kind = EXPR_KIND_WHERE; - qry = transform_cypher_clause(child_parse_state, c); - - - - free_cypher_parsestate(child_parse_state); - - return qry; -} -static Node *make_null_const(int location) { - A_Const *n; - - n = makeNode(A_Const); - n->val.type = T_Null; - n->location = location; - - return (Node *)n; -} -static Node *make_int_const(int i, int location) { - A_Const *n; - - n = makeNode(A_Const); - n->val.type = T_Integer; - n->val.val.ival = i; - n->location = location; - - return (Node *)n; -} - -static Node *make_string_const(char *s, int location) { - A_Const *n; - - n = makeNode(A_Const); - n->val.type = T_String; - n->val.val.str = s; - n->location = location; - - return (Node *)n; -} - -static ParseNamespaceItem *add_vle_to_query(cypher_parsestate *cpstate, Node *n) { - ParseState *pstate = &cpstate->pstate; - RangeTblEntry *rte = NULL; - List *namespace = NULL; - int rtindex; - - n = transform_VLE_Function(cpstate, n, &rte, &rtindex, &namespace); - Assert(n != NULL); - - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); - - setNamespaceLateralState(namespace, true, true); - - pstate->p_joinlist = lappend(pstate->p_joinlist, n); - pstate->p_namespace = list_concat(pstate->p_namespace, namespace); - - setNamespaceLateralState(pstate->p_namespace, false, true); - - return lfirst(list_head(namespace)); -} - -static FuncCall *make_vle_func_call(cypher_parsestate *cpstate, cypher_node *prev_node, cypher_relationship *rel, cypher_node *next_node) -{ - ColumnRef *cref; - A_Indices *ai = (A_Indices *)rel->varlen; - List *args = NIL; - - - // start node - cref = makeNode(ColumnRef); - cref->location = -1; - cref->fields = list_make1(makeString(prev_node->name)); - args = lappend(args, cref); - - //end node - Assert(next_node->name != NULL); - - cref = makeNode(ColumnRef); - cref->location = -1; - cref->fields = list_make1(makeString(next_node->name)); - args = lappend(args, cref); - - // lower bound - if (ai == NULL || ai->lidx == NULL) - args = lappend(args, make_null_const(-1)); - else - args = lappend(args, ai->lidx); - - // upper bound - if (ai == NULL || ai->uidx == NULL) - args = lappend(args, make_null_const(-1)); - else - args = lappend(args, ai->uidx); - - // direction - args = lappend(args, make_int_const(rel->dir, -1)); - - if (rel->label == NULL) - args = lappend(args, make_null_const(-1)); - else - args = lappend(args, make_string_const(rel->label, -1)); - - if (rel->props == NULL) - args = lappend(args, make_null_const(-1)); - else - args = lappend(args, rel->props); - - return makeFuncCall(list_make1(makeString("vle")), args, COERCE_SQL_SYNTAX, -1); -} - -static transform_entity *handle_vertex(cypher_parsestate *cpstate, Query *query, - cypher_path *path, cypher_node *node, int i) -{ - Expr *expr = NULL; - transform_entity *entity = NULL; - - if(node->name == NULL && !INCLUDE_NODE_IN_JOIN_TREE(path, node) && i + 1 < list_length(path->path)) { - cypher_relationship *rel = list_nth(path->path, i + 1); - - if (rel->varlen != NULL) - node->name = get_next_default_alias(cpstate); - } - - // transform vertex - expr = transform_cypher_node(cpstate, node, &query->targetList, list_length(path->path) == 1 ? true :INCLUDE_NODE_IN_JOIN_TREE(path, node)); - - entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, expr, node->name); - - // transform properties if they exist - if (node->props) - { - Node *n = NULL; - - n = create_property_constraints(cpstate, entity, node->props); - - cpstate->property_constraint_quals = lappend(cpstate->property_constraint_quals, n); - } - - return entity; -} - -static transform_entity *handle_edge(cypher_parsestate *cpstate, Query *query, - cypher_path *path, cypher_relationship *rel, - int i, ListCell *lc, transform_entity *prev_entity) -{ - transform_entity *entity = NULL; - Expr *expr = NULL; - - /* - * In the case where the MATCH is one edge and two vertices, the - * edge is bidirectional, and neither vertex is included in the - * join tree, we need to force one of the vertices into the join - * tree to ensure the output is generated correctly. - */ - if (list_length(path->path) == 3 && rel->dir == CYPHER_REL_DIR_NONE && !prev_entity->in_join_tree) { - cypher_node *node = list_nth(path->path, 2);//(cypher_node *)lfirst(lnext(path->path, lc)); - - if (!INCLUDE_NODE_IN_JOIN_TREE(path, node)) - { - /* - * Assigning a variable name here will ensure that when - * the next vertex is processed, the vertex will be - * included in the join tree. - */ - node->name = get_next_default_alias(cpstate); - } - } - - expr = transform_cypher_edge(cpstate, rel, &query->targetList); - - entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)rel, expr, rel->name); - if (rel->props) { - Node *n = create_property_constraints(cpstate, entity, rel->props); - - cpstate->property_constraint_quals = lappend(cpstate->property_constraint_quals, n); - } - - return entity; -} - -static List *insert_vle_entity(List *entities, transform_entity *next_entity, transform_entity *vle_entity) { - entities = list_truncate(entities, list_length(entities) - 1); - entities = lappend(entities, vle_entity); - - entities = lappend(entities, next_entity); - - return entities; -} - -static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace) { - ParseState *pstate = &cpstate->pstate; - - Assert(IsA(n, RangeFunction)); - - if (IsA(n, RangeFunction)) { - RangeTblRef *rtr; - RangeTblEntry *rte; - ParseNamespaceItem *nsitem; - int rtindex; - - nsitem = transform_RangeFunction(cpstate, (RangeFunction *) n); - rte = nsitem->p_rte; - rtindex = list_length(pstate->p_rtable); - Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); - *top_rte = rte; - *top_rti = rtindex; - *namespace = list_make1(nsitem); - rtr = makeNode(RangeTblRef); - rtr->rtindex = rtindex; - return (Node *) rtr; - } - - return NULL; -} - -// setNamespaceLateralState - subroutine to update LATERAL flags in a namespace list. -static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok) { - ListCell *lc; - - foreach(lc, namespace) { - ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc); - - nsitem->p_lateral_only = lateral_only; - nsitem->p_lateral_ok = lateral_ok; - } -} - -// transform a function call appearing in FROM -static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, RangeFunction *r) { - ParseState *pstate = NULL; - List *funcexprs = NIL; - List *funcnames = NIL; - List *coldeflists = NIL; - bool is_lateral = false; - ListCell *lc = NULL; - ParseNamespaceItem *pnsi; - - pstate = &cpstate->pstate; - - Assert(!pstate->p_lateral_active); - pstate->p_lateral_active = true; - - // transform the raw expressions - foreach(lc, r->functions) - { - List *pair = (List*)lfirst(lc); - Node *fexpr; - List *coldeflist; - Node *newfexpr; - Node *last_srf; - - // Disassemble the function-call/column-def-list pairs - Assert(list_length(pair) == 2); - fexpr = (Node*) linitial(pair); - coldeflist = (List*) lsecond(pair); - - // normal case ... - last_srf = pstate->p_last_srf; - - // transform the function expression - newfexpr = transform_cypher_expr(cpstate, fexpr, EXPR_KIND_FROM_FUNCTION); - - // nodeFunctionscan.c requires SRFs to be at top level - if (pstate->p_last_srf != last_srf && pstate->p_last_srf != newfexpr) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-returning functions must appear at top level of FROM"), - parser_errposition(pstate, exprLocation(pstate->p_last_srf)))); - - funcexprs = lappend(funcexprs, newfexpr); - funcnames = lappend(funcnames, FigureColname(fexpr)); - - if (coldeflist && r->coldeflist) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("multiple column definition lists are not allowed for the same function"), - parser_errposition(pstate, exprLocation((Node *) r->coldeflist)))); - - coldeflists = lappend(coldeflists, coldeflist); - } - - pstate->p_lateral_active = false; - - /* - * We must assign collations now so that the RTE exposes correct collation - * info for Vars created from it. - */ - assign_list_collations(pstate, funcexprs); - - // currently this is not used by the VLE - Assert(r->coldeflist == NULL); - - // mark the RTE as LATERAL - is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0); - - // build an RTE for the function - pnsi = addRangeTableEntryForFunction(pstate, funcnames, funcexprs, coldeflists, r, is_lateral, true); - - return pnsi; -} - -static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { - ParseState *pstate = (ParseState *)cpstate; - ListCell *lc; - List *quals = NIL; - Expr *q = NULL; - Expr *expr = NULL; - - foreach (lc, pattern) { - List *qual = NULL; - cypher_path *path = (cypher_path *) lfirst(lc); - - qual = transform_match_path(cpstate, query, path); - - quals = list_concat(quals, qual); - } - - if (quals != NIL) { - q = makeBoolExpr(AND_EXPR, quals, -1); - expr = (Expr *)sql_transform_expr(&cpstate->pstate, (Node *)q, EXPR_KIND_WHERE); - } - - if (cpstate->property_constraint_quals != NIL) { - Expr *prop_qual = makeBoolExpr(AND_EXPR, cpstate->property_constraint_quals, -1); - - if (expr == NULL) - expr = prop_qual; - else - expr = makeBoolExpr(AND_EXPR, list_make2(expr, prop_qual), -1); - } - - // transform the where clause quals and add to the quals, - if (where != NULL) { - Expr *where_qual; - - where_qual = (Expr *)transform_cypher_expr(cpstate, where, EXPR_KIND_WHERE); - if (expr == NULL) { - expr = where_qual; - } else { - /* - * coerce the WHERE clause to a boolean before AND with the property - * contraints, otherwise there could be evaluation issues. - */ - where_qual = (Expr *)coerce_to_boolean(pstate, (Node *)where_qual, "WHERE"); - - expr = makeBoolExpr(AND_EXPR, list_make2(expr, where_qual), -1); - } - } - - /* - * Coerce to WHERE clause to a bool, denoting whether the constructed - * clause is true or false. - */ - if (expr != NULL) - expr = (Expr *)coerce_to_boolean(pstate, (Node *)expr, "WHERE"); - - query->rtable = cpstate->pstate.p_rtable; - query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr); -} - -/* - * Creates a FuncCall node that will prevent an edge from being joined - * to twice. - */ -static FuncCall *prevent_duplicate_edges(cypher_parsestate *cpstate, List *entities) { - List *edges = NIL; - ListCell *lc; - List *qualified_function_name; - Value *ag_catalog, *edge_fn; - - ag_catalog = makeString("postgraph"); - edge_fn = makeString("_ag_enforce_edge_uniqueness"); - - qualified_function_name = list_make2(ag_catalog, edge_fn); - - bool has_vle = false; - foreach (lc, entities) { - transform_entity *entity = lfirst(lc); - - if (entity->type == ENT_VLE_EDGE) { - has_vle = true; - break; - } - } - - /* - * if the match clause uses the VLE code, we need to do the old slow way... for now. - * that could probably be fixed by exposes the edges being scanned, but we will worry - * about that later. For MATCH with no VLE edges, just use <> between each id in the path - * let the optimizer do its magic. - */ - if (has_vle) { - // iterate through each entity, collecting the access node for each edge - foreach (lc, entities) - { - transform_entity *entity = lfirst(lc); - Node *edge; - - if (entity->type == ENT_EDGE) { - edge = make_qual(cpstate, entity, AG_EDGE_COLNAME_ID); - - edges = lappend(edges, edge); - } else if (entity->type == ENT_VLE_EDGE) { - edges = lappend(edges, entity->expr); - } - } - - return makeFuncCall(qualified_function_name, edges, COERCE_SQL_SYNTAX, -1); - } - - for (int i = 0; i < list_length(entities); i++) { - transform_entity *entity_i = (transform_entity *)list_nth_cell(entities, i)->ptr_value; - - if (entity_i->type == ENT_EDGE) { - for (int j = i + 1; j < list_length(entities); j++) { - transform_entity *entity_j = (transform_entity *)list_nth_cell(entities, j)->ptr_value; - if (entity_j->type == ENT_EDGE) { - Node *left = make_qual(cpstate, entity_i, AG_EDGE_COLNAME_ID); - Node *right = make_qual(cpstate, entity_j, AG_EDGE_COLNAME_ID); - - edges = lappend (edges, makeSimpleCypherA_Expr(AEXPR_OP, "<>", left, right, -1)); - } - - } - } - } - return makeBoolExpr(AND_EXPR, edges, -1); -} - -/* - * For any given edge, the previous entity is joined with the edge - * via the prev_qual node, and the next entity is join with the - * next_qual node. If there is a filter on the previous vertex label, - * create a filter, same with the next node. - */ -static List *make_directed_edge_join_conditions(cypher_parsestate *cpstate, transform_entity *prev_entity, - transform_entity *next_entity, Node *prev_qual, Node *next_qual, char *prev_node_filter, char *next_node_filter) { - List *quals = NIL; - - if (prev_entity->in_join_tree) - quals = list_concat(quals, join_to_entity(cpstate, prev_entity, prev_qual, JOIN_SIDE_LEFT)); - - if (next_entity->in_join_tree && next_entity->type != ENT_VLE_EDGE) - quals = list_concat(quals, join_to_entity(cpstate, next_entity, next_qual, JOIN_SIDE_RIGHT)); - - if (prev_node_filter != NULL && !IS_DEFAULT_LABEL_VERTEX(prev_node_filter)) { - A_Expr *qual = filter_vertices_on_label_id(cpstate, prev_qual, prev_node_filter); - - quals = lappend(quals, qual); - } - - if (next_node_filter != NULL && !IS_DEFAULT_LABEL_VERTEX(next_node_filter)) { - A_Expr *qual; - qual = filter_vertices_on_label_id(cpstate, next_qual, next_node_filter); - - quals = lappend(quals, qual); - } - - return quals; -} - -/* - * The joins are driven by edges. Under specific conditions, it becomes - * necessary to have knowledge about the previous edge and vertex and - * the next vertex and edge. - * - * [prev_edge]-(prev_node)-[edge]-(next_node)-[next_edge] - * - * prev_edge and next_edge are allowed to be null. - * prev_node and next_node are not allowed to be null. - */ -static List *make_join_condition_for_edge(cypher_parsestate *cpstate, transform_entity *prev_edge, - transform_entity *prev_node, transform_entity *entity, - transform_entity *next_node, transform_entity *next_edge) { - char *next_label_name_to_filter = NULL; - char *prev_label_name_to_filter = NULL; - transform_entity *next_entity; - transform_entity *prev_entity; - - if (entity->type == ENT_VLE_EDGE) - return NIL; - - // If the previous node is not in the join tree, set the previous label filter. - if (!prev_node->in_join_tree) - prev_label_name_to_filter = prev_node->entity.node->label; - - /* - * If the next node is not in the join tree and there is not - * another edge, set the label filter. When there is another - * edge, we don't need to set it, because that edge will set the - * filter for that node. - */ - if (!next_node->in_join_tree && next_edge == NULL) - next_label_name_to_filter = next_node->entity.node->label; - - /* - * When the previous node is not in the join tree, and there - * is a previous edge, set the previous entity to that edge. - * Otherwise, use the previous node/ - */ - if (!prev_node->in_join_tree && prev_edge != NULL) - prev_entity = prev_edge; - else - prev_entity = prev_node; - - /* - * When the next node is not in the join tree, and there - * is a next edge, set the next entity to that edge. - * Otherwise, use the next node. - */ - if (!next_node->in_join_tree && next_edge != NULL) - next_entity = next_edge; - else - next_entity = next_node; - - switch (entity->entity.rel->dir) - { - case CYPHER_REL_DIR_RIGHT: - { - Node *prev_qual = make_qual(cpstate, entity, AG_EDGE_COLNAME_START_ID); - Node *next_qual = make_qual(cpstate, entity, AG_EDGE_COLNAME_END_ID); - - return make_directed_edge_join_conditions(cpstate, prev_entity, next_node, prev_qual, - next_qual, prev_label_name_to_filter, - next_label_name_to_filter); - } - case CYPHER_REL_DIR_LEFT: - { - Node *prev_qual = make_qual(cpstate, entity, AG_EDGE_COLNAME_END_ID); - Node *next_qual = make_qual(cpstate, entity, AG_EDGE_COLNAME_START_ID); - - return make_directed_edge_join_conditions(cpstate, prev_entity, next_node, prev_qual, - next_qual, prev_label_name_to_filter, - next_label_name_to_filter); - } - case CYPHER_REL_DIR_NONE: - { - /* - * For undirected relationships, we can use the left directed - * relationship OR'd by the right directed relationship. - */ - Node *start_id_expr = make_qual(cpstate, entity, AG_EDGE_COLNAME_START_ID); - Node *end_id_expr = make_qual(cpstate, entity, AG_EDGE_COLNAME_END_ID); - List *first_join_quals = NIL, *second_join_quals = NIL; - Expr *first_qual, *second_qual; - Expr *or_qual; - - first_join_quals = make_directed_edge_join_conditions(cpstate, prev_entity, next_entity, start_id_expr, - end_id_expr, prev_label_name_to_filter, - next_label_name_to_filter); - - second_join_quals = make_directed_edge_join_conditions(cpstate, prev_entity, next_entity, end_id_expr, - start_id_expr, prev_label_name_to_filter, - next_label_name_to_filter); - - first_qual = makeBoolExpr(AND_EXPR, first_join_quals, -1); - second_qual = makeBoolExpr(AND_EXPR, second_join_quals, -1); - - or_qual = makeBoolExpr(OR_EXPR, list_make2(first_qual, second_qual), -1); - - return list_make1(or_qual); - } - default: - return NULL; - } -} - -/* - * For the given entity, join it to the current edge, via the passed qual node. The side denotes if the entity is on the right - * or left of the current edge. Which we will need to know if the passed entity is a directed edge. - */ -static List *join_to_entity(cypher_parsestate *cpstate, transform_entity *entity, Node *qual, enum transform_entity_join_side side) { - ParseState *pstate = (ParseState *)cpstate; - A_Expr *expr; - List *quals = NIL; - - if (entity->type == ENT_VERTEX) { - Node *id_qual = make_qual(cpstate, entity, AG_EDGE_COLNAME_ID); - - expr = makeSimpleCypherA_Expr(AEXPR_OP, "=", qual, (Node *)id_qual, -1); - - quals = lappend(quals, expr); - } else if (entity->type == ENT_EDGE) { - List *edge_quals = make_edge_quals(cpstate, entity, side); - - if (list_length(edge_quals) > 1) - expr = makeSimpleCypherA_Expr(AEXPR_IN, "=", qual, (Node *)edge_quals, -1); - else - expr = makeSimpleCypherA_Expr(AEXPR_OP, "=", qual, linitial(edge_quals), -1); - - quals = lappend(quals, expr); - } else { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unknown entity type to join to"))); - } - - return quals; -} - -// makes the quals neccessary when an edge is joining to another edge. -static List *make_edge_quals(cypher_parsestate *cpstate, transform_entity *edge, enum transform_entity_join_side side) { - ParseState *pstate = (ParseState *)cpstate; - char *left_dir; - char *right_dir; - - Assert(edge->type == ENT_EDGE); - - switch (side) { - case JOIN_SIDE_LEFT: - left_dir = AG_EDGE_COLNAME_START_ID; - right_dir = AG_EDGE_COLNAME_END_ID; - break; - case JOIN_SIDE_RIGHT: - left_dir = AG_EDGE_COLNAME_END_ID; - right_dir = AG_EDGE_COLNAME_START_ID; - break; - default: - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unknown join type found"), parser_errposition(pstate, edge->entity.rel->location))); - } - - switch (edge->entity.rel->dir) { - case CYPHER_REL_DIR_LEFT: - return list_make1(make_qual(cpstate, edge, left_dir)); - case CYPHER_REL_DIR_RIGHT: - return list_make1(make_qual(cpstate, edge, right_dir)); - case CYPHER_REL_DIR_NONE: - return list_make2(make_qual(cpstate, edge, left_dir), make_qual(cpstate, edge, right_dir)); - default: - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unknown relationship direction"))); - } - return NIL; -} - -/* - * Creates a node that will create a filter on the passed field node - * that removes all labels that do not have the same label_id - */ -static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, Node *id_field, char *label) { - label_cache_data *lcd = search_label_name_graph_cache(label, cpstate->graph_oid); - A_Const *n; - FuncCall *fc; - Value *catalog, *extract_label_id; - int32 label_id = lcd->id; - - n = makeNode(A_Const); - n->val.type = T_Integer; - n->val.val.ival = label_id; - n->location = -1; - - catalog = makeString(CATALOG_SCHEMA); - extract_label_id = makeString("_extract_label_id"); - - fc = makeFuncCall(list_make2(catalog, extract_label_id), list_make1(id_field), COERCE_EXPLICIT_CALL, -1); - - return makeSimpleCypherA_Expr(AEXPR_OP, "=", (Node *)fc, (Node *)n, -1); -} - -/* - * Creates the Contains operator to process property contraints for a vertex/ - * edge in a MATCH clause. creates the gtype @> with the enitity's properties - * on the right and the contraints in the MATCH clause on the left. - */ -static Node *create_property_constraints(cypher_parsestate *cpstate, transform_entity *entity, Node *property_constraints) { - ParseState *pstate = (ParseState *)cpstate; - char *entity_name; - ColumnRef *cr; - Node *prop_expr, *const_expr; - Node *last_srf = pstate->p_last_srf; - ParseNamespaceItem *pnsi; - - cr = makeNode(ColumnRef); - - entity_name = get_entity_name(entity); - - cr->fields = list_make2(makeString(entity_name), makeString("properties")); - - // use Postgres to get the properties' transform node - if ((pnsi = find_pnsi(cpstate, entity_name))) - prop_expr = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); - else - prop_expr = sql_transform_expr(pstate, (Node *)cr, EXPR_KIND_WHERE); - - // use cypher to get the constraints' transform node - const_expr = transform_cypher_expr(cpstate, property_constraints, EXPR_KIND_WHERE); - - return (Node *)make_op(pstate, list_make2(makeString("postgraph"), makeString("@>")), prop_expr, const_expr, last_srf, -1); -} - - -/* - * For the given path, transform each entity within the path, create - * the path variable if needed, and construct the quals to enforce the - * correct join tree, and enforce edge uniqueness. - */ -static List *transform_match_path(cypher_parsestate *cpstate, Query *query, cypher_path *path) { - List *qual = NIL; - List *entities = NIL; - FuncCall *duplicate_edge_qual; - List *join_quals; - - // transform the entities in the path - entities = transform_match_entities(cpstate, query, path); - - // create the path variable, if needed. - if (path->var_name != NULL) { - TargetEntry *path_te; - - path_te = transform_match_create_path_variable(cpstate, path, entities); - query->targetList = lappend(query->targetList, path_te); - } - - // construct the quals for the join tree - join_quals = make_path_join_quals(cpstate, entities); - qual = list_concat(qual, join_quals); - - // construct the qual to prevent duplicate edges - if (list_length(entities) > 3) { - duplicate_edge_qual = prevent_duplicate_edges(cpstate, entities); - qual = lappend(qual, duplicate_edge_qual); - } - - return qual; -} - -RangeFunction *make_range_function(FuncCall *func, Alias *alias, bool is_lateral, bool ordinality, bool is_rows_from) { - RangeFunction *rf = makeNode(RangeFunction); - - rf->lateral = is_lateral; - rf->ordinality = ordinality; - rf->is_rowsfrom = is_rows_from; - rf->functions = list_make1(list_make2(func, NIL)); - rf->alias = alias; - - return rf; -} - -Alias *make_alias(char *name, List *colnames) { - Alias *alias = makeNode(Alias); - - alias->aliasname = name; - alias->colnames = colnames; - - return alias; -} - - -static transform_entity *transform_VLE_edge_entity(cypher_parsestate *cpstate, cypher_relationship *rel, - Query *query, FuncCall *func) { - ParseState *pstate = (ParseState *)cpstate; - - if (!rel->name) - rel->name = get_next_default_alias(cpstate); - - Alias *alias = make_alias(rel->name, NIL); - RangeFunction *rf = make_range_function(func, alias, false, false, false); - - ParseNamespaceItem *pnsi = add_vle_to_query(cpstate, (Node*)rf); - - Node *var = scanNSItemForColumn(pstate, pnsi, 0, "edges", -1); - - TargetEntry *te = makeTargetEntry((Expr*)var, pstate->p_next_resno++, rel->name, false); - query->targetList = lappend(query->targetList, te); - - transform_entity *entity = make_transform_entity(cpstate, ENT_VLE_EDGE, (Node *)rel, (Expr *)var, rel->name); - - return entity; -} - -/* - * Iterate through the path and construct all edges and necessary vertices - */ -static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, cypher_path *path) { - ListCell *lc = NULL; - List *entities = NIL; - transform_entity *prev_node_entity = NULL; - - /* - * Iterate through every node in the path, construct the expr node - * that is needed for the remaining steps - */ - for (int i = 0; i < list_length(path->path); i++) { - Expr *expr = NULL; - transform_entity *entity = NULL; - - - if (i % 2 == 0) { - cypher_node *node = NULL; - bool output_node = false; - - node = list_nth(path->path, i); - - entity = handle_vertex(cpstate, query, path, node, i); - - cpstate->entities = lappend(cpstate->entities, entity); - entities = lappend(entities, entity); - prev_node_entity = entity; - } else { - cypher_relationship *rel = NULL; - - rel = list_nth(path->path, i); - - if (!rel->varlen) { - entity = handle_edge(cpstate, query, path, rel, i, lc, prev_node_entity); - - cpstate->entities = lappend(cpstate->entities, entity); - entities = lappend(entities, entity); - } else { - transform_entity *vle_entity = NULL; - transform_entity *next_entity = NULL; - FuncCall *fnode = NULL; - - cypher_node *node = (cypher_node *)list_nth(path->path, i + 1); - - if (!INCLUDE_NODE_IN_JOIN_TREE(path, node)) - node->name = get_next_default_alias(cpstate); - - next_entity = handle_vertex(cpstate, query, path, node, i + 1); - - cpstate->entities = lappend(cpstate->entities, next_entity); - entities = lappend(entities, next_entity); - - fnode = make_vle_func_call(cpstate, prev_node_entity->entity.node, rel, node); - - vle_entity = transform_VLE_edge_entity(cpstate, rel, query, fnode); - - insert_vle_entity(cpstate->entities, next_entity, vle_entity); - insert_vle_entity(entities, next_entity, vle_entity); - - prev_node_entity = next_entity; - - i++; - } - } - } - return entities; -} - -/* - * Iterate through the list of entities setup the join conditions. Joins - * are driven through edges. To correctly setup the joins, we must - * aquire information about the previous edge and vertex, and the next - * edge and vertex. - */ -static List *make_path_join_quals(cypher_parsestate *cpstate, List *entities) { - transform_entity *prev_node = NULL, *prev_edge = NULL, *edge = NULL, *next_node = NULL, *next_edge = NULL; - ListCell *lc; - List *quals = NIL; - List *join_quals; - - // for vertex only queries, there is no work to do - if (list_length(entities) < 3) - return NIL; - - lc = list_head(entities); - for (;;) { - /* - * Initial setup, set the initial vertex as the previous vertex - * and get the first edge - */ - if (prev_node == NULL) { - prev_node = lfirst(lc); - lc = lnext(entities, lc); - edge = lfirst(lc); - } - - // Retrieve the next node and edge in the pattern. - if (lnext(entities, lc) != NULL) { - lc = lnext(entities, lc); - next_node = lfirst(lc); - - if (lnext(entities, lc) != NULL) { - lc = lnext(entities, lc); - next_edge = lfirst(lc); - } - } - - // create the join quals for the node - join_quals = make_join_condition_for_edge(cpstate, prev_edge, prev_node, edge, next_node, next_edge); - - quals = list_concat(quals, join_quals); - - /* Set the edge as the previous edge and the next edge as - * the current edge. If there is not a new edge, exit the - * for loop. - */ - prev_edge = edge; - prev_node = next_node; - edge = next_edge; - next_node = NULL; - next_edge = NULL; - - if (edge == NULL) - return quals; - } -} - -/* - * Create the path variable. Takes the list of entities, extracts the variable - * and passes as the argument list for the _gtype_build_path function. - */ -static TargetEntry * -transform_match_create_path_variable(cypher_parsestate *cpstate, cypher_path *path, List *entities) { - ParseState *pstate = (ParseState *)cpstate; - Oid build_path_oid; - FuncExpr *fexpr; - int resno; - List *entity_exprs = NIL; - ListCell *lc; - - // extract the expr for each entity - foreach (lc, entities) { - transform_entity *entity = lfirst(lc); - - if (entity->expr != NULL) - entity_exprs = lappend(entity_exprs, entity->expr); - } - - // get the oid for the path creation function - build_path_oid = get_ag_func_oid("build_traversal", 1, ANYOID); - - // build the expr node for the function - fexpr = makeFuncExpr(build_path_oid, TRAVERSALOID, entity_exprs, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - - resno = cpstate->pstate.p_next_resno++; - - // create the target entry - return makeTargetEntry((Expr *)fexpr, resno, path->var_name, false); -} - -/* - * Maps a column name to the a function access name. In others word when - * passed the name for the vertex's id column name, return the function name - * for the vertex's gtype id element, etc. - */ -static char *get_accessor_function_name(enum transform_entity_type type, char *name) { - if (type == ENT_VERTEX) { - if (!strcmp(AG_VERTEX_COLNAME_ID, name)) - return AG_VERTEX_ACCESS_FUNCTION_ID; - else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name)) - return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; - } - if (type == ENT_EDGE) - { - if (!strcmp(AG_EDGE_COLNAME_ID, name)) - return AG_EDGE_ACCESS_FUNCTION_ID; - else if (!strcmp(AG_EDGE_COLNAME_START_ID, name)) - return AG_EDGE_ACCESS_FUNCTION_START_ID; - else if (!strcmp(AG_EDGE_COLNAME_END_ID, name)) - return AG_EDGE_ACCESS_FUNCTION_END_ID; - else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name)) - return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; - } - - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("column %s does not have an accessor function", name))); - - // keeps compiler silent - return NULL; -} - -static char *get_alias_colref_name(char *colname, char *name) { - if (!strcmp(AG_EDGE_COLNAME_ID, colname)) - return make_id_alias(name); - else if (!strcmp(AG_EDGE_COLNAME_START_ID, colname)) - return make_startid_alias(name); - else if (!strcmp(AG_EDGE_COLNAME_END_ID, colname)) - return make_endid_alias(name); - else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, colname)) - return make_property_alias(name); - - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("column %s does not have an accessor function", name))); - - // keeps compiler silent - return NULL; -} - -static Node *make_id_expr(cypher_parsestate *cpstate, FuncExpr *arg) { - ParseState *pstate = (ParseState *)cpstate; - - Oid func_oid = get_ag_func_oid("id", 1, VERTEXOID); - - List *args = list_make1(arg); - - FuncExpr *func_expr = makeFuncExpr(func_oid, GTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = -1; - - return (Node *)func_expr; -} - -/* - * For the given entity and column name, construct an expression that will - * access the column or get the access function if the entity is a variable. - */ -static Node *make_qual(cypher_parsestate *cpstate, transform_entity *entity, char *col_name) { - List *qualified_name, *args; - Node *node; - - if (IsA(entity->expr, Var)) { - char *function_name; - - function_name = get_accessor_function_name(entity->type, col_name); - - qualified_name = list_make2(makeString(CATALOG_SCHEMA), makeString(function_name)); - - - args = list_make1(entity->expr); - node = (Node *)makeFuncCall(qualified_name, args, COERCE_EXPLICIT_CALL, -1); - } else { - int sublevels; - char *entity_name; - if (entity->type == ENT_EDGE) - entity_name = entity->entity.node->name; - else if (entity->type == ENT_VERTEX) - entity_name = entity->entity.rel->name; - else - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unknown entity type"))); - - ParseNamespaceItem *pnsi = refnameNamespaceItem(cpstate, NULL, entity_name, -1, &sublevels); - - if (!pnsi) { - Node *expr = colNameToVar(cpstate, get_alias_colref_name(col_name, entity_name), false, -1); - - if (expr) - return expr; - - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("could not find ParseNamespaceItem %s here", get_alias_colref_name(col_name, entity_name)))); - } - return scanNSItemForColumn(cpstate, pnsi, sublevels, col_name, -1); - } - - return node; -} - -static Expr *transform_cypher_edge(cypher_parsestate *cpstate, cypher_relationship *rel, List **target_list) { - ParseState *pstate = (ParseState *)cpstate; - char *schema_name; - char *rel_name; - RangeVar *label_range_var; - Alias *alias; - int resno; - TargetEntry *te; - Expr *expr; - ParseNamespaceItem *pnsi; - - if (!rel->label) { - rel->label = AG_DEFAULT_LABEL_EDGE; - } else { - label_cache_data *lcd = search_label_name_graph_cache(rel->label, cpstate->graph_oid); - - if (lcd == NULL) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("label %s does not exists", rel->label), - parser_errposition(pstate, rel->location))); - - if (lcd->kind != LABEL_KIND_EDGE) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("label %s is for vertices, not edges", rel->label), - parser_errposition(pstate, rel->location))); - } - - if (rel->name != NULL) { - TargetEntry *te; - Node *expr; - - /* - * If we are in a WHERE clause transform, we don't want to create new - * variables, we want to use the existing ones. So, error if otherwise. - */ - if (pstate->p_expr_kind == EXPR_KIND_WHERE) { - transform_entity *entity = find_transform_entity(cpstate, rel->name, ENT_EDGE); - if (entity) - return entity->expr; - else - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("variable `%s` does not exist", rel->name), - parser_errposition(pstate, rel->location))); - } - - te = findTarget(*target_list, rel->name); - expr = colNameToVar(pstate, rel->name, false, rel->location); - - if (expr != NULL) - return (Expr*)expr; - - if (te != NULL) { - transform_entity *entity = find_variable(cpstate, rel->name); - if (entity != NULL && (entity->type != ENT_EDGE || !IS_DEFAULT_LABEL_EDGE(rel->label) || rel->props)) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("variable %s already exists", rel->name), - parser_errposition(pstate, rel->location))); - return te->expr; - } - } - - if (!rel->name) - rel->name = get_next_default_alias(cpstate); - - schema_name = get_graph_namespace_name(cpstate->graph_name); - rel_name = get_label_relation_name(rel->label, cpstate->graph_oid); - label_range_var = makeRangeVar(schema_name, rel_name, -1); - alias = makeAlias(rel->name, NIL); - - pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); - Assert(pnsi != NULL); - - addNSItemToQuery(pstate, pnsi, true, true, false); - - resno = pstate->p_next_resno++; - - expr = (Expr *)make_edge_expr(cpstate, pnsi); - - if (rel->name) { - te = makeTargetEntry(expr, resno, rel->name, false); - *target_list = lappend(*target_list, te); - } - - // id field - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(id, resno, make_id_alias(rel->name), false); - *target_list = lappend(*target_list, te); - - // startid field - Node *startid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(startid, resno, make_startid_alias(rel->name), false); - *target_list = lappend(*target_list, te); - - // endid field - Node *endid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(startid, resno, make_endid_alias(rel->name), false); - *target_list = lappend(*target_list, te); - - // property field - Node *prop = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(startid, resno, make_property_alias(rel->name), false); - *target_list = lappend(*target_list, te); - - return expr; -} - -static char *make_property_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); - - str[0] = '_'; - str[1] = 'p'; - str[2] = 'r'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\0'; - - return str; -} - -static char *make_id_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); - - str[0] = '_'; - str[1] = 'i'; - str[2] = 'd'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - str[i + 4] = '_'; - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\0'; - - return str; -} - -static char *make_startid_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); - - str[0] = '_'; - str[1] = 's'; - str[2] = 't'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\0'; - - return str; -} - -static char *make_endid_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); - - str[0] = '_'; - str[1] = 'e'; - str[2] = 'n'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\0'; - - return str; -} - -//ltree -#include "ltree.h" - - -PG_FUNCTION_INFO_V1(lquery_in); - -static Expr *transform_cypher_node(cypher_parsestate *cpstate, cypher_node *node, List **target_list, bool output_node) { - ParseState *pstate = (ParseState *)cpstate; - char *schema_name; - char *rel_name; - RangeVar *label_range_var; - Alias *alias; - int resno; - TargetEntry *te; - Expr *expr; - ParseNamespaceItem *pnsi; - bool is_default_label = false; - - if (!node->label || !strcmp(node->label, AG_DEFAULT_LABEL_VERTEX)) { - node->label = AG_DEFAULT_LABEL_VERTEX; - is_default_label = true; - } else { - label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); - - if (lcd == NULL) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("label %s does not exists", node->label), - parser_errposition(pstate, node->location))); - - if (lcd->kind != LABEL_KIND_VERTEX) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("label %s is for edges, not vertices", node->label), - parser_errposition(pstate, node->location))); - } - - if (!output_node) - return NULL; - - if (node->name) { - TargetEntry *te; - Node *expr; - - /* - * If we are in a WHERE clause transform, we don't want to create new - * variables, we want to use the existing ones. So, error if otherwise. - */ - if (pstate->p_expr_kind == EXPR_KIND_WHERE) { - transform_entity *entity = find_transform_entity(cpstate, node->name, ENT_VERTEX); - if (entity) - return entity->expr; - } else { - te = findTarget(*target_list, node->name); - expr = colNameToVar(pstate, node->name, false, node->location); - - if (expr) - return (Expr*)expr; - - if (te != NULL) { - transform_entity *entity = find_variable(cpstate, node->name); - if (entity != NULL && (entity->type != ENT_VERTEX || !IS_DEFAULT_LABEL_VERTEX(node->label) || node->props)) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("variable %s already exists", node->name), - parser_errposition(pstate, node->location))); - - return te->expr; - } - } - } else { - node->name = get_next_default_alias(cpstate); - } - - schema_name = get_graph_namespace_name(cpstate->graph_name); - - Datum label_lquery; - if (is_default_label) - label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(node->label)); - else - label_lquery = DirectFunctionCall2(ltree_addltree, - DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), - DirectFunctionCall1(ltree_in, CStringGetDatum(node->label))); - - int ltq_query_args[2]; - ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - - Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); - - ScanKeyData scan_keys[1]; - ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); - - Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); - SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); - - HeapTuple tuple = systable_getnext(scan_desc); - - if (!HeapTupleIsValid(tuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("not found %s", node->label))); - - bool is_null; - rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); - - - systable_endscan(scan_desc); - table_close(label_catalog, AccessShareLock); - - //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); - label_range_var = makeRangeVar(schema_name, rel_name, -1); - alias = makeAlias(node->name, NIL); - - pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); - Assert(pnsi != NULL); - - addNSItemToQuery(pstate, pnsi, true, true, true); - - resno = pstate->p_next_resno++; - - expr = (Expr *)make_vertex_expr(cpstate, pnsi); - - // make target entry and add it - te = makeTargetEntry(expr, resno, node->name, false); - *target_list = lappend(*target_list, te); - - // id field - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(id, resno, make_id_alias(node->name), false); - *target_list = lappend(*target_list, te); - - /* - * properties field - */ - Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(props, resno, make_property_alias(node->name), false); - *target_list = lappend(*target_list, te); - - - return expr; -} - -static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { - ParseState *pstate = (ParseState *)cpstate; - - Oid func_oid = get_ag_func_oid("build_edge", 5, GRAPHIDOID, GRAPHIDOID, GRAPHIDOID, OIDOID, GTYPEOID); - - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); - - Node *start_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); - - Node *end_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); - - Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), - ObjectIdGetDatum(cpstate->graph_oid), false, true); - - Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1); - - List *args = list_make5(id, start_id, end_id, graph_oid_const, props); - - FuncExpr *func_expr = makeFuncExpr(func_oid, EDGEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = -1; - - return (Node *)func_expr; -} - -static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { - ParseState *pstate = (ParseState *)cpstate; - - Oid func_oid = get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID); - - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - - Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), - ObjectIdGetDatum(cpstate->graph_oid), false, true); - - Node * props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); - - List *args = list_make3(id, graph_oid_const, props); - - FuncExpr *func_expr = makeFuncExpr(func_oid, VERTEXOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = -1; - - return (Node *)func_expr; -} - -static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause) { - ParseState *pstate = (ParseState *)cpstate; - cypher_create *self = (cypher_create *)clause->self; - cypher_create_target_nodes *target_nodes; - Const *null_const; - List *transformed_pattern; - FuncExpr *func_expr; - Query *query; - TargetEntry *tle; - - target_nodes = make_ag_node(cypher_create_target_nodes); - target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE; - target_nodes->graph_oid = cpstate->graph_oid; - - query = makeNode(Query); - query->commandType = CMD_SELECT; - query->targetList = NIL; - - if (clause->prev) { - handle_prev_clause(cpstate, query, clause->prev, true); - - target_nodes->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE; - } - - /* - * Create the Const Node to hold the pattern. skip the parse node, - * because we would not be able to control how our pointer to the - * internal type is copied. - */ - transformed_pattern = transform_cypher_create_pattern(cpstate, query, self->pattern); - - target_nodes->paths = transformed_pattern; - if (!clause->next) - target_nodes->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; - - - func_expr = make_clause_func_expr(CREATE_CLAUSE_FUNCTION_NAME, (Node *)target_nodes); - - // Create the target entry - tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, "_create_clause", false); - query->targetList = lappend(query->targetList, tle); - - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); -if (clause->next) - return query; - -{ - Query *topquery; - cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); - topquery = makeNode(Query); - topquery->commandType = CMD_SELECT; - topquery->targetList = NIL; - - ParseState *pstate = (ParseState *) cpstate; - int rtindex; - - ParseNamespaceItem *pnsi = transform_cypher_clause_as_subquery_2(new_cpstate, clause, NULL, false, query); - topquery->rtable = new_cpstate->pstate.p_rtable; - topquery->jointree = makeFromExpr(new_cpstate->pstate.p_joinlist, NULL); - - return topquery; -} - -} - -static List *transform_cypher_create_pattern(cypher_parsestate *cpstate, Query *query, List *pattern) { - ListCell *lc; - List *transformed_pattern = NIL; - - foreach (lc, pattern) { - cypher_create_path *transformed_path; - - transformed_path = transform_cypher_create_path(cpstate, &query->targetList, lfirst(lc)); - - transformed_pattern = lappend(transformed_pattern, transformed_path); - } - - return transformed_pattern; -} - -static cypher_create_path *transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, cypher_path *path) { - ParseState *pstate = (ParseState *)cpstate; - ListCell *lc; - List *transformed_path = NIL; - cypher_create_path *ccp = make_ag_node(cypher_create_path); - bool in_path = path->var_name != NULL; - - ccp->path_attr_num = InvalidAttrNumber; - - foreach (lc, path->path) { - if (is_ag_node(lfirst(lc), cypher_node)) { - cypher_node *node = lfirst(lc); - transform_entity *entity; - - cypher_target_node *rel = - transform_create_cypher_node(cpstate, target_list, node); - - if (in_path) - rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; - - transformed_path = lappend(transformed_path, rel); - - entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, NULL, NULL); - - cpstate->entities = lappend(cpstate->entities, entity); - } else if (is_ag_node(lfirst(lc), cypher_relationship)) { - cypher_relationship *edge = lfirst(lc); - transform_entity *entity; - - cypher_target_node *rel = transform_create_cypher_edge(cpstate, target_list, edge); - - if (in_path) - rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; - - transformed_path = lappend(transformed_path, rel); - - entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)edge, NULL, NULL); - - cpstate->entities = lappend(cpstate->entities, entity); - } else { - ereport(ERROR, (errmsg_internal("unreconized node in create pattern"))); - } - } - - ccp->target_nodes = transformed_path; - - /* - * If this path a variable, create a placeholder entry that we can fill - * in with during the execution phase. - */ - if (path->var_name) { - TargetEntry *te; - - if (list_length(transformed_path) < 1) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("paths require at least 1 vertex"), - parser_errposition(pstate, path->location))); - - te = placeholder_traversal(cpstate, path->var_name); - - ccp->path_attr_num = te->resno; - - *target_list = lappend(*target_list, te); - } - - return ccp; -} - -static cypher_target_node * transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, cypher_relationship *edge) { - ParseState *pstate = (ParseState *)cpstate; - cypher_target_node *rel = make_ag_node(cypher_target_node); - Expr *props; - Relation label_relation; - RangeVar *rv; - RangeTblEntry *rte; - TargetEntry *te; - char *alias; - AttrNumber resno; - ParseNamespaceItem *pnsi; - - if (edge->label) - { - label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); - - if (lcd && lcd->kind != LABEL_KIND_EDGE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("label %s is for vertices, not edges", edge->label), - parser_errposition(pstate, edge->location))); - } - - rel->type = LABEL_KIND_EDGE; - rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; - rel->label_name = edge->label; - rel->resultRelInfo = NULL; - - if (edge->name) { - /* - * Variables can be declared in a CREATE clause, but not used if - * it already exists. - */ - if (variable_exists(cpstate, edge->name)) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("variable %s already exists", edge->name))); - - rel->variable_name = edge->name; - te = placeholder_edge(cpstate, edge->name); - rel->tuple_position = te->resno; - *target_list = lappend(*target_list, te); - - rel->flags |= CYPHER_TARGET_NODE_IS_VAR; - } else { - rel->variable_name = NULL; - rel->tuple_position = 0; - edge->name = get_next_default_alias(cpstate); - } - - if (edge->dir == CYPHER_REL_DIR_NONE) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only directed relationships are allowed in CREATE"), - parser_errposition(&cpstate->pstate, edge->location))); - - rel->dir = edge->dir; - - if (!edge->label) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("relationships must be specify a label in CREATE."), - parser_errposition(&cpstate->pstate, edge->location))); - - // create the label entry if it does not exist - if (!label_exists(edge->label, cpstate->graph_oid)) { - List *parent; - RangeVar *rv; - - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_EDGE); - - parent = list_make1(rv); - - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, parent, NULL); - } - - // lock the relation of the label - rv = makeRangeVar(cpstate->graph_name, edge->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); - - // Store the relid - rel->relid = RelationGetRelid(label_relation); - - pnsi = addRangeTableEntryForRelation((ParseState *)cpstate, label_relation, AccessShareLock, NULL, false, false); - rte = pnsi->p_rte; - rte->requiredPerms = ACL_INSERT; - - // Build Id expression, always use the default logic - rel->id_expr = (Expr *)build_column_default(label_relation, Anum_ag_label_edge_table_id); - - // Build properties expression, if no map is given, use the default logic - alias = make_property_alias(edge->name);//get_next_default_alias(cpstate); - resno = pstate->p_next_resno++; - - props = cypher_create_properties(cpstate, rel, label_relation, edge->props, ENT_EDGE); - - rel->prop_attr_num = resno - 1; - te = makeTargetEntry(props, resno, alias, false); - - *target_list = lappend(*target_list, te); - - - // id field - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(id, resno, make_id_alias(edge->name), false); - *target_list = lappend(*target_list, te); - - // startid field - Node *startid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(startid, resno, make_startid_alias(edge->name), false); - *target_list = lappend(*target_list, te); - - // endid field - Node *endid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(startid, resno, make_endid_alias(edge->name), false); - *target_list = lappend(*target_list, te); - - - // Keep the lock - table_close(label_relation, NoLock); - - return rel; -} - -static bool variable_exists(cypher_parsestate *cpstate, char *name) { - ParseState *pstate = (ParseState *)cpstate; - Node *id; - - if (name == NULL) - return false; - - ParseNamespaceItem *pnsi = find_pnsi(cpstate, PREV_CYPHER_CLAUSE_ALIAS); - if (pnsi) { - id = scanNSItemForColumn(pstate, pnsi, 0, name, -1); - - return id != NULL; - } - - return false; -} - -static cypher_target_node * transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, cypher_node *node) { - ParseState *pstate = (ParseState *)cpstate; - - if (node->label) { - label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); - - if (lcd && lcd->kind != LABEL_KIND_VERTEX) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("label %s is for edges, not vertices", node->label), - parser_errposition(pstate, node->location))); - } - - /* - * Check if the variable already exists, if so find the entity and - * setup the target node - */ - if (node->name) { - transform_entity *entity; - - entity = find_variable(cpstate, node->name); - - if (entity) { - if (entity->type != ENT_VERTEX) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("variable %s already exists", node->name), - parser_errposition(pstate, node->location))); - - return transform_create_cypher_existing_node(cpstate, target_list, entity->declared_in_current_clause, node); - } - } - - // otherwise transform the target node as a new node - return transform_create_cypher_new_node(cpstate, target_list, node); -} - -static int get_target_entry_resno(cypher_parsestate *cpstate, List *target_list, char *name) { - ListCell *lc; - - foreach (lc, target_list) { - TargetEntry *te = (TargetEntry *)lfirst(lc); - - if (!strcmp(te->resname, name)) { - enum transform_entity_type type = find_transform_entity_type(cpstate, name); - if (type == ENT_VERTEX) - te->expr = add_volatile_vertex_wrapper(te->expr); - else if (type == ENT_EDGE) - te->expr = add_volatile_edge_wrapper(te->expr); - else if (type == ENT_VLE_EDGE) - te->expr = add_volatile_vle_edge_wrapper(te->expr); - else - te->expr = add_volatile_wrapper(te->expr); - return te->resno; - } - } - - return -1; -} - -/* -* Transform logic for a previously declared variable in a CREATE clause. -* All we need from the variable node is its id, and whether we can skip -* some tests in the execution phase.. -*/ -static cypher_target_node *transform_create_cypher_existing_node( - cypher_parsestate *cpstate, List **target_list, bool declared_in_current_clause, cypher_node *node) { - cypher_target_node *rel = make_ag_node(cypher_target_node); - - rel->type = LABEL_KIND_VERTEX; - rel->flags = CYPHER_TARGET_NODE_FLAG_NONE; - rel->resultRelInfo = NULL; - rel->variable_name = node->name; - - if (node->props) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("previously declared nodes in a create clause cannot have properties"))); - if (node->label) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("previously declared variables cannot have a label"))); - /* - * When the variable is declared in the same clause this vertex is a part of - * we can skip some expensive checks in the execution phase. - */ - if (declared_in_current_clause) - rel->flags |= EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE; - - /* - * Get the AttrNumber the variable is stored in, so we can extract the id - * later. - */ - rel->tuple_position = get_target_entry_resno(cpstate, *target_list, node->name); - - return rel; -} - - -static Node * -make_int_placeholder(cypher_parsestate *cpstate) { - - Datum agt = integer_to_gtype(0); - - // typtypmod, typcollation, typlen, and typbyval of gtype are hard-coded. - Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, agt, false, false); - c->location = -1; - - return (Node *)c; -} - -static cypher_target_node *transform_create_cypher_new_node(cypher_parsestate *cpstate, List **target_list, cypher_node *node) { - ParseState *pstate = (ParseState *)cpstate; - cypher_target_node *rel = make_ag_node(cypher_target_node); - Relation label_relation; - RangeVar *rv; - RangeTblEntry *rte; - TargetEntry *te; - Expr *props; - char *alias; - int resno; - ParseNamespaceItem *pnsi; - - rel->type = LABEL_KIND_VERTEX; - rel->tuple_position = InvalidAttrNumber; - rel->variable_name = NULL; - rel->resultRelInfo = NULL; - - if (!node->label) { - rel->label_name = ""; - /* - * If no label is specified, assign the generic label name that - * all labels are descendents of. - */ - node->label = AG_DEFAULT_LABEL_VERTEX; - } else { - rel->label_name = node->label; - } - - if (!node->name) { - node->name = get_next_default_alias(cpstate); - } - - - // create the label entry if it does not exist - if (!label_exists(node->label, cpstate->graph_oid)) - { - List *parent; - RangeVar *rv; - - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_VERTEX); - - parent = list_make1(rv); - - create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, parent, NULL); - } - - rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; - - rv = makeRangeVar(cpstate->graph_name, node->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); - - // Store the relid - rel->relid = RelationGetRelid(label_relation); - - pnsi = addRangeTableEntryForRelation((ParseState *)cpstate, label_relation, AccessShareLock, NULL, false, false); - rte = pnsi->p_rte; - rte->requiredPerms = ACL_INSERT; - - // id - rel->id_expr = (Expr *)build_column_default(label_relation, Anum_ag_label_vertex_table_id); - - resno = pstate->p_next_resno++; - te = makeTargetEntry(make_int_placeholder(cpstate), resno, make_id_alias(node->name), false); - *target_list = lappend(*target_list, te); - - // properties - alias = make_property_alias(node->name);//get_next_default_alias(cpstate); - resno = pstate->p_next_resno++; - - props = cypher_create_properties(cpstate, rel, label_relation, node->props, ENT_VERTEX); - - rel->prop_attr_num = resno - 1; - te = makeTargetEntry(props, resno, alias, false); - *target_list = lappend(*target_list, te); - - table_close(label_relation, NoLock); - - if (node->name) { - rel->variable_name = node->name; - te = placeholder_vertex(cpstate, node->name); - rel->tuple_position = te->resno; - *target_list = lappend(*target_list, te); - rel->flags |= CYPHER_TARGET_NODE_IS_VAR; - } else { - node->name = get_next_default_alias(cpstate); - } - // id field - /* - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); - resno = pstate->p_next_resno++; - - te = makeTargetEntry(id, resno, make_id_alias(edge->name), false); - *target_list = lappend(*target_list, te); - */ - return rel; -} - - -static TargetEntry *placeholder_edge(cypher_parsestate *cpstate, char *name) { - ParseState *pstate = (ParseState *)cpstate; - Expr *n; - int resno; - - n = (Expr *)makeNullConst(EDGEOID, -1, InvalidOid); - - resno = pstate->p_next_resno++; - - return makeTargetEntry(n, resno, name, false); -} - -static TargetEntry *placeholder_vertex(cypher_parsestate *cpstate, char *name) { - ParseState *pstate = (ParseState *)cpstate; - Expr *n; - int resno; - - n = (Expr *)makeNullConst(VERTEXOID, -1, InvalidOid); - - resno = pstate->p_next_resno++; - - return makeTargetEntry(n, resno, name, false); -} - -static TargetEntry *placeholder_traversal(cypher_parsestate *cpstate, char *name) { - ParseState *pstate = (ParseState *)cpstate; - Expr *n; - int resno; - - n = (Expr *)makeNullConst(TRAVERSALOID, -1, InvalidOid); - - resno = pstate->p_next_resno++; - - return makeTargetEntry(n, resno, name, false); -} - - -/* - * Build the target list for an entity that is not a previously declared - * variable. - */ -static Expr *cypher_create_properties(cypher_parsestate *cpstate, cypher_target_node *rel, - Relation label_relation, Node *props, enum transform_entity_type type) { - Expr *properties; - - if (props != NULL && is_ag_node(props, cypher_param)) { - ParseState *pstate = (ParseState *) cpstate; - cypher_param *param = (cypher_param *)props; - - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("properties in a CREATE clause as a parameter is not supported"), - parser_errposition(pstate, param->location))); - } - - if (props) - properties = (Expr *)transform_cypher_expr(cpstate, props, EXPR_KIND_INSERT_TARGET); - else if (type == ENT_VERTEX) - properties = (Expr *)build_column_default(label_relation, Anum_ag_label_vertex_table_properties); - else if (type == ENT_EDGE) - properties = (Expr *)build_column_default(label_relation, Anum_ag_label_edge_table_properties); - else - ereport(ERROR, (errmsg_internal("unreconized entity type"))); - - // add a volatile wrapper call to prevent the optimizer from removing it - return (Expr *)add_volatile_wrapper(properties); -} - -/* - * This function is similar to transformFromClause() that is called with a - * single RangeSubselect. - */ -static ParseNamespaceItem * -transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, cypher_clause *clause, Alias *alias, bool add_rte_to_query, Query *query) { - ParseState *pstate = (ParseState *)cpstate; - ParseExprKind old_expr_kind = pstate->p_expr_kind; - bool lateral = pstate->p_lateral_active; - - - if (!alias) - alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - - ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); - - /* - * NOTE: skip namespace conflicts check if the rte will be the only - * RangeTblEntry in pstate - */ - if (list_length(pstate->p_rtable) > 1) { - List *namespace = NULL; - int rtindex = 0; - - rtindex = list_length(pstate->p_rtable); - - if (pnsi->p_rte != rt_fetch(rtindex, pstate->p_rtable)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("rte must be last entry in p_rtable"))); - - namespace = list_make1(pnsi); - - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); - } - - //if (add_rte_to_query) - addNSItemToQuery(pstate, pnsi, true, false, true); - - return pnsi; -} - - -/* - * This function is similar to transformFromClause() that is called with a - * single RangeSubselect. - */ -static ParseNamespaceItem * -transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, - Alias *alias, bool add_rte_to_query) { - ParseState *pstate = (ParseState *)cpstate; - ParseExprKind old_expr_kind = pstate->p_expr_kind; - bool lateral = pstate->p_lateral_active; - - /* - * We allow expression kinds of none, where, and subselect. Others MAY need - * to be added depending. However, at this time, only these are needed. - */ - Assert(pstate->p_expr_kind == EXPR_KIND_NONE || pstate->p_expr_kind == EXPR_KIND_OTHER || - pstate->p_expr_kind == EXPR_KIND_WHERE || pstate->p_expr_kind == EXPR_KIND_FROM_SUBSELECT); - - /* - * As these are all sub queries, if this is just of type NONE, note it as a - * SUBSELECT. Other types will be dealt with as needed. - */ - if (pstate->p_expr_kind == EXPR_KIND_NONE) { - pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; - } else if (pstate->p_expr_kind == EXPR_KIND_OTHER) { - // this is a lateral subselect for the MERGE - pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; - lateral = true; - } - /* - * If this is a WHERE, pass it through and set lateral to true because it - * needs to see what comes before it. - */ - Query *query = analyze_cypher_clause(transform, clause, cpstate); - - pstate->p_expr_kind = old_expr_kind; - - if (!alias) - alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - - ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); - - /* - * NOTE: skip namespace conflicts check if the rte will be the only - * RangeTblEntry in pstate - */ - if (list_length(pstate->p_rtable) > 1) { - List *namespace = NULL; - int rtindex = 0; - - rtindex = list_length(pstate->p_rtable); - - if (pnsi->p_rte != rt_fetch(rtindex, pstate->p_rtable)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("rte must be last entry in p_rtable"))); - - namespace = list_make1(pnsi); - - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); - } - - if (add_rte_to_query) - addNSItemToQuery(pstate, pnsi, true, false, true); - - return pnsi; -} - -/* - * When we are done transforming a clause, before transforming the next clause - * iterate through the transform entities and mark them as not belonging to - * the clause that is currently being transformed. - */ -static void advance_transform_entities_to_next_clause(List *entities) { - ListCell *lc; - - foreach (lc, entities) { - transform_entity *entity = lfirst(lc); - - entity->declared_in_current_clause = false; - } -} - -static Query *analyze_cypher_clause(transform_method transform, cypher_clause *clause, cypher_parsestate *parent_cpstate) { - cypher_parsestate *cpstate; - Query *query; - ParseState *parent_pstate = (ParseState*)parent_cpstate; - ParseState *pstate; - - cpstate = make_cypher_parsestate(parent_cpstate); - pstate = (ParseState*)cpstate; - - // copy the expr_kind down to the child - pstate->p_expr_kind = parent_pstate->p_expr_kind; - - if (pstate->p_expr_kind == EXPR_KIND_WHERE) { - cpstate->entities = list_concat(NIL, parent_cpstate->entities); - } - - query = transform(cpstate, clause); - - advance_transform_entities_to_next_clause(cpstate->entities); - - parent_cpstate->entities = list_concat(parent_cpstate->entities, cpstate->entities); - - free_cypher_parsestate(cpstate); - - return query; -} - -static TargetEntry *findTarget(List *targetList, char *resname) { - ListCell *lt; - TargetEntry *te = NULL; - - if (resname == NULL) - return NULL; - - foreach (lt, targetList) { - te = lfirst(lt); - - if (te->resjunk) - continue; - - if (strcmp(te->resname, resname) == 0) - return te; - } - - return NULL; -} - -/* - * Wrap the expression with a volatile function, to prevent the optimer from - * elimating the expression. - */ -static Expr *add_volatile_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, GTYPEOID); - - return (Expr *)makeFuncExpr(oid, GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} - -static Expr *add_volatile_edge_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, EDGEOID); - - return (Expr *)makeFuncExpr(oid, EDGEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} - -static Expr *add_volatile_vle_edge_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, VARIABLEEDGEOID); - - return (Expr *)makeFuncExpr(oid, VARIABLEEDGEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} - -static Expr *add_volatile_vertex_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, VERTEXOID); - - return (Expr *)makeFuncExpr(oid, VERTEXOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} - -static Expr *add_volatile_traversal_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, TRAVERSALOID); - - return (Expr *)makeFuncExpr(oid, TRAVERSALOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} - - -/* - * from postgresql parse_sub_analyze - * Modified entry point for recursively analyzing a sub-statement in union. - */ -Query *cypher_parse_sub_analyze_union(cypher_clause *clause, cypher_parsestate *cpstate, CommonTableExpr *parentCTE, - bool locked_from_parent, bool resolve_unknowns) { - cypher_parsestate *state = make_cypher_parsestate(cpstate); - Query *query; - - state->pstate.p_parent_cte = parentCTE; - state->pstate.p_locked_from_parent = locked_from_parent; - state->pstate.p_resolve_unknowns = resolve_unknowns; - - query = transform_cypher_clause(state, clause); - - free_cypher_parsestate(state); - - return query; -} - -/* - * from postgresql parse_sub_analyze - * Entry point for recursively analyzing a sub-statement. - */ -Query *cypher_parse_sub_analyze(Node *parseTree, cypher_parsestate *cpstate, CommonTableExpr *parentCTE, - bool locked_from_parent, bool resolve_unknowns) { - ParseState *pstate = make_parsestate((ParseState*)cpstate); - cypher_clause *clause; - Query *query; - - pstate->p_parent_cte = parentCTE; - pstate->p_locked_from_parent = locked_from_parent; - pstate->p_resolve_unknowns = resolve_unknowns; - - clause = palloc0(sizeof(cypher_clause)); - clause->self = parseTree; - query = transform_cypher_clause(cpstate, clause); - - free_parsestate(pstate); - - return query; -} + return (Node *) rtr; +} /* - * Function for transforming MERGE. - * - * There are two cases for the form Query that is returned from here will - * take: - * - * 1. If there is no previous clause, the query will have a subquery that - * represents the path as a select staement, similar to match with a targetList - * that is all declared variables and the FuncExpr that represents the MERGE - * clause with its needed metadata information, that will be caught in the - * planner phase and converted into a path. - * - * 2. If there is a previous clause then the query will have two subqueries. - * The first query will be for the previous clause that we recursively handle. - * The second query will be for the path that this MERGE clause defines. The - * two subqueries will be joined together using a LATERAL LEFT JOIN with the - * previous query on the left and the MERGE path subquery on the right. Like - * case 1 the targetList will have all the decalred variables and a FuncExpr - * that represents the MERGE clause with its needed metadata information, that - * will be caught in the planner phase and converted into a path. - * - * This will allow us to be capable of handling the 2 cases that exist with a - * MERGE clause correctly. - * - * Case 1: the path already exists. In this case we do not need to create - * the path and MERGE will simply pass the tuple information up the execution - * tree. - * - * Case 2: the path does not exist. In this case the LEFT part of the join - * will not prevent the tuples from the previous clause from being emitted. We - * can catch when this happens in the execution phase and create the missing - * data, before passing up the execution tree. - * - * It should be noted that both cases can happen in the same query. If the - * MERGE clause references a variable from a previous clause, it could be that - * for one tuple the path exists (or there is multiple paths that exist and all - * paths must be emitted) and for another the path does not exist. This is - * similar to OPTIONAL MATCH, however with the added feature of creating the - * path if not there, rather than just emiting NULL. + * For cases where we need to join two subqueries together (OPTIONAL MATCH and + * MERGE) we need to take the columns available in each rte and merge them + * together. The l_rte has precedence when there is a conflict, because that + * means that the pattern create in the current clause is referencing a + * variable declared in a previous clause (the l_rte). The output is the + * res_colnames and res_colvars that are passed in. */ -static Query *transform_cypher_merge(cypher_parsestate *cpstate, cypher_clause *clause) { - ParseState *pstate = (ParseState *) cpstate; - cypher_clause *merge_clause_as_match; - cypher_create_path *merge_path; - cypher_merge *self = (cypher_merge *)clause->self; - cypher_merge_information *merge_information; - Query *query; - FuncExpr *func_expr; - TargetEntry *tle; - - Assert(is_ag_node(self->path, cypher_path)); - - merge_information = make_ag_node(cypher_merge_information); - - query = makeNode(Query); - query->commandType = CMD_SELECT; - query->targetList = NIL; - - Const *null_const = makeNullConst(GTYPEOID, -1, InvalidOid); - tle = makeTargetEntry((Expr *)null_const, pstate->p_next_resno++, "_null", false); - query->targetList = lappend(query->targetList, tle); - - merge_information->flags = CYPHER_CLAUSE_FLAG_NONE; +static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, + ParseNamespaceItem *r_pnsi, List **res_colnames, List **res_colvars) { + List *l_colnames, *l_colvars; + List *r_colnames, *r_colvars; + ListCell *r_lname, *r_lvar; + List *colnames = NIL; + List *colvars = NIL; - // make the merge node into a match node - merge_clause_as_match = convert_merge_to_match(self); + expandRTE(l_pnsi->p_rte, l_pnsi->p_rtindex, 0, -1, false, &l_colnames, &l_colvars); + expandRTE(r_pnsi->p_rte, r_pnsi->p_rtindex, 0, -1, false, &r_colnames, &r_colvars); - /* - * If there is a previous clause we need to turn this query into a lateral - * join. See the function transform_merge_make_lateral_join for details. - */ - if (clause->prev != NULL) { - merge_path = transform_merge_make_lateral_join(cpstate, query, clause, merge_clause_as_match); + // add in all colnames and colvars from the l_rte. + *res_colnames = list_concat(*res_colnames, l_colnames); + *res_colvars = list_concat(*res_colvars, l_colvars); - merge_information->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE; - } else { - // make the merge node into a match node - cypher_clause *merge_clause_as_match = convert_merge_to_match(self); + // find new columns and if they are a var, pass them in. + forboth(r_lname, r_colnames, r_lvar, r_colvars) { + char *r_colname = strVal(lfirst(r_lname)); + ListCell *lname; + ListCell *lvar; + Var *var = NULL; - // Create the metadata needed for creating missing paths. - merge_path = transform_cypher_merge_path(cpstate, &query->targetList, (cypher_path *)self->path); + forboth(lname, *res_colnames, lvar, *res_colvars) { + char *colname = strVal(lfirst(lname)); - /* - * If there is not a previous clause, then treat the MERGE's path - * itself as the previous clause. We need to do this because if the - * pattern exists, then we need to path all paths that match the - * query patterns in the execution phase. WE way to do that by - * converting the merge to a match and have the match logic create the - * query. the merge execution phase will just pass the results up the - * execution tree if the path exists. - */ - handle_prev_clause(cpstate, query, merge_clause_as_match, false); + if (strcmp(r_colname, colname) == 0) { + var = lfirst(lvar); + break; + } + } - /* - * For the metadata need to create paths, find the tuple position that - * will represent the entity in the execution phase. - */ - transform_cypher_merge_mark_tuple_position(cpstate, query->targetList, merge_path); + if (var == NULL) { + colnames = lappend(colnames, lfirst(r_lname)); + colvars = lappend(colvars, lfirst(r_lvar)); + } } - merge_information->graph_oid = cpstate->graph_oid; - merge_information->path = merge_path; - - if (!clause->next) - merge_information->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; - - // Creates the function expression that the planner will find and convert to a MERGE path. - func_expr = make_clause_func_expr(MERGE_CLAUSE_FUNCTION_NAME, (Node *)merge_information); - - // Create the target entry - tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, "_merge_clause", false); - - merge_information->merge_function_attr = tle->resno; - query->targetList = lappend(query->targetList, tle); - - markTargetListOrigins(pstate, query->targetList); - - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - - query->hasSubLinks = pstate->p_hasSubLinks; - - assign_query_collations(pstate, query); - - if (clause->next) - return query; - { - Query *topquery; - cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); - topquery = makeNode(Query); - topquery->commandType = CMD_SELECT; - topquery->targetList = NIL; - - ParseState *pstate = (ParseState *) cpstate; - int rtindex; - - ParseNamespaceItem *pnsi = transform_cypher_clause_as_subquery_2(new_cpstate, clause, NULL, false, query); - topquery->rtable = new_cpstate->pstate.p_rtable; - topquery->jointree = makeFromExpr(new_cpstate->pstate.p_joinlist, NULL); - - return topquery; - } + *res_colnames = list_concat(*res_colnames, colnames); + *res_colvars = list_concat(*res_colvars, colvars); } /* - * This function does the heavy lifting of transforming a MERGE clause that has - * a clause before it in the query of turning that into a lateral left join. - * The previous clause will still be able to emit tuples if the path defined in - * MERGE clause is not found. In that case variable assinged in the MERGE - * clause will be emitted as NULL (same as OPTIONAL MATCH). + * transform_cypher_match_clause + * Transform the previous clauses and OPTIONAL MATCH clauses to be LATERAL LEFT JOIN + * transform_cypher_match_clause to construct a result value. */ -static cypher_create_path * -transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, - cypher_clause *isolated_merge_clause) { - cypher_create_path *merge_path; - ParseState *pstate = (ParseState *) cpstate; - int i; - Alias *l_alias; - Alias *r_alias; +static RangeTblEntry *transform_cypher_match_clause(cypher_parsestate *cpstate, cypher_clause *clause) { + cypher_clause *prevclause; RangeTblEntry *l_rte, *r_rte; ParseNamespaceItem *l_nsitem, *r_nsitem; - JoinExpr *j = makeNode(JoinExpr); + ParseState *pstate = (ParseState *) cpstate; + JoinExpr* j = makeNode(JoinExpr); List *res_colnames = NIL, *res_colvars = NIL; + Alias *l_alias, *r_alias; ParseNamespaceItem *jnsitem; - ParseExprKind tmp; - cypher_merge *self = (cypher_merge *)clause->self; - cypher_path *path; - - Assert(is_ag_node(self->path, cypher_path)); + int i = 0; - path = (cypher_path *)self->path; + j->jointype = JOIN_INNER; - r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL); l_alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); + r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL); - j->jointype = JOIN_LEFT; - - /* - * transform the previous clause - */ j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, &l_nsitem, l_alias); pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); - + /* - * Get the merge path now. This is the only moment where it is simple - * to know if a variable was declared in the MERGE clause or a previous - * clause. Unlike create, we do not add these missing variables to the - * targetList, we just create all the metadata necessary to make the - * potentially missing parts of the path. + * Remove the previous clause so when the transform_clause_for_join function + * transforms the OPTIONAL MATCH, the previous clause will not be transformed + * again. */ - merge_path = transform_cypher_merge_path(cpstate, &query->targetList, path); + prevclause = clause->prev; + clause->prev = NULL; - /* - * Transform this MERGE clause as a match clause, mark the parsestate - * with the flag that a lateral join is active - */ + //set the lateral flag to true pstate->p_lateral_active = true; - tmp = pstate->p_expr_kind; - pstate->p_expr_kind = EXPR_KIND_OTHER; - // transform MERGE - j->rarg = transform_clause_for_join(cpstate, isolated_merge_clause, &r_rte, &r_nsitem, r_alias); + j->rarg = transform_clause_for_join(cpstate, clause, &r_rte, &r_nsitem, r_alias); - // deactivate the lateral flag + // we are done transform the lateral left join pstate->p_lateral_active = false; - pstate->p_namespace = NIL; - /* - * Resolve the column names and variables between the two subqueries, - * in most cases, we can expect there to be overlap + * We are done with the previous clause in the transform phase, but + * reattach the previous clause for semantics. */ + clause->prev = prevclause; + + pstate->p_namespace = NIL; + + // get the colnames and colvars from the rtes get_res_cols(pstate, l_nsitem, r_nsitem, &res_colnames, &res_colvars); - // make the RTE for the join jnsitem = addRangeTableEntryForJoin(pstate, res_colnames, NULL, j->jointype, 0, res_colvars, NIL, - NIL, j->alias, NULL, true); + NIL, j->alias, NULL, false); j->rtindex = jnsitem->p_rtindex; - /* - * The index of a node in the p_joinexpr list is expected to match the - * rtindex the join expression is for. Add NULLs for all the previous - * rtindexes and add the JoinExpr. - */ for (i = list_length(pstate->p_joinexprs) + 1; i < j->rtindex; i++) pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL); - pstate->p_joinexprs = lappend(pstate->p_joinexprs, j); + Assert(list_length(pstate->p_joinexprs) == j->rtindex); - Assert(list_length(pstate->p_joinexprs) == j->rtindex); - - pstate->p_joinlist = lappend(pstate->p_joinlist, j); - - pstate->p_expr_kind = tmp; - - // add jnsitem to column namespace only - addNSItemToQuery(pstate, jnsitem, false, true, true); - - /* - * Create the targetList from the joined subqueries, add everything. - */ - query->targetList = list_concat(query->targetList, make_target_list_from_join(pstate, jnsitem->p_rte)); + pstate->p_joinlist = lappend(pstate->p_joinlist, j); - /* - * For the metadata need to create paths, find the tuple position that - * will represent the entity in the execution phase. - */ - transform_cypher_merge_mark_tuple_position(cpstate, query->targetList, merge_path); + // add jrte to column namespace only + addNSItemToQuery(pstate, jnsitem, false, false, true); - return merge_path; + return jnsitem->p_rte; } -/* - * Iterate through the path and find the TargetEntry in the target_list - * that each cypher_target_node is referencing. Add the volatile wrapper - * function to keep the optimizer from removing the TargetEntry. - */ -static void transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate, List *target_list, cypher_create_path *path) { - ListCell *lc = NULL; +static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause) { + ParseState *pstate = (ParseState *)cpstate; + cypher_match *self = (cypher_match *)clause->self; + Query *query; + Node *where = self->where; + Node *order_by = self->order_by; + self->order_by = NULL; + query = makeNode(Query); + query->commandType = CMD_SELECT; - if (path->var_name) { - TargetEntry *te = findTarget(target_list, path->var_name); + // If there is no previous clause, transform to a general MATCH clause. + if (clause->prev && (self->order_by)) { + RangeTblEntry *rte = transform_cypher_match_clause(cpstate, clause); + + query->targetList = make_target_list_from_join(pstate, rte); + query->rtable = pstate->p_rtable; + query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + } else if (clause->prev) { + ParseNamespaceItem *pnsi = transform_prev_cypher_clause(cpstate, clause->prev, true); /* - * Add the volatile wrapper function around the expression, this - * ensures the optimizer will not remove the expression, if nothing - * other than a private data structure needs it. + * add all the target entries in rte to the current target list to pass + * all the variables that are introduced in the previous clause to the + * next clause */ - te->expr = add_volatile_traversal_wrapper(te->expr); - // Mark the tuple position the target_node is for. - path->path_attr_num = te->resno; + query->targetList = expandNSItemAttrs(pstate, get_namespace_item(pstate, pnsi->p_rte), 0, -1); + transform_match_pattern(cpstate, query, self->pattern, where); + } else { + transform_match_pattern(cpstate, query, self->pattern, where); } - foreach (lc, path->target_nodes) { - cypher_target_node *node = lfirst(lc); + // ORDER BY + query->sortClause = transform_cypher_order_by(cpstate, order_by, &query->targetList, EXPR_KIND_ORDER_BY); - TargetEntry *te = findTarget(target_list, node->variable_name); - enum transform_entity_type type = find_transform_entity_type(cpstate, node->variable_name); - /* - * Add the volatile wrapper function around the expression, this - * ensures the optimizer will not remove the expression, if nothing - * other than a private data structure needs it. - */ - if (type == ENT_VERTEX) - te->expr = add_volatile_vertex_wrapper(te->expr); - else if (type == ENT_EDGE) - te->expr = add_volatile_edge_wrapper(te->expr); - else if (type == ENT_VLE_EDGE) - te->expr = add_volatile_vle_edge_wrapper(te->expr); - else - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("rte must be last entry in p_rtable"))); + markTargetListOrigins(pstate, query->targetList); - // Mark the tuple position the target_node is for. - node->tuple_position = te->resno; - } + query->hasSubLinks = pstate->p_hasSubLinks; + query->hasWindowFuncs = pstate->p_hasWindowFuncs; + query->hasTargetSRFs = pstate->p_hasTargetSRFs; + query->hasAggs = pstate->p_hasAggs; + + assign_query_collations(pstate, query); + + return query; } -/* - * Creates the target nodes for a merge path. If MERGE has a path that doesn't - * exist then in the MERGE clause we act like a CREATE clause. This function - * sets up the metadata needed for that process. - */ -static cypher_create_path *transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, cypher_path *path) { + +static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { + ParseState *pstate = (ParseState *)cpstate; ListCell *lc; - List *transformed_path = NIL; - cypher_create_path *ccp = make_ag_node(cypher_create_path); - bool in_path = path->var_name != NULL; + List *quals = NIL; - ccp->path_attr_num = InvalidAttrNumber; + Expr *expr = NULL; - foreach (lc, path->path) { - if (is_ag_node(lfirst(lc), cypher_node)) { - cypher_node *node = lfirst(lc); + foreach (lc, pattern) { + List *qual = NULL; + cypher_path *path = (cypher_path *) lfirst(lc); + // TODO: implement the new match logic + //qual = transform_match_path(cpstate, query, path); - cypher_target_node *rel = transform_merge_cypher_node(cpstate, target_list, node); + quals = list_concat(quals, NULL); + } - if (in_path) - rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; + // AND the quals for each path together + Expr *q = NULL; + /* + if (quals != NIL) { + q = makeBoolExpr(AND_EXPR, quals, -1); + expr = (Expr *)sql_transform_expr(&cpstate->pstate, (Node *)q, EXPR_KIND_WHERE); + }*/ - transformed_path = lappend(transformed_path, rel); - } else if (is_ag_node(lfirst(lc), cypher_relationship)) { - cypher_relationship *edge = lfirst(lc); + /* + / property constraints on MATCH Pattern + if (cpstate->property_constraint_quals != NIL) { + Expr *prop_qual = makeBoolExpr(AND_EXPR, cpstate->property_constraint_quals, -1); - cypher_target_node *rel = transform_merge_cypher_edge(cpstate, target_list, edge); + if (expr == NULL) + expr = prop_qual; + else + expr = makeBoolExpr(AND_EXPR, list_make2(expr, prop_qual), -1); + } + */ - if (in_path) - rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; + + // WHERE Clause + /* + if (where != NULL) { + Expr *where_qual; - transformed_path = lappend(transformed_path, rel); + where_qual = (Expr *)transform_cypher_expr(cpstate, where, EXPR_KIND_WHERE); + if (expr == NULL) { + expr = where_qual; } else { - ereport(ERROR, (errmsg_internal("unreconized node in create pattern"))); + where_qual = (Expr *)coerce_to_boolean(pstate, (Node *)where_qual, "WHERE"); + + expr = makeBoolExpr(AND_EXPR, list_make2(expr, where_qual), -1); } } + */ - // store the path's variable name - if (path->var_name) - ccp->var_name = path->var_name; - - ccp->target_nodes = transformed_path; + /* + * Coerce to WHERE clause to a bool, denoting whether the constructed + * clause is true or false. + */ + //if (expr != NULL) + // expr = (Expr *)coerce_to_boolean(pstate, (Node *)expr, "WHERE"); - return ccp; + query->rtable = cpstate->pstate.p_rtable; + //query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr); + query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)NULL); } -/* - * Transforms the parse cypher_relationship to a target_entry for merge. - * All edges that have variables assigned in a merge must be declared in - * the merge. Throw an error otherwise. - */ -static cypher_target_node * transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, cypher_relationship *edge) { - ParseState *pstate = (ParseState *)cpstate; - cypher_target_node *rel = make_ag_node(cypher_target_node); - Relation label_relation; - RangeVar *rv; - RangeTblEntry *rte; - ParseNamespaceItem *pnsi; - - if (edge->name != NULL) { - transform_entity *entity = find_transform_entity(cpstate, edge->name, ENT_EDGE); - // We found a variable with this variable name, throw an error. - if (entity != NULL) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("variable %s already exists", edge->name), - parser_errposition(pstate, edge->location))); +static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte) { + List *targetlist = NIL; + ListCell *lt; + ListCell *ln; - rel->flags |= CYPHER_TARGET_NODE_IS_VAR; - } else { - // assign a default variable name. - edge->name = get_next_default_alias(cpstate); - } + AssertArg(rte->rtekind == RTE_JOIN); - rel->type = LABEL_KIND_EDGE; + forboth(lt, rte->joinaliasvars, ln, rte->eref->colnames) { + Var *varnode = lfirst(lt); + char *resname = strVal(lfirst(ln)); + TargetEntry *tmp; - // all edges are marked with insert - rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; - rel->label_name = edge->label; - rel->variable_name = edge->name; - rel->resultRelInfo = NULL; + tmp = makeTargetEntry((Expr *) varnode, (AttrNumber) pstate->p_next_resno++, pstrdup(resname), false); + targetlist = lappend(targetlist, tmp); + } - rel->dir = edge->dir; + return targetlist; +} - if (!edge->label) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("edges declared in a MERGE clause must have a label"), - parser_errposition(&cpstate->pstate, edge->location))); - // check to see if the label exists, create the label entry if it does not. - if (edge->label && !label_exists(edge->label, cpstate->graph_oid)) { - List *parent; - RangeVar *rv; - // setup the default edge table as the parent table, that we will inherit from. - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_EDGE); +/* + * This function is similar to transformFromClause() that is called with a + * single RangeSubselect. + */ +static ParseNamespaceItem * +transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, + Alias *alias, bool add_rte_to_query) { + ParseState *pstate = (ParseState *)cpstate; + ParseExprKind old_expr_kind = pstate->p_expr_kind; + bool lateral = pstate->p_lateral_active; - parent = list_make1(rv); + /* + * We allow expression kinds of none, where, and subselect. Others MAY need + * to be added depending. However, at this time, only these are needed. + */ + Assert(pstate->p_expr_kind == EXPR_KIND_NONE || pstate->p_expr_kind == EXPR_KIND_OTHER || + pstate->p_expr_kind == EXPR_KIND_WHERE || pstate->p_expr_kind == EXPR_KIND_FROM_SUBSELECT); - // create the label - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, parent, NULL); + /* + * As these are all sub queries, if this is just of type NONE, note it as a + * SUBSELECT. Other types will be dealt with as needed. + */ + if (pstate->p_expr_kind == EXPR_KIND_NONE) { + pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; + } else if (pstate->p_expr_kind == EXPR_KIND_OTHER) { + // this is a lateral subselect for the MERGE + pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; + lateral = true; } + /* + * If this is a WHERE, pass it through and set lateral to true because it + * needs to see what comes before it. + */ + Query *query = analyze_cypher_clause(transform, clause, cpstate); - // lock the relation of the label - rv = makeRangeVar(cpstate->graph_name, edge->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); - - // Store the relid - rel->relid = RelationGetRelid(label_relation); - - pnsi = addRangeTableEntryForRelation((ParseState *)cpstate, label_relation, AccessShareLock, NULL, false, false); - rte = pnsi->p_rte; - rte->requiredPerms = ACL_INSERT; - - // Build Id expression, always use the default logic - rel->id_expr = (Expr *)build_column_default(label_relation, Anum_ag_label_edge_table_id); + pstate->p_expr_kind = old_expr_kind; - rel->prop_expr = cypher_create_properties(cpstate, rel, label_relation, edge->props, ENT_EDGE); + if (!alias) + alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - // Keep the lock - table_close(label_relation, NoLock); + ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); - return rel; -} + /* + * NOTE: skip namespace conflicts check if the rte will be the only + * RangeTblEntry in pstate + */ + if (list_length(pstate->p_rtable) > 1) { + List *namespace = NULL; + int rtindex = 0; -/* - * Function for creating the metadata MERGE will need if MERGE does not find a path to exist - */ -static cypher_target_node *transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, cypher_node *node) { - cypher_target_node *rel = make_ag_node(cypher_target_node); - Relation label_relation; - RangeVar *rv; - RangeTblEntry *rte; - ParseNamespaceItem *pnsi; + rtindex = list_length(pstate->p_rtable); - if (node->name) { - transform_entity *entity = find_transform_entity(cpstate, node->name, ENT_VERTEX); + if (pnsi->p_rte != rt_fetch(rtindex, pstate->p_rtable)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rte must be last entry in p_rtable"))); - // the vertex was previously declared, we do not need to do any setup to create the node. - if (entity) { - rel->type = LABEL_KIND_VERTEX; - rel->tuple_position = InvalidAttrNumber; - rel->variable_name = node->name; - rel->resultRelInfo = NULL; + namespace = list_make1(pnsi); - rel->flags |= CYPHER_TARGET_NODE_MERGE_EXISTS; - return rel; - } - rel->flags |= CYPHER_TARGET_NODE_IS_VAR; - } else { - // assign a default variable name. - node->name = get_next_default_alias(cpstate); + checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); } - rel->type = LABEL_KIND_VERTEX; - rel->tuple_position = InvalidAttrNumber; - rel->variable_name = node->name; - rel->resultRelInfo = NULL; + if (add_rte_to_query) + addNSItemToQuery(pstate, pnsi, true, false, true); - if (!node->label) { - rel->label_name = ""; - node->label = AG_DEFAULT_LABEL_VERTEX; - } else { - rel->label_name = node->label; - } + return pnsi; +} - // check to see if the label exists, create the label entry if it does not. - if (node->label && !label_exists(node->label, cpstate->graph_oid)) { - List *parent; - RangeVar *rv; +static Query *analyze_cypher_clause(transform_method transform, cypher_clause *clause, cypher_parsestate *parent_cpstate) { + cypher_parsestate *cpstate; + Query *query; + ParseState *parent_pstate = (ParseState*)parent_cpstate; + ParseState *pstate; - // setup the default vertex table as the parent table, that we will inherit from. - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_VERTEX); + cpstate = make_cypher_parsestate(parent_cpstate); + pstate = (ParseState*)cpstate; - parent = list_make1(rv); + // copy the expr_kind down to the child + pstate->p_expr_kind = parent_pstate->p_expr_kind; - // create the label - create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, parent, NULL); + if (pstate->p_expr_kind == EXPR_KIND_WHERE) { + cpstate->entities = list_concat(NIL, parent_cpstate->entities); } - rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; + query = transform(cpstate, clause); - rv = makeRangeVar(cpstate->graph_name, node->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); + advance_transform_entities_to_next_clause(cpstate->entities); - // Store the relid - rel->relid = RelationGetRelid(label_relation); + parent_cpstate->entities = list_concat(parent_cpstate->entities, cpstate->entities); - pnsi = addRangeTableEntryForRelation((ParseState *)cpstate, label_relation, AccessShareLock, NULL, false, false); - rte = pnsi->p_rte; - rte->requiredPerms = ACL_INSERT; + free_cypher_parsestate(cpstate); - // id - rel->id_expr = (Expr *)build_column_default(label_relation, Anum_ag_label_vertex_table_id); + return query; +} - rel->prop_expr = cypher_create_properties(cpstate, rel, label_relation, node->props, ENT_VERTEX); +/* + * When we are done transforming a clause, before transforming the next clause + * iterate through the transform entities and mark them as not belonging to + * the clause that is currently being transformed. + */ +static void advance_transform_entities_to_next_clause(List *entities) { + ListCell *lc; - table_close(label_relation, NoLock); + foreach (lc, entities) { + transform_entity *entity = lfirst(lc); - return rel; + entity->declared_in_current_clause = false; + } } -// Takes a MERGE parse node and converts it to a MATCH parse node -static cypher_clause *convert_merge_to_match(cypher_merge *merge) { - cypher_match *match = make_ag_node(cypher_match); - cypher_clause *clause = palloc(sizeof(cypher_clause)); +/* + * from postgresql parse_sub_analyze + * Entry point for recursively analyzing a sub-statement. + */ +Query *cypher_parse_sub_analyze(Node *parseTree, cypher_parsestate *cpstate, CommonTableExpr *parentCTE, + bool locked_from_parent, bool resolve_unknowns) { + ParseState *pstate = make_parsestate((ParseState*)cpstate); + cypher_clause *clause; + Query *query; - // match supports multiple paths, whereas merge only supports one. - match->pattern = list_make1(merge->path); - // MERGE does not support where - match->where = NULL; + pstate->p_parent_cte = parentCTE; + pstate->p_locked_from_parent = locked_from_parent; + pstate->p_resolve_unknowns = resolve_unknowns; - /* - * We do not want the transform logic to transform the previous clauses - * with this, just handle this one clause. - */ - clause->prev = NULL; - clause->self = (Node *)match; - clause->next = NULL; + clause = palloc0(sizeof(cypher_clause)); + clause->self = parseTree; + query = transform_cypher_clause(cpstate, clause); - return clause; + free_parsestate(pstate); + + return query; } /* @@ -5748,67 +746,3 @@ static ParseNamespaceItem *get_namespace_item(ParseState *pstate, RangeTblEntry return NULL; } - -/* - * Creates the function expression that represents the clause. Adds the - * extensible node that represents the metadata that the clause needs to - * handle the clause in the execution phase. - */ -static FuncExpr *make_clause_func_expr(char *function_name, Node *clause_information) { - StringInfo str = makeStringInfo(); - /* - * Serialize the clause_information data structure. In certain - * cases (Prepared Statements and PL/pgsql), the MemoryContext that - * it is stored in will be destroyed. We need to get it into a format - * that Postgres' can copy between MemoryContexts. Just making it into - * an ExtensibleNode does not work, because there are certain parts of - * Postgres that cannot handle an ExtensibleNode in a function call. - * So we serialize the data structure and place it into a Const node - * that can handle these situations AND be copied correctly. - */ - outNode(str, clause_information); - - Const *c = makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false); - - Oid func_oid = get_ag_func_oid(function_name, 1, INTERNALOID); - - return makeFuncExpr(func_oid, GTYPEOID, list_make1(c), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} - -/* - * Utility function that helps a clause add the information needed to - * the query from the previous clause. - */ -static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, bool first_rte) { - ParseState *pstate = (ParseState *) cpstate; - int rtindex; - ParseNamespaceItem *pnsi; - - pnsi = transform_prev_cypher_clause(cpstate, clause, true); - - rtindex = list_length(pstate->p_rtable); - - // rte is the first RangeTblEntry in pstate - if (first_rte) - Assert(rtindex == 1); - - // add all the rte's attributes to the current queries targetlist - query->targetList = list_concat(query->targetList, expandNSItemAttrs(pstate, pnsi, 0, -1)); -} - -ParseNamespaceItem *find_pnsi(cypher_parsestate *cpstate, char *varname) { - ParseState *pstate = (ParseState *) cpstate; - ListCell *lc; - - foreach (lc, pstate->p_namespace) { - ParseNamespaceItem *pnsi = (ParseNamespaceItem *)lfirst(lc); - Alias *alias = pnsi->p_rte->alias; - if (!alias) - continue; - - if (!strcmp(alias->aliasname, varname)) - return pnsi; - } - - return NULL; -} diff --git a/src/backend/parser/cypher_grouping.c b/src/backend/parser/cypher_grouping.c new file mode 100644 index 00000000..d1aa6d3c --- /dev/null +++ b/src/backend/parser/cypher_grouping.c @@ -0,0 +1,874 @@ + +#include "postgraph.h" + +#include "access/nbtree.h" +#include "access/sysattr.h" +#include "access/heapam.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_type_d.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/nodes.h" +#include "nodes/parsenodes.h" +#include "nodes/pg_list.h" +#include "nodes/primnodes.h" +#include "optimizer/optimizer.h" +#include "parser/parse_clause.h" +#include "parser/parse_coerce.h" +#include "parser/parse_collate.h" +#include "parser/cypher_analyze.h" +#include "parser/parse_expr.h" +#include "parser/parse_func.h" +#include "parser/parse_node.h" +#include "parser/parse_oper.h" +#include "parser/parse_relation.h" +#include "parser/parse_target.h" +#include "parser/parsetree.h" +#include "rewrite/rewriteHandler.h" +#include "utils/catcache.h" +#include "utils/typcache.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" + +#include "catalog/ag_graph.h" +#include "catalog/ag_label.h" +#include "commands/label_commands.h" +#include "nodes/ag_nodes.h" +#include "nodes/cypher_nodes.h" +#include "parser/cypher_clause.h" +#include "parser/cypher_expr.h" +#include "parser/cypher_item.h" +#include "parser/cypher_parse_agg.h" +#include "parser/cypher_parse_node.h" +#include "parser/cypher_transform_entity.h" +#include "utils/ag_cache.h" +#include "utils/ag_func.h" +#include "utils/gtype.h" +#include "utils/graphid.h" +#include "utils/vertex.h" +#include "utils/edge.h" +#include "utils/traversal.h" +#include "utils/variable_edge.h" + +static WindowClause * +findWindowClause(List *wclist, const char *name); + static Node *flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets); +static Index transform_group_clause_expr(List **flatresult, Bitmapset *seen_local, cypher_parsestate *cpstate, Node *gexpr, List **targetlist, List *sortClause, ParseExprKind exprKind, bool toplevel); +static List *add_target_to_group_list(cypher_parsestate *cpstate, TargetEntry *tle, List *grouplist, List *targetlist, int location); +static Node * +transformGroupingSet(List **flatresult, ParseState *pstate, GroupingSet *gset, List **targetlist, List *sortClause, + ParseExprKind exprKind, bool useSQL99, bool toplevel); + static Node * +transform_frame_offset(ParseState *pstate, int frameOptions, + Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc, + Node *clause); +static void +checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName); + + List * transform_group_clause(cypher_parsestate *cpstate, List *grouplist, List **groupingSets, + List **targetlist, List *sortClause, ParseExprKind exprKind) { + ParseState *pstate = (ParseState *)cpstate; + List *result = NIL; + List *flat_grouplist; + List *gsets = NIL; + ListCell *gl; + bool hasGroupingSets = false; + Bitmapset *seen_local = NULL; + + /* + * Recursively flatten implicit RowExprs. (Technically this is only needed + * for GROUP BY, per the syntax rules for grouping sets, but we do it + * anyway.) + */ + flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist, true, &hasGroupingSets); + + /* + * If the list is now empty, but hasGroupingSets is true, it's because we + * elided redundant empty grouping sets. Restore a single empty grouping + * set to leave a canonical form: GROUP BY () + */ + + if (flat_grouplist == NIL && hasGroupingSets) + flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY, NIL, exprLocation((Node *) grouplist))); + + + foreach(gl, flat_grouplist) { + Node *gexpr = (Node *) lfirst(gl); + + if (IsA(gexpr, GroupingSet)) { + GroupingSet *gset = (GroupingSet *) gexpr; + + switch (gset->kind) + { + case GROUPING_SET_EMPTY: + gsets = lappend(gsets, gset); + break; + case GROUPING_SET_SIMPLE: + /* can't happen */ + Assert(false); + break; + case GROUPING_SET_SETS: + case GROUPING_SET_CUBE: + case GROUPING_SET_ROLLUP: + gsets = lappend(gsets, + transformGroupingSet(&result, pstate, gset, targetlist, sortClause, exprKind, true, true)); + break; + } + + } else { + Index ref = transform_group_clause_expr(&result, seen_local, cpstate, gexpr, targetlist, + sortClause, exprKind, true); + if (ref > 0) { + seen_local = bms_add_member(seen_local, ref); + if (hasGroupingSets) { + seen_local = bms_add_member(seen_local, ref); + if (hasGroupingSets) + gsets = lappend(gsets, + makeGroupingSet(GROUPING_SET_SIMPLE, list_make1_int(ref), exprLocation(gexpr))); + + } + } + } + } + + + if (groupingSets) + *groupingSets = gsets; + + return result; +} + + +/* + * findWindowClause + * Find the named WindowClause in the list, or return NULL if not there + */ +static WindowClause * +findWindowClause(List *wclist, const char *name) +{ + ListCell *l; + + foreach(l, wclist) + { + WindowClause *wc = (WindowClause *) lfirst(l); + + if (wc->name && strcmp(wc->name, name) == 0) + return wc; + } + + return NULL; +} +/* + * transformWindowDefinitions - + * transform window definitions (WindowDef to WindowClause) + */ +List * +transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist) +{ + List *result = NIL; + Index winref = 0; + ListCell *lc; + + foreach(lc, windowdefs) + { + WindowDef *windef = (WindowDef *) lfirst(lc); + WindowClause *refwc = NULL; + List *partitionClause; + List *orderClause; + Oid rangeopfamily = InvalidOid; + Oid rangeopcintype = InvalidOid; + WindowClause *wc; + + winref++; + + /* + * Check for duplicate window names. + */ + if (windef->name && findWindowClause(result, windef->name) != NULL) + ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("window \"%s\" is already defined", windef->name), + parser_errposition(pstate, windef->location))); + + /* + * If it references a previous window, look that up. + */ + if (windef->refname) + { + refwc = findWindowClause(result, windef->refname); + if (refwc == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("window \"%s\" does not exist", + windef->refname), + parser_errposition(pstate, windef->location))); + } + + /* + * Transform PARTITION and ORDER specs, if any. These are treated + * almost exactly like top-level GROUP BY and ORDER BY clauses, + * including the special handling of nondefault operator semantics. + */ + orderClause = transform_cypher_order_by(pstate, windef->orderClause, targetlist, EXPR_KIND_ORDER_BY); + partitionClause = transform_group_clause(pstate, windef->partitionClause, NULL, targetlist, + orderClause, EXPR_KIND_GROUP_BY); + + /* + * And prepare the new WindowClause. + */ + wc = makeNode(WindowClause); + wc->name = windef->name; + wc->refname = windef->refname; + + /* + * Per spec, a windowdef that references a previous one copies the + * previous partition clause (and mustn't specify its own). It can + * specify its own ordering clause, but only if the previous one had + * none. It always specifies its own frame clause, and the previous + * one must not have a frame clause. Yeah, it's bizarre that each of + * these cases works differently, but SQL:2008 says so; see 7.11 + * syntax rule 10 and general rule 1. The frame + * clause rule is especially bizarre because it makes "OVER foo" + * different from "OVER (foo)", and requires the latter to throw an + * error if foo has a nondefault frame clause. Well, ours not to + * reason why, but we do go out of our way to throw a useful error + * message for such cases. + */ + if (refwc) + { + if (partitionClause) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("cannot override PARTITION BY clause of window \"%s\"", windef->refname), + parser_errposition(pstate, windef->location))); + wc->partitionClause = copyObject(refwc->partitionClause); + } + else + wc->partitionClause = partitionClause; + if (refwc) { + if (orderClause && refwc->orderClause) + ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("cannot override ORDER BY clause of window \"%s\"", windef->refname), + parser_errposition(pstate, windef->location))); + if (orderClause) { + wc->orderClause = orderClause; + wc->copiedOrder = false; + } else { + wc->orderClause = copyObject(refwc->orderClause); + wc->copiedOrder = true; + } + } else { + wc->orderClause = orderClause; + wc->copiedOrder = false; + } + if (refwc && refwc->frameOptions != FRAMEOPTION_DEFAULTS) { + /* + * Use this message if this is a WINDOW clause, or if it's an OVER + * clause that includes ORDER BY or framing clauses. (We already + * rejected PARTITION BY above, so no need to check that.) + */ + if (windef->name || orderClause || windef->frameOptions != FRAMEOPTION_DEFAULTS) + ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("cannot copy window \"%s\" because it has a frame clause", windef->refname), + parser_errposition(pstate, windef->location))); + /* Else this clause is just OVER (foo), so say this: */ + ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("cannot copy window \"%s\" because it has a frame clause", windef->refname), + errhint("Omit the parentheses in this OVER clause."), + parser_errposition(pstate, windef->location))); + } + wc->frameOptions = windef->frameOptions; + + /* + * RANGE offset PRECEDING/FOLLOWING requires exactly one ORDER BY + * column; check that and get its sort opfamily info. + */ + if ((wc->frameOptions & FRAMEOPTION_RANGE) && + (wc->frameOptions & (FRAMEOPTION_START_OFFSET | FRAMEOPTION_END_OFFSET))) + { + SortGroupClause *sortcl; + Node *sortkey; + int16 rangestrategy; + + if (list_length(wc->orderClause) != 1) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column"), + parser_errposition(pstate, windef->location))); + sortcl = castNode(SortGroupClause, linitial(wc->orderClause)); + sortkey = get_sortgroupclause_expr(sortcl, *targetlist); + /* Find the sort operator in pg_amop */ + if (!get_ordering_op_properties(sortcl->sortop, + &rangeopfamily, + &rangeopcintype, + &rangestrategy)) + elog(ERROR, "operator %u is not a valid ordering operator", + sortcl->sortop); + /* Record properties of sort ordering */ + wc->inRangeColl = exprCollation(sortkey); + wc->inRangeAsc = (rangestrategy == BTLessStrategyNumber); + wc->inRangeNullsFirst = sortcl->nulls_first; + } + + /* Per spec, GROUPS mode requires an ORDER BY clause */ + if (wc->frameOptions & FRAMEOPTION_GROUPS) + { + if (wc->orderClause == NIL) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("GROUPS mode requires an ORDER BY clause"), + parser_errposition(pstate, windef->location))); + } + + /* Process frame offset expressions */ + wc->startOffset = transform_frame_offset(pstate, wc->frameOptions, + rangeopfamily, rangeopcintype, + &wc->startInRangeFunc, + windef->startOffset); + wc->endOffset = transform_frame_offset(pstate, wc->frameOptions, + rangeopfamily, rangeopcintype, + &wc->endInRangeFunc, + windef->endOffset); + wc->winref = winref; + + result = lappend(result, wc); + } + + return result; +} + + + +/* + * transformFrameOffset + * Process a window frame offset expression + * + * In RANGE mode, rangeopfamily is the sort opfamily for the input ORDER BY + * column, and rangeopcintype is the input data type the sort operator is + * registered with. We expect the in_range function to be registered with + * that same type. (In binary-compatible cases, it might be different from + * the input column's actual type, so we can't use that for the lookups.) + * We'll return the OID of the in_range function to *inRangeFunc. + */ +static Node * +transform_frame_offset(ParseState *pstate, int frameOptions, + Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc, + Node *clause) +{ + const char *constructName = NULL; + Node *node; + + *inRangeFunc = InvalidOid; /* default result */ + + /* Quick exit if no offset expression */ + if (clause == NULL) + return NULL; + + if (frameOptions & FRAMEOPTION_ROWS) + { + /* Transform the raw expression tree */ + node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_ROWS); + + /* + * Like LIMIT clause, simply coerce to int8 + */ + constructName = "ROWS"; + node = coerce_to_specific_type(pstate, node, INT8OID, constructName); + } + else if (frameOptions & FRAMEOPTION_RANGE) + { + /* + * We must look up the in_range support function that's to be used, + * possibly choosing one of several, and coerce the "offset" value to + * the appropriate input type. + */ + Oid nodeType; + Oid preferredType; + int nfuncs = 0; + int nmatches = 0; + Oid selectedType = InvalidOid; + Oid selectedFunc = InvalidOid; + CatCList *proclist; + int i; + + /* Transform the raw expression tree */ + node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE); + nodeType = exprType(node); + + /* + * If there are multiple candidates, we'll prefer the one that exactly + * matches nodeType; or if nodeType is as yet unknown, prefer the one + * that exactly matches the sort column type. (The second rule is + * like what we do for "known_type operator unknown".) + */ + preferredType = (nodeType != UNKNOWNOID) ? nodeType : rangeopcintype; + + /* Find the in_range support functions applicable to this case */ + proclist = SearchSysCacheList2(AMPROCNUM, + ObjectIdGetDatum(rangeopfamily), + ObjectIdGetDatum(rangeopcintype)); + for (i = 0; i < proclist->n_members; i++) + { + HeapTuple proctup = &proclist->members[i]->tuple; + Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); + + /* The search will find all support proc types; ignore others */ + if (procform->amprocnum != BTINRANGE_PROC) + continue; + nfuncs++; + + /* Ignore function if given value can't be coerced to that type */ + if (!can_coerce_type(1, &nodeType, &procform->amprocrighttype, + COERCION_IMPLICIT)) + continue; + nmatches++; + + /* Remember preferred match, or any match if didn't find that */ + if (selectedType != preferredType) + { + selectedType = procform->amprocrighttype; + selectedFunc = procform->amproc; + } + } + ReleaseCatCacheList(proclist); + + /* + * Throw error if needed. It seems worth taking the trouble to + * distinguish "no support at all" from "you didn't match any + * available offset type". + */ + if (nfuncs == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s", + format_type_be(rangeopcintype)), + parser_errposition(pstate, exprLocation(node)))); + if (nmatches == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s and offset type %s", + format_type_be(rangeopcintype), + format_type_be(nodeType)), + errhint("Cast the offset value to an appropriate type."), + parser_errposition(pstate, exprLocation(node)))); + if (nmatches != 1 && selectedType != preferredType) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("RANGE with offset PRECEDING/FOLLOWING has multiple interpretations for column type %s and offset type %s", + format_type_be(rangeopcintype), + format_type_be(nodeType)), + errhint("Cast the offset value to the exact intended type."), + parser_errposition(pstate, exprLocation(node)))); + + /* OK, coerce the offset to the right type */ + constructName = "RANGE"; + node = coerce_to_specific_type(pstate, node, + selectedType, constructName); + *inRangeFunc = selectedFunc; + } + else if (frameOptions & FRAMEOPTION_GROUPS) + { + /* Transform the raw expression tree */ + node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_GROUPS); + + /* + * Like LIMIT clause, simply coerce to int8 + */ + constructName = "GROUPS"; + node = coerce_to_specific_type(pstate, node, INT8OID, constructName); + } + else + { + Assert(false); + node = NULL; + } + + /* Disallow variables in frame offsets */ + checkExprIsVarFree(pstate, node, constructName); + + return node; +} + + +/* + * checkExprIsVarFree + * Check that given expr has no Vars of the current query level + * (aggregates and window functions should have been rejected already). + * + * This is used to check expressions that have to have a consistent value + * across all rows of the query, such as a LIMIT. Arguably it should reject + * volatile functions, too, but we don't do that --- whatever value the + * function gives on first execution is what you get. + * + * constructName does not affect the semantics, but is used in error messages + */ +static void +checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName) +{ + if (contain_vars_of_level(n, 0)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("argument of %s must not contain variables", constructName), + parser_errposition(pstate, locate_var_of_level(n, 0)))); + } +} + +// from PG's addTargetToGroupList +static List *add_target_to_group_list(cypher_parsestate *cpstate, TargetEntry *tle, List *grouplist, List *targetlist, int location) { + ParseState *pstate = (ParseState *)cpstate; + Oid restype = exprType((Node *) tle->expr); + + // if tlist item is an UNKNOWN literal, change it to TEXT + if (restype == UNKNOWNOID) { + tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, restype, GTYPEOID, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); + restype = GTYPEOID; + } + + // avoid making duplicate grouplist entries + if (!targetIsInSortList(tle, InvalidOid, grouplist)) { + SortGroupClause *grpcl = makeNode(SortGroupClause); + Oid sortop; + Oid eqop; + bool hashable; + ParseCallbackState pcbstate; + + // determine the eqop and optional sortop + setup_parser_errposition_callback(&pcbstate, pstate, location); + get_sort_group_operators(restype, false, true, false, &sortop, &eqop, NULL, &hashable); + cancel_parser_errposition_callback(&pcbstate); + + grpcl->tleSortGroupRef = assignSortGroupRef(tle, targetlist); + grpcl->eqop = eqop; + grpcl->sortop = sortop; + grpcl->nulls_first = false; // OK with or without sortop + grpcl->hashable = hashable; + + grouplist = lappend(grouplist, grpcl); + } + + return grouplist; +} + +// see findTargetlistEntrySQL99() +static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, + ParseExprKind expr_kind) { + Node *expr; + ListCell *lt; + TargetEntry *te; + + expr = transform_cypher_expr(cpstate, node, expr_kind); + + foreach (lt, *target_list) { + Node *te_expr; + + te = lfirst(lt); + te_expr = strip_implicit_coercions((Node *)te->expr); + + if (equal(expr, te_expr)) + return te; + } + + te = transform_cypher_item(cpstate, node, expr, expr_kind, NULL, true); + + *target_list = lappend(*target_list, te); + + return te; +} +// from PG's transformGroupClauseExpr +static Index transform_group_clause_expr(List **flatresult, Bitmapset *seen_local, cypher_parsestate *cpstate, Node *gexpr, List **targetlist, List *sortClause, ParseExprKind exprKind, bool toplevel) { + TargetEntry *tle = NULL; + bool found = false; + + tle = find_target_list_entry(cpstate, gexpr, targetlist, exprKind); + + if (tle->ressortgroupref > 0) { + ListCell *sl; + + /* + * Eliminate duplicates (GROUP BY x, x) but only at local level. + * (Duplicates in grouping sets can affect the number of returned + * rows, so can't be dropped indiscriminately.) + * + * Since we don't care about anything except the sortgroupref, we can + * use a bitmapset rather than scanning lists. + */ + if (bms_is_member(tle->ressortgroupref, seen_local)) + return 0; + + /* + * If we're already in the flat clause list, we don't need to consider + * adding ourselves again. + */ + found = targetIsInSortList(tle, InvalidOid, *flatresult); + if (found) + return tle->ressortgroupref; + + /* + * If the GROUP BY tlist entry also appears in ORDER BY, copy operator + * info from the (first) matching ORDER BY item. This means that if + * you write something like "GROUP BY foo ORDER BY foo USING <<<", the + * GROUP BY operation silently takes on the equality semantics implied + * by the ORDER BY. There are two reasons to do this: it improves the + * odds that we can implement both GROUP BY and ORDER BY with a single + * sort step, and it allows the user to choose the equality semantics + * used by GROUP BY, should she be working with a datatype that has + * more than one equality operator. + * + * If we're in a grouping set, though, we force our requested ordering + * to be NULLS LAST, because if we have any hope of using a sorted agg + * for the job, we're going to be tacking on generated NULL values + * after the corresponding groups. If the user demands nulls first, + * another sort step is going to be inevitable, but that's the + * planner's problem. + */ + foreach(sl, sortClause) { + SortGroupClause *sc = (SortGroupClause *) lfirst(sl); + + if (sc->tleSortGroupRef == tle->ressortgroupref) { + SortGroupClause *grpc = copyObject(sc); + + if (!toplevel) + grpc->nulls_first = false; + *flatresult = lappend(*flatresult, grpc); + found = true; + break; + } + } + } + + /* + * If no match in ORDER BY, just add it to the result using default + * sort/group semantics. + */ + if (!found) + *flatresult = add_target_to_group_list(cpstate, tle, *flatresult, *targetlist, exprLocation(gexpr)); + + // _something_ must have assigned us a sortgroupref by now... + + return tle->ressortgroupref; +} + +/* + * Transform a list of expressions within a GROUP BY clause or grouping set. + * + * The list of expressions belongs to a single clause within which duplicates + * can be safely eliminated. + * + * Returns an integer list of ressortgroupref values. + * + * flatresult reference to flat list of SortGroupClause nodes + * pstate ParseState + * list nodes to transform + * targetlist reference to TargetEntry list + * sortClause ORDER BY clause (SortGroupClause nodes) + * exprKind expression kind + * useSQL99 SQL99 rather than SQL92 syntax + * toplevel false if within any grouping set + */ +static List * transformGroupClauseList(List **flatresult, ParseState *pstate, List *list, List **targetlist, + List *sortClause, ParseExprKind exprKind, bool useSQL99, bool toplevel) { + Bitmapset *seen_local = NULL; + List *result = NIL; + ListCell *gl; + + foreach(gl, list) + { + Node *gexpr = (Node *) lfirst(gl); + + Index ref = transform_group_clause_expr(flatresult, seen_local, pstate, gexpr, targetlist, sortClause, exprKind, toplevel); + + if (ref > 0) { + seen_local = bms_add_member(seen_local, ref); + result = lappend_int(result, ref); + } + } + + return result; +} + +/* + * Transform a grouping set and (recursively) its content. + * + * The grouping set might be a GROUPING SETS node with other grouping sets + * inside it, but SETS within SETS have already been flattened out before + * reaching here. + * + * Returns the transformed node, which now contains SIMPLE nodes with lists + * of ressortgrouprefs rather than expressions. + * + * flatresult reference to flat list of SortGroupClause nodes + * pstate ParseState + * gset grouping set to transform + * targetlist reference to TargetEntry list + * sortClause ORDER BY clause (SortGroupClause nodes) + * exprKind expression kind + * useSQL99 SQL99 rather than SQL92 syntax + * toplevel false if within any grouping set + */ +static Node * +transformGroupingSet(List **flatresult, ParseState *pstate, GroupingSet *gset, List **targetlist, List *sortClause, + ParseExprKind exprKind, bool useSQL99, bool toplevel) { + ListCell *gl; + List *content = NIL; + + Assert(toplevel || gset->kind != GROUPING_SET_SETS); + + foreach(gl, gset->content) + { + Node *n = lfirst(gl); + + if (IsA(n, List)) + { + List *l = transformGroupClauseList(flatresult, pstate, (List *) n, targetlist, sortClause, + exprKind, useSQL99, false); + + content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE, l, exprLocation(n))); + } + else if (IsA(n, GroupingSet)) + { + GroupingSet *gset2 = (GroupingSet *) lfirst(gl); + + content = lappend(content, transformGroupingSet(flatresult, pstate, gset2, targetlist, sortClause, + exprKind, useSQL99, false)); + } + else + { + Index ref = transform_group_clause_expr(flatresult, NULL, pstate, n, targetlist, sortClause, + exprKind, false); + + content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE, list_make1_int(ref), exprLocation(n))); + } + } + + /* Arbitrarily cap the size of CUBE, which has exponential growth */ + if (gset->kind == GROUPING_SET_CUBE) + { + if (list_length(content) > 12) + ereport(ERROR, (errcode(ERRCODE_TOO_MANY_COLUMNS), + errmsg("CUBE is limited to 12 elements"), + parser_errposition(pstate, gset->location))); + } + + return (Node *) makeGroupingSet(gset->kind, content, gset->location); +} + + + + +/*------------------------------------------------------------------------- + * Flatten out parenthesized sublists in grouping lists, and some cases + * of nested grouping sets. + * + * Inside a grouping set (ROLLUP, CUBE, or GROUPING SETS), we expect the + * content to be nested no more than 2 deep: i.e. ROLLUP((a,b),(c,d)) is + * ok, but ROLLUP((a,(b,c)),d) is flattened to ((a,b,c),d), which we then + * (later) normalize to ((a,b,c),(d)). + * + * CUBE or ROLLUP can be nested inside GROUPING SETS (but not the reverse), + * and we leave that alone if we find it. But if we see GROUPING SETS inside + * GROUPING SETS, we can flatten and normalize as follows: + * GROUPING SETS (a, (b,c), GROUPING SETS ((c,d),(e)), (f,g)) + * becomes + * GROUPING SETS ((a), (b,c), (c,d), (e), (f,g)) + * + * This is per the spec's syntax transformations, but these are the only such + * transformations we do in parse analysis, so that queries retain the + * originally specified grouping set syntax for CUBE and ROLLUP as much as + * possible when deparsed. (Full expansion of the result into a list of + * grouping sets is left to the planner.) + * + * When we're done, the resulting list should contain only these possible + * elements: + * - an expression + * - a CUBE or ROLLUP with a list of expressions nested 2 deep + * - a GROUPING SET containing any of: + * - expression lists + * - empty grouping sets + * - CUBE or ROLLUP nodes with lists nested 2 deep + * The return is a new list, but doesn't deep-copy the old nodes except for + * GroupingSet nodes. + * + * As a side effect, flag whether the list has any GroupingSet nodes. + *------------------------------------------------------------------------- + */ +static Node *flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets) { + // just in case of pathological input + check_stack_depth(); + + if (expr == (Node *) NIL) + return (Node *) NIL; + + switch (expr->type) + { + case T_RowExpr: + { + RowExpr *r = (RowExpr *) expr; + + if (r->row_format == COERCE_IMPLICIT_CAST) + return flatten_grouping_sets((Node *) r->args, false, NULL); + break; + } + case T_GroupingSet: + { + GroupingSet *gset = (GroupingSet *) expr; + ListCell *l2; + List *result_set = NIL; + + if (hasGroupingSets) + *hasGroupingSets = true; + + /* + * at the top level, we skip over all empty grouping sets; the + * caller can supply the canonical GROUP BY () if nothing is + * left. + */ + if (toplevel && gset->kind == GROUPING_SET_EMPTY) + return (Node *) NIL; + + foreach(l2, gset->content) { + Node *n1 = lfirst(l2); + Node *n2 = flatten_grouping_sets(n1, false, NULL); + + if (IsA(n1, GroupingSet) && ((GroupingSet *) n1)->kind == GROUPING_SET_SETS) + result_set = list_concat(result_set, (List *) n2); + else + result_set = lappend(result_set, n2); + } + + /* + * At top level, keep the grouping set node; but if we're in a + * nested grouping set, then we need to concat the flattened + * result into the outer list if it's simply nested. + */ + + if (toplevel || (gset->kind != GROUPING_SET_SETS)) + return (Node *) makeGroupingSet(gset->kind, result_set, gset->location); + else + return (Node *) result_set; + + break; + } + case T_List: + { + List *result = NIL; + ListCell *l; + + foreach(l, (List *) expr) { + Node *n = NULL; + + n = flatten_grouping_sets(lfirst(l), toplevel, hasGroupingSets); + + if (n != (Node *) NIL) { + if (IsA(n, List)) + result = list_concat(result, (List *) n); + else + result = lappend(result, n); + } + } + return (Node *) result; + } + default: + break; + } + return expr; +} diff --git a/src/backend/postgraph.c b/src/backend/postgraph.c index bb52c6af..335daaac 100644 --- a/src/backend/postgraph.c +++ b/src/backend/postgraph.c @@ -21,7 +21,7 @@ #include "fmgr.h" -#include "access/ivfflat.h" + #include "catalog/ag_catalog.h" #include "nodes/ag_nodes.h" #include "optimizer/cypher_paths.h" @@ -35,19 +35,14 @@ void _PG_init(void) { register_ag_nodes(); set_rel_pathlist_init(); - //object_access_hook_init(); - //process_utility_hook_init(); parse_analyze_init(); parse_init(); - IvfflatInit(); } void _PG_fini(void); void _PG_fini(void) { - //process_utility_hook_fini(); - //object_access_hook_fini(); set_rel_pathlist_fini(); parse_analyze_fini(); parse_fini(); diff --git a/src/backend/utils/adt/gtype.c b/src/backend/utils/adt/gtype.c index 8a76e8cf..e2191d65 100644 --- a/src/backend/utils/adt/gtype.c +++ b/src/backend/utils/adt/gtype.c @@ -2723,10 +2723,6 @@ gtype_value *get_gtype_value(char *funcname, gtype *agt_arg, return agtv_value; } -/* - * Helper function to step through and retrieve keys from an object. - * borrowed and modified from get_next_object_pair() in gtype_vle.c - */ static gtype_iterator *get_next_object_key(gtype_iterator *it, gtype_container *agtc, gtype_value *key) { gtype_iterator_token itok; gtype_value tmp; @@ -2773,13 +2769,12 @@ static gtype_iterator *get_next_object_key(gtype_iterator *it, gtype_container * PG_FUNCTION_INFO_V1(vertex_keys); Datum vertex_keys(PG_FUNCTION_ARGS) { - gtype *agt_arg = NULL; gtype_value *agtv_result = NULL; gtype_value obj_key = {0}; gtype_iterator *it = NULL; gtype_parse_state *parse_state = NULL; vertex *v = AG_GET_ARG_VERTEX(0); - agt_arg = extract_vertex_properties(v); + gtype *agt_arg = extract_vertex_properties(v); agtv_result = push_gtype_value(&parse_state, WGT_BEGIN_ARRAY, NULL); @@ -3139,3 +3134,66 @@ cypher_load_csv(PG_FUNCTION_ARGS) //else SRF_RETURN_DONE(funcctx); } + +#include "common/hashfn.h" +// TODO: move this somewhere that makes sense, that idiot John moved it into +// the vle code for some stupid reason that only an idiot could explain +// function checks the edges in a MATCH clause to see if they are unique or not. +PG_FUNCTION_INFO_V1(_ag_enforce_edge_uniqueness); +Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) { + Datum *args; + bool *nulls; + Oid *types; + + int nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + + // configure the hash table + HASHCTL exists_ctl; + MemSet(&exists_ctl, 0, sizeof(exists_ctl)); + exists_ctl.keysize = sizeof(int64); + exists_ctl.entrysize = sizeof(int64); + exists_ctl.hash = tag_hash; + + HTAB *exists_hash = hash_create("sdfg", nargs, &exists_ctl, HASH_ELEM | HASH_FUNCTION); + + // insert arguments into hash table + for (int i = 0; i < nargs; i++) { + + if (nulls[i]) + continue; + + if (types[i] == GRAPHIDOID) { + graphid edge_id = DatumGetInt64(args[i]); + + bool found; + int64 *value = (int64 *)hash_search(exists_hash, (void *)&edge_id, HASH_ENTER, &found); + + // if we found it, we're done, we have a duplicate + if (found) + PG_RETURN_BOOL(false); + + *value = edge_id; + + continue; + } + else if (types[i] == VARIABLEEDGEOID) { + VariableEdge *ve = DATUM_GET_VARIABLE_EDGE(args[i]); + char *ptr = &ve->children[1]; + for (int i = 0; i < ve->children[0]; i++, ptr = ptr + VARSIZE(ptr)) { + if (i % 2 == 1) { + continue; + } else { + edge *e = (edge *)ptr; + + graphid i = *((int64 *)(&e->children[0])); + bool found; + (int64 *)hash_search(exists_hash, (void *)&i, HASH_ENTER, &found); + if (found) + PG_RETURN_BOOL(false); + } + } + } + } + + PG_RETURN_BOOL(true); +} \ No newline at end of file diff --git a/src/backend/utils/adt/gtype_funcs.c b/src/backend/utils/adt/gtype_funcs.c new file mode 100644 index 00000000..94cfe03f --- /dev/null +++ b/src/backend/utils/adt/gtype_funcs.c @@ -0,0 +1,2224 @@ +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "postgres.h" + +#include + +#include "access/genam.h" +#include "access/htup_details.h" +#include "catalog/namespace.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "catalog/pg_aggregate_d.h" +#include "catalog/pg_collation_d.h" +#include "catalog/pg_operator_d.h" +#include "executor/nodeAgg.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "parser/parse_coerce.h" +#include "portability/instr_time.h" +#include "nodes/pg_list.h" +#include "utils/builtins.h" +#include "utils/float.h" +#include "utils/fmgroids.h" +#include "utils/int8.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/timestamp.h" +#include "utils/typcache.h" +#include "utils/rangetypes.h" +#include "utils/geo_decls.h" +#include "common/int128.h" + +#include "utils/gtype.h" +#include "utils/edge.h" +#include "utils/variable_edge.h" +#include "utils/vector.h" +#include "utils/vertex.h" +#include "utils/gtype_parser.h" +#include "utils/gtype_typecasting.h" +#include "catalog/ag_graph.h" +#include "catalog/ag_label.h" +#include "utils/graphid.h" +#include "utils/numeric.h" + + + +#define GTYPETRIGFUNC( name) \ +PG_FUNCTION_INFO_V1(gtype_##name); \ +Datum \ +gtype_##name(PG_FUNCTION_ARGS) \ +{ \ + gtype *gt= AG_GET_ARG_GTYPE_P(0); \ + gtype_value gtv = { \ + .type =AGTV_FLOAT, \ + .val.float_value = DatumGetFloat8(DirectFunctionCall1(d##name, GT_ARG_TO_FLOAT8_DATUM(0))) \ + }; \ + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); \ +} \ +/* keep compiler quiet - no extra ; */ \ +extern int no_such_variable + +GTYPETRIGFUNC(sin); +GTYPETRIGFUNC(cos); +GTYPETRIGFUNC(tan); +GTYPETRIGFUNC(sinh); +GTYPETRIGFUNC(cosh); +GTYPETRIGFUNC(tanh); +GTYPETRIGFUNC(cot); +GTYPETRIGFUNC(asin); +GTYPETRIGFUNC(acos); +GTYPETRIGFUNC(atan); +GTYPETRIGFUNC(asinh); +GTYPETRIGFUNC(acosh); +GTYPETRIGFUNC(atanh); + +GTYPETRIGFUNC(sind); +GTYPETRIGFUNC(cosd); +GTYPETRIGFUNC(tand); +GTYPETRIGFUNC(cotd); +GTYPETRIGFUNC(asind); +GTYPETRIGFUNC(acosd); +GTYPETRIGFUNC(atand); + + +PG_FUNCTION_INFO_V1(gtype_atan2); +Datum +gtype_atan2(PG_FUNCTION_ARGS) { + Datum x = convert_to_scalar(gtype_to_float8_internal, AG_GET_ARG_GTYPE_P(0), "float"); + Datum y = convert_to_scalar(gtype_to_float8_internal, AG_GET_ARG_GTYPE_P(1), "float"); + + gtype_value gtv_result; + gtv_result.type = AGTV_FLOAT; + gtv_result.val.float_value = DatumGetFloat8(DirectFunctionCall2(datan2, y, x)); + + PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); +} + +PG_FUNCTION_INFO_V1(gtype_degrees); + +Datum gtype_degrees(PG_FUNCTION_ARGS) +{ + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + gtype_value gtv_result; + gtv_result.type = AGTV_FLOAT; + gtv_result.val.float_value = + DatumGetFloat8(DirectFunctionCall1(degrees, convert_to_scalar(gtype_to_float8_internal, gt, "float"))); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v_result)); +} + +PG_FUNCTION_INFO_V1(gtype_radians); + +Datum gtype_radians(PG_FUNCTION_ARGS) +{ + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + gtype_value gtv_result; + gtv_result.type = AGTV_FLOAT; + gtv_result.val.float_value = + DatumGetFloat8(DirectFunctionCall1(radians, convert_to_scalar(gtype_to_float8_internal, gt, "float"))); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v_result)); +} + +PG_FUNCTION_INFO_V1(gtype_gcd); +Datum +gtype_gcd(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + if (GTYPE_IS_FLOAT(lhs) || GTE_IS_NUMERIC(lhs->root.children[0]) || GTYPE_IS_FLOAT(rhs) || GTE_IS_NUMERIC(rhs->root.children[0])) { + Datum x = convert_to_scalar(gtype_to_numeric_internal, lhs, "numeric"); + Datum y = convert_to_scalar(gtype_to_numeric_internal, rhs, "numeric"); + + gtype_value gtv_result; + gtv_result.type = AGTV_NUMERIC; + gtv_result.val.numeric = DatumGetNumeric(DirectFunctionCall2(numeric_gcd, y, x)); + + PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); + } else { + Datum x = convert_to_scalar(gtype_to_int8_internal, lhs, "int8"); + Datum y = convert_to_scalar(gtype_to_int8_internal, rhs, "int8"); + + gtype_value gtv_result; + gtv_result.type = AGTV_INTEGER; + gtv_result.val.int_value = DatumGetInt64(DirectFunctionCall2(int8gcd, y, x)); + + PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); + } +} + +PG_FUNCTION_INFO_V1(gtype_lcm); +Datum +gtype_lcm(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + if (GTYPE_IS_FLOAT(lhs) || GTE_IS_NUMERIC(lhs->root.children[0]) || GTYPE_IS_FLOAT(rhs) || GTE_IS_NUMERIC(rhs->root.children[0])) { + Datum x = convert_to_scalar(gtype_to_numeric_internal, lhs, "numeric"); + Datum y = convert_to_scalar(gtype_to_numeric_internal, rhs, "numeric"); + + gtype_value gtv_result; + gtv_result.type = AGTV_NUMERIC; + gtv_result.val.numeric = DatumGetNumeric(DirectFunctionCall2(numeric_lcm, y, x)); + + PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); + } else { + Datum x = convert_to_scalar(gtype_to_int8_internal, lhs, "int8"); + Datum y = convert_to_scalar(gtype_to_int8_internal, rhs, "int8"); + + gtype_value gtv_result; + gtv_result.type = AGTV_INTEGER; + gtv_result.val.int_value = DatumGetInt64(DirectFunctionCall2(int8lcm, y, x)); + + PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); + } +} + +PG_FUNCTION_INFO_V1(gtype_round_w_precision); + +Datum gtype_round_w_precision(PG_FUNCTION_ARGS) +{ + gtype *gt = AG_GET_ARG_GTYPE_P(0); + gtype *prec = AG_GET_ARG_GTYPE_P(1); + + Datum x = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + Datum y = convert_to_scalar(gtype_to_int4_internal, prec, "int4"); + + gtype_value gtv; + gtv.type = AGTV_NUMERIC; + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(numeric_round, x, y)); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_round); + +Datum gtype_round(PG_FUNCTION_ARGS) +{ + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_numeric(gt)) { + Datum x = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + + gtype_value gtv; + gtv.type = AGTV_NUMERIC; + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(numeric_round, x, Int32GetDatum(0))); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } else { + Datum x = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + + gtype_value gtv; + gtv.type = AGTV_FLOAT; + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall1(dround, x)); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } +} + +PG_FUNCTION_INFO_V1(gtype_ceil); + +Datum gtype_ceil(PG_FUNCTION_ARGS) +{ + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_numeric(gt)) { + Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + + Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_ceil, arg)); + + gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } else { + Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + + float8 result = DatumGetFloat8(DirectFunctionCall1(dceil, arg)); + + gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } +} + +PG_FUNCTION_INFO_V1(gtype_floor); + +Datum gtype_floor(PG_FUNCTION_ARGS) +{ + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_numeric(gt)) { + Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + + Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_floor, arg)); + + gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } else { + Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + + float8 result = DatumGetFloat8(DirectFunctionCall1(dfloor, arg)); + + gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } +} + +PG_FUNCTION_INFO_V1(gtype_abs); + +Datum gtype_abs(PG_FUNCTION_ARGS) +{ + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + Datum x; + gtype_value gtv; + if (is_gtype_numeric(gt)) { + gtv.type = AGTV_NUMERIC; + x = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_abs, x)); + } + else if (is_gtype_float(gt)) { + gtv.type = AGTV_FLOAT; + x = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall1(float8abs, x)); + } + else { + gtv.type = AGTV_INTEGER; + x = convert_to_scalar(gtype_to_int8_internal, gt, "int"); + gtv.val.int_value = DatumGetInt64(DirectFunctionCall1(int8abs, x)); + } + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_sign); +Datum gtype_sign(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + Datum x; + gtype_value gtv; + if (is_gtype_numeric(gt)) { + gtv.type = AGTV_NUMERIC; + x = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_sign, x)); + } else { + gtv.type = AGTV_FLOAT; + x = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall1(dsign, x)); + } + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_log); +Datum +gtype_log(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_numeric(gt)) { + Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + + Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_ln, arg)); + + gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } else { + Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + + float8 result = DatumGetFloat8(DirectFunctionCall1(dlog1, arg)); + + gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } +} + +PG_FUNCTION_INFO_V1(gtype_log10); +Datum +gtype_log10(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_numeric(gt)) { + Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + + Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_log, arg)); + + gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } else { + Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + + float8 result = DatumGetFloat8(DirectFunctionCall1(dlog10, arg)); + + gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } +} + +PG_FUNCTION_INFO_V1(gtype_e); +Datum +gtype_e(PG_FUNCTION_ARGS) { + gtype_value agtv_result = { + .type = AGTV_FLOAT, + .val.float_value = DatumGetFloat8(DirectFunctionCall1(dexp, Float8GetDatum(1))) + }; + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(gtype_pi); +Datum +gtype_pi(PG_FUNCTION_ARGS) { + gtype_value agtv_result = { + .type = AGTV_FLOAT, + .val.float_value = M_PI + }; + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(gtype_rand); +Datum +gtype_rand(PG_FUNCTION_ARGS) { + gtype_value agtv_result = { + .type = AGTV_FLOAT, + .val.float_value = DatumGetFloat8(DirectFunctionCall1(random, Float8GetDatum(1))) + }; + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(gtype_exp); +Datum +gtype_exp(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_numeric(gt)) { + Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + + Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_exp, arg)); + + gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } else { + Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + + float8 result = DatumGetFloat8(DirectFunctionCall1(dexp, arg)); + + gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } + +} + +PG_FUNCTION_INFO_V1(gtype_sqrt); +Datum +gtype_sqrt(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_numeric(gt)) { + Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); + + Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_sqrt, arg)); + + gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } else { + Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); + + float8 result = DatumGetFloat8(DirectFunctionCall1(dsqrt, arg)); + + gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); + } +} + +PG_FUNCTION_INFO_V1(gtype_cbrt); +Datum +gtype_cbrt(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + gtype_value gtv_result; + gtv_result.type = AGTV_FLOAT; + gtv_result.val.float_value = + DatumGetFloat8(DirectFunctionCall1(dcbrt, convert_to_scalar(gtype_to_float8_internal, gt, "float"))); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v_result)); +} + +PG_FUNCTION_INFO_V1(gtype_factorial); +Datum +gtype_factorial(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + gtype_value gtv_result; + gtv_result.type = AGTV_NUMERIC; + gtv_result.val.numeric = + DatumGetNumeric(DirectFunctionCall1(numeric_fac, convert_to_scalar(gtype_to_int8_internal, gt, "int"))); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v_result)); +} + + +PG_FUNCTION_INFO_V1(gtype_abbrev); +Datum +gtype_abbrev(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + Datum d; + if (GT_IS_CIDR(gt)) + d = DirectFunctionCall1(cidr_abbrev, GT_TO_CIDR_DATUM(gt)); + else + d = DirectFunctionCall1(inet_abbrev, GT_TO_INET_DATUM(gt)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_broadcast); +Datum +gtype_broadcast(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(network_broadcast, GT_ARG_TO_INET_DATUM(0)); + + gtype_value gtv; + gtv.type = AGTV_INET; + memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_family); +Datum +gtype_family(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(network_family, GT_ARG_TO_INET_DATUM(0)); + + gtype_value gtv = { .type = AGTV_INTEGER, .val.int_value = DatumGetInt32(d) }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + +} + +PG_FUNCTION_INFO_V1(gtype_host); +Datum +gtype_host(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(network_host, GT_ARG_TO_INET_DATUM(0)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_hostmask); +Datum +gtype_hostmask(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(network_hostmask, GT_ARG_TO_INET_DATUM(0)); + + gtype_value gtv; + gtv.type = AGTV_INET; + memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_inet_merge); +Datum +gtype_inet_merge(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall2(inet_merge, GT_ARG_TO_INET_DATUM(0), GT_ARG_TO_INET_DATUM(1)); + + gtype_value gtv; + gtv.type = AGTV_CIDR; + memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_inet_same_family); +Datum +gtype_inet_same_family(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall2(inet_same_family, GT_ARG_TO_INET_DATUM(0), GT_ARG_TO_INET_DATUM(1)); + + gtype_value gtv = { .type = AGTV_BOOL, .val.int_value = DatumGetBool(d) }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_masklen); +Datum +gtype_masklen(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(network_masklen, GT_ARG_TO_INET_DATUM(0)); + + gtype_value gtv = { .type = AGTV_INTEGER, .val.int_value = DatumGetInt32(d) }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_netmask); +Datum +gtype_netmask(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(network_netmask, GT_ARG_TO_INET_DATUM(0)); + + gtype_value gtv; + gtv.type = AGTV_INET; + memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_network); +Datum +gtype_network(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(network_network, GT_ARG_TO_INET_DATUM(0)); + + gtype_value gtv; + gtv.type = AGTV_CIDR; + memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_set_masklen); +Datum +gtype_set_masklen(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + gtype_value gtv; + Datum d; + if (GT_IS_CIDR(gt)) { + d = DirectFunctionCall2(cidr_set_masklen, GT_TO_CIDR_DATUM(gt), GT_ARG_TO_INT4_DATUM(1)); + gtv.type = AGTV_CIDR; + } else { + d = DirectFunctionCall2(inet_set_masklen, GT_TO_INET_DATUM(gt), GT_ARG_TO_INT4_DATUM(1)); + gtv.type = AGTV_INET; + } + + memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_trunc); +Datum +gtype_trunc(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + gtype_value gtv; + Datum d; + if (GT_IS_MACADDR(gt)) { + d = DirectFunctionCall1(macaddr_trunc, GT_TO_MAC_DATUM(gt)); + gtv.type = AGTV_MAC; + } else { + d = DirectFunctionCall1(macaddr8_trunc, GT_TO_MAC8_DATUM(gt)); + gtv.type = AGTV_MAC8; + } + + memcpy(>v.val.inet, DatumGetMacaddrP(d), sizeof(char) * 6); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_macaddr8_set7bit); +Datum +gtype_macaddr8_set7bit(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(macaddr8_set7bit, GT_ARG_TO_MAC8_DATUM(0)); + + gtype_value gtv; + gtv.type = AGTV_MAC8; + memcpy(>v.val.inet, DatumGetMacaddr8P(d), sizeof(char) * 8); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + + +int +timetz_cmp_internal(TimeTzADT *time1, TimeTzADT *time2) { + TimeOffset t1, t2; + + // Primary sort is by true (GMT-equivalent) time + t1 = time1->time + (time1->zone * USECS_PER_SEC); + t2 = time2->time + (time2->zone * USECS_PER_SEC); + + if (t1 > t2) + return 1; + if (t1 < t2) + return -1; + + /* + * If same GMT time, sort by timezone; we only want to say that two + * timetz's are equal if both the time and zone parts are equal. + */ + if (time1->zone > time2->zone) + return 1; + if (time1->zone < time2->zone) + return -1; + + return 0; +} + + +static inline INT128 +interval_cmp_value(const Interval *interval) +{ + INT128 span; + int64 dayfraction; + int64 days; + + /* + * Separate time field into days and dayfraction, then add the month and + * day fields to the days part. We cannot overflow int64 days here. + */ + dayfraction = interval->time % USECS_PER_DAY; + days = interval->time / USECS_PER_DAY; + days += interval->month * INT64CONST(30); + days += interval->day; + + // Widen dayfraction to 128 bits + span = int64_to_int128(dayfraction); + + // Scale up days to microseconds, forming a 128-bit product + int128_add_int64_mul_int64(&span, days, USECS_PER_DAY); + + return span; +} + +int +interval_cmp_internal(Interval *interval1, Interval *interval2) +{ + INT128 span1 = interval_cmp_value(interval1); + INT128 span2 = interval_cmp_value(interval2); + + return int128_compare(span1, span2); +} + +PG_FUNCTION_INFO_V1(gtype_age_today); +Datum gtype_age_today(PG_FUNCTION_ARGS) +{ + Timestamp ts; + gtype *arg1 = AG_GET_ARG_GTYPE_P(0); + gtype_value agtv_result, *agtv1; + Interval *i; + + if (is_gtype_null(arg1)) + PG_RETURN_NULL(); + agtv1 = get_ith_gtype_value_from_container(&arg1->root, 0); + + ts = TimestampGetDatum(GetCurrentTransactionStartTimestamp()); + ts = DatumGetTimestamp(DirectFunctionCall2(timestamp_trunc, cstring_to_text_with_len("day",3), ts)); + + if (agtv1->type != AGTV_TIMESTAMP) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age(gtype) only supports timestamps"))); + + i = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, + TimestampGetDatum(agtv1->val.int_value), + ts)); + + agtv_result.type = AGTV_INTERVAL; + agtv_result.val.interval.time = i->time; + agtv_result.val.interval.day = i->day; + agtv_result.val.interval.month = i->month; + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(gtype_age_w2args); +Datum +gtype_age_w2args(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + if (is_gtype_null(lhs) || is_gtype_null(rhs)) + PG_RETURN_NULL(); + + gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); + gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); + + Interval *i; + if (lhs_value->type == AGTV_TIMESTAMP && rhs_value->type == AGTV_TIMESTAMP) + i = DatumGetIntervalP(DirectFunctionCall2(timestamp_age, + TimestampGetDatum(lhs_value->val.int_value), + TimestampGetDatum(rhs_value->val.int_value))); + else if (lhs_value->type == AGTV_TIMESTAMPTZ && rhs_value->type == AGTV_TIMESTAMPTZ) + i = DatumGetIntervalP(DirectFunctionCall2(timestamptz_age, + TimestampTzGetDatum(lhs_value->val.int_value), + TimestampTzGetDatum(rhs_value->val.int_value))); + else + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for age(gtype, gtype)"), + errhint("You may have to use explicit casts."))); + + gtype_value gtv; + gtv.type = AGTV_INTERVAL; + gtv.val.interval.time = i->time; + gtv.val.interval.day = i->day; + gtv.val.interval.month = i->month; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_extract); +Datum +gtype_extract(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + if (is_gtype_null(lhs) || is_gtype_null(rhs)) + PG_RETURN_NULL(); + + gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); + gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); + + if (lhs_value->type != AGTV_STRING) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for EXTRACT arg 1 must be a string"), + errhint("You may have to use explicit casts."))); + + char *field = lhs_value->val.string.val; + + gtype_value gtv; + if (rhs_value->type == AGTV_TIMESTAMP) { + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_timestamp, + CStringGetTextDatum(field), + TimestampGetDatum(rhs_value->val.int_value))); + } else if (rhs_value->type == AGTV_TIMESTAMPTZ) { + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_timestamptz, + CStringGetTextDatum(field), + TimestampTzGetDatum(rhs_value->val.int_value))); + } else if (rhs_value->type == AGTV_DATE) { + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_date, + CStringGetTextDatum(field), + DateADTGetDatum(rhs_value->val.date))); + } else if (rhs_value->type == AGTV_TIME) { + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_time, + CStringGetTextDatum(field), + TimeADTGetDatum(rhs_value->val.int_value))); + } else if (rhs_value->type == AGTV_TIMETZ) { + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_timetz, + CStringGetTextDatum(field), + TimeTzADTPGetDatum(&rhs_value->val.timetz))); + } else if (rhs_value->type == AGTV_INTERVAL) { + gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_interval, + CStringGetTextDatum(field), + IntervalPGetDatum(&rhs_value->val.interval))); + } else { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for EXTRACT(gtype, gtype)"), + errhint("You may have to use explicit casts."))); + } + + gtv.type = AGTV_NUMERIC; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_date_part); +Datum +gtype_date_part(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + if (is_gtype_null(lhs) || is_gtype_null(rhs)) + PG_RETURN_NULL(); + + gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); + gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); + + if (lhs_value->type != AGTV_STRING) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for EXTRACT arg 1 must be a string"), + errhint("You may have to use explicit casts."))); + + char *field = lhs_value->val.string.val; + + gtype_value gtv; + if (rhs_value->type == AGTV_TIMESTAMP) { + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(timestamp_part, + CStringGetTextDatum(field), + TimestampGetDatum(rhs_value->val.int_value))); + } else if (rhs_value->type == AGTV_TIMESTAMPTZ) { + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(timestamptz_part, + CStringGetTextDatum(field), + TimestampTzGetDatum(rhs_value->val.int_value))); + } else if (rhs_value->type == AGTV_DATE) { + Datum ts = gtype_to_timestamptz_internal(rhs_value); + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(timestamptz_part, + CStringGetTextDatum(field), ts)); + } else if (rhs_value->type == AGTV_TIME) { + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(time_part, + CStringGetTextDatum(field), + TimeADTGetDatum(rhs_value->val.int_value))); + } else if (rhs_value->type == AGTV_TIMETZ) { + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(timetz_part, + CStringGetTextDatum(field), + TimeTzADTPGetDatum(&rhs_value->val.timetz))); + } else if (rhs_value->type == AGTV_INTERVAL) { + gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(interval_part, + CStringGetTextDatum(field), + IntervalPGetDatum(&rhs_value->val.interval))); + } else { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for date_part(gtype, gtype)"), + errhint("You may have to use explicit casts."))); + } + + gtv.type = AGTV_FLOAT; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_make_date); + +Datum gtype_make_date(PG_FUNCTION_ARGS) +{ + gtype_value agtv, *agtv_year, *agtv_month, *agtv_day; + gtype *agt_year = AG_GET_ARG_GTYPE_P(0); + gtype *agt_month = AG_GET_ARG_GTYPE_P(1); + gtype *agt_day = AG_GET_ARG_GTYPE_P(2); + + if (is_gtype_null(agt_year) || is_gtype_null(agt_month) || is_gtype_null(agt_day)) + PG_RETURN_NULL(); + + agtv_year = get_ith_gtype_value_from_container(&agt_year->root, 0); + agtv_month = get_ith_gtype_value_from_container(&agt_month->root, 0); + agtv_day = get_ith_gtype_value_from_container(&agt_day->root, 0); + + + if (agtv_year->type != AGTV_INTEGER || agtv_month->type != AGTV_INTEGER || agtv_day->type != AGTV_INTEGER) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("make_date(gtype, gtype, gtype) only supports integer arugments"))); + + + agtv.type = AGTV_DATE; + agtv.val.date = DatumGetDateADT(DirectFunctionCall3(make_date, + Int32GetDatum((int32)agtv_year->val.int_value), + Int32GetDatum((int32)agtv_month->val.int_value), + Int32GetDatum((int32)agtv_day->val.int_value))); + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); +} + +PG_FUNCTION_INFO_V1(gtype_make_time); + +Datum gtype_make_time(PG_FUNCTION_ARGS) +{ + gtype_value agtv, *agtv_hour, *agtv_minute, *agtv_second; + gtype *agt_hour = AG_GET_ARG_GTYPE_P(0); + gtype *agt_minute = AG_GET_ARG_GTYPE_P(1); + gtype *agt_second = AG_GET_ARG_GTYPE_P(2); + + if (is_gtype_null(agt_hour) || is_gtype_null(agt_minute) || is_gtype_null(agt_second)) + PG_RETURN_NULL(); + + agtv_hour = get_ith_gtype_value_from_container(&agt_hour->root, 0); + agtv_minute = get_ith_gtype_value_from_container(&agt_minute->root, 0); + agtv_second = get_ith_gtype_value_from_container(&agt_second->root, 0); + + + if (agtv_hour->type != AGTV_INTEGER || agtv_minute->type != AGTV_INTEGER || agtv_second->type != AGTV_FLOAT) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("make_time(gtype, gtype, gtype) only supports integer arugments"))); + + + agtv.type = AGTV_TIME; + agtv.val.int_value = DatumGetTimeADT(DirectFunctionCall3(make_time, + Int32GetDatum((int32)agtv_hour->val.int_value), + Int32GetDatum((int32)agtv_minute->val.int_value), + Float8GetDatum(agtv_second->val.float_value))); + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); +} + +PG_FUNCTION_INFO_V1(gtype_make_timestamp); + +Datum gtype_make_timestamp(PG_FUNCTION_ARGS) +{ + gtype_value agtv; + gtype_value *agtv_hour, *agtv_minute, *agtv_second; + gtype_value *agtv_year, *agtv_month, *agtv_day; + + gtype *agt_year = AG_GET_ARG_GTYPE_P(0); + gtype *agt_month = AG_GET_ARG_GTYPE_P(1); + gtype *agt_day = AG_GET_ARG_GTYPE_P(2); + gtype *agt_hour = AG_GET_ARG_GTYPE_P(3); + gtype *agt_minute = AG_GET_ARG_GTYPE_P(4); + gtype *agt_second = AG_GET_ARG_GTYPE_P(5); + + if (is_gtype_null(agt_year) || is_gtype_null(agt_month) || is_gtype_null(agt_day) || + is_gtype_null(agt_hour) || is_gtype_null(agt_minute) || is_gtype_null(agt_second)) + PG_RETURN_NULL(); + + agtv_year = get_ith_gtype_value_from_container(&agt_year->root, 0); + agtv_month = get_ith_gtype_value_from_container(&agt_month->root, 0); + agtv_day = get_ith_gtype_value_from_container(&agt_day->root, 0); + agtv_hour = get_ith_gtype_value_from_container(&agt_hour->root, 0); + agtv_minute = get_ith_gtype_value_from_container(&agt_minute->root, 0); + agtv_second = get_ith_gtype_value_from_container(&agt_second->root, 0); + + if (agtv_year->type != AGTV_INTEGER || agtv_month->type != AGTV_INTEGER || agtv_day->type != AGTV_INTEGER || + agtv_hour->type != AGTV_INTEGER || agtv_minute->type != AGTV_INTEGER || agtv_second->type != AGTV_FLOAT) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("make_time(gtype, gtype, gtype) only supports integer arugments"))); + + + agtv.type = AGTV_TIMESTAMP; + agtv.val.int_value = DatumGetTimestamp(DirectFunctionCall6(make_timestamp, + Int32GetDatum((int32)agtv_year->val.int_value), + Int32GetDatum((int32)agtv_month->val.int_value), + Int32GetDatum((int32)agtv_day->val.int_value), + Int32GetDatum((int32)agtv_hour->val.int_value), + Int32GetDatum((int32)agtv_minute->val.int_value), + Float8GetDatum(agtv_second->val.float_value))); + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); +} + +PG_FUNCTION_INFO_V1(gtype_make_timestamptz); + +Datum gtype_make_timestamptz(PG_FUNCTION_ARGS) +{ + gtype_value agtv; + gtype_value *agtv_hour, *agtv_minute, *agtv_second; + gtype_value *agtv_year, *agtv_month, *agtv_day; + + gtype *agt_year = AG_GET_ARG_GTYPE_P(0); + gtype *agt_month = AG_GET_ARG_GTYPE_P(1); + gtype *agt_day = AG_GET_ARG_GTYPE_P(2); + gtype *agt_hour = AG_GET_ARG_GTYPE_P(3); + gtype *agt_minute = AG_GET_ARG_GTYPE_P(4); + gtype *agt_second = AG_GET_ARG_GTYPE_P(5); + + if (is_gtype_null(agt_year) || is_gtype_null(agt_month) || is_gtype_null(agt_day) || + is_gtype_null(agt_hour) || is_gtype_null(agt_minute) || is_gtype_null(agt_second)) + PG_RETURN_NULL(); + + agtv_year = get_ith_gtype_value_from_container(&agt_year->root, 0); + agtv_month = get_ith_gtype_value_from_container(&agt_month->root, 0); + agtv_day = get_ith_gtype_value_from_container(&agt_day->root, 0); + agtv_hour = get_ith_gtype_value_from_container(&agt_hour->root, 0); + agtv_minute = get_ith_gtype_value_from_container(&agt_minute->root, 0); + agtv_second = get_ith_gtype_value_from_container(&agt_second->root, 0); + + if (agtv_year->type != AGTV_INTEGER || agtv_month->type != AGTV_INTEGER || agtv_day->type != AGTV_INTEGER || + agtv_hour->type != AGTV_INTEGER || agtv_minute->type != AGTV_INTEGER) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("make_timestamptz expected an integer arugment"))); + + + if (agtv_second->type != AGTV_FLOAT) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("make_timestamptz second arguement expects a float"))); + + agtv.type = AGTV_TIMESTAMPTZ; + agtv.val.int_value = DatumGetTimestampTz(DirectFunctionCall6(make_timestamp, + Int32GetDatum((int32)agtv_year->val.int_value), + Int32GetDatum((int32)agtv_month->val.int_value), + Int32GetDatum((int32)agtv_day->val.int_value), + Int32GetDatum((int32)agtv_hour->val.int_value), + Int32GetDatum((int32)agtv_minute->val.int_value), + Float8GetDatum(agtv_second->val.float_value))); + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); +} + +PG_FUNCTION_INFO_V1(gtype_make_timestamptz_wtimezone); + +Datum gtype_make_timestamptz_wtimezone(PG_FUNCTION_ARGS) +{ + gtype_value agtv; + gtype_value *agtv_hour, *agtv_minute, *agtv_second; + gtype_value *agtv_year, *agtv_month, *agtv_day; + gtype_value *agtv_timezone; + gtype *agt_year = AG_GET_ARG_GTYPE_P(0); + gtype *agt_month = AG_GET_ARG_GTYPE_P(1); + gtype *agt_day = AG_GET_ARG_GTYPE_P(2); + gtype *agt_hour = AG_GET_ARG_GTYPE_P(3); + gtype *agt_minute = AG_GET_ARG_GTYPE_P(4); + gtype *agt_second = AG_GET_ARG_GTYPE_P(5); + gtype *agt_timezone = AG_GET_ARG_GTYPE_P(6); + + if (is_gtype_null(agt_year) || is_gtype_null(agt_month) || is_gtype_null(agt_day) || + is_gtype_null(agt_hour) || is_gtype_null(agt_minute) || is_gtype_null(agt_second) || + is_gtype_null(agt_timezone)) + PG_RETURN_NULL(); + + agtv_year = get_ith_gtype_value_from_container(&agt_year->root, 0); + agtv_month = get_ith_gtype_value_from_container(&agt_month->root, 0); + agtv_day = get_ith_gtype_value_from_container(&agt_day->root, 0); + agtv_hour = get_ith_gtype_value_from_container(&agt_hour->root, 0); + agtv_minute = get_ith_gtype_value_from_container(&agt_minute->root, 0); + agtv_second = get_ith_gtype_value_from_container(&agt_second->root, 0); + agtv_timezone = get_ith_gtype_value_from_container(&agt_timezone->root, 0); + + if (agtv_year->type != AGTV_INTEGER || agtv_month->type != AGTV_INTEGER || agtv_day->type != AGTV_INTEGER || + agtv_hour->type != AGTV_INTEGER || agtv_minute->type != AGTV_INTEGER) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("make_timestamptz expected an integer arugment"))); + + + if (agtv_second->type != AGTV_FLOAT) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("make_timestamptz second arguement expects a float"))); + + if (agtv_timezone->type != AGTV_STRING) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("make_timestamptz timezone arguemnent must be a string"))); + + + agtv.type = AGTV_TIMESTAMPTZ; + agtv.val.int_value = DatumGetTimestampTz(DirectFunctionCall7(make_timestamptz_at_timezone, + Int32GetDatum((int32)agtv_year->val.int_value), + Int32GetDatum((int32)agtv_month->val.int_value), + Int32GetDatum((int32)agtv_day->val.int_value), + Int32GetDatum((int32)agtv_hour->val.int_value), + Int32GetDatum((int32)agtv_minute->val.int_value), + Float8GetDatum(agtv_second->val.float_value), + PointerGetDatum(cstring_to_text_with_len(agtv_timezone->val.string.val, + agtv_timezone->val.string.len)))); + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); +} + +PG_FUNCTION_INFO_V1(gtype_isfinite); +Datum gtype_isfinite(PG_FUNCTION_ARGS) +{ + gtype *agt = AG_GET_ARG_GTYPE_P(0); + gtype_value *agtv, agtv_result; + bool result = false; + + if (is_gtype_null(agt)) + PG_RETURN_NULL(); + + agtv = get_ith_gtype_value_from_container(&agt->root, 0); + + if (agtv->type == AGTV_DATE) + result = DatumGetBool(DirectFunctionCall1(date_finite, DateADTGetDatum(agtv->val.date))); + else if (agtv->type == AGTV_INTERVAL) + result = true; + else if(agtv->type == AGTV_TIMESTAMP || agtv->type == AGTV_TIMESTAMPTZ) + { + result = DatumGetBool(DirectFunctionCall1(timestamp_finite, TimestampGetDatum(agtv->val.int_value))); + } + else + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for isfinite(gtype)"), + errhint("You may have to use explicit casts."))); + + + agtv_result.type = AGTV_BOOL; + agtv_result.val.boolean = result; + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(gtype_justify_days); +Datum +gtype_justify_days(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_null(gt)) + PG_RETURN_NULL(); + + gtype_value *gt_value = get_ith_gtype_value_from_container(>->root, 0); + + gtype_value gtv; + if (gt_value->type != AGTV_INTERVAL) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for justify_days(gtype)"), + errhint("You may have to use explicit casts."))); + + Interval *i = DatumGetIntervalP(DirectFunctionCall1(interval_justify_days, + IntervalPGetDatum(>_value->val.interval))); + gtv.type = AGTV_INTERVAL; + gtv.val.interval.time = i->time; + gtv.val.interval.day = i->day; + gtv.val.interval.month = i->month; + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_justify_hours); +Datum +gtype_justify_hours(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_null(gt)) + PG_RETURN_NULL(); + + gtype_value *gt_value = get_ith_gtype_value_from_container(>->root, 0); + + gtype_value gtv; + if (gt_value->type != AGTV_INTERVAL) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for justify_days(gtype)"), + errhint("You may have to use explicit casts."))); + + Interval *i = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours, + IntervalPGetDatum(>_value->val.interval))); + gtv.type = AGTV_INTERVAL; + gtv.val.interval.time = i->time; + gtv.val.interval.day = i->day; + gtv.val.interval.month = i->month; + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_justify_interval); +Datum +gtype_justify_interval(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + if (is_gtype_null(gt)) + PG_RETURN_NULL(); + + gtype_value *gt_value = get_ith_gtype_value_from_container(>->root, 0); + + gtype_value gtv; + if (gt_value->type != AGTV_INTERVAL) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for justify_days(gtype)"), + errhint("You may have to use explicit casts."))); + + Interval *i = DatumGetIntervalP(DirectFunctionCall1(interval_justify_interval, + IntervalPGetDatum(>_value->val.interval))); + gtv.type = AGTV_INTERVAL; + gtv.val.interval.time = i->time; + gtv.val.interval.day = i->day; + gtv.val.interval.month = i->month; + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + +} + +PG_FUNCTION_INFO_V1(gtype_date_trunc); +Datum +gtype_date_trunc(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + if (is_gtype_null(lhs) || is_gtype_null(rhs)) + PG_RETURN_NULL(); + + gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); + gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); + + if (lhs_value->type != AGTV_STRING) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for EXTRACT arg 1 must be a string"), + errhint("You may have to use explicit casts."))); + + char *field = lhs_value->val.string.val; + + gtype_value gtv; + if (rhs_value->type == AGTV_TIMESTAMP) { + gtv.val.int_value = DatumGetTimestamp(DirectFunctionCall2(timestamp_trunc, + CStringGetTextDatum(field), + TimestampGetDatum(rhs_value->val.int_value))); + gtv.type = AGTV_TIMESTAMP; + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } else if (rhs_value->type == AGTV_TIMESTAMPTZ) { + gtv.val.int_value = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_trunc, + CStringGetTextDatum(field), + TimestampTzGetDatum(rhs_value->val.int_value))); + gtv.type = AGTV_TIMESTAMPTZ; + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } else if (rhs_value->type == AGTV_INTERVAL) { + Interval *i = DatumGetIntervalP(DirectFunctionCall2(interval_trunc, + CStringGetTextDatum(field), + IntervalPGetDatum(&rhs_value->val.interval))); + gtv.type = AGTV_INTERVAL; + gtv.val.interval.time = i->time; + gtv.val.interval.day = i->day; + gtv.val.interval.month = i->month; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } else { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for date_trun(gtype, gtype)"), + errhint("You may have to use explicit casts."))); + } + + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(gtype_date_trunc_zone); +Datum +gtype_date_trunc_zone(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + gtype *zone = AG_GET_ARG_GTYPE_P(2); + + if (is_gtype_null(lhs) || is_gtype_null(rhs)) + PG_RETURN_NULL(); + + gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); + gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); + gtype_value *zone_value = get_ith_gtype_value_from_container(&zone->root, 0); + + if (lhs_value->type != AGTV_STRING) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for date_trunc arg 1 must be a string"), + errhint("You may have to use explicit casts."))); + + char *field = lhs_value->val.string.val; + + if (lhs_value->type != AGTV_STRING) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for date_trunc arg 3 must be a string"), + errhint("You may have to use explicit casts."))); + + char *zone_str = zone_value->val.string.val; + + + gtype_value gtv; + if (rhs_value->type == AGTV_TIMESTAMPTZ) { + gtv.val.int_value = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_trunc_zone, + CStringGetTextDatum(field), + TimestampTzGetDatum(rhs_value->val.int_value), + CStringGetTextDatum(zone_str))); + gtv.type = AGTV_TIMESTAMPTZ; + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } else { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for date_trun(gtype, gtype, gtype)"), + errhint("You may have to use explicit casts."))); + } + + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(gtype_date_bin); +Datum +gtype_date_bin(PG_FUNCTION_ARGS) { + gtype *stride = AG_GET_ARG_GTYPE_P(0); + gtype *source = AG_GET_ARG_GTYPE_P(1); + gtype *origin = AG_GET_ARG_GTYPE_P(2); + + if (is_gtype_null(stride) || is_gtype_null(source) || is_gtype_null(origin)) + PG_RETURN_NULL(); + + gtype_value *stride_value = get_ith_gtype_value_from_container(&stride->root, 0); + gtype_value *source_value = get_ith_gtype_value_from_container(&source->root, 0); + gtype_value *origin_value = get_ith_gtype_value_from_container(&origin->root, 0); + + if ((source_value->type == AGTV_TIMESTAMP || source_value->type == AGTV_DATE) && + (origin_value->type == AGTV_TIMESTAMP || origin_value->type == AGTV_DATE)) { + Datum source_ts, origin_ts; + + if (source_value->type == AGTV_DATE) + source_ts = gtype_to_timestamp_internal(source_value); + else + source_ts = TimestampGetDatum(source_value->val.int_value); + + if (origin_value->type == AGTV_DATE) + origin_ts = gtype_to_timestamp_internal(origin_value); + else + origin_ts = TimestampGetDatum(origin_value->val.int_value); + + + Timestamp ts = DatumGetTimestamp(DirectFunctionCall3(timestamp_bin, + IntervalPGetDatum(&stride_value->val.interval), + source_ts, origin_ts)); + + gtype_value gtv; + gtv.type = AGTV_TIMESTAMP; + gtv.val.int_value = ts; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } else if (source_value->type == AGTV_TIMESTAMPTZ && origin_value->type == AGTV_TIMESTAMPTZ) { + TimestampTz ts = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_bin, + IntervalPGetDatum(&stride_value->val.interval), + TimestampTzGetDatum(source_value->val.int_value), + TimestampTzGetDatum(origin_value->val.int_value))); + gtype_value gtv; + gtv.type = AGTV_TIMESTAMPTZ; + gtv.val.int_value = ts; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } else + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid input for date_bin(gtype, gtype, gtype)"), + errhint("You may have to use explicit casts."))); + + PG_RETURN_NULL(); +} + +enum overlap_type { + ovt_timestamp, + ovt_time, + ovt_timetz +} overlap_type; + +PG_FUNCTION_INFO_V1(gtype_overlaps); + +Datum gtype_overlaps(PG_FUNCTION_ARGS) +{ + gtype *arg1 = AG_GET_ARG_GTYPE_P(0); + gtype *arg2 = AG_GET_ARG_GTYPE_P(1); + gtype *arg3 = AG_GET_ARG_GTYPE_P(2); + gtype *arg4 = AG_GET_ARG_GTYPE_P(3); + enum overlap_type type; + bool result; + gtype_value agtv_result; + + Datum d1; + if (GT_IS_DATE(arg1) || GT_IS_TIMESTAMP(arg1) || GT_IS_TIMESTAMPTZ(arg1)) { + d1 = GT_TO_TIMESTAMP_DATUM(arg1); + type = ovt_timestamp; + } else if (GT_IS_TIME(arg1)) { + d1 = GT_TO_TIME_DATUM(arg1); + type = ovt_time; + } else if (GT_IS_TIMETZ(arg1)) { + d1 = GT_TO_TIMETZ_DATUM(arg1); + type = ovt_timetz; + } else { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid argument type for overlap expression"))); + } + + Datum d2; + if (GT_IS_INTERVAL(arg2)) { + if (type == ovt_timestamp) + d2 = DirectFunctionCall2(timestamp_pl_interval, d1, GT_TO_INTERVAL_DATUM(arg2)); + else if (type == ovt_time) + d2 = DirectFunctionCall2(time_pl_interval, d1, GT_TO_INTERVAL_DATUM(arg2)); + else + d2 = DirectFunctionCall2(timetz_pl_interval, d1, GT_TO_INTERVAL_DATUM(arg2)); + } else { + if (type == ovt_timestamp) + d2 = GT_TO_TIMESTAMP_DATUM(arg2); + else if (type == ovt_time) + d2 = GT_TO_TIME_DATUM(arg2); + else + d2 = GT_TO_TIMETZ_DATUM(arg2); + } + + Datum d3; + if (type == ovt_timestamp) + d3 = GT_TO_TIMESTAMP_DATUM(arg3); + else if (type == ovt_time) + d3 = GT_TO_TIME_DATUM(arg3); + else + d3 = GT_TO_TIMETZ_DATUM(arg3); + + + Datum d4; + if (GT_IS_INTERVAL(arg4)) { + if (type == ovt_timestamp) + d4 = DirectFunctionCall2(timestamp_pl_interval, d3, GT_TO_INTERVAL_DATUM(arg4)); + else if (type == ovt_time) + d4 = DirectFunctionCall2(time_pl_interval, d3, GT_TO_INTERVAL_DATUM(arg4)); + else + d4 = DirectFunctionCall2(timetz_pl_interval, d3, GT_TO_INTERVAL_DATUM(arg4)); + } else { + if (type == ovt_timestamp) + d4 = GT_TO_TIMESTAMP_DATUM(arg4); + else if (type == ovt_time) + d4 = GT_TO_TIME_DATUM(arg4); + else + d4 = GT_TO_TIMETZ_DATUM(arg4); + } + + if (type == ovt_timestamp) + result = DatumGetBool(DirectFunctionCall4(overlaps_timestamp, d1, d2, d3, d4)); + else if (type == ovt_time) + result = DatumGetBool(DirectFunctionCall4(overlaps_time, d1, d2, d3, d4)); + else + result = DatumGetBool(DirectFunctionCall4(overlaps_timetz, d1, d2, d3, d4)); + + + agtv_result.type = AGTV_BOOL; + agtv_result.val.boolean = result; + + PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); +} + + + +PG_FUNCTION_INFO_V1(gtype_tsquery_or); +Datum gtype_tsquery_or(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + if (GT_IS_TSQUERY(lhs) || GT_IS_TSQUERY(rhs)) { + TSQuery tsquery = DatumGetPointer(DirectFunctionCall2(tsquery_or, + GT_ARG_TO_TSQUERY_DATUM(0), + GT_ARG_TO_TSQUERY_DATUM(1))); + + gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsquery = tsquery }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + } else { + TSVector tsvector = DatumGetPointer(DirectFunctionCall2(tsvector_concat, + GT_ARG_TO_TSVECTOR_DATUM(0), + GT_ARG_TO_TSVECTOR_DATUM(1))); + + gtype_value gtv = { .type = AGTV_TSVECTOR, .val.tsvector = tsvector }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + + } +} + +PG_FUNCTION_INFO_V1(gtype_tsquery_not); +Datum gtype_tsquery_not(PG_FUNCTION_ARGS) { + TSQuery tsquery = DatumGetPointer(DirectFunctionCall1(tsquery_not, GT_ARG_TO_TSQUERY_DATUM(0))); + + gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsquery = tsquery }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +/* + * Text Search Functions + */ +PG_FUNCTION_INFO_V1(gtype_ts_delete); +Datum gtype_ts_delete(PG_FUNCTION_ARGS) { + TSVector tsvector = DatumGetPointer(DirectFunctionCall2(tsvector_delete_str, + GT_ARG_TO_TSVECTOR_DATUM(0), + GT_ARG_TO_TEXT_DATUM(1))); + + gtype_value gtv = { .type = AGTV_TSVECTOR, .val.tsvector = tsvector }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_ts_strip); +Datum gtype_ts_strip(PG_FUNCTION_ARGS) { + TSVector tsvector = DatumGetPointer(DirectFunctionCall1(tsvector_strip, GT_ARG_TO_TSVECTOR_DATUM(0))); + + gtype_value gtv = { .type = AGTV_TSVECTOR, .val.tsvector = tsvector }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_tsquery_phrase); +Datum gtype_tsquery_phrase(PG_FUNCTION_ARGS) { + TSQuery tsquery = DatumGetPointer(DirectFunctionCall3(tsquery_phrase_distance, + GT_ARG_TO_TSQUERY_DATUM(0), + GT_ARG_TO_TSQUERY_DATUM(1), + Int32GetDatum(1))); + + gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_tsquery_phrase_distance); +Datum gtype_tsquery_phrase_distance(PG_FUNCTION_ARGS) { + TSQuery tsquery = DatumGetPointer(DirectFunctionCall3(tsquery_phrase_distance, + GT_ARG_TO_TSQUERY_DATUM(0), + GT_ARG_TO_TSQUERY_DATUM(1), + GT_ARG_TO_INT4_DATUM(2))); + + gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_plainto_tsquery); +Datum gtype_plainto_tsquery(PG_FUNCTION_ARGS) { + TSQuery tsquery = DatumGetPointer(DirectFunctionCall1(plainto_tsquery, GT_ARG_TO_TEXT_DATUM(0))); + + gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_phraseto_tsquery); +Datum gtype_phraseto_tsquery(PG_FUNCTION_ARGS) { + TSQuery tsquery = DatumGetPointer(DirectFunctionCall1(phraseto_tsquery, GT_ARG_TO_TEXT_DATUM(0))); + + gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_websearch_to_tsquery); +Datum gtype_websearch_to_tsquery(PG_FUNCTION_ARGS) { + TSQuery tsquery = DatumGetPointer(DirectFunctionCall1(websearch_to_tsquery, GT_ARG_TO_TEXT_DATUM(0))); + + gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_toupper); +// toUpper(gtype) +Datum gtype_toupper(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1Coll(upper, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_tolower); +// toLower(gtype) +Datum gtype_tolower(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1Coll(lower, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_rtrim); +// rtrim(gtype) +Datum gtype_rtrim(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1(rtrim1, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_ltrim); +//ltrim(gtype) +Datum gtype_ltrim(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1(ltrim1, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_trim); +// trim(gtype) +Datum gtype_trim(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1(btrim1, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_right); +// gtype(gtype, gtype) +Datum gtype_right(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall2(text_right, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_INT8_DATUM(1)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_left); +// left(gtype, gtype) +Datum gtype_left(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall2(text_left, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_INT8_DATUM(1)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_initcap); +// initCap(gtype) +Datum gtype_initcap(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1Coll(initcap, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_substring_w_len); +// substring(gtype, gtype, gtype) +Datum +gtype_substring_w_len(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall3(text_substr, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_INT4_DATUM(1), GT_ARG_TO_INT4_DATUM(2)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_substring); +// substring(gtype, gtype) +Datum gtype_substring(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall2(text_substr_no_len, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_INT4_DATUM(1)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_split); +// split(gtype, gtype) +Datum gtype_split(PG_FUNCTION_ARGS) +{ + Datum text_array = DirectFunctionCall2Coll(regexp_split_to_array, DEFAULT_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)); + + gtype_in_state in_state; + memset(&in_state, 0, sizeof(gtype_in_state)); + + array_to_gtype_internal(text_array, &in_state); + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(in_state.res)); +} + +PG_FUNCTION_INFO_V1(gtype_replace); +// replace(gtype, gtype, gtype) +Datum gtype_replace(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall3Coll(replace_text, DEFAULT_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1), GT_ARG_TO_TEXT_DATUM(2)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_like); +// gtype ~~~ gtype +Datum gtype_like(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(textlike, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); +} + +PG_FUNCTION_INFO_V1(gtype_not_like); +// gtype !~~ gtype +Datum gtype_not_like(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(textnlike, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); +} + +PG_FUNCTION_INFO_V1(gtype_ilike); +// gtype ~~* gtype +Datum gtype_ilike(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(texticlike, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); +} + +PG_FUNCTION_INFO_V1(gtype_not_ilike); +// gtype !~~* gtype +Datum gtype_not_ilike(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(texticnlike, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); +} + +PG_FUNCTION_INFO_V1(gserialized_contains_2d); + +PG_FUNCTION_INFO_V1(gtype_eq_tilde); +// gtype ~ gtype +Datum gtype_eq_tilde(PG_FUNCTION_ARGS) +{ + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + if (GT_IS_GEOMETRY(lhs) || GT_IS_GEOMETRY(rhs)) + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2(gserialized_contains_2d, GT_TO_GEOMETRY_DATUM(lhs), GT_TO_GEOMETRY_DATUM(rhs)))); + + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(textregexeq, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); +} + +PG_FUNCTION_INFO_V1(gtype_match_case_insensitive); +// gtype ~* gtype +Datum gtype_match_case_insensitive(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(texticregexeq, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); +} + +PG_FUNCTION_INFO_V1(gtype_regex_not_cs); +// gtype !~ gtype +Datum gtype_regex_not_cs(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(textregexne, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); +} + + +PG_FUNCTION_INFO_V1(gtype_regex_not_ci); +// gtype !~ gtype +Datum gtype_regex_not_ci(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(texticregexne, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); +} + +PG_FUNCTION_INFO_V1(gtype_sha224); +// sha224(gtype) +Datum gtype_sha224(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1Coll(sha224_bytea, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_BYTEA, .val.bytea = DatumGetPointer(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_sha256); +// sha256(gtype) +Datum gtype_sha256(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1Coll(sha256_bytea, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_BYTEA, .val.bytea = DatumGetPointer(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_sha384); +// sha384(gtype) +Datum gtype_sha384(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1Coll(sha384_bytea, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_BYTEA, .val.bytea = DatumGetPointer(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_sha512); +// sha512(gtype) +Datum gtype_sha512(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1Coll(sha512_bytea, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_BYTEA, .val.bytea = DatumGetPointer(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_md5); +// md5(gtype) +Datum gtype_md5(PG_FUNCTION_ARGS) +{ + Datum d = DirectFunctionCall1Coll(md5_text, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); + + gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + + +static Datum _make_range(PG_FUNCTION_ARGS, Datum d1, Datum d2, Oid rngtypid, enum gtype_value_type gt_type); + +/* + * Given a string representing the flags for the range type, return the flags + * represented as a char. + */ +static char +range_parse_flags(const char *flags_str) { + char flags = 0; + + if (flags_str[0] == '\0' || flags_str[1] == '\0' || flags_str[2] != '\0') + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); + + switch (flags_str[0]) { + case '[': + flags |= RANGE_LB_INC; + break; + case '(': + break; + default: + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); + } + + switch (flags_str[1]) { + case ']': + flags |= RANGE_UB_INC; + break; + case ')': + break; + default: + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); + } + + return flags; +} + +PG_FUNCTION_INFO_V1(gtype_intrange); +Datum gtype_intrange(PG_FUNCTION_ARGS) { + PG_RETURN_DATUM(_make_range(fcinfo, + PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_INT8_DATUM(0), + PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_INT8_DATUM(1), + INT8RANGEOID, + AGTV_RANGE_INT)); +} + +PG_FUNCTION_INFO_V1(gtype_numrange); +Datum gtype_numrange(PG_FUNCTION_ARGS) { + PG_RETURN_DATUM(_make_range(fcinfo, + PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_NUMERIC_DATUM(0), + PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_NUMERIC_DATUM(1), + NUMRANGEOID, + AGTV_RANGE_NUM)); +} + +PG_FUNCTION_INFO_V1(gtype_tsrange); +Datum gtype_tsrange(PG_FUNCTION_ARGS) { + PG_RETURN_DATUM(_make_range(fcinfo, + PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_TIMESTAMP_DATUM(0), + PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_TIMESTAMP_DATUM(1), + TSRANGEOID, + AGTV_RANGE_TS)); +} + +PG_FUNCTION_INFO_V1(gtype_tstzrange); +Datum gtype_tstzrange(PG_FUNCTION_ARGS) { + PG_RETURN_DATUM(_make_range(fcinfo, + PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_TIMESTAMPTZ_DATUM(0), + PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_TIMESTAMPTZ_DATUM(1), + TSTZRANGEOID, + AGTV_RANGE_TSTZ)); +} + +PG_FUNCTION_INFO_V1(gtype_daterange); +Datum gtype_daterange(PG_FUNCTION_ARGS) { + PG_RETURN_DATUM(_make_range(fcinfo, + PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_DATE_DATUM(0), + PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_DATE_DATUM(1), + DATERANGEOID, + AGTV_RANGE_DATE)); +} + +static Datum _make_range(PG_FUNCTION_ARGS, Datum d1, Datum d2, Oid rngtypid, enum gtype_value_type gt_type) { + RangeType *range; + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + char flags; + + typcache = range_get_typcache(fcinfo, rngtypid); + + if (PG_NARGS() == 3 && PG_ARGISNULL(2)) + ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range constructor flags argument must not be null"))); + + lower.val = d1; + lower.infinite = PG_ARGISNULL(0); + + upper.val = d2; + upper.infinite = PG_ARGISNULL(1); + + if (PG_NARGS() != 3) { + lower.inclusive = true; + lower.lower = true; + + upper.inclusive = false; + upper.lower = false; + } else { + char flags = range_parse_flags(GT_TO_STRING(AG_GET_ARG_GTYPE_P(2))); + lower.inclusive = (flags & RANGE_LB_INC) != 0; + lower.lower = true; + + upper.inclusive = (flags & RANGE_UB_INC) != 0; + upper.lower = false; + } + + gtype_value gtv = { .type=gt_type, .val.range=make_range(typcache, &lower, &upper, false)}; + + return GTYPE_P_GET_DATUM(gtype_value_to_gtype(>v)); +} + + + +Datum +PostGraphDirectFunctionCall2(PGFunction func, Oid collation, bool *is_null, Datum arg1, Datum arg2) +{ + LOCAL_FCINFO(fcinfo, 3); + fcinfo->flinfo = palloc0(sizeof(FmgrInfo)); + + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 3, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) { + *is_null = true; + return NULL; + } + + *is_null = false; + + return result; +} + + +PG_FUNCTION_INFO_V1(gtype_intersection_point); +Datum +gtype_intersection_point(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + Datum d; + bool is_null; + if (GT_IS_LSEG(lhs) || GT_IS_LSEG(rhs)) { + d = PostGraphDirectFunctionCall2(lseg_interpt, 100, &is_null, GT_TO_LSEG_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + + gtype_value gtv = { .type = AGTV_POINT, .val.box=DatumGetPointP(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + + } else if (GT_IS_LINE(lhs) || GT_IS_LINE(rhs)) { + d = PostGraphDirectFunctionCall2(line_interpt, 100, &is_null, GT_TO_LINE_DATUM(lhs), GT_TO_LINE_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + + gtype_value gtv = { .type = AGTV_POINT, .val.box=DatumGetPointP(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + + } else if (GT_IS_BOX(lhs) && GT_IS_BOX(rhs)) { + d = PostGraphDirectFunctionCall2(box_intersect, 100, &is_null, GT_TO_BOX_DATUM(lhs), GT_TO_BOX_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + + gtype_value gtv = { .type = AGTV_BOX, .val.box=DatumGetBoxP(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); + + } else { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid type for gtype # gtype"))); + } + + PG_RETURN_NULL(); +} + + +PG_FUNCTION_INFO_V1(gtype_closest_point); +Datum +gtype_closest_point(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + Datum d; + bool is_null = false; + if (GT_IS_POINT(lhs) && GT_IS_BOX(rhs)) { + d = PostGraphDirectFunctionCall2(close_pb, 100, &is_null, GT_TO_POINT_DATUM(lhs), GT_TO_BOX_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + } else if (GT_IS_POINT(lhs) && GT_IS_LSEG(rhs)) { + d = PostGraphDirectFunctionCall2(close_ps, 100, &is_null, GT_TO_POINT_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + } else if (GT_IS_POINT(lhs) && GT_IS_LINE(rhs)) { + d = PostGraphDirectFunctionCall2(close_pl, 100, &is_null, GT_TO_POINT_DATUM(lhs), GT_TO_LINE_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + } else if (GT_IS_LSEG(lhs) && GT_IS_BOX(rhs)) { + d = PostGraphDirectFunctionCall2(close_sb, 100, &is_null, GT_TO_LSEG_DATUM(lhs), GT_TO_BOX_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + } else if (GT_IS_LSEG(lhs) && GT_IS_LSEG(rhs)) { + d = PostGraphDirectFunctionCall2(close_lseg, 100, &is_null, GT_TO_LSEG_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + } else if (GT_IS_LINE(lhs) && GT_IS_LSEG(rhs)) { + d = PostGraphDirectFunctionCall2(close_ls, 100, &is_null, GT_TO_LINE_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + } else { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid type for gtype # gtype"))); + } + + gtype_value gtv = { .type = AGTV_POINT, .val.box=DatumGetPointP(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_center); +Datum +gtype_center(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + Datum d; + if (GT_IS_BOX(gt)) + d = DirectFunctionCall1(box_center, GT_TO_BOX_DATUM(gt)); + else if (GT_IS_LSEG(gt)) + d = DirectFunctionCall1(lseg_center, GT_TO_LSEG_DATUM(gt)); + else if (GT_IS_CIRCLE(gt)) + d = DirectFunctionCall1(circle_center, GT_TO_CIRCLE_DATUM(gt)); + else + d = DirectFunctionCall1(poly_center, GT_TO_POLYGON_DATUM(gt)); + + gtype_value gtv = { .type = AGTV_POINT, .val.box=DatumGetPointP(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_distance); +Datum +gtype_distance(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + Datum d; + if (GT_IS_LSEG(gt)) + d = DirectFunctionCall1(lseg_length, GT_TO_LSEG_DATUM(gt)); + else + d = DirectFunctionCall1(path_length, GT_TO_PATH_DATUM(gt)); + + gtype_value gtv = { .type = AGTV_FLOAT, .val.float_value=DatumGetFloat8(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + + +PG_FUNCTION_INFO_V1(gtype_vertical); +Datum +gtype_vertical(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + Datum d; + if (GT_IS_LSEG(gt)) + d = DirectFunctionCall1(lseg_vertical, GT_TO_LSEG_DATUM(gt)); + else + d = DirectFunctionCall1(line_vertical, GT_TO_LINE_DATUM(gt)); + + PG_RETURN_BOOL(DatumGetBool(d)); +} + +PG_FUNCTION_INFO_V1(gtype_horizontal); +Datum +gtype_horizontal(PG_FUNCTION_ARGS) { + gtype *gt = AG_GET_ARG_GTYPE_P(0); + + Datum d; + if (GT_IS_LSEG(gt)) + d = DirectFunctionCall1(lseg_horizontal, GT_TO_LSEG_DATUM(gt)); + else + d = DirectFunctionCall1(line_horizontal, GT_TO_LINE_DATUM(gt)); + + PG_RETURN_BOOL(DatumGetBool(d)); +} + + +PG_FUNCTION_INFO_V1(gtype_perp); +Datum +gtype_perp(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + Datum d; + if (GT_IS_LSEG(lhs) || GT_IS_LSEG(rhs)) + d = DirectFunctionCall2(lseg_perp, GT_TO_LSEG_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); + else + d = DirectFunctionCall2(line_perp, GT_TO_LINE_DATUM(lhs), GT_TO_LINE_DATUM(rhs)); + + PG_RETURN_BOOL(DatumGetBool(d)); +} + +PG_FUNCTION_INFO_V1(gtype_parallel); +Datum +gtype_parallel(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + Datum d; + if (GT_IS_LSEG(lhs) || GT_IS_LSEG(rhs)) + d = DirectFunctionCall2(lseg_parallel, GT_TO_LSEG_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); + else + d = DirectFunctionCall2(line_parallel, GT_TO_LINE_DATUM(lhs), GT_TO_LINE_DATUM(rhs)); + + PG_RETURN_BOOL(DatumGetBool(d)); +} + +PG_FUNCTION_INFO_V1(gtype_bound_box); +Datum +gtype_bound_box(PG_FUNCTION_ARGS) { + gtype *lhs = AG_GET_ARG_GTYPE_P(0); + gtype *rhs = AG_GET_ARG_GTYPE_P(1); + + bool is_null; + Datum d = PostGraphDirectFunctionCall2(boxes_bound_box, 100, &is_null, GT_TO_BOX_DATUM(lhs), GT_TO_BOX_DATUM(rhs)); + + if (is_null) + PG_RETURN_NULL(); + + gtype_value gtv = { .type = AGTV_BOX, .val.box=DatumGetBoxP(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_height); +Datum +gtype_height(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(box_height, GT_TO_BOX_DATUM(AG_GET_ARG_GTYPE_P(0))); + + gtype_value gtv = { .type = AGTV_FLOAT, .val.float_value=DatumGetFloat8(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + +PG_FUNCTION_INFO_V1(gtype_width); +Datum +gtype_width(PG_FUNCTION_ARGS) { + Datum d = DirectFunctionCall1(box_width, GT_TO_BOX_DATUM(AG_GET_ARG_GTYPE_P(0))); + + gtype_value gtv = { .type = AGTV_FLOAT, .val.float_value=DatumGetFloat8(d)}; + + AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); +} + diff --git a/src/backend/utils/adt/gtype_geometric.c b/src/backend/utils/adt/gtype_geometric.c deleted file mode 100644 index eeb8ada4..00000000 --- a/src/backend/utils/adt/gtype_geometric.c +++ /dev/null @@ -1,290 +0,0 @@ -/* - * PostGraph - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - -#include "postgraph.h" - -// Postgres -#include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "utils/geo_decls.h" - -// PostGraph -#include "utils/gtype.h" -#include "utils/gtype_typecasting.h" - - -Datum -PostGraphDirectFunctionCall2(PGFunction func, Oid collation, bool *is_null, Datum arg1, Datum arg2) -{ - LOCAL_FCINFO(fcinfo, 3); - fcinfo->flinfo = palloc0(sizeof(FmgrInfo)); - - Datum result; - - InitFunctionCallInfoData(*fcinfo, NULL, 3, collation, NULL, NULL); - - fcinfo->args[0].value = arg1; - fcinfo->args[0].isnull = false; - fcinfo->args[1].value = arg2; - fcinfo->args[1].isnull = false; - - result = (*func) (fcinfo); - - /* Check for null result, since caller is clearly not expecting one */ - if (fcinfo->isnull) { - *is_null = true; - return NULL; - } - - *is_null = false; - - return result; -} - - -PG_FUNCTION_INFO_V1(gtype_intersection_point); -Datum -gtype_intersection_point(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - Datum d; - bool is_null; - if (GT_IS_LSEG(lhs) || GT_IS_LSEG(rhs)) { - d = PostGraphDirectFunctionCall2(lseg_interpt, 100, &is_null, GT_TO_LSEG_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - - gtype_value gtv = { .type = AGTV_POINT, .val.box=DatumGetPointP(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - - } else if (GT_IS_LINE(lhs) || GT_IS_LINE(rhs)) { - d = PostGraphDirectFunctionCall2(line_interpt, 100, &is_null, GT_TO_LINE_DATUM(lhs), GT_TO_LINE_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - - gtype_value gtv = { .type = AGTV_POINT, .val.box=DatumGetPointP(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - - } else if (GT_IS_BOX(lhs) && GT_IS_BOX(rhs)) { - d = PostGraphDirectFunctionCall2(box_intersect, 100, &is_null, GT_TO_BOX_DATUM(lhs), GT_TO_BOX_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - - gtype_value gtv = { .type = AGTV_BOX, .val.box=DatumGetBoxP(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - - } else { - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("invalid type for gtype # gtype"))); - } - - PG_RETURN_NULL(); -} - - -PG_FUNCTION_INFO_V1(gtype_closest_point); -Datum -gtype_closest_point(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - Datum d; - bool is_null = false; - if (GT_IS_POINT(lhs) && GT_IS_BOX(rhs)) { - d = PostGraphDirectFunctionCall2(close_pb, 100, &is_null, GT_TO_POINT_DATUM(lhs), GT_TO_BOX_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - } else if (GT_IS_POINT(lhs) && GT_IS_LSEG(rhs)) { - d = PostGraphDirectFunctionCall2(close_ps, 100, &is_null, GT_TO_POINT_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - } else if (GT_IS_POINT(lhs) && GT_IS_LINE(rhs)) { - d = PostGraphDirectFunctionCall2(close_pl, 100, &is_null, GT_TO_POINT_DATUM(lhs), GT_TO_LINE_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - } else if (GT_IS_LSEG(lhs) && GT_IS_BOX(rhs)) { - d = PostGraphDirectFunctionCall2(close_sb, 100, &is_null, GT_TO_LSEG_DATUM(lhs), GT_TO_BOX_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - } else if (GT_IS_LSEG(lhs) && GT_IS_LSEG(rhs)) { - d = PostGraphDirectFunctionCall2(close_lseg, 100, &is_null, GT_TO_LSEG_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - } else if (GT_IS_LINE(lhs) && GT_IS_LSEG(rhs)) { - d = PostGraphDirectFunctionCall2(close_ls, 100, &is_null, GT_TO_LINE_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - } else { - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("invalid type for gtype # gtype"))); - } - - gtype_value gtv = { .type = AGTV_POINT, .val.box=DatumGetPointP(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_center); -Datum -gtype_center(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - Datum d; - if (GT_IS_BOX(gt)) - d = DirectFunctionCall1(box_center, GT_TO_BOX_DATUM(gt)); - else if (GT_IS_LSEG(gt)) - d = DirectFunctionCall1(lseg_center, GT_TO_LSEG_DATUM(gt)); - else if (GT_IS_CIRCLE(gt)) - d = DirectFunctionCall1(circle_center, GT_TO_CIRCLE_DATUM(gt)); - else - d = DirectFunctionCall1(poly_center, GT_TO_POLYGON_DATUM(gt)); - - gtype_value gtv = { .type = AGTV_POINT, .val.box=DatumGetPointP(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_distance); -Datum -gtype_distance(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - Datum d; - if (GT_IS_LSEG(gt)) - d = DirectFunctionCall1(lseg_length, GT_TO_LSEG_DATUM(gt)); - else - d = DirectFunctionCall1(path_length, GT_TO_PATH_DATUM(gt)); - - gtype_value gtv = { .type = AGTV_FLOAT, .val.float_value=DatumGetFloat8(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_vertical); -Datum -gtype_vertical(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - Datum d; - if (GT_IS_LSEG(gt)) - d = DirectFunctionCall1(lseg_vertical, GT_TO_LSEG_DATUM(gt)); - else - d = DirectFunctionCall1(line_vertical, GT_TO_LINE_DATUM(gt)); - - PG_RETURN_BOOL(DatumGetBool(d)); -} - -PG_FUNCTION_INFO_V1(gtype_horizontal); -Datum -gtype_horizontal(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - Datum d; - if (GT_IS_LSEG(gt)) - d = DirectFunctionCall1(lseg_horizontal, GT_TO_LSEG_DATUM(gt)); - else - d = DirectFunctionCall1(line_horizontal, GT_TO_LINE_DATUM(gt)); - - PG_RETURN_BOOL(DatumGetBool(d)); -} - - -PG_FUNCTION_INFO_V1(gtype_perp); -Datum -gtype_perp(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - Datum d; - if (GT_IS_LSEG(lhs) || GT_IS_LSEG(rhs)) - d = DirectFunctionCall2(lseg_perp, GT_TO_LSEG_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); - else - d = DirectFunctionCall2(line_perp, GT_TO_LINE_DATUM(lhs), GT_TO_LINE_DATUM(rhs)); - - PG_RETURN_BOOL(DatumGetBool(d)); -} - -PG_FUNCTION_INFO_V1(gtype_parallel); -Datum -gtype_parallel(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - Datum d; - if (GT_IS_LSEG(lhs) || GT_IS_LSEG(rhs)) - d = DirectFunctionCall2(lseg_parallel, GT_TO_LSEG_DATUM(lhs), GT_TO_LSEG_DATUM(rhs)); - else - d = DirectFunctionCall2(line_parallel, GT_TO_LINE_DATUM(lhs), GT_TO_LINE_DATUM(rhs)); - - PG_RETURN_BOOL(DatumGetBool(d)); -} - -PG_FUNCTION_INFO_V1(gtype_bound_box); -Datum -gtype_bound_box(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - bool is_null; - Datum d = PostGraphDirectFunctionCall2(boxes_bound_box, 100, &is_null, GT_TO_BOX_DATUM(lhs), GT_TO_BOX_DATUM(rhs)); - - if (is_null) - PG_RETURN_NULL(); - - gtype_value gtv = { .type = AGTV_BOX, .val.box=DatumGetBoxP(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_height); -Datum -gtype_height(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(box_height, GT_TO_BOX_DATUM(AG_GET_ARG_GTYPE_P(0))); - - gtype_value gtv = { .type = AGTV_FLOAT, .val.float_value=DatumGetFloat8(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_width); -Datum -gtype_width(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(box_width, GT_TO_BOX_DATUM(AG_GET_ARG_GTYPE_P(0))); - - gtype_value gtv = { .type = AGTV_FLOAT, .val.float_value=DatumGetFloat8(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - diff --git a/src/backend/utils/adt/gtype_network.c b/src/backend/utils/adt/gtype_network.c deleted file mode 100644 index dd7dafc2..00000000 --- a/src/backend/utils/adt/gtype_network.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * PostGraph - * Copyright (C) 2023 by PostGraph - * - * 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. - * - * For PostgreSQL Database Management System: - * (formerly known as Postgres, then as Postgres95) - * - * Portions Copyright (c) 2020-2023, Apache Software Foundation - * Portions Copyright (c) 1996-2010, Bitnine Global - * Portions Copyright (c) 1996-2010, The PostgreSQL Global Development Group - * Portions Copyright (c) 1994, The Regents of the University of California - */ - -#include "postgres.h" - -#include "fmgr.h" -#include "utils/builtins.h" -#include "utils/fmgrprotos.h" -#include "utils/inet.h" - -#include "utils/gtype.h" -#include "utils/gtype_typecasting.h" - -PG_FUNCTION_INFO_V1(gtype_abbrev); -Datum -gtype_abbrev(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - Datum d; - if (GT_IS_CIDR(gt)) - d = DirectFunctionCall1(cidr_abbrev, GT_TO_CIDR_DATUM(gt)); - else - d = DirectFunctionCall1(inet_abbrev, GT_TO_INET_DATUM(gt)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_broadcast); -Datum -gtype_broadcast(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(network_broadcast, GT_ARG_TO_INET_DATUM(0)); - - gtype_value gtv; - gtv.type = AGTV_INET; - memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_family); -Datum -gtype_family(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(network_family, GT_ARG_TO_INET_DATUM(0)); - - gtype_value gtv = { .type = AGTV_INTEGER, .val.int_value = DatumGetInt32(d) }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - -} - -PG_FUNCTION_INFO_V1(gtype_host); -Datum -gtype_host(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(network_host, GT_ARG_TO_INET_DATUM(0)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_hostmask); -Datum -gtype_hostmask(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(network_hostmask, GT_ARG_TO_INET_DATUM(0)); - - gtype_value gtv; - gtv.type = AGTV_INET; - memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_inet_merge); -Datum -gtype_inet_merge(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall2(inet_merge, GT_ARG_TO_INET_DATUM(0), GT_ARG_TO_INET_DATUM(1)); - - gtype_value gtv; - gtv.type = AGTV_CIDR; - memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_inet_same_family); -Datum -gtype_inet_same_family(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall2(inet_same_family, GT_ARG_TO_INET_DATUM(0), GT_ARG_TO_INET_DATUM(1)); - - gtype_value gtv = { .type = AGTV_BOOL, .val.int_value = DatumGetBool(d) }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_masklen); -Datum -gtype_masklen(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(network_masklen, GT_ARG_TO_INET_DATUM(0)); - - gtype_value gtv = { .type = AGTV_INTEGER, .val.int_value = DatumGetInt32(d) }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_netmask); -Datum -gtype_netmask(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(network_netmask, GT_ARG_TO_INET_DATUM(0)); - - gtype_value gtv; - gtv.type = AGTV_INET; - memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_network); -Datum -gtype_network(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(network_network, GT_ARG_TO_INET_DATUM(0)); - - gtype_value gtv; - gtv.type = AGTV_CIDR; - memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_set_masklen); -Datum -gtype_set_masklen(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - gtype_value gtv; - Datum d; - if (GT_IS_CIDR(gt)) { - d = DirectFunctionCall2(cidr_set_masklen, GT_TO_CIDR_DATUM(gt), GT_ARG_TO_INT4_DATUM(1)); - gtv.type = AGTV_CIDR; - } else { - d = DirectFunctionCall2(inet_set_masklen, GT_TO_INET_DATUM(gt), GT_ARG_TO_INT4_DATUM(1)); - gtv.type = AGTV_INET; - } - - memcpy(>v.val.inet, DatumGetInetPP(d), sizeof(char) * 22); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_trunc); -Datum -gtype_trunc(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - gtype_value gtv; - Datum d; - if (GT_IS_MACADDR(gt)) { - d = DirectFunctionCall1(macaddr_trunc, GT_TO_MAC_DATUM(gt)); - gtv.type = AGTV_MAC; - } else { - d = DirectFunctionCall1(macaddr8_trunc, GT_TO_MAC8_DATUM(gt)); - gtv.type = AGTV_MAC8; - } - - memcpy(>v.val.inet, DatumGetMacaddrP(d), sizeof(char) * 6); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_macaddr8_set7bit); -Datum -gtype_macaddr8_set7bit(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall1(macaddr8_set7bit, GT_ARG_TO_MAC8_DATUM(0)); - - gtype_value gtv; - gtv.type = AGTV_MAC8; - memcpy(>v.val.inet, DatumGetMacaddr8P(d), sizeof(char) * 8); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - diff --git a/src/backend/utils/adt/gtype_numbers.c b/src/backend/utils/adt/gtype_numbers.c deleted file mode 100644 index 9656964f..00000000 --- a/src/backend/utils/adt/gtype_numbers.c +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include "postgres.h" - -#include - -#include "access/genam.h" -#include "access/htup_details.h" -#include "catalog/namespace.h" -#include "catalog/pg_collation.h" -#include "catalog/pg_operator.h" -#include "catalog/pg_type.h" -#include "catalog/pg_aggregate_d.h" -#include "catalog/pg_collation_d.h" -#include "catalog/pg_operator_d.h" -#include "executor/nodeAgg.h" -#include "funcapi.h" -#include "libpq/pqformat.h" -#include "miscadmin.h" -#include "parser/parse_coerce.h" -#include "portability/instr_time.h" -#include "nodes/pg_list.h" -#include "utils/builtins.h" -#include "utils/float.h" -#include "utils/fmgroids.h" -#include "utils/int8.h" -#include "utils/lsyscache.h" -#include "utils/rel.h" -#include "utils/snapmgr.h" -#include "utils/timestamp.h" -#include "utils/typcache.h" - -#include "utils/gtype.h" -#include "utils/edge.h" -#include "utils/variable_edge.h" -#include "utils/vector.h" -#include "utils/vertex.h" -#include "utils/gtype_parser.h" -#include "utils/gtype_typecasting.h" -#include "catalog/ag_graph.h" -#include "catalog/ag_label.h" -#include "utils/graphid.h" -#include "utils/numeric.h" - - - -#define GTYPETRIGFUNC( name) \ -PG_FUNCTION_INFO_V1(gtype_##name); \ -Datum \ -gtype_##name(PG_FUNCTION_ARGS) \ -{ \ - gtype *gt= AG_GET_ARG_GTYPE_P(0); \ - gtype_value gtv = { \ - .type =AGTV_FLOAT, \ - .val.float_value = DatumGetFloat8(DirectFunctionCall1(d##name, GT_ARG_TO_FLOAT8_DATUM(0))) \ - }; \ - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); \ -} \ -/* keep compiler quiet - no extra ; */ \ -extern int no_such_variable - -GTYPETRIGFUNC(sin); -GTYPETRIGFUNC(cos); -GTYPETRIGFUNC(tan); -GTYPETRIGFUNC(sinh); -GTYPETRIGFUNC(cosh); -GTYPETRIGFUNC(tanh); -GTYPETRIGFUNC(cot); -GTYPETRIGFUNC(asin); -GTYPETRIGFUNC(acos); -GTYPETRIGFUNC(atan); -GTYPETRIGFUNC(asinh); -GTYPETRIGFUNC(acosh); -GTYPETRIGFUNC(atanh); - -GTYPETRIGFUNC(sind); -GTYPETRIGFUNC(cosd); -GTYPETRIGFUNC(tand); -GTYPETRIGFUNC(cotd); -GTYPETRIGFUNC(asind); -GTYPETRIGFUNC(acosd); -GTYPETRIGFUNC(atand); - - -PG_FUNCTION_INFO_V1(gtype_atan2); -Datum -gtype_atan2(PG_FUNCTION_ARGS) { - Datum x = convert_to_scalar(gtype_to_float8_internal, AG_GET_ARG_GTYPE_P(0), "float"); - Datum y = convert_to_scalar(gtype_to_float8_internal, AG_GET_ARG_GTYPE_P(1), "float"); - - gtype_value gtv_result; - gtv_result.type = AGTV_FLOAT; - gtv_result.val.float_value = DatumGetFloat8(DirectFunctionCall2(datan2, y, x)); - - PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); -} - -PG_FUNCTION_INFO_V1(gtype_degrees); - -Datum gtype_degrees(PG_FUNCTION_ARGS) -{ - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - gtype_value gtv_result; - gtv_result.type = AGTV_FLOAT; - gtv_result.val.float_value = - DatumGetFloat8(DirectFunctionCall1(degrees, convert_to_scalar(gtype_to_float8_internal, gt, "float"))); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v_result)); -} - -PG_FUNCTION_INFO_V1(gtype_radians); - -Datum gtype_radians(PG_FUNCTION_ARGS) -{ - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - gtype_value gtv_result; - gtv_result.type = AGTV_FLOAT; - gtv_result.val.float_value = - DatumGetFloat8(DirectFunctionCall1(radians, convert_to_scalar(gtype_to_float8_internal, gt, "float"))); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v_result)); -} - -PG_FUNCTION_INFO_V1(gtype_gcd); -Datum -gtype_gcd(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - if (GTYPE_IS_FLOAT(lhs) || GTE_IS_NUMERIC(lhs->root.children[0]) || GTYPE_IS_FLOAT(rhs) || GTE_IS_NUMERIC(rhs->root.children[0])) { - Datum x = convert_to_scalar(gtype_to_numeric_internal, lhs, "numeric"); - Datum y = convert_to_scalar(gtype_to_numeric_internal, rhs, "numeric"); - - gtype_value gtv_result; - gtv_result.type = AGTV_NUMERIC; - gtv_result.val.numeric = DatumGetNumeric(DirectFunctionCall2(numeric_gcd, y, x)); - - PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); - } else { - Datum x = convert_to_scalar(gtype_to_int8_internal, lhs, "int8"); - Datum y = convert_to_scalar(gtype_to_int8_internal, rhs, "int8"); - - gtype_value gtv_result; - gtv_result.type = AGTV_INTEGER; - gtv_result.val.int_value = DatumGetInt64(DirectFunctionCall2(int8gcd, y, x)); - - PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); - } -} - -PG_FUNCTION_INFO_V1(gtype_lcm); -Datum -gtype_lcm(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - if (GTYPE_IS_FLOAT(lhs) || GTE_IS_NUMERIC(lhs->root.children[0]) || GTYPE_IS_FLOAT(rhs) || GTE_IS_NUMERIC(rhs->root.children[0])) { - Datum x = convert_to_scalar(gtype_to_numeric_internal, lhs, "numeric"); - Datum y = convert_to_scalar(gtype_to_numeric_internal, rhs, "numeric"); - - gtype_value gtv_result; - gtv_result.type = AGTV_NUMERIC; - gtv_result.val.numeric = DatumGetNumeric(DirectFunctionCall2(numeric_lcm, y, x)); - - PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); - } else { - Datum x = convert_to_scalar(gtype_to_int8_internal, lhs, "int8"); - Datum y = convert_to_scalar(gtype_to_int8_internal, rhs, "int8"); - - gtype_value gtv_result; - gtv_result.type = AGTV_INTEGER; - gtv_result.val.int_value = DatumGetInt64(DirectFunctionCall2(int8lcm, y, x)); - - PG_RETURN_POINTER(gtype_value_to_gtype(>v_result)); - } -} - -PG_FUNCTION_INFO_V1(gtype_round_w_precision); - -Datum gtype_round_w_precision(PG_FUNCTION_ARGS) -{ - gtype *gt = AG_GET_ARG_GTYPE_P(0); - gtype *prec = AG_GET_ARG_GTYPE_P(1); - - Datum x = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - Datum y = convert_to_scalar(gtype_to_int4_internal, prec, "int4"); - - gtype_value gtv; - gtv.type = AGTV_NUMERIC; - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(numeric_round, x, y)); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_round); - -Datum gtype_round(PG_FUNCTION_ARGS) -{ - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_numeric(gt)) { - Datum x = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - - gtype_value gtv; - gtv.type = AGTV_NUMERIC; - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(numeric_round, x, Int32GetDatum(0))); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } else { - Datum x = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - - gtype_value gtv; - gtv.type = AGTV_FLOAT; - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall1(dround, x)); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } -} - -PG_FUNCTION_INFO_V1(gtype_ceil); - -Datum gtype_ceil(PG_FUNCTION_ARGS) -{ - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_numeric(gt)) { - Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - - Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_ceil, arg)); - - gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } else { - Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - - float8 result = DatumGetFloat8(DirectFunctionCall1(dceil, arg)); - - gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } -} - -PG_FUNCTION_INFO_V1(gtype_floor); - -Datum gtype_floor(PG_FUNCTION_ARGS) -{ - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_numeric(gt)) { - Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - - Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_floor, arg)); - - gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } else { - Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - - float8 result = DatumGetFloat8(DirectFunctionCall1(dfloor, arg)); - - gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } -} - -PG_FUNCTION_INFO_V1(gtype_abs); - -Datum gtype_abs(PG_FUNCTION_ARGS) -{ - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - Datum x; - gtype_value gtv; - if (is_gtype_numeric(gt)) { - gtv.type = AGTV_NUMERIC; - x = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_abs, x)); - } - else if (is_gtype_float(gt)) { - gtv.type = AGTV_FLOAT; - x = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall1(float8abs, x)); - } - else { - gtv.type = AGTV_INTEGER; - x = convert_to_scalar(gtype_to_int8_internal, gt, "int"); - gtv.val.int_value = DatumGetInt64(DirectFunctionCall1(int8abs, x)); - } - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_sign); -Datum gtype_sign(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - Datum x; - gtype_value gtv; - if (is_gtype_numeric(gt)) { - gtv.type = AGTV_NUMERIC; - x = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_sign, x)); - } else { - gtv.type = AGTV_FLOAT; - x = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall1(dsign, x)); - } - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_log); -Datum -gtype_log(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_numeric(gt)) { - Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - - Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_ln, arg)); - - gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } else { - Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - - float8 result = DatumGetFloat8(DirectFunctionCall1(dlog1, arg)); - - gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } -} - -PG_FUNCTION_INFO_V1(gtype_log10); -Datum -gtype_log10(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_numeric(gt)) { - Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - - Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_log, arg)); - - gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } else { - Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - - float8 result = DatumGetFloat8(DirectFunctionCall1(dlog10, arg)); - - gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } -} - -PG_FUNCTION_INFO_V1(gtype_e); -Datum -gtype_e(PG_FUNCTION_ARGS) { - gtype_value agtv_result = { - .type = AGTV_FLOAT, - .val.float_value = DatumGetFloat8(DirectFunctionCall1(dexp, Float8GetDatum(1))) - }; - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); -} - -PG_FUNCTION_INFO_V1(gtype_pi); -Datum -gtype_pi(PG_FUNCTION_ARGS) { - gtype_value agtv_result = { - .type = AGTV_FLOAT, - .val.float_value = M_PI - }; - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); -} - -PG_FUNCTION_INFO_V1(gtype_rand); -Datum -gtype_rand(PG_FUNCTION_ARGS) { - gtype_value agtv_result = { - .type = AGTV_FLOAT, - .val.float_value = DatumGetFloat8(DirectFunctionCall1(random, Float8GetDatum(1))) - }; - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); -} - -PG_FUNCTION_INFO_V1(gtype_exp); -Datum -gtype_exp(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_numeric(gt)) { - Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - - Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_exp, arg)); - - gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } else { - Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - - float8 result = DatumGetFloat8(DirectFunctionCall1(dexp, arg)); - - gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } - -} - -PG_FUNCTION_INFO_V1(gtype_sqrt); -Datum -gtype_sqrt(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_numeric(gt)) { - Datum arg = convert_to_scalar(gtype_to_numeric_internal, gt, "numeric"); - - Numeric result = DatumGetNumeric(DirectFunctionCall1(numeric_sqrt, arg)); - - gtype_value agtv = { .type = AGTV_NUMERIC, .val.numeric = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } else { - Datum arg = convert_to_scalar(gtype_to_float8_internal, gt, "float"); - - float8 result = DatumGetFloat8(DirectFunctionCall1(dsqrt, arg)); - - gtype_value agtv = { .type = AGTV_FLOAT, .val.float_value = result }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(&agtv)); - } -} - -PG_FUNCTION_INFO_V1(gtype_cbrt); -Datum -gtype_cbrt(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - gtype_value gtv_result; - gtv_result.type = AGTV_FLOAT; - gtv_result.val.float_value = - DatumGetFloat8(DirectFunctionCall1(dcbrt, convert_to_scalar(gtype_to_float8_internal, gt, "float"))); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v_result)); -} - -PG_FUNCTION_INFO_V1(gtype_factorial); -Datum -gtype_factorial(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - gtype_value gtv_result; - gtv_result.type = AGTV_NUMERIC; - gtv_result.val.numeric = - DatumGetNumeric(DirectFunctionCall1(numeric_fac, convert_to_scalar(gtype_to_int8_internal, gt, "int"))); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v_result)); -} - diff --git a/src/backend/utils/adt/gtype_range.c b/src/backend/utils/adt/gtype_range.c deleted file mode 100644 index 997ab45b..00000000 --- a/src/backend/utils/adt/gtype_range.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * PostGraph - * Copyright (C) 2023 by PostGraph - * - * 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. - */ - -#include "postgraph.h" - -// Postgres -#include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "utils/rangetypes.h" - -// PostGraph -#include "utils/gtype.h" -#include "utils/gtype_typecasting.h" - -static Datum _make_range(PG_FUNCTION_ARGS, Datum d1, Datum d2, Oid rngtypid, enum gtype_value_type gt_type); - -/* - * Given a string representing the flags for the range type, return the flags - * represented as a char. - */ -static char -range_parse_flags(const char *flags_str) { - char flags = 0; - - if (flags_str[0] == '\0' || flags_str[1] == '\0' || flags_str[2] != '\0') - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("invalid range bound flags"), - errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); - - switch (flags_str[0]) { - case '[': - flags |= RANGE_LB_INC; - break; - case '(': - break; - default: - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("invalid range bound flags"), - errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); - } - - switch (flags_str[1]) { - case ']': - flags |= RANGE_UB_INC; - break; - case ')': - break; - default: - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("invalid range bound flags"), - errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); - } - - return flags; -} - -PG_FUNCTION_INFO_V1(gtype_intrange); -Datum gtype_intrange(PG_FUNCTION_ARGS) { - PG_RETURN_DATUM(_make_range(fcinfo, - PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_INT8_DATUM(0), - PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_INT8_DATUM(1), - INT8RANGEOID, - AGTV_RANGE_INT)); -} - -PG_FUNCTION_INFO_V1(gtype_numrange); -Datum gtype_numrange(PG_FUNCTION_ARGS) { - PG_RETURN_DATUM(_make_range(fcinfo, - PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_NUMERIC_DATUM(0), - PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_NUMERIC_DATUM(1), - NUMRANGEOID, - AGTV_RANGE_NUM)); -} - -PG_FUNCTION_INFO_V1(gtype_tsrange); -Datum gtype_tsrange(PG_FUNCTION_ARGS) { - PG_RETURN_DATUM(_make_range(fcinfo, - PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_TIMESTAMP_DATUM(0), - PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_TIMESTAMP_DATUM(1), - TSRANGEOID, - AGTV_RANGE_TS)); -} - -PG_FUNCTION_INFO_V1(gtype_tstzrange); -Datum gtype_tstzrange(PG_FUNCTION_ARGS) { - PG_RETURN_DATUM(_make_range(fcinfo, - PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_TIMESTAMPTZ_DATUM(0), - PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_TIMESTAMPTZ_DATUM(1), - TSTZRANGEOID, - AGTV_RANGE_TSTZ)); -} - -PG_FUNCTION_INFO_V1(gtype_daterange); -Datum gtype_daterange(PG_FUNCTION_ARGS) { - PG_RETURN_DATUM(_make_range(fcinfo, - PG_ARGISNULL(0) ? (Datum) 0 : GT_ARG_TO_DATE_DATUM(0), - PG_ARGISNULL(1) ? (Datum) 0 : GT_ARG_TO_DATE_DATUM(1), - DATERANGEOID, - AGTV_RANGE_DATE)); -} - -static Datum _make_range(PG_FUNCTION_ARGS, Datum d1, Datum d2, Oid rngtypid, enum gtype_value_type gt_type) { - RangeType *range; - TypeCacheEntry *typcache; - RangeBound lower; - RangeBound upper; - char flags; - - typcache = range_get_typcache(fcinfo, rngtypid); - - if (PG_NARGS() == 3 && PG_ARGISNULL(2)) - ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("range constructor flags argument must not be null"))); - - lower.val = d1; - lower.infinite = PG_ARGISNULL(0); - - upper.val = d2; - upper.infinite = PG_ARGISNULL(1); - - if (PG_NARGS() != 3) { - lower.inclusive = true; - lower.lower = true; - - upper.inclusive = false; - upper.lower = false; - } else { - char flags = range_parse_flags(GT_TO_STRING(AG_GET_ARG_GTYPE_P(2))); - lower.inclusive = (flags & RANGE_LB_INC) != 0; - lower.lower = true; - - upper.inclusive = (flags & RANGE_UB_INC) != 0; - upper.lower = false; - } - - gtype_value gtv = { .type=gt_type, .val.range=make_range(typcache, &lower, &upper, false)}; - - return GTYPE_P_GET_DATUM(gtype_value_to_gtype(>v)); -} diff --git a/src/backend/utils/adt/gtype_string.c b/src/backend/utils/adt/gtype_string.c deleted file mode 100644 index bee116c8..00000000 --- a/src/backend/utils/adt/gtype_string.c +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include "postgraph.h" - -#include "catalog/pg_collation_d.h" -#include "utils/builtins.h" - -#include "utils/gtype.h" -#include "utils/gtype_typecasting.h" - - -PG_FUNCTION_INFO_V1(gtype_toupper); -// toUpper(gtype) -Datum gtype_toupper(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1Coll(upper, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_tolower); -// toLower(gtype) -Datum gtype_tolower(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1Coll(lower, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_rtrim); -// rtrim(gtype) -Datum gtype_rtrim(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1(rtrim1, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_ltrim); -//ltrim(gtype) -Datum gtype_ltrim(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1(ltrim1, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_trim); -// trim(gtype) -Datum gtype_trim(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1(btrim1, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_right); -// gtype(gtype, gtype) -Datum gtype_right(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall2(text_right, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_INT8_DATUM(1)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_left); -// left(gtype, gtype) -Datum gtype_left(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall2(text_left, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_INT8_DATUM(1)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_initcap); -// initCap(gtype) -Datum gtype_initcap(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1Coll(initcap, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(d) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_substring_w_len); -// substring(gtype, gtype, gtype) -Datum -gtype_substring_w_len(PG_FUNCTION_ARGS) { - Datum d = DirectFunctionCall3(text_substr, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_INT4_DATUM(1), GT_ARG_TO_INT4_DATUM(2)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_substring); -// substring(gtype, gtype) -Datum gtype_substring(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall2(text_substr_no_len, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_INT4_DATUM(1)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_split); -// split(gtype, gtype) -Datum gtype_split(PG_FUNCTION_ARGS) -{ - Datum text_array = DirectFunctionCall2Coll(regexp_split_to_array, DEFAULT_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)); - - gtype_in_state in_state; - memset(&in_state, 0, sizeof(gtype_in_state)); - - array_to_gtype_internal(text_array, &in_state); - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(in_state.res)); -} - -PG_FUNCTION_INFO_V1(gtype_replace); -// replace(gtype, gtype, gtype) -Datum gtype_replace(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall3Coll(replace_text, DEFAULT_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1), GT_ARG_TO_TEXT_DATUM(2)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_like); -// gtype ~~~ gtype -Datum gtype_like(PG_FUNCTION_ARGS) -{ - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(textlike, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); -} - -PG_FUNCTION_INFO_V1(gtype_not_like); -// gtype !~~ gtype -Datum gtype_not_like(PG_FUNCTION_ARGS) -{ - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(textnlike, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); -} - -PG_FUNCTION_INFO_V1(gtype_ilike); -// gtype ~~* gtype -Datum gtype_ilike(PG_FUNCTION_ARGS) -{ - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(texticlike, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); -} - -PG_FUNCTION_INFO_V1(gtype_not_ilike); -// gtype !~~* gtype -Datum gtype_not_ilike(PG_FUNCTION_ARGS) -{ - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(texticnlike, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); -} - -PG_FUNCTION_INFO_V1(gserialized_contains_2d); - -PG_FUNCTION_INFO_V1(gtype_eq_tilde); -// gtype ~ gtype -Datum gtype_eq_tilde(PG_FUNCTION_ARGS) -{ - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - if (GT_IS_GEOMETRY(lhs) || GT_IS_GEOMETRY(rhs)) - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2(gserialized_contains_2d, GT_TO_GEOMETRY_DATUM(lhs), GT_TO_GEOMETRY_DATUM(rhs)))); - - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(textregexeq, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); -} - -PG_FUNCTION_INFO_V1(gtype_match_case_insensitive); -// gtype ~* gtype -Datum gtype_match_case_insensitive(PG_FUNCTION_ARGS) -{ - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(texticregexeq, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); -} - -PG_FUNCTION_INFO_V1(gtype_regex_not_cs); -// gtype !~ gtype -Datum gtype_regex_not_cs(PG_FUNCTION_ARGS) -{ - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(textregexne, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); -} - - -PG_FUNCTION_INFO_V1(gtype_regex_not_ci); -// gtype !~ gtype -Datum gtype_regex_not_ci(PG_FUNCTION_ARGS) -{ - PG_RETURN_BOOL(DatumGetBool(DirectFunctionCall2Coll(texticregexne, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0), GT_ARG_TO_TEXT_DATUM(1)))); -} - -PG_FUNCTION_INFO_V1(gtype_sha224); -// sha224(gtype) -Datum gtype_sha224(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1Coll(sha224_bytea, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_BYTEA, .val.bytea = DatumGetPointer(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_sha256); -// sha256(gtype) -Datum gtype_sha256(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1Coll(sha256_bytea, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_BYTEA, .val.bytea = DatumGetPointer(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_sha384); -// sha384(gtype) -Datum gtype_sha384(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1Coll(sha384_bytea, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_BYTEA, .val.bytea = DatumGetPointer(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_sha512); -// sha512(gtype) -Datum gtype_sha512(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1Coll(sha512_bytea, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_BYTEA, .val.bytea = DatumGetPointer(d)}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_md5); -// md5(gtype) -Datum gtype_md5(PG_FUNCTION_ARGS) -{ - Datum d = DirectFunctionCall1Coll(md5_text, C_COLLATION_OID, GT_ARG_TO_TEXT_DATUM(0)); - - gtype_value gtv = { .type = AGTV_STRING, .val.string = { VARSIZE(d), text_to_cstring(DatumGetTextP(d)) }}; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - diff --git a/src/backend/utils/adt/gtype_temporal.c b/src/backend/utils/adt/gtype_temporal.c deleted file mode 100644 index cccbbf44..00000000 --- a/src/backend/utils/adt/gtype_temporal.c +++ /dev/null @@ -1,831 +0,0 @@ -/* - * Used to support gtype temporal values. - * - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include "postgraph.h" - -#include "common/int128.h" -#include "utils/builtins.h" -#include "utils/date.h" -#include "utils/fmgrprotos.h" -#include "utils/timestamp.h" - -#include "utils/gtype.h" -#include "utils/gtype_temporal.h" -#include "utils/gtype_typecasting.h" - -int -timetz_cmp_internal(TimeTzADT *time1, TimeTzADT *time2) { - TimeOffset t1, t2; - - // Primary sort is by true (GMT-equivalent) time - t1 = time1->time + (time1->zone * USECS_PER_SEC); - t2 = time2->time + (time2->zone * USECS_PER_SEC); - - if (t1 > t2) - return 1; - if (t1 < t2) - return -1; - - /* - * If same GMT time, sort by timezone; we only want to say that two - * timetz's are equal if both the time and zone parts are equal. - */ - if (time1->zone > time2->zone) - return 1; - if (time1->zone < time2->zone) - return -1; - - return 0; -} - - -static inline INT128 -interval_cmp_value(const Interval *interval) -{ - INT128 span; - int64 dayfraction; - int64 days; - - /* - * Separate time field into days and dayfraction, then add the month and - * day fields to the days part. We cannot overflow int64 days here. - */ - dayfraction = interval->time % USECS_PER_DAY; - days = interval->time / USECS_PER_DAY; - days += interval->month * INT64CONST(30); - days += interval->day; - - // Widen dayfraction to 128 bits - span = int64_to_int128(dayfraction); - - // Scale up days to microseconds, forming a 128-bit product - int128_add_int64_mul_int64(&span, days, USECS_PER_DAY); - - return span; -} - -int -interval_cmp_internal(Interval *interval1, Interval *interval2) -{ - INT128 span1 = interval_cmp_value(interval1); - INT128 span2 = interval_cmp_value(interval2); - - return int128_compare(span1, span2); -} - -PG_FUNCTION_INFO_V1(gtype_age_today); -Datum gtype_age_today(PG_FUNCTION_ARGS) -{ - Timestamp ts; - gtype *arg1 = AG_GET_ARG_GTYPE_P(0); - gtype_value agtv_result, *agtv1; - Interval *i; - - if (is_gtype_null(arg1)) - PG_RETURN_NULL(); - agtv1 = get_ith_gtype_value_from_container(&arg1->root, 0); - - ts = TimestampGetDatum(GetCurrentTransactionStartTimestamp()); - ts = DatumGetTimestamp(DirectFunctionCall2(timestamp_trunc, cstring_to_text_with_len("day",3), ts)); - - if (agtv1->type != AGTV_TIMESTAMP) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("age(gtype) only supports timestamps"))); - - i = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, - TimestampGetDatum(agtv1->val.int_value), - ts)); - - agtv_result.type = AGTV_INTERVAL; - agtv_result.val.interval.time = i->time; - agtv_result.val.interval.day = i->day; - agtv_result.val.interval.month = i->month; - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); -} - -PG_FUNCTION_INFO_V1(gtype_age_w2args); -Datum -gtype_age_w2args(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - if (is_gtype_null(lhs) || is_gtype_null(rhs)) - PG_RETURN_NULL(); - - gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); - gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); - - Interval *i; - if (lhs_value->type == AGTV_TIMESTAMP && rhs_value->type == AGTV_TIMESTAMP) - i = DatumGetIntervalP(DirectFunctionCall2(timestamp_age, - TimestampGetDatum(lhs_value->val.int_value), - TimestampGetDatum(rhs_value->val.int_value))); - else if (lhs_value->type == AGTV_TIMESTAMPTZ && rhs_value->type == AGTV_TIMESTAMPTZ) - i = DatumGetIntervalP(DirectFunctionCall2(timestamptz_age, - TimestampTzGetDatum(lhs_value->val.int_value), - TimestampTzGetDatum(rhs_value->val.int_value))); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for age(gtype, gtype)"), - errhint("You may have to use explicit casts."))); - - gtype_value gtv; - gtv.type = AGTV_INTERVAL; - gtv.val.interval.time = i->time; - gtv.val.interval.day = i->day; - gtv.val.interval.month = i->month; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_extract); -Datum -gtype_extract(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - if (is_gtype_null(lhs) || is_gtype_null(rhs)) - PG_RETURN_NULL(); - - gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); - gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); - - if (lhs_value->type != AGTV_STRING) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for EXTRACT arg 1 must be a string"), - errhint("You may have to use explicit casts."))); - - char *field = lhs_value->val.string.val; - - gtype_value gtv; - if (rhs_value->type == AGTV_TIMESTAMP) { - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_timestamp, - CStringGetTextDatum(field), - TimestampGetDatum(rhs_value->val.int_value))); - } else if (rhs_value->type == AGTV_TIMESTAMPTZ) { - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_timestamptz, - CStringGetTextDatum(field), - TimestampTzGetDatum(rhs_value->val.int_value))); - } else if (rhs_value->type == AGTV_DATE) { - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_date, - CStringGetTextDatum(field), - DateADTGetDatum(rhs_value->val.date))); - } else if (rhs_value->type == AGTV_TIME) { - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_time, - CStringGetTextDatum(field), - TimeADTGetDatum(rhs_value->val.int_value))); - } else if (rhs_value->type == AGTV_TIMETZ) { - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_timetz, - CStringGetTextDatum(field), - TimeTzADTPGetDatum(&rhs_value->val.timetz))); - } else if (rhs_value->type == AGTV_INTERVAL) { - gtv.val.numeric = DatumGetNumeric(DirectFunctionCall2(extract_interval, - CStringGetTextDatum(field), - IntervalPGetDatum(&rhs_value->val.interval))); - } else { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for EXTRACT(gtype, gtype)"), - errhint("You may have to use explicit casts."))); - } - - gtv.type = AGTV_NUMERIC; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_date_part); -Datum -gtype_date_part(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - if (is_gtype_null(lhs) || is_gtype_null(rhs)) - PG_RETURN_NULL(); - - gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); - gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); - - if (lhs_value->type != AGTV_STRING) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for EXTRACT arg 1 must be a string"), - errhint("You may have to use explicit casts."))); - - char *field = lhs_value->val.string.val; - - gtype_value gtv; - if (rhs_value->type == AGTV_TIMESTAMP) { - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(timestamp_part, - CStringGetTextDatum(field), - TimestampGetDatum(rhs_value->val.int_value))); - } else if (rhs_value->type == AGTV_TIMESTAMPTZ) { - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(timestamptz_part, - CStringGetTextDatum(field), - TimestampTzGetDatum(rhs_value->val.int_value))); - } else if (rhs_value->type == AGTV_DATE) { - Datum ts = gtype_to_timestamptz_internal(rhs_value); - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(timestamptz_part, - CStringGetTextDatum(field), ts)); - } else if (rhs_value->type == AGTV_TIME) { - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(time_part, - CStringGetTextDatum(field), - TimeADTGetDatum(rhs_value->val.int_value))); - } else if (rhs_value->type == AGTV_TIMETZ) { - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(timetz_part, - CStringGetTextDatum(field), - TimeTzADTPGetDatum(&rhs_value->val.timetz))); - } else if (rhs_value->type == AGTV_INTERVAL) { - gtv.val.float_value = DatumGetFloat8(DirectFunctionCall2(interval_part, - CStringGetTextDatum(field), - IntervalPGetDatum(&rhs_value->val.interval))); - } else { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for date_part(gtype, gtype)"), - errhint("You may have to use explicit casts."))); - } - - gtv.type = AGTV_FLOAT; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_make_date); - -Datum gtype_make_date(PG_FUNCTION_ARGS) -{ - gtype_value agtv, *agtv_year, *agtv_month, *agtv_day; - gtype *agt_year = AG_GET_ARG_GTYPE_P(0); - gtype *agt_month = AG_GET_ARG_GTYPE_P(1); - gtype *agt_day = AG_GET_ARG_GTYPE_P(2); - - if (is_gtype_null(agt_year) || is_gtype_null(agt_month) || is_gtype_null(agt_day)) - PG_RETURN_NULL(); - - agtv_year = get_ith_gtype_value_from_container(&agt_year->root, 0); - agtv_month = get_ith_gtype_value_from_container(&agt_month->root, 0); - agtv_day = get_ith_gtype_value_from_container(&agt_day->root, 0); - - - if (agtv_year->type != AGTV_INTEGER || agtv_month->type != AGTV_INTEGER || agtv_day->type != AGTV_INTEGER) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("make_date(gtype, gtype, gtype) only supports integer arugments"))); - - - agtv.type = AGTV_DATE; - agtv.val.date = DatumGetDateADT(DirectFunctionCall3(make_date, - Int32GetDatum((int32)agtv_year->val.int_value), - Int32GetDatum((int32)agtv_month->val.int_value), - Int32GetDatum((int32)agtv_day->val.int_value))); - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); -} - -PG_FUNCTION_INFO_V1(gtype_make_time); - -Datum gtype_make_time(PG_FUNCTION_ARGS) -{ - gtype_value agtv, *agtv_hour, *agtv_minute, *agtv_second; - gtype *agt_hour = AG_GET_ARG_GTYPE_P(0); - gtype *agt_minute = AG_GET_ARG_GTYPE_P(1); - gtype *agt_second = AG_GET_ARG_GTYPE_P(2); - - if (is_gtype_null(agt_hour) || is_gtype_null(agt_minute) || is_gtype_null(agt_second)) - PG_RETURN_NULL(); - - agtv_hour = get_ith_gtype_value_from_container(&agt_hour->root, 0); - agtv_minute = get_ith_gtype_value_from_container(&agt_minute->root, 0); - agtv_second = get_ith_gtype_value_from_container(&agt_second->root, 0); - - - if (agtv_hour->type != AGTV_INTEGER || agtv_minute->type != AGTV_INTEGER || agtv_second->type != AGTV_FLOAT) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("make_time(gtype, gtype, gtype) only supports integer arugments"))); - - - agtv.type = AGTV_TIME; - agtv.val.int_value = DatumGetTimeADT(DirectFunctionCall3(make_time, - Int32GetDatum((int32)agtv_hour->val.int_value), - Int32GetDatum((int32)agtv_minute->val.int_value), - Float8GetDatum(agtv_second->val.float_value))); - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); -} - -PG_FUNCTION_INFO_V1(gtype_make_timestamp); - -Datum gtype_make_timestamp(PG_FUNCTION_ARGS) -{ - gtype_value agtv; - gtype_value *agtv_hour, *agtv_minute, *agtv_second; - gtype_value *agtv_year, *agtv_month, *agtv_day; - - gtype *agt_year = AG_GET_ARG_GTYPE_P(0); - gtype *agt_month = AG_GET_ARG_GTYPE_P(1); - gtype *agt_day = AG_GET_ARG_GTYPE_P(2); - gtype *agt_hour = AG_GET_ARG_GTYPE_P(3); - gtype *agt_minute = AG_GET_ARG_GTYPE_P(4); - gtype *agt_second = AG_GET_ARG_GTYPE_P(5); - - if (is_gtype_null(agt_year) || is_gtype_null(agt_month) || is_gtype_null(agt_day) || - is_gtype_null(agt_hour) || is_gtype_null(agt_minute) || is_gtype_null(agt_second)) - PG_RETURN_NULL(); - - agtv_year = get_ith_gtype_value_from_container(&agt_year->root, 0); - agtv_month = get_ith_gtype_value_from_container(&agt_month->root, 0); - agtv_day = get_ith_gtype_value_from_container(&agt_day->root, 0); - agtv_hour = get_ith_gtype_value_from_container(&agt_hour->root, 0); - agtv_minute = get_ith_gtype_value_from_container(&agt_minute->root, 0); - agtv_second = get_ith_gtype_value_from_container(&agt_second->root, 0); - - if (agtv_year->type != AGTV_INTEGER || agtv_month->type != AGTV_INTEGER || agtv_day->type != AGTV_INTEGER || - agtv_hour->type != AGTV_INTEGER || agtv_minute->type != AGTV_INTEGER || agtv_second->type != AGTV_FLOAT) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("make_time(gtype, gtype, gtype) only supports integer arugments"))); - - - agtv.type = AGTV_TIMESTAMP; - agtv.val.int_value = DatumGetTimestamp(DirectFunctionCall6(make_timestamp, - Int32GetDatum((int32)agtv_year->val.int_value), - Int32GetDatum((int32)agtv_month->val.int_value), - Int32GetDatum((int32)agtv_day->val.int_value), - Int32GetDatum((int32)agtv_hour->val.int_value), - Int32GetDatum((int32)agtv_minute->val.int_value), - Float8GetDatum(agtv_second->val.float_value))); - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); -} - -PG_FUNCTION_INFO_V1(gtype_make_timestamptz); - -Datum gtype_make_timestamptz(PG_FUNCTION_ARGS) -{ - gtype_value agtv; - gtype_value *agtv_hour, *agtv_minute, *agtv_second; - gtype_value *agtv_year, *agtv_month, *agtv_day; - - gtype *agt_year = AG_GET_ARG_GTYPE_P(0); - gtype *agt_month = AG_GET_ARG_GTYPE_P(1); - gtype *agt_day = AG_GET_ARG_GTYPE_P(2); - gtype *agt_hour = AG_GET_ARG_GTYPE_P(3); - gtype *agt_minute = AG_GET_ARG_GTYPE_P(4); - gtype *agt_second = AG_GET_ARG_GTYPE_P(5); - - if (is_gtype_null(agt_year) || is_gtype_null(agt_month) || is_gtype_null(agt_day) || - is_gtype_null(agt_hour) || is_gtype_null(agt_minute) || is_gtype_null(agt_second)) - PG_RETURN_NULL(); - - agtv_year = get_ith_gtype_value_from_container(&agt_year->root, 0); - agtv_month = get_ith_gtype_value_from_container(&agt_month->root, 0); - agtv_day = get_ith_gtype_value_from_container(&agt_day->root, 0); - agtv_hour = get_ith_gtype_value_from_container(&agt_hour->root, 0); - agtv_minute = get_ith_gtype_value_from_container(&agt_minute->root, 0); - agtv_second = get_ith_gtype_value_from_container(&agt_second->root, 0); - - if (agtv_year->type != AGTV_INTEGER || agtv_month->type != AGTV_INTEGER || agtv_day->type != AGTV_INTEGER || - agtv_hour->type != AGTV_INTEGER || agtv_minute->type != AGTV_INTEGER) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("make_timestamptz expected an integer arugment"))); - - - if (agtv_second->type != AGTV_FLOAT) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("make_timestamptz second arguement expects a float"))); - - agtv.type = AGTV_TIMESTAMPTZ; - agtv.val.int_value = DatumGetTimestampTz(DirectFunctionCall6(make_timestamp, - Int32GetDatum((int32)agtv_year->val.int_value), - Int32GetDatum((int32)agtv_month->val.int_value), - Int32GetDatum((int32)agtv_day->val.int_value), - Int32GetDatum((int32)agtv_hour->val.int_value), - Int32GetDatum((int32)agtv_minute->val.int_value), - Float8GetDatum(agtv_second->val.float_value))); - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); -} - -PG_FUNCTION_INFO_V1(gtype_make_timestamptz_wtimezone); - -Datum gtype_make_timestamptz_wtimezone(PG_FUNCTION_ARGS) -{ - gtype_value agtv; - gtype_value *agtv_hour, *agtv_minute, *agtv_second; - gtype_value *agtv_year, *agtv_month, *agtv_day; - gtype_value *agtv_timezone; - gtype *agt_year = AG_GET_ARG_GTYPE_P(0); - gtype *agt_month = AG_GET_ARG_GTYPE_P(1); - gtype *agt_day = AG_GET_ARG_GTYPE_P(2); - gtype *agt_hour = AG_GET_ARG_GTYPE_P(3); - gtype *agt_minute = AG_GET_ARG_GTYPE_P(4); - gtype *agt_second = AG_GET_ARG_GTYPE_P(5); - gtype *agt_timezone = AG_GET_ARG_GTYPE_P(6); - - if (is_gtype_null(agt_year) || is_gtype_null(agt_month) || is_gtype_null(agt_day) || - is_gtype_null(agt_hour) || is_gtype_null(agt_minute) || is_gtype_null(agt_second) || - is_gtype_null(agt_timezone)) - PG_RETURN_NULL(); - - agtv_year = get_ith_gtype_value_from_container(&agt_year->root, 0); - agtv_month = get_ith_gtype_value_from_container(&agt_month->root, 0); - agtv_day = get_ith_gtype_value_from_container(&agt_day->root, 0); - agtv_hour = get_ith_gtype_value_from_container(&agt_hour->root, 0); - agtv_minute = get_ith_gtype_value_from_container(&agt_minute->root, 0); - agtv_second = get_ith_gtype_value_from_container(&agt_second->root, 0); - agtv_timezone = get_ith_gtype_value_from_container(&agt_timezone->root, 0); - - if (agtv_year->type != AGTV_INTEGER || agtv_month->type != AGTV_INTEGER || agtv_day->type != AGTV_INTEGER || - agtv_hour->type != AGTV_INTEGER || agtv_minute->type != AGTV_INTEGER) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("make_timestamptz expected an integer arugment"))); - - - if (agtv_second->type != AGTV_FLOAT) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("make_timestamptz second arguement expects a float"))); - - if (agtv_timezone->type != AGTV_STRING) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("make_timestamptz timezone arguemnent must be a string"))); - - - agtv.type = AGTV_TIMESTAMPTZ; - agtv.val.int_value = DatumGetTimestampTz(DirectFunctionCall7(make_timestamptz_at_timezone, - Int32GetDatum((int32)agtv_year->val.int_value), - Int32GetDatum((int32)agtv_month->val.int_value), - Int32GetDatum((int32)agtv_day->val.int_value), - Int32GetDatum((int32)agtv_hour->val.int_value), - Int32GetDatum((int32)agtv_minute->val.int_value), - Float8GetDatum(agtv_second->val.float_value), - PointerGetDatum(cstring_to_text_with_len(agtv_timezone->val.string.val, - agtv_timezone->val.string.len)))); - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv)); -} - -PG_FUNCTION_INFO_V1(gtype_isfinite); -Datum gtype_isfinite(PG_FUNCTION_ARGS) -{ - gtype *agt = AG_GET_ARG_GTYPE_P(0); - gtype_value *agtv, agtv_result; - bool result = false; - - if (is_gtype_null(agt)) - PG_RETURN_NULL(); - - agtv = get_ith_gtype_value_from_container(&agt->root, 0); - - if (agtv->type == AGTV_DATE) - result = DatumGetBool(DirectFunctionCall1(date_finite, DateADTGetDatum(agtv->val.date))); - else if (agtv->type == AGTV_INTERVAL) - result = true; - else if(agtv->type == AGTV_TIMESTAMP || agtv->type == AGTV_TIMESTAMPTZ) - { - result = DatumGetBool(DirectFunctionCall1(timestamp_finite, TimestampGetDatum(agtv->val.int_value))); - } - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for isfinite(gtype)"), - errhint("You may have to use explicit casts."))); - - - agtv_result.type = AGTV_BOOL; - agtv_result.val.boolean = result; - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); -} - -PG_FUNCTION_INFO_V1(gtype_justify_days); -Datum -gtype_justify_days(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_null(gt)) - PG_RETURN_NULL(); - - gtype_value *gt_value = get_ith_gtype_value_from_container(>->root, 0); - - gtype_value gtv; - if (gt_value->type != AGTV_INTERVAL) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for justify_days(gtype)"), - errhint("You may have to use explicit casts."))); - - Interval *i = DatumGetIntervalP(DirectFunctionCall1(interval_justify_days, - IntervalPGetDatum(>_value->val.interval))); - gtv.type = AGTV_INTERVAL; - gtv.val.interval.time = i->time; - gtv.val.interval.day = i->day; - gtv.val.interval.month = i->month; - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - - -PG_FUNCTION_INFO_V1(gtype_justify_hours); -Datum -gtype_justify_hours(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_null(gt)) - PG_RETURN_NULL(); - - gtype_value *gt_value = get_ith_gtype_value_from_container(>->root, 0); - - gtype_value gtv; - if (gt_value->type != AGTV_INTERVAL) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for justify_days(gtype)"), - errhint("You may have to use explicit casts."))); - - Interval *i = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours, - IntervalPGetDatum(>_value->val.interval))); - gtv.type = AGTV_INTERVAL; - gtv.val.interval.time = i->time; - gtv.val.interval.day = i->day; - gtv.val.interval.month = i->month; - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_justify_interval); -Datum -gtype_justify_interval(PG_FUNCTION_ARGS) { - gtype *gt = AG_GET_ARG_GTYPE_P(0); - - if (is_gtype_null(gt)) - PG_RETURN_NULL(); - - gtype_value *gt_value = get_ith_gtype_value_from_container(>->root, 0); - - gtype_value gtv; - if (gt_value->type != AGTV_INTERVAL) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for justify_days(gtype)"), - errhint("You may have to use explicit casts."))); - - Interval *i = DatumGetIntervalP(DirectFunctionCall1(interval_justify_interval, - IntervalPGetDatum(>_value->val.interval))); - gtv.type = AGTV_INTERVAL; - gtv.val.interval.time = i->time; - gtv.val.interval.day = i->day; - gtv.val.interval.month = i->month; - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - -} - -PG_FUNCTION_INFO_V1(gtype_date_trunc); -Datum -gtype_date_trunc(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - if (is_gtype_null(lhs) || is_gtype_null(rhs)) - PG_RETURN_NULL(); - - gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); - gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); - - if (lhs_value->type != AGTV_STRING) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for EXTRACT arg 1 must be a string"), - errhint("You may have to use explicit casts."))); - - char *field = lhs_value->val.string.val; - - gtype_value gtv; - if (rhs_value->type == AGTV_TIMESTAMP) { - gtv.val.int_value = DatumGetTimestamp(DirectFunctionCall2(timestamp_trunc, - CStringGetTextDatum(field), - TimestampGetDatum(rhs_value->val.int_value))); - gtv.type = AGTV_TIMESTAMP; - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } else if (rhs_value->type == AGTV_TIMESTAMPTZ) { - gtv.val.int_value = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_trunc, - CStringGetTextDatum(field), - TimestampTzGetDatum(rhs_value->val.int_value))); - gtv.type = AGTV_TIMESTAMPTZ; - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } else if (rhs_value->type == AGTV_INTERVAL) { - Interval *i = DatumGetIntervalP(DirectFunctionCall2(interval_trunc, - CStringGetTextDatum(field), - IntervalPGetDatum(&rhs_value->val.interval))); - gtv.type = AGTV_INTERVAL; - gtv.val.interval.time = i->time; - gtv.val.interval.day = i->day; - gtv.val.interval.month = i->month; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } else { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for date_trun(gtype, gtype)"), - errhint("You may have to use explicit casts."))); - } - - PG_RETURN_NULL(); -} - -PG_FUNCTION_INFO_V1(gtype_date_trunc_zone); -Datum -gtype_date_trunc_zone(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - gtype *zone = AG_GET_ARG_GTYPE_P(2); - - if (is_gtype_null(lhs) || is_gtype_null(rhs)) - PG_RETURN_NULL(); - - gtype_value *lhs_value = get_ith_gtype_value_from_container(&lhs->root, 0); - gtype_value *rhs_value = get_ith_gtype_value_from_container(&rhs->root, 0); - gtype_value *zone_value = get_ith_gtype_value_from_container(&zone->root, 0); - - if (lhs_value->type != AGTV_STRING) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for date_trunc arg 1 must be a string"), - errhint("You may have to use explicit casts."))); - - char *field = lhs_value->val.string.val; - - if (lhs_value->type != AGTV_STRING) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for date_trunc arg 3 must be a string"), - errhint("You may have to use explicit casts."))); - - char *zone_str = zone_value->val.string.val; - - - gtype_value gtv; - if (rhs_value->type == AGTV_TIMESTAMPTZ) { - gtv.val.int_value = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_trunc_zone, - CStringGetTextDatum(field), - TimestampTzGetDatum(rhs_value->val.int_value), - CStringGetTextDatum(zone_str))); - gtv.type = AGTV_TIMESTAMPTZ; - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } else { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for date_trun(gtype, gtype, gtype)"), - errhint("You may have to use explicit casts."))); - } - - PG_RETURN_NULL(); -} - -PG_FUNCTION_INFO_V1(gtype_date_bin); -Datum -gtype_date_bin(PG_FUNCTION_ARGS) { - gtype *stride = AG_GET_ARG_GTYPE_P(0); - gtype *source = AG_GET_ARG_GTYPE_P(1); - gtype *origin = AG_GET_ARG_GTYPE_P(2); - - if (is_gtype_null(stride) || is_gtype_null(source) || is_gtype_null(origin)) - PG_RETURN_NULL(); - - gtype_value *stride_value = get_ith_gtype_value_from_container(&stride->root, 0); - gtype_value *source_value = get_ith_gtype_value_from_container(&source->root, 0); - gtype_value *origin_value = get_ith_gtype_value_from_container(&origin->root, 0); - - if ((source_value->type == AGTV_TIMESTAMP || source_value->type == AGTV_DATE) && - (origin_value->type == AGTV_TIMESTAMP || origin_value->type == AGTV_DATE)) { - Datum source_ts, origin_ts; - - if (source_value->type == AGTV_DATE) - source_ts = gtype_to_timestamp_internal(source_value); - else - source_ts = TimestampGetDatum(source_value->val.int_value); - - if (origin_value->type == AGTV_DATE) - origin_ts = gtype_to_timestamp_internal(origin_value); - else - origin_ts = TimestampGetDatum(origin_value->val.int_value); - - - Timestamp ts = DatumGetTimestamp(DirectFunctionCall3(timestamp_bin, - IntervalPGetDatum(&stride_value->val.interval), - source_ts, origin_ts)); - - gtype_value gtv; - gtv.type = AGTV_TIMESTAMP; - gtv.val.int_value = ts; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } else if (source_value->type == AGTV_TIMESTAMPTZ && origin_value->type == AGTV_TIMESTAMPTZ) { - TimestampTz ts = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_bin, - IntervalPGetDatum(&stride_value->val.interval), - TimestampTzGetDatum(source_value->val.int_value), - TimestampTzGetDatum(origin_value->val.int_value))); - gtype_value gtv; - gtv.type = AGTV_TIMESTAMPTZ; - gtv.val.int_value = ts; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid input for date_bin(gtype, gtype, gtype)"), - errhint("You may have to use explicit casts."))); - - PG_RETURN_NULL(); -} - -enum overlap_type { - ovt_timestamp, - ovt_time, - ovt_timetz -} overlap_type; - -PG_FUNCTION_INFO_V1(gtype_overlaps); - -Datum gtype_overlaps(PG_FUNCTION_ARGS) -{ - gtype *arg1 = AG_GET_ARG_GTYPE_P(0); - gtype *arg2 = AG_GET_ARG_GTYPE_P(1); - gtype *arg3 = AG_GET_ARG_GTYPE_P(2); - gtype *arg4 = AG_GET_ARG_GTYPE_P(3); - enum overlap_type type; - bool result; - gtype_value agtv_result; - - Datum d1; - if (GT_IS_DATE(arg1) || GT_IS_TIMESTAMP(arg1) || GT_IS_TIMESTAMPTZ(arg1)) { - d1 = GT_TO_TIMESTAMP_DATUM(arg1); - type = ovt_timestamp; - } else if (GT_IS_TIME(arg1)) { - d1 = GT_TO_TIME_DATUM(arg1); - type = ovt_time; - } else if (GT_IS_TIMETZ(arg1)) { - d1 = GT_TO_TIMETZ_DATUM(arg1); - type = ovt_timetz; - } else { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid argument type for overlap expression"))); - } - - Datum d2; - if (GT_IS_INTERVAL(arg2)) { - if (type == ovt_timestamp) - d2 = DirectFunctionCall2(timestamp_pl_interval, d1, GT_TO_INTERVAL_DATUM(arg2)); - else if (type == ovt_time) - d2 = DirectFunctionCall2(time_pl_interval, d1, GT_TO_INTERVAL_DATUM(arg2)); - else - d2 = DirectFunctionCall2(timetz_pl_interval, d1, GT_TO_INTERVAL_DATUM(arg2)); - } else { - if (type == ovt_timestamp) - d2 = GT_TO_TIMESTAMP_DATUM(arg2); - else if (type == ovt_time) - d2 = GT_TO_TIME_DATUM(arg2); - else - d2 = GT_TO_TIMETZ_DATUM(arg2); - } - - Datum d3; - if (type == ovt_timestamp) - d3 = GT_TO_TIMESTAMP_DATUM(arg3); - else if (type == ovt_time) - d3 = GT_TO_TIME_DATUM(arg3); - else - d3 = GT_TO_TIMETZ_DATUM(arg3); - - - Datum d4; - if (GT_IS_INTERVAL(arg4)) { - if (type == ovt_timestamp) - d4 = DirectFunctionCall2(timestamp_pl_interval, d3, GT_TO_INTERVAL_DATUM(arg4)); - else if (type == ovt_time) - d4 = DirectFunctionCall2(time_pl_interval, d3, GT_TO_INTERVAL_DATUM(arg4)); - else - d4 = DirectFunctionCall2(timetz_pl_interval, d3, GT_TO_INTERVAL_DATUM(arg4)); - } else { - if (type == ovt_timestamp) - d4 = GT_TO_TIMESTAMP_DATUM(arg4); - else if (type == ovt_time) - d4 = GT_TO_TIME_DATUM(arg4); - else - d4 = GT_TO_TIMETZ_DATUM(arg4); - } - - if (type == ovt_timestamp) - result = DatumGetBool(DirectFunctionCall4(overlaps_timestamp, d1, d2, d3, d4)); - else if (type == ovt_time) - result = DatumGetBool(DirectFunctionCall4(overlaps_time, d1, d2, d3, d4)); - else - result = DatumGetBool(DirectFunctionCall4(overlaps_timetz, d1, d2, d3, d4)); - - - agtv_result.type = AGTV_BOOL; - agtv_result.val.boolean = result; - - PG_RETURN_POINTER(gtype_value_to_gtype(&agtv_result)); -} - diff --git a/src/backend/utils/adt/gtype_tsearch.c b/src/backend/utils/adt/gtype_tsearch.c deleted file mode 100644 index 842a09cd..00000000 --- a/src/backend/utils/adt/gtype_tsearch.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * PostGraph - * Copyright (C) 2023 by PostGraph - * - * 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. - */ - -#include "postgraph.h" - -// Postgres -#include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "tsearch/ts_utils.h" - -// PostGraph -#include "utils/gtype.h" -#include "utils/gtype_typecasting.h" - - -PG_FUNCTION_INFO_V1(gtype_tsquery_or); -Datum gtype_tsquery_or(PG_FUNCTION_ARGS) { - gtype *lhs = AG_GET_ARG_GTYPE_P(0); - gtype *rhs = AG_GET_ARG_GTYPE_P(1); - - if (GT_IS_TSQUERY(lhs) || GT_IS_TSQUERY(rhs)) { - TSQuery tsquery = DatumGetPointer(DirectFunctionCall2(tsquery_or, - GT_ARG_TO_TSQUERY_DATUM(0), - GT_ARG_TO_TSQUERY_DATUM(1))); - - gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsquery = tsquery }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - } else { - TSVector tsvector = DatumGetPointer(DirectFunctionCall2(tsvector_concat, - GT_ARG_TO_TSVECTOR_DATUM(0), - GT_ARG_TO_TSVECTOR_DATUM(1))); - - gtype_value gtv = { .type = AGTV_TSVECTOR, .val.tsvector = tsvector }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); - - } -} - -PG_FUNCTION_INFO_V1(gtype_tsquery_not); -Datum gtype_tsquery_not(PG_FUNCTION_ARGS) { - TSQuery tsquery = DatumGetPointer(DirectFunctionCall1(tsquery_not, GT_ARG_TO_TSQUERY_DATUM(0))); - - gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsquery = tsquery }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -/* - * Text Search Functions - */ -PG_FUNCTION_INFO_V1(gtype_ts_delete); -Datum gtype_ts_delete(PG_FUNCTION_ARGS) { - TSVector tsvector = DatumGetPointer(DirectFunctionCall2(tsvector_delete_str, - GT_ARG_TO_TSVECTOR_DATUM(0), - GT_ARG_TO_TEXT_DATUM(1))); - - gtype_value gtv = { .type = AGTV_TSVECTOR, .val.tsvector = tsvector }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_ts_strip); -Datum gtype_ts_strip(PG_FUNCTION_ARGS) { - TSVector tsvector = DatumGetPointer(DirectFunctionCall1(tsvector_strip, GT_ARG_TO_TSVECTOR_DATUM(0))); - - gtype_value gtv = { .type = AGTV_TSVECTOR, .val.tsvector = tsvector }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_tsquery_phrase); -Datum gtype_tsquery_phrase(PG_FUNCTION_ARGS) { - TSQuery tsquery = DatumGetPointer(DirectFunctionCall3(tsquery_phrase_distance, - GT_ARG_TO_TSQUERY_DATUM(0), - GT_ARG_TO_TSQUERY_DATUM(1), - Int32GetDatum(1))); - - gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_tsquery_phrase_distance); -Datum gtype_tsquery_phrase_distance(PG_FUNCTION_ARGS) { - TSQuery tsquery = DatumGetPointer(DirectFunctionCall3(tsquery_phrase_distance, - GT_ARG_TO_TSQUERY_DATUM(0), - GT_ARG_TO_TSQUERY_DATUM(1), - GT_ARG_TO_INT4_DATUM(2))); - - gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_plainto_tsquery); -Datum gtype_plainto_tsquery(PG_FUNCTION_ARGS) { - TSQuery tsquery = DatumGetPointer(DirectFunctionCall1(plainto_tsquery, GT_ARG_TO_TEXT_DATUM(0))); - - gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_phraseto_tsquery); -Datum gtype_phraseto_tsquery(PG_FUNCTION_ARGS) { - TSQuery tsquery = DatumGetPointer(DirectFunctionCall1(phraseto_tsquery, GT_ARG_TO_TEXT_DATUM(0))); - - gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - -PG_FUNCTION_INFO_V1(gtype_websearch_to_tsquery); -Datum gtype_websearch_to_tsquery(PG_FUNCTION_ARGS) { - TSQuery tsquery = DatumGetPointer(DirectFunctionCall1(websearch_to_tsquery, GT_ARG_TO_TEXT_DATUM(0))); - - gtype_value gtv = { .type = AGTV_TSQUERY, .val.tsvector = tsquery }; - - AG_RETURN_GTYPE_P(gtype_value_to_gtype(>v)); -} - diff --git a/src/backend/utils/adt/queue.c b/src/backend/utils/adt/queue.c deleted file mode 100644 index 65ca3ebe..00000000 --- a/src/backend/utils/adt/queue.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 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. - */ - -#include "postgres.h" - -#include "utils/graphid.h" -#include "utils/queue.h" - -/* defines */ -/* - * A simple linked list queuenode for graphid lists (int64). PG's implementation - * has too much overhead for this type of list as it only directly supports - * regular ints, not int64s, of which a graphid currently is. - */ -typedef struct QueueNode -{ - graphid id; - struct QueueNode *next; -} QueueNode; - -/* a container for a linked list of QueueNodes */ -typedef struct Queue -{ - QueueNode *head; - QueueNode *tail; - int64 size; -} Queue; - -/* declarations */ - -/* definitions */ -/* return the next QueueNode */ -QueueNode *next_queue_node(QueueNode *queuenode) { - return queuenode->next; -} - -/* return the graphid */ -graphid get_graphid(QueueNode *queuenode) { - return queuenode->id; -} - -/* get the size of the passed queue */ -int64 queue_size(Queue *queue) { - return queue->size; -} - -/* return a reference to the head entry in the queue, or NULL if empty */ -QueueNode *peek_queue_head(Queue *queue) { - if (queue == NULL) - return NULL; - - return queue->head; -} - -/* return a reference to the tail entry in the queue */ -QueueNode *peek_queue_tail(Queue *queue) -{ - return queue->tail; -} - -/* return a reference to the head entry of a list */ -QueueNode *get_list_head(Queue *list) -{ - return list->head; -} - -/* get the size of the passed list */ -int64 get_list_size(Queue *list) -{ - return list->size; -} - -/* - * Helper function to add a graphid to the end of a Queue container. - * If the container is NULL, it creates the container with the entry. - */ -Queue *append_graphid(Queue *container, graphid id) -{ - QueueNode *new_queuenode = NULL; - - /* create the new link */ - new_queuenode = palloc0(sizeof(QueueNode)); - new_queuenode->id = id; - new_queuenode->next = NULL; - - if (container == NULL) { - container = palloc0(sizeof(Queue)); - container->head = new_queuenode; - container->tail = new_queuenode; - container->size = 1; - } - else - { - container->tail->next = new_queuenode; - container->tail = new_queuenode; - container->size++; - } - - return container; -} - -/* free (delete) a Queue list */ -void free_queue(Queue *container) -{ - QueueNode *curr_queuenode = NULL; - QueueNode *next_queuenode = NULL; - - /* if the container is NULL we don't need to delete anything */ - if (container == NULL) - { - return; - } - - /* otherwise, start from the head, free, and delete the links */ - curr_queuenode = container->head; - while (curr_queuenode != NULL) - { - next_queuenode = curr_queuenode->next; - /* we can do this because this is just a list of ints */ - pfree(curr_queuenode); - curr_queuenode = next_queuenode; - } - - /* free the container */ - pfree(container); -} - -/* helper function to create a new, empty, graphid queue */ -Queue *new_graphid_queue(void) -{ - Queue *queue = NULL; - - /* allocate the container for the queue */ - queue = palloc0(sizeof(Queue)); - - /* set it to its initial values */ - queue->head = NULL; - queue->tail = NULL; - queue->size = 0; - - /* return the new queue */ - return queue; -} - -/* helper function to free a graphid queue's contents but, not the container */ -void free_graphid_queue(Queue *queue) -{ - Assert(queue != NULL); - - if (queue == NULL) - { - elog(ERROR, "free_graphid_queue: NULL queue"); - } - - /* while there are entries */ - while (queue->head != NULL) - { - /* get the next element in the queue */ - QueueNode *next = queue->head->next; - - /* free the head element */ - pfree(queue->head); - /* move the head to the next */ - queue->head = next; - } - - /* reset the tail and size */ - queue->tail = NULL; - queue->size = 0; -} - -/* - * Helper function for a generic push graphid (int64) to a queue. If the queue - * is NULL, it will error out. - */ -void push_graphid_queue(Queue *queue, graphid id) -{ - QueueNode *new_queuenode = NULL; - - Assert(queue != NULL); - - if (queue == NULL) - { - elog(ERROR, "push_graphid_queue: NULL queue"); - } - - /* create the new element */ - new_queuenode = palloc0(sizeof(QueueNode)); - new_queuenode->id = id; - - /* insert (push) the new element on the top */ - new_queuenode->next = queue->head; - queue->head = new_queuenode; - queue->size++; -} - -/* - * Helper function for a generic pop graphid (int64) from a queue. If the queue - * is empty, it will error out. You should verify that the queue isn't empty - * prior to calling. - */ -graphid pop_graphid_queue(Queue *queue) -{ - QueueNode *queuenode = NULL; - graphid id; - - Assert(queue != NULL); - Assert(queue->size != 0); - - if (queue == NULL) - elog(ERROR, "pop_graphid_queue: NULL queue"); - - if (queue->size <= 0) - elog(ERROR, "pop_graphid_queue: empty queue"); - - - /* remove the element from the top of the queue */ - queuenode = queue->head; - id = queuenode->id; - queue->head = queue->head->next; - queue->size--; - - /* return the id */ - return id; -} diff --git a/src/backend/utils/adt/variable_edge.c b/src/backend/utils/adt/variable_edge.c index c6c56313..e712b7af 100644 --- a/src/backend/utils/adt/variable_edge.c +++ b/src/backend/utils/adt/variable_edge.c @@ -265,28 +265,6 @@ Datum variable_edge_contains_edge(PG_FUNCTION_ARGS) { } -// match 2 VLE edges -PG_FUNCTION_INFO_V1(match_vles); -Datum match_vles(PG_FUNCTION_ARGS) { - VariableEdge *lhs = AG_GET_ARG_VARIABLE_EDGE(0); - VariableEdge *rhs = AG_GET_ARG_VARIABLE_EDGE(1); - - edge *left_edge = (edge *)&lhs->children[1]; - - char *ptr = &rhs->children[1]; - for (int i = 0; i < rhs->children[0] - 1; i++, ptr = ptr + VARSIZE(ptr)); - edge *right_edge = (edge *)ptr; - - graphid left_start = *((int64 *)(&left_edge->children[2])); - graphid left_end = *((int64 *)(&left_edge->children[4])); - - graphid right_start = *((int64 *)(&right_edge->children[2])); - graphid right_end = *((int64 *)(&right_edge->children[4])); - -PG_RETURN_BOOL(left_start == right_start || left_end == right_start || - left_start == right_end || left_end == right_end); -} - PG_FUNCTION_INFO_V1(variable_edge_edges_overlap); Datum variable_edge_edges_overlap(PG_FUNCTION_ARGS) { VariableEdge *lhs = AG_GET_ARG_VARIABLE_EDGE(0); diff --git a/src/backend/utils/path_finding/dfs.c b/src/backend/utils/path_finding/dfs.c deleted file mode 100644 index 1a41f031..00000000 --- a/src/backend/utils/path_finding/dfs.c +++ /dev/null @@ -1,833 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * Portions Copyright (c) 2020-2023, Apache Software Foundation - */ - -#include "postgres.h" - -#include "access/heapam.h" -#include "catalog/namespace.h" -#include "catalog/pg_type.h" -#include "common/hashfn.h" -#include "funcapi.h" -#include "utils/lsyscache.h" - -#include "utils/path_finding.h" -#include "catalog/ag_graph.h" -#include "utils/graphid.h" -#include "utils/queue.h" -#include "nodes/cypher_nodes.h" -#include "utils/vertex.h" -#include "utils/edge.h" -#include "utils/variable_edge.h" - -// defines -#define GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc) \ - (graphid *) (&vpc->graphid_array_data) -#define EDGE_STATE_HTAB_NAME "Edge state " -#define EDGE_STATE_HTAB_INITIAL_SIZE 100000 -#define EXISTS_HTAB_NAME "known edges" -#define EXISTS_HTAB_NAME_INITIAL_SIZE 1000 -#define MAXIMUM_NUMBER_OF_CACHED_LOCAL_CONTEXTS 5 - -// edge state entry for the edge_state_hashtable -typedef struct edge_state_entry -{ - graphid edge_id; // edge id, it is also the hash key - bool visited; // like visited but more descriptive -} edge_state_entry; - -typedef struct path_finding_context -{ - char *graph_name; // name of the graph - Oid graph_oid; // graph oid for searching - graph_context *ggctx; // global graph context pointer - graphid vsid; // starting vertex id - graphid veid; // ending vertex id - char *label_name; // edge label name for match - gtype *properties; // edge property constraint as gtype - int64 lidx; // lower (start) bound index - int64 uidx; // upper (end) bound index - bool uidx_infinite; // flag if the upper bound is omitted - cypher_rel_dir edge_direction; // the direction of the edge - HTAB *edge_state_hashtable; // local state hashtable for our edges - HTAB *exists_hash; - Queue *dfs_vertex_queue; // dfs queue for vertices - Queue *dfs_edge_queue; // dfs queue for edges - Queue *dfs_path_queue; // dfs queue containing the path - QueueNode *next_vertex; // for VLE_FUNCTION_PATHS_TO - struct path_finding_context *next; // the next chained path_finding_context -} path_finding_context; - -/* - * Container to hold the graphid array that contains one valid path. This - * structure will allow it to be easily passed as an GTYPE pointer. The - * structure is set up to contains a BINARY container that can be accessed by - * functions that need to process the path. - */ -typedef struct path_container -{ - char vl_len_[4]; - uint32 header; - uint32 graph_oid; - int64 graphid_array_size; - int64 container_size_bytes; - graphid graphid_array_data; -} path_container; - -// gtype functions -static bool check_edge_constraints(path_finding_context *path_ctx, edge_entry *ee); -// VLE local context functions -static path_finding_context *build_vle_context(FunctionCallInfo fcinfo, FuncCallContext *funcctx); -static void create_hashtable(path_finding_context *path_ctx); -// VLE graph traversal functions -static edge_state_entry *get_edge_state(path_finding_context *path_ctx, graphid edge_id); -// graphid data structures -static void load_initial_dfs_queues(path_finding_context *path_ctx); -static bool dfs_find_a_path_between(path_finding_context *path_ctx); -static bool do_vsid_and_veid_exist(path_finding_context *path_ctx); -static void add_edges(path_finding_context *path_ctx, graphid vertex_id); -static graphid get_next_vertex(path_finding_context *path_ctx, edge_entry *ee); -// VLE path and edge building functions -static path_container *create_path_container(int64 path_size); -static path_container *build_path_container(path_finding_context *path_ctx); -VariableEdge *create_variable_edge(path_container *vpc); - -static void -append_to_buffer(StringInfo buffer, const char *data, int len) { - int offset = reserve_from_buffer(buffer, len); - memcpy(buffer->data + offset, data, len); -} - - -// helper function to create the local VLE edge state hashtable. -static void create_hashtable(path_finding_context *path_ctx) { - HASHCTL edge_state_ctl; - char *graph_name = NULL; - char *eshn = NULL; - int glen; - int elen; - - // get the graph name and length - graph_name = path_ctx->graph_name; - - // initialize the edge state hashtable - MemSet(&edge_state_ctl, 0, sizeof(edge_state_ctl)); - edge_state_ctl.keysize = sizeof(int64); - edge_state_ctl.entrysize = sizeof(edge_state_entry); - edge_state_ctl.hash = tag_hash; - path_ctx->edge_state_hashtable = hash_create(EDGE_STATE_HTAB_NAME, EDGE_STATE_HTAB_INITIAL_SIZE, &edge_state_ctl, - HASH_ELEM | HASH_FUNCTION); - - - MemSet(&edge_state_ctl, 0, sizeof(edge_state_ctl)); - edge_state_ctl.keysize = sizeof(int64); - edge_state_ctl.entrysize = sizeof(edge_state_entry); - edge_state_ctl.hash = tag_hash; - - HASHCTL exists_ctl; - MemSet(&exists_ctl, 0, sizeof(exists_ctl)); - exists_ctl.keysize = sizeof(int64); - exists_ctl.entrysize = sizeof(int64); - exists_ctl.hash = tag_hash; - path_ctx->exists_hash = hash_create(EXISTS_HTAB_NAME, 20, &exists_ctl, HASH_ELEM | HASH_FUNCTION); - -} - -/* - * Helper function to compare the edge constraint (properties we are looking - * for in a matching edge) against an edge entry's property. - */ -static bool check_edge_constraints(path_finding_context *path_ctx, edge_entry *ee) -{ - gtype *edge_property = NULL; - gtype_container *agtc_edge_property = NULL; - gtype_container *agtc_properties = NULL; - gtype_iterator *constraint_it = NULL; - gtype_iterator *property_it = NULL; - char *label_name = NULL; - int num_propertiess = 0; - int num_edge_properties = 0; - // get the edge label name from the oid - label_name = get_rel_name(get_edge_entry_label_table_oid(ee)); - - if (path_ctx->label_name != NULL && strcmp(path_ctx->label_name, label_name) != 0) - return false; - - if (path_ctx->properties == NULL) - return true; - - label_name = get_rel_name(get_edge_entry_label_table_oid(ee)); - edge_property = DATUM_GET_GTYPE_P(get_edge_entry_properties(ee)); - agtc_properties = &path_ctx->properties->root; - agtc_edge_property = &edge_property->root; - // get the number of properties in the edge to be matched - num_edge_properties = GTYPE_CONTAINER_SIZE(agtc_edge_property); - - /* - * Check to see if the edge_properties object has AT LEAST as many pairs - * to compare as the properties object has pairs. If not, it - * can't possibly match. - */ - if (AGT_ROOT_COUNT(path_ctx->properties) > num_edge_properties) - return false; - - // get the iterators - constraint_it = gtype_iterator_init(agtc_properties); - property_it = gtype_iterator_init(agtc_edge_property); - - // return the value of deep contains - return gtype_deep_contains(&property_it, &constraint_it); -} - -// helper function to check if our start and end vertices exist -static bool do_vsid_and_veid_exist(path_finding_context *path_ctx) -{ - // if we are using both start and end - return ((get_vertex_entry(path_ctx->ggctx, path_ctx->vsid) != NULL) && (get_vertex_entry(path_ctx->ggctx, path_ctx->veid) != NULL)); -} - -// load the initial edges into the dfs_edge_queue -static void load_initial_dfs_queues(path_finding_context *path_ctx) -{ - if (!do_vsid_and_veid_exist(path_ctx)) - return; - - // add in the edges for the start vertex - add_edges(path_ctx, path_ctx->vsid); -} - -/* - * build the local VLE context. - */ -static path_finding_context *build_vle_context(FunctionCallInfo fcinfo, FuncCallContext *funcctx) { - MemoryContext oldctx = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - // get the graph name - this is a required argument - gtype_value *agtv_temp = get_gtype_value("age_vle", AG_GET_ARG_GTYPE_P(0), AGTV_STRING, true); - char *graph_name = pnstrdup(agtv_temp->val.string.val, agtv_temp->val.string.len); - Oid graph_oid = get_graph_oid(graph_name); - - /* - * Create or retrieve the GRAPH global context for this graph. This function - * will also purge off invalidated contexts. - */ - graph_context *ggctx = manage_graph_contexts(graph_name, graph_oid); - - // allocate and initialize local VLE context - path_finding_context *path_ctx = palloc0(sizeof(path_finding_context)); - - // set the graph name and id - path_ctx->graph_name = graph_name; - path_ctx->graph_oid = graph_oid; - - // set the global context referenced by this local VLE context - path_ctx->ggctx = ggctx; - - // initialize the next vertex, in this case the first - path_ctx->next_vertex = peek_queue_head(get_graph_vertices(ggctx)); - Assert(path_ctx->next_vertex); - - // start id - vertex *v = AG_GET_ARG_VERTEX(1); - path_ctx->vsid = *((int64 *)(&v->children[0])); - - // end id - determines which path function is used. - v = AG_GET_ARG_VERTEX(2); - path_ctx->veid = *((int64 *)(&v->children[0])); - - // get the left range index - if (PG_ARGISNULL(3) || is_gtype_null(AG_GET_ARG_GTYPE_P(3))) { - path_ctx->lidx = 1; - } else { - agtv_temp = get_gtype_value("age_vle", AG_GET_ARG_GTYPE_P(3), AGTV_INTEGER, true); - path_ctx->lidx = agtv_temp->val.int_value; - } - - // get the right range index. NULL means infinite - if (PG_ARGISNULL(4) || is_gtype_null(AG_GET_ARG_GTYPE_P(4))) { - path_ctx->uidx_infinite = true; - path_ctx->uidx = -1; - } else { - agtv_temp = get_gtype_value("age_vle", AG_GET_ARG_GTYPE_P(4), AGTV_INTEGER, true); - path_ctx->uidx = agtv_temp->val.int_value; - path_ctx->uidx_infinite = false; - } - // get edge direction - agtv_temp = get_gtype_value("age_vle", AG_GET_ARG_GTYPE_P(5), AGTV_INTEGER, true); - path_ctx->edge_direction = agtv_temp->val.int_value; - - // label name - if (PG_ARGISNULL(6) || is_gtype_null(AG_GET_ARG_GTYPE_P(6))) { - path_ctx->label_name = NULL; - } else { - agtv_temp = get_gtype_value("age_vle", AG_GET_ARG_GTYPE_P(6), AGTV_STRING, true); - path_ctx->label_name = agtv_temp->val.string.val; - } - - if (PG_ARGISNULL(7) || is_gtype_null(AG_GET_ARG_GTYPE_P(7))) { - path_ctx->properties = NULL; - } else { - path_ctx->properties = AG_GET_ARG_GTYPE_P(7); - - } - - create_hashtable(path_ctx); - - // initialize the dfs queues - path_ctx->dfs_vertex_queue = new_graphid_queue(); - path_ctx->dfs_edge_queue = new_graphid_queue(); - path_ctx->dfs_path_queue = new_graphid_queue(); - - // load in the starting edge(s) - load_initial_dfs_queues(path_ctx); - - path_ctx->next = NULL; - - MemoryContextSwitchTo(oldctx); - - return path_ctx; -} - -/* - * Helper function to get the specified edge's state. If it does not find it, it - * creates and initializes it. - */ -static edge_state_entry *get_edge_state(path_finding_context *path_ctx, graphid edge_id) { - edge_state_entry *ese = NULL; - bool found = false; - - // retrieve the edge_state_entry from the edge state hashtable - ese = (edge_state_entry *)hash_search(path_ctx->edge_state_hashtable, (void *)&edge_id, HASH_ENTER, &found); - - // if it isn't found, it needs to be created and initialized - if (!found) { - // the edge id is also the hash key for resolving collisions - ese->edge_id = edge_id; - ese->visited = false; - } - return ese; -} - -/* - * Helper function to get the id of the next vertex to move to. This is to - * simplify finding the next vertex due to the VLE edge's direction. - */ -static graphid get_next_vertex(path_finding_context *path_ctx, edge_entry *ee) -{ - graphid id; - - // get the result based on the specified VLE edge direction - switch (path_ctx->edge_direction) { - case CYPHER_REL_DIR_RIGHT: - id = get_end_id(ee); - break; - - case CYPHER_REL_DIR_LEFT: - id = get_start_id(ee); - break; - - case CYPHER_REL_DIR_NONE: - { - Queue *vertex_queue = NULL; - graphid parent_vertex_id; - - vertex_queue = path_ctx->dfs_vertex_queue; - /* - * Get the parent vertex of this edge. When we are looking at edges - * as bi-directional, where we go to next depends on where we came - * from. This is because we can go against an edge. - */ - parent_vertex_id = PEEK_GRAPHID_STACK(vertex_queue); - // find the terminal vertex - if (get_start_id(ee) == parent_vertex_id) - id = get_end_id(ee); - else if (get_end_id(ee) == parent_vertex_id) - id = get_start_id(ee); - else - elog(ERROR, "get_next_vertex: no parent match"); - - break; - } - - default: - elog(ERROR, "get_next_vertex: unknown edge direction"); - } - - return id; -} - -/* - * find one path between two vertices. - * - * This function will always return on either a valid path found (true) or none - * found (false). If one is found, the position (vertex & edge) will still be in - * the queue. Each successive invocation within the SRF will then look for the - * next available path until there aren't any left. - */ -static bool dfs_find_a_path_between(path_finding_context *path_ctx) -{ - Assert(path_ctx); - - Queue *vertex_queue = path_ctx->dfs_vertex_queue; - Queue *edge_queue = path_ctx->dfs_edge_queue; - Queue *path_queue = path_ctx->dfs_path_queue; - graphid end_vertex_id = path_ctx->veid; - - // while we have edges to process - while (!IS_GRAPHID_STACK_EMPTY(edge_queue)) { - graphid edge_id; - graphid next_vertex_id; - edge_state_entry *ese = NULL; - edge_entry *ee = NULL; - bool found = false; - - // get an edge, but leave it on the queue for now - edge_id = PEEK_GRAPHID_STACK(edge_queue); - // get the edge's state - ese = get_edge_state(path_ctx, edge_id); - /* - * If the edge is already in use, it means that the edge is in the path. - * So, we need to see if it is the last path entry (we are backing up - - * we need to remove the edge from the path queue and reset its state - * and from the edge queue as we are done with it) or an interior edge - * in the path (loop - we need to remove the edge from the edge queue - * and start with the next edge). - */ - if (ese->visited) { - graphid path_edge_id; - - // get the edge id on the top of the path queue (last edge) - path_edge_id = PEEK_GRAPHID_STACK(path_queue); - - // If the ids are the same, we're backing up. So, remove it from the path queue and reset visited. - if (edge_id == path_edge_id) { - pop_graphid_queue(path_queue); - ese->visited = false; - } - // now remove it from the edge queue - pop_graphid_queue(edge_queue); - - /* - * Remove its source vertex, if we are looking at edges as - * bi-directional. We only maintain the vertex queue when the - * edge_direction is CYPHER_REL_DIR_NONE. This is to save space - * and time. - */ - if (path_ctx->edge_direction == CYPHER_REL_DIR_NONE) - pop_graphid_queue(vertex_queue); - - // move to the next edge - continue; - } - - /* - * Mark it and push it on the path queue. There is no need to push it on - * the edge queue as it is already there. - */ - ese->visited = true; - push_graphid_queue(path_queue, edge_id); - - // now get the edge entry so we can get the next vertex to move to - ee = get_edge_entry(path_ctx->ggctx, edge_id); - next_vertex_id = get_next_vertex(path_ctx, ee); - - /* - * Is this the end of a path that meets our requirements? Is its length - * within the bounds specified? - */ - if (next_vertex_id == end_vertex_id && queue_size(path_queue) >= path_ctx->lidx && - (path_ctx->uidx_infinite || queue_size(path_queue) <= path_ctx->uidx)) - found = true; - - /* - * If we have found the end vertex but, we are not within our upper - * bounds, we need to back up. We still need to continue traversing - * the graph if we aren't within our lower bounds, though. - */ - if (next_vertex_id == end_vertex_id && !path_ctx->uidx_infinite && queue_size(path_queue) > path_ctx->uidx) - continue; - - // add in the edges for the next vertex if we won't exceed the bounds - if (path_ctx->uidx_infinite || queue_size(path_queue) < path_ctx->uidx) - add_edges(path_ctx, next_vertex_id); - - if (found) - return true; - } - - return false; -} - - -// add in valid vertex edges as part of the dfs path algorithm. -static void add_edges(path_finding_context *path_ctx, graphid vertex_id) { - Queue *edges = NULL; - - // get the vertex entry - vertex_entry *ve = get_vertex_entry(path_ctx->ggctx, vertex_id); - Assert(ve); - - Queue *vertex_queue = path_ctx->dfs_vertex_queue; - Queue *edge_queue = path_ctx->dfs_edge_queue; - - // set to the first edge for each edge list for the specified direction - QueueNode *edge_out = NULL; - if (path_ctx->edge_direction != CYPHER_REL_DIR_LEFT) { - edges = get_vertex_entry_edges_out(ve); - edge_out = (edges != NULL) ? get_list_head(edges) : NULL; - } - - QueueNode *edge_in = NULL; - if (path_ctx->edge_direction != CYPHER_REL_DIR_RIGHT) { - edges = get_vertex_entry_edges_in(ve); - edge_in = (edges != NULL) ? get_list_head(edges) : NULL; - } - - // set to the first selfloop edge - edges = get_vertex_entry_edges_self(ve); - QueueNode *edge_self = (edges != NULL) ? get_list_head(edges) : NULL; - - // add in valid vertex edges - while (edge_out || edge_in || edge_self) { - graphid edge_id; - - // get the edge_id from the next available edge - if (edge_out) - edge_id = get_graphid(edge_out); - else if (edge_in) - edge_id = get_graphid(edge_in); - else - edge_id = get_graphid(edge_self); - - // get the edge entry - edge_entry *ee = get_edge_entry(path_ctx->ggctx, edge_id); - Assert(ee); - - // get its state - edge_state_entry *ese = get_edge_state(path_ctx, edge_id); - - // Don't add any edges that we have already seen because they will cause a loop to form. - if (!ese->visited && check_edge_constraints(path_ctx, ee)) { - /* - * We need to maintain our source vertex for each edge added - * if the edge_direction is CYPHER_REL_DIR_NONE. This is due - * to the edges having a fixed direction and the dfs - * algorithm working strictly through edges. With an - * un-directional edge, you don't know the vertex that - * you just came from. So, we need to store it. - */ - if (path_ctx->edge_direction == CYPHER_REL_DIR_NONE) - push_graphid_queue(vertex_queue, get_vertex_entry_id(ve)); - push_graphid_queue(edge_queue, edge_id); - } - - // get the next edge - if (edge_out) - edge_out = next_queue_node(edge_out); - else if (edge_in) - edge_in = next_queue_node(edge_in); - else - edge_self = next_queue_node(edge_self); - } -} - -/* - * Helper function to create the VLE path container that holds the graphid array - * containing the found path. The path_size is the total number of vertices and - * edges in the path. - */ -static path_container *create_path_container(int64 path_size) -{ - path_container *vpc = NULL; - int container_size_bytes = 0; - - container_size_bytes = sizeof(graphid) * (path_size + 4); - - // allocate the container - vpc = palloc(container_size_bytes); - - // initialze the PG headers - SET_VARSIZE(vpc, container_size_bytes); - - // initialize the container - vpc->header = GT_FBINARY; - vpc->graphid_array_size = path_size; - vpc->container_size_bytes = container_size_bytes; - - return vpc; -} - -/* - * Helper function to build a path_container containing the graphid array - * from the path_queue. The graphid array will be a complete path (vertices and - * edges interleaved) - - * - * start vertex, first edge,... nth edge, end vertex - * - * The path_container is allocated in such a way as to wrap the array and - * include the following additional data - - * - * The header is to allow the graphid array to be encoded as an gtype - * container of type BINARY. This way the array doesn't need to be - * transformed back and forth. - * - * The graph oid to facilitate the retrieval of the correct vertex and edge - * entries. - * - * The total number of elements in the array. - * - * The total size of the container for copying. - */ -static path_container *build_path_container(path_finding_context *path_ctx) { - Queue *queue = path_ctx->dfs_path_queue; - graphid *graphid_array = NULL; - QueueNode *edge = NULL; - graphid vid = 0; - - if (!queue) - return NULL; - - int ssize = queue_size(queue); - - /* - * Create the container. Note that the path size will always be 2 times the - * number of edges plus 1 -> (u)-[e]-(v) - */ - path_container *vpc = create_path_container((ssize * 2) + 1); - - // set the graph_oid - vpc->graph_oid = path_ctx->graph_oid; - - // get the graphid_array from the container - graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); - - // get and store the start vertex - vid = path_ctx->vsid; - graphid_array[0] = vid; - - // get the head of the queue - edge = peek_queue_head(queue); - - /* - * We need to fill in the array from the back to the front. This is due - * to the order of the path queue - last in first out. - */ - int index = vpc->graphid_array_size - 2; - - // copy while we have an edge to copy - while (edge) { - // 0 is the vsid, we should never get here - Assert(index > 0); - - // store and set to the next edge - graphid_array[index] = get_graphid(edge); - edge = next_queue_node(edge); - - // we need to skip over the interior vertices - index -= 2; - } - - // now add in the interior vertices, starting from the first edge - for (index = 1; index < vpc->graphid_array_size - 1; index += 2) { - edge_entry *ee = get_edge_entry(path_ctx->ggctx, graphid_array[index]); - - graphid_array[index+1] = (vid == get_start_id(ee)) ? get_end_id(ee) : get_start_id(ee); - } - - // return the container - return vpc; -} - -/* - * 0 - gtype REQUIRED (graph name as string) - * 1 - gtype REQUIRED (start vertex as a vertex or the integer id) - * 2 - gtype OPTIONAL (end vertex as a vertex or the integer id) - * 3 - gtype REQUIRED (edge prototype to match as an edge) - * 4 - gtype OPTIONAL lidx (lower range index) - * 5 - gtype OPTIONAL uidx (upper range index) - * 6 - gtype REQUIRED edge direction (enum) as an integer. REQUIRED - */ -PG_FUNCTION_INFO_V1(gtype_vle); -Datum gtype_vle(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - bool found_a_path; - MemoryContext oldctx; - - // Initialization for the first call to the SRF - if (SRF_IS_FIRSTCALL()) { - // all of these arguments need to be non NULL - if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(5)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("age_vle: invalid NULL argument passed"))); - - funcctx = SRF_FIRSTCALL_INIT(); - - funcctx->user_fctx = build_vle_context(fcinfo, funcctx); - - //if (((path_finding_context *)funcctx->user_fctx)->lidx == 0) - // SRF_RETURN_NEXT(funcctx, PointerGetDatum(build_path_container(funcctx->user_fctx))); - // SRF_RETURN_NEXT(funcctx, PointerGetDatum(gtype_value_to_gtype(agtv_materialize_vle_path(build_path_container(funcctx->user_fctx))))); - - } - - // stuff done on every call of the function - funcctx = SRF_PERCALL_SETUP(); - - /* - * All work done in dfs_find_a_path needs to be done in a context that - * survives multiple SRF calls. So switch to the appropriate context. - */ - oldctx = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - found_a_path = dfs_find_a_path_between(funcctx->user_fctx); - - // switch back to a more volatile context - MemoryContextSwitchTo(oldctx); - - /* - * If we find a path, we need to convert the path_queue into a list that - * the outside world can use. - */ - if (found_a_path) { - path_container *vpc = build_path_container(funcctx->user_fctx); - - Assert(((path_finding_context *)funcctx->user_fctx)->dfs_path_queue > 0); - - // return the result and signal that the function is not yet done - SRF_RETURN_NEXT(funcctx, PointerGetDatum(create_variable_edge(vpc))); - } else { - hash_destroy(((path_finding_context *)funcctx->user_fctx)->edge_state_hashtable); - hash_destroy(((path_finding_context *)funcctx->user_fctx)->exists_hash); - SRF_RETURN_DONE(funcctx); - } -} - -VariableEdge *create_variable_edge(path_container *vpc) { - StringInfoData buffer; - initStringInfo(&buffer); - int32 size; - - graphid *graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); - int graphid_array_size = vpc->graphid_array_size; - - graph_context *ggctx = find_graph_context(vpc->graph_oid); - - Assert(ggctx != NULL); - - // header - reserve_from_buffer(&buffer, VARHDRSZ); - size = graphid_array_size - 2; - append_to_buffer(&buffer, (char *)&size, sizeof(prentry)); - - int cnt = 0; - for (int index = 0; index < graphid_array_size; index += 2) { - // get the vertex entry from the hashtable - if (index != 0 && index + 1 != graphid_array_size) { - vertex_entry *ve = get_vertex_entry(ggctx, graphid_array[index]); - - //char *label_name = get_rel_name(get_vertex_entry_label_table_oid(ve)); - graphid id = get_vertex_entry_id(ve); - gtype *prop = DATUM_GET_GTYPE_P(get_vertex_entry_properties(ve)); - Datum d = VERTEX_GET_DATUM(create_vertex(id, vpc->graph_oid, prop)); - - append_to_buffer(&buffer, DATUM_GET_VERTEX(d), VARSIZE(d)); - cnt++; - } - if (index + 1 == graphid_array_size) - break; - - // get the edge entry from the hashtable - edge_entry *ee = get_edge_entry(ggctx, graphid_array[index+1]); - - char *label_name = get_rel_name(get_edge_entry_label_table_oid(ee)); - graphid id = get_vertex_entry_id(ee); - graphid startid = get_start_id(ee); - graphid endid = get_end_id(ee); - gtype *prop = DATUM_GET_GTYPE_P(get_edge_entry_properties(ee)); - Datum d = EDGE_GET_DATUM(create_edge(id, startid, endid, vpc->graph_oid, prop)); - - append_to_buffer(&buffer, DATUM_GET_EDGE(d), VARSIZE(d)); - cnt++; - } - - VariableEdge *p = (VariableEdge *)buffer.data; - - SET_VARSIZE(p, buffer.len); - - return p; -} - -// function checks the edges in a MATCH clause to see if they are unique or not. -PG_FUNCTION_INFO_V1(_ag_enforce_edge_uniqueness); -Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) { - Datum *args; - bool *nulls; - Oid *types; - - int nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - // configure the hash table - HASHCTL exists_ctl; - MemSet(&exists_ctl, 0, sizeof(exists_ctl)); - exists_ctl.keysize = sizeof(int64); - exists_ctl.entrysize = sizeof(int64); - exists_ctl.hash = tag_hash; - - HTAB *exists_hash = hash_create(EXISTS_HTAB_NAME, nargs, &exists_ctl, HASH_ELEM | HASH_FUNCTION); - - // insert arguments into hash table - for (int i = 0; i < nargs; i++) { - - if (nulls[i]) - continue; - - if (types[i] == GRAPHIDOID) { - graphid edge_id = DatumGetInt64(args[i]); - - bool found; - int64 *value = (int64 *)hash_search(exists_hash, (void *)&edge_id, HASH_ENTER, &found); - - // if we found it, we're done, we have a duplicate - if (found) - PG_RETURN_BOOL(false); - - *value = edge_id; - - continue; - } - else if (types[i] == VARIABLEEDGEOID) { - VariableEdge *ve = DATUM_GET_VARIABLE_EDGE(args[i]); - char *ptr = &ve->children[1]; - for (int i = 0; i < ve->children[0]; i++, ptr = ptr + VARSIZE(ptr)) { - if (i % 2 == 1) { - continue; - } else { - edge *e = (edge *)ptr; - - graphid i = *((int64 *)(&e->children[0])); - bool found; - (int64 *)hash_search(exists_hash, (void *)&i, HASH_ENTER, &found); - if (found) - PG_RETURN_BOOL(false); - } - } - } - } - - PG_RETURN_BOOL(true); -} diff --git a/src/backend/utils/path_finding/global_graph.c b/src/backend/utils/path_finding/global_graph.c deleted file mode 100644 index 701f9b78..00000000 --- a/src/backend/utils/path_finding/global_graph.c +++ /dev/null @@ -1,662 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * Portions Copyright (c) 2020-2023, Apache Software Foundation - */ - - -#include "postgres.h" - -#include "access/heapam.h" -#include "access/relscan.h" -#include "access/skey.h" -#include "access/table.h" -#include "access/tableam.h" -#include "catalog/namespace.h" -#include "commands/label_commands.h" -#include "utils/lsyscache.h" -#include "utils/memutils.h" -#include "utils/rel.h" -#include "utils/snapmgr.h" -#include "commands/label_commands.h" -#include "common/hashfn.h" - -#include "catalog/ag_graph.h" -#include "catalog/ag_label.h" -#include "utils/global_graph.h" -#include "utils/queue.h" -#include "utils/gtype.h" -#include "catalog/ag_graph.h" -#include "catalog/ag_label.h" -#include "utils/graphid.h" -#include "utils/queue.h" - -// defines -#define VERTEX_HTAB_NAME "Vertex to edge lists " // added a space at end for -#define EDGE_HTAB_NAME "Edge to vertex mapping " // the graph name to follow -#define VERTEX_HTAB_INITIAL_SIZE 1000000 -#define EDGE_HTAB_INITIAL_SIZE 1000000 - -// internal data structures implementation - -// vertex entry for the vertex_hastable -typedef struct vertex_entry -{ - graphid id; // vertex id, it is also the hash key - Queue *in; // List of entering edges graphids (int64) - Queue *out; // List of exiting edges graphids (int64) - Queue *loop; // List of selfloop edges graphids (int64) - Oid oid; // the label table oid - Datum properties; // datum property value -} vertex_entry; - -// edge entry for the edge_hashtable -typedef struct edge_entry -{ - graphid id; // edge id, it is also the hash key - Oid oid; // the label table oid - Datum properties; // datum property value - graphid start_id; // start vertex - graphid end_id; // end vertex -} edge_entry; - -/* - * GRAPH global context per graph. They are chained together via next. - * Be aware that the global pointer will point to the root BUT that - * the root will change as new graphs are added to the top. - */ -typedef struct graph_context -{ - char *graph_name; // graph name - Oid graph_oid; // graph oid for searching - HTAB *vertex_hashtable; // hashtable to hold vertex edge lists - HTAB *edge_hashtable; // hashtable to hold edge to vertex map - TransactionId xmin; // transaction ids for this graph - TransactionId xmax; - CommandId curcid; // currentCommandId graph was created with - int64 vertex_cnt; // number of loaded vertices in this graph - int64 edge_cnt; // number of loaded edges in this graph - Queue *vertices; // vertices for vertex hashtable cleanup - struct graph_context *next; // next graph -} graph_context; - -// global variable to hold the per process GRAPH global context -static graph_context *global_graph_contexts = NULL; - -// declarations -// GRAPH global context functions -static void free_graph_context(graph_context *ggctx); -static void create_hashtables(graph_context *ggctx); -static void load_hashtables(graph_context *ggctx); -static void load_vertex_hashtable(graph_context *ggctx); -static void load_edge_hashtable(graph_context *ggctx); -static void freeze_hashtables(graph_context *ggctx); -static List *get_labels(Snapshot snapshot, Oid graph_oid, char label_type); -static bool insert_edge(graph_context *ggctx, graphid id, Datum properties, graphid start_id, graphid end_id, Oid oid); -static void insert_vertex(graph_context *ggctx, graphid start_id, graphid end_id, graphid id); -static void insert_vertex_entry(graph_context *ggctx, graphid id, Oid oid, Datum properties); - -//XXX: To Be Removed -bool is_ggctx_invalid(graph_context *ggctx) { - Snapshot snap = GetActiveSnapshot(); - return (ggctx->xmin != snap->xmin || ggctx->xmax != snap->xmax || ggctx->curcid != snap->curcid); - -} -/* - * Helper function to create the global vertex and edge hashtables. One - * hashtable will hold the vertex, its edges (both incoming and exiting) as a - * list, and its properties datum. The other hashtable will hold the edge, its - * properties datum, and its source and target vertex. - */ -static void create_hashtables(graph_context *ggctx) -{ - HASHCTL vertex_ctl; - HASHCTL edge_ctl; - - // initialize the vertex hashtable - MemSet(&vertex_ctl, 0, sizeof(vertex_ctl)); - vertex_ctl.keysize = sizeof(int64); - vertex_ctl.entrysize = sizeof(vertex_entry); - vertex_ctl.hash = tag_hash; - ggctx->vertex_hashtable = hash_create(VERTEX_HTAB_NAME, VERTEX_HTAB_INITIAL_SIZE, &vertex_ctl, HASH_ELEM | HASH_FUNCTION); - - // initialize the edge hashtable - MemSet(&edge_ctl, 0, sizeof(edge_ctl)); - edge_ctl.keysize = sizeof(int64); - edge_ctl.entrysize = sizeof(edge_entry); - edge_ctl.hash = tag_hash; - ggctx->edge_hashtable = hash_create(EDGE_HTAB_NAME, EDGE_HTAB_INITIAL_SIZE, &edge_ctl, HASH_ELEM | HASH_FUNCTION); -} - -// helper function to get a List of all label names for the specified graph -static List *get_labels(Snapshot snapshot, Oid graph_oid, char label_type) { - List *labels = NIL; - ScanKeyData scan_keys[2]; - Relation ag_label; - TableScanDesc scan_desc; - HeapTuple tuple; - TupleDesc tupdesc; - - // we need a valid snapshot - Assert(snapshot != NULL); - - // setup scan keys to get all edges for the given graph oid - ScanKeyInit(&scan_keys[1], Anum_ag_label_graph, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(graph_oid)); - ScanKeyInit(&scan_keys[0], Anum_ag_label_kind, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(label_type)); - - // setup the table to be scanned, ag_label in this case - ag_label = table_open(ag_label_relation_id(), ShareLock); - scan_desc = table_beginscan(ag_label, snapshot, 2, scan_keys); - - // get the tupdesc - we don't need to release this one - tupdesc = RelationGetDescr(ag_label); - - // get all of the label names - while((tuple = heap_getnext(scan_desc, ForwardScanDirection)) != NULL) { - Name label; - bool is_null = false; - - // something is wrong if this tuple isn't valid - Assert(HeapTupleIsValid(tuple)); - // get the label name - label = DatumGetName(heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null)); - Assert(!is_null); - // add it to our list - labels = lappend(labels, label); - } - - // close up scan - table_endscan(scan_desc); - table_close(ag_label, ShareLock); - - return labels; -} - -/* - * Helper function to insert one edge/edge->vertex, key/value pair, in the - * current GRAPH global edge hashtable. - */ -static bool insert_edge(graph_context *ggctx, graphid id, Datum properties, graphid start_id, graphid end_id, Oid oid) -{ - edge_entry *value = NULL; - bool found = false; - - // search for the edge - value = (edge_entry *)hash_search(ggctx->edge_hashtable, (void *)&id, HASH_ENTER, &found); - /* - * If we found the key, either we have a duplicate, or we made a mistake and - * inserted it already. Either way, this isn't good so don't insert it and - * return false. Likewise, if the value returned is NULL, don't do anything, - * just return false. This way the caller can decide what to do. - */ - if (found || value == NULL) - return false; - - // not sure if we really need to zero out the entry, as we set everything - MemSet(value, 0, sizeof(edge_entry)); - - /* - * Set the edge id - this is important as this is the hash key value used - * for hash function collisions. - */ - value->id = id; - value->properties = properties; - value->start_id = start_id; - value->end_id = end_id; - value->oid = oid; - - // increment the number of loaded edges - ggctx->edge_cnt++; - - return true; -} - -/* - * Helper function to insert an entire vertex into the current GRAPH global - * vertex hashtable. It will return false if there is a duplicate. - */ -static void insert_vertex_entry(graph_context *ggctx, graphid id, Oid oid, Datum properties) { - vertex_entry *ve = NULL; - bool found; - - // search for the vertex - ve = (vertex_entry *)hash_search(ggctx->vertex_hashtable, (void *)&id, HASH_ENTER, &found); - - // again, MemSet may not be needed here - MemSet(ve, 0, sizeof(vertex_entry)); - - /* - * Set the vertex id - this is important as this is the hash key value - * used for hash function collisions. - */ - ve->id = id; - // set the label table oid for this vertex - ve->oid = oid; - // set the datum vertex properties - ve->properties = properties; - // set the NIL edge list - ve->in = NULL; - ve->out = NULL; - ve->loop = NULL; - - // we also need to store the vertex id for clean up of vertex lists - ggctx->vertices = append_graphid(ggctx->vertices, id); - - // increment the number of loaded vertices - ggctx->vertex_cnt++; -} - -/* - * Helper function to append one edge to an existing vertex in the current - * global vertex hashtable. - */ -static void insert_vertex(graph_context *ggctx, graphid start_id, graphid end_id, graphid id) { - vertex_entry *value = NULL; - bool found = false; - bool is_selfloop = false; - - // is it a self loop - is_selfloop = (start_id == end_id); - - // search for the start vertex of the edge - value = (vertex_entry *)hash_search(ggctx->vertex_hashtable, (void *)&start_id, HASH_FIND, &found); - // vertices were preloaded so it must be there - Assert(found); - - // if it is a self loop, add the edge to loop and we're done - if (is_selfloop) { - value->loop = append_graphid(value->loop, id); - return; - } - - // add the edge to the out list of the start vertex - value->out = append_graphid(value->out, id); - - // search for the end vertex of the edge - value = (vertex_entry *)hash_search(ggctx->vertex_hashtable, (void *)&end_id, HASH_FIND, &found); - // vertices were preloaded so it must be there - Assert(found); - - // add the edge to the in list of the end vertex - value->in = append_graphid(value->in, id); -} - -// helper routine to load all vertices into the GRAPH global vertex hashtable -static void load_vertex_hashtable(graph_context *ggctx) { - ListCell *lc; - - Oid graph_oid = ggctx->graph_oid; - Oid graph_namespace_oid = get_namespace_oid(ggctx->graph_name, false); - - Snapshot snapshot = GetActiveSnapshot(); - - List *label_names = get_labels(snapshot, graph_oid, LABEL_TYPE_VERTEX); - - foreach (lc, label_names) { - TableScanDesc scan_desc; - HeapTuple tuple; - TupleDesc tupdesc; - - char *label_name = lfirst(lc); - - Oid oid = get_relname_relid(label_name, graph_namespace_oid); - - Relation graph_vertex_label = table_open(oid, ShareLock); - scan_desc = table_beginscan(graph_vertex_label, snapshot, 0, NULL); - - tupdesc = RelationGetDescr(graph_vertex_label); - - while((tuple = heap_getnext(scan_desc, ForwardScanDirection))) { - graphid id; - Datum properties; - bool isnull; - HeapTupleData htd; - - Assert(HeapTupleIsValid(tuple)); - - HeapTupleHeader hth = tuple->t_data; - htd.t_len = HeapTupleHeaderGetDatumLength(hth); - htd.t_data = hth; - - Assert(HeapTupleIsValid(tuple)); - // id - id = DATUM_GET_GRAPHID(heap_getattr(&htd, 1, tupdesc, &isnull)); - // properties - properties = heap_getattr(&htd, 2, tupdesc, &isnull); - - // insert vertex into vertex hashtable - insert_vertex_entry(ggctx, id, oid, properties); - } - - table_endscan(scan_desc); - table_close(graph_vertex_label, ShareLock); - } -} - -/* - * Helper function to load all of the GRAPH global hashtables (vertex & edge) - * for the current global context. - */ -static void load_hashtables(graph_context *ggctx) -{ - // initialize statistics - ggctx->vertex_cnt = 0; - ggctx->edge_cnt = 0; - - load_vertex_hashtable(ggctx); - load_edge_hashtable(ggctx); -} - -/* - * Helper routine to load all edges into the GRAPH global edge and vertex - * hashtables. - */ -static void load_edge_hashtable(graph_context *ggctx) { - Oid graph_oid; - Oid graph_namespace_oid; - Snapshot snapshot; - List *labels = NIL; - ListCell *lc; - - graph_oid = ggctx->graph_oid; - graph_namespace_oid = get_namespace_oid(ggctx->graph_name, false); - snapshot = GetActiveSnapshot(); - - labels = get_labels(snapshot, graph_oid, LABEL_TYPE_EDGE); - - foreach (lc, labels) { - HeapTuple tuple; - char *label = lfirst(lc); - - Oid oid = get_relname_relid(label, graph_namespace_oid); - Relation graph_edge_label = table_open(oid, ShareLock); - TableScanDesc scan_desc = table_beginscan(graph_edge_label, snapshot, 0, NULL); - TupleDesc tupdesc = RelationGetDescr(graph_edge_label); - Assert (tupdesc->natts == 4); - - while((tuple = heap_getnext(scan_desc, ForwardScanDirection)) != NULL) { - graphid id, start_id, end_id; - Datum properties; - bool inserted = false; - bool isnull; - HeapTupleHeader hth; - HeapTupleData tmptup, *htd; - - hth = tuple->t_data; - tmptup.t_len = HeapTupleHeaderGetDatumLength(hth); - tmptup.t_data = hth; - htd = &tmptup; - - Assert(HeapTupleIsValid(tuple)); - // id - id = DATUM_GET_GRAPHID(heap_getattr(htd, 1, tupdesc, &isnull)); - // start_id - start_id = DATUM_GET_GRAPHID(heap_getattr(htd, 2, tupdesc, &isnull)); - // end_id - end_id = DATUM_GET_GRAPHID(heap_getattr(htd, 3, tupdesc, &isnull)); - // properties - properties = heap_getattr(htd, 4, tupdesc, &isnull); - - // insert edge into edge hashtable - inserted = insert_edge(ggctx, id, properties, start_id, end_id, oid); - Assert (inserted); - - // insert the edge into the start and end vertices edge lists - insert_vertex(ggctx, start_id, end_id, id); - } - - // end the scan and close the relation - table_endscan(scan_desc); - table_close(graph_edge_label, ShareLock); - } -} - -static void freeze_hashtables(graph_context *ggctx) { - hash_freeze(ggctx->vertex_hashtable); - hash_freeze(ggctx->edge_hashtable); -} - -/* - * Helper function to free the entire specified GRAPH global context. After - * running this you should not use the pointer in ggctx. - */ -static void free_graph_context(graph_context *ggctx) -{ - QueueNode *curr_vertex = NULL; - - // don't do anything if NULL - if (!ggctx) - return; - - // free the graph name - pfree(ggctx->graph_name); - ggctx->graph_name = NULL; - - ggctx->graph_oid = InvalidOid; - ggctx->next = NULL; - - // free the vertex edge lists, starting with the head - curr_vertex = peek_queue_head(ggctx->vertices); - while (curr_vertex) { - QueueNode *next_vertex = NULL; - vertex_entry *value = NULL; - bool found = false; - graphid id; - - // get the next vertex in the list, if any - next_vertex = next_queue_node(curr_vertex); - - // get the current vertex id - id = get_graphid(curr_vertex); - - // retrieve the vertex entry - value = (vertex_entry *)hash_search(ggctx->vertex_hashtable, (void *)&id, HASH_FIND, &found); - Assert(found); - - // free the edge list associated with this vertex - free_queue(value->in); - free_queue(value->out); - free_queue(value->loop); - - value->in = NULL; - value->out = NULL; - value->loop = NULL; - - // move to the next vertex - curr_vertex = next_vertex; - } - - // free the vertices list - free_queue(ggctx->vertices); - ggctx->vertices = NULL; - - // free the hashtables - hash_destroy(ggctx->vertex_hashtable); - hash_destroy(ggctx->edge_hashtable); - - ggctx->vertex_hashtable = NULL; - ggctx->edge_hashtable = NULL; - - pfree(ggctx); - ggctx = NULL; -} - -/* - * It will create the - * context for the graph specified, provided it isn't already built and valid. - * During processing it will free (delete) all invalid GRAPH contexts. It - * returns the GRAPH global context for the specified graph. - */ -graph_context *manage_graph_contexts(char *graph_name, Oid graph_oid) { - graph_context *new_ggctx = NULL; - graph_context *curr_ggctx = NULL; - graph_context *prev_ggctx = NULL; - - // we need a higher context, or one that isn't destroyed by SRF exit - MemoryContext oldctx = MemoryContextSwitchTo(TopMemoryContext); - - // free the invalidated GRAPH global contexts first - prev_ggctx = NULL; - curr_ggctx = global_graph_contexts; - while (curr_ggctx) { - graph_context *next_ggctx = curr_ggctx->next; - - if (is_ggctx_invalid(curr_ggctx)) { - if (prev_ggctx == NULL) - global_graph_contexts = next_ggctx; - else - prev_ggctx->next = curr_ggctx->next; - - free_graph_context(curr_ggctx); - } else { - prev_ggctx = curr_ggctx; - } - - curr_ggctx = next_ggctx; - } - - curr_ggctx = global_graph_contexts; - while (curr_ggctx) { - if (curr_ggctx->graph_oid == graph_oid) { - MemoryContextSwitchTo(oldctx); - return curr_ggctx; - } - curr_ggctx = curr_ggctx->next; - } - - new_ggctx = palloc0(sizeof(graph_context)); - - if (global_graph_contexts) - new_ggctx->next = global_graph_contexts; - else - new_ggctx->next = NULL; - - global_graph_contexts = new_ggctx; - - new_ggctx->graph_name = pstrdup(graph_name); - new_ggctx->graph_oid = graph_oid; - - new_ggctx->xmin = GetActiveSnapshot()->xmin; - new_ggctx->xmax = GetActiveSnapshot()->xmax; - new_ggctx->curcid = GetActiveSnapshot()->curcid; - - new_ggctx->vertices = NULL; - - create_hashtables(new_ggctx); - load_hashtables(new_ggctx); - freeze_hashtables(new_ggctx); - - MemoryContextSwitchTo(oldctx); - - return new_ggctx; -} - -/* - * Helper function to retrieve a vertex_entry from the graph's vertex hash - * table. If there isn't one, it returns a NULL. The latter is necessary for - * checking if the vsid and veid entries exist. - */ -vertex_entry *get_vertex_entry(graph_context *ggctx, graphid id) -{ - vertex_entry *ve = NULL; - bool found = false; - - // retrieve the current vertex entry - ve = (vertex_entry *)hash_search(ggctx->vertex_hashtable, (void *)&id, HASH_FIND, &found); - return ve; -} - -// helper function to retrieve an edge_entry from the graph's edge hash table -edge_entry *get_edge_entry(graph_context *ggctx, graphid id) -{ - edge_entry *ee = NULL; - bool found = false; - - // retrieve the current edge entry - ee = (edge_entry *)hash_search(ggctx->edge_hashtable, (void *)&id, HASH_FIND, &found); - // it should be found, otherwise we have problems - Assert(found); - - return ee; -} - -/* - * Helper function to find the graph_context used by the specified - * graph_oid. If not found, it returns NULL. - */ -graph_context *find_graph_context(Oid graph_oid) { - graph_context *ggctx = global_graph_contexts; - - while(ggctx) { - if (ggctx->graph_oid == graph_oid) - return ggctx; - - ggctx = ggctx->next; - } - - return NULL; -} - -// graph vertices accessor -Queue *get_graph_vertices(graph_context *ggctx) { - return ggctx->vertices; -} - -// vertex_entry accessor functions -graphid get_vertex_entry_id(vertex_entry *ve) { - return ve->id; -} - -Queue *get_vertex_entry_edges_in(vertex_entry *ve) { - return ve->in; -} - -Queue *get_vertex_entry_edges_out(vertex_entry *ve) { - return ve->out; -} - -Queue *get_vertex_entry_edges_self(vertex_entry *ve) { - return ve->loop; -} - -Oid get_vertex_entry_label_table_oid(vertex_entry *ve) { - return ve->oid; -} - -Datum get_vertex_entry_properties(vertex_entry *ve) { - return ve->properties; -} - -// edge_entry accessor functions -graphid get_edge_entry_id(edge_entry *ee) { - return ee->id; -} - -Oid get_edge_entry_label_table_oid(edge_entry *ee) { - return ee->oid; -} - -Datum get_edge_entry_properties(edge_entry *ee) { - return ee->properties; -} - -graphid get_start_id(edge_entry *ee) { - return ee->start_id; -} - -graphid get_end_id(edge_entry *ee) { - return ee->end_id; -} diff --git a/src/include/access/ivfflat.h b/src/include/access/ivfflat.h deleted file mode 100644 index 3fceb66b..00000000 --- a/src/include/access/ivfflat.h +++ /dev/null @@ -1,324 +0,0 @@ -#ifndef IVFFLAT_H -#define IVFFLAT_H - -#include "postgres.h" - -#if PG_VERSION_NUM < 110000 -#error "Requires PostgreSQL 11+" -#endif - -#include "access/generic_xlog.h" -#include "access/parallel.h" -#include "access/reloptions.h" -#include "nodes/execnodes.h" -#include "port.h" /* for random() */ -#include "utils/sampling.h" -#include "utils/tuplesort.h" -#include "utils/vector.h" -#include "utils/gtype.h" -#if PG_VERSION_NUM >= 150000 -#include "common/pg_prng.h" -#endif - -#if PG_VERSION_NUM < 120000 -#include "access/relscan.h" -#endif - -#ifdef IVFFLAT_BENCH -#include "portability/instr_time.h" -#endif - - - -#define IVFFLAT_MAX_DIM 2000 - -/* Support functions */ -#define IVFFLAT_DISTANCE_PROC 1 -#define IVFFLAT_NORM_PROC 2 -#define IVFFLAT_KMEANS_DISTANCE_PROC 3 -#define IVFFLAT_KMEANS_NORM_PROC 4 - -#define IVFFLAT_VERSION 1 -#define IVFFLAT_MAGIC_NUMBER 0x14FF1A7 -#define IVFFLAT_PAGE_ID 0xFF84 - -/* Preserved page numbers */ -#define IVFFLAT_METAPAGE_BLKNO 0 -#define IVFFLAT_HEAD_BLKNO 1 /* first list page */ - -#define IVFFLAT_DEFAULT_ELEMENTS 0 - - -#define IVFFLAT_DEFAULT_LISTS 100 -#define IVFFLAT_MAX_LISTS 32768 - -/* Build phases */ -/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */ -#define PROGRESS_IVFFLAT_PHASE_KMEANS 2 -#define PROGRESS_IVFFLAT_PHASE_ASSIGN 3 -#define PROGRESS_IVFFLAT_PHASE_LOAD 4 - -#define IVFFLAT_LIST_SIZE(_dim) (offsetof(IvfflatListData, center) + VECTOR_SIZE(_dim)) - -#define IvfflatPageGetOpaque(page) ((IvfflatPageOpaque) PageGetSpecialPointer(page)) -#define IvfflatPageGetMeta(page) ((IvfflatMetaPageData *) PageGetContents(page)) - -#ifdef IVFFLAT_BENCH -#define IvfflatBench(name, code) \ - do { \ - instr_time start; \ - instr_time duration; \ - INSTR_TIME_SET_CURRENT(start); \ - (code); \ - INSTR_TIME_SET_CURRENT(duration); \ - INSTR_TIME_SUBTRACT(duration, start); \ - elog(INFO, "%s: %.3f ms", name, INSTR_TIME_GET_MILLISEC(duration)); \ - } while (0) -#else -#define IvfflatBench(name, code) (code) -#endif - -#if PG_VERSION_NUM >= 150000 -#define RandomDouble() pg_prng_double(&pg_global_prng_state) -#define RandomInt() pg_prng_uint32(&pg_global_prng_state) -#else -#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE) -#define RandomInt() random() -#endif - -/* Variables */ -extern int ivfflat_probes; -static int index_dims; -static int index_lists; -static relopt_kind ivfflat_relopt_kind; - -typedef struct VectorArrayData -{ - int length; - int maxlen; - int dim; - gtype *items; -} VectorArrayData; - -typedef VectorArrayData * VectorArray; - -typedef struct ListInfo -{ - BlockNumber blkno; - OffsetNumber offno; -} ListInfo; - -/* IVFFlat index options */ -typedef struct IvfflatOptions -{ - int32 vl_len_; /* varlena header (do not touch directly!) */ - int lists; /* number of lists */ - int dimensions; -} IvfflatOptions; - -typedef struct IvfflatSpool -{ - Tuplesortstate *sortstate; - Relation heap; - Relation index; -} IvfflatSpool; - -typedef struct IvfflatShared -{ - /* Immutable state */ - Oid heaprelid; - Oid indexrelid; - bool isconcurrent; - int scantuplesortstates; - - /* Worker progress */ - ConditionVariable workersdonecv; - - /* Mutex for mutable state */ - slock_t mutex; - - /* Mutable state */ - int nparticipantsdone; - double reltuples; - double indtuples; - -#ifdef IVFFLAT_KMEANS_DEBUG - double inertia; -#endif - -#if PG_VERSION_NUM < 120000 - ParallelHeapScanDescData heapdesc; /* must come last */ -#endif -} IvfflatShared; - -#if PG_VERSION_NUM >= 120000 -#define ParallelTableScanFromIvfflatShared(shared) \ - (ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(IvfflatShared))) -#endif - -typedef struct IvfflatLeader -{ - ParallelContext *pcxt; - int nparticipanttuplesorts; - IvfflatShared *ivfshared; - Sharedsort *sharedsort; - Snapshot snapshot; - gtype *ivfcenters; -} IvfflatLeader; - -typedef struct IvfflatBuildState -{ - /* Info */ - Relation heap; - Relation index; - IndexInfo *indexInfo; - - /* Settings */ - int dimensions; - int lists; - - /* Statistics */ - double indtuples; - double reltuples; - - /* Support functions */ - FmgrInfo *procinfo; - FmgrInfo *normprocinfo; - FmgrInfo *kmeansnormprocinfo; - Oid collation; - - /* Variables */ - VectorArray samples; - VectorArray centers; - ListInfo *listInfo; - gtype *normvec; - -#ifdef IVFFLAT_KMEANS_DEBUG - double inertia; - double *listSums; - int *listCounts; -#endif - - /* Sampling */ - BlockSamplerData bs; - ReservoirStateData rstate; - int rowstoskip; - - /* Sorting */ - Tuplesortstate *sortstate; - TupleDesc tupdesc; - TupleTableSlot *slot; - - /* Memory */ - MemoryContext tmpCtx; - - /* Parallel builds */ - IvfflatLeader *ivfleader; -} IvfflatBuildState; - -typedef struct IvfflatMetaPageData -{ - uint32 magicNumber; - uint32 version; - uint16 dimensions; - uint16 lists; -} IvfflatMetaPageData; - -typedef IvfflatMetaPageData * IvfflatMetaPage; - -typedef struct IvfflatPageOpaqueData -{ - BlockNumber nextblkno; - uint16 unused; - uint16 page_id; /* for identification of IVFFlat indexes */ -} IvfflatPageOpaqueData; - -typedef IvfflatPageOpaqueData * IvfflatPageOpaque; - -typedef struct IvfflatListData -{ - BlockNumber startPage; - BlockNumber insertPage; - gtype center; -} IvfflatListData; - -typedef IvfflatListData * IvfflatList; - -typedef struct IvfflatScanList -{ - pairingheap_node ph_node; - BlockNumber startPage; - double distance; -} IvfflatScanList; - -typedef struct IvfflatScanOpaqueData -{ - int probes; - bool first; - Buffer buf; - - /* Sorting */ - Tuplesortstate *sortstate; - TupleDesc tupdesc; - TupleTableSlot *slot; - bool isnull; - - /* Support functions */ - FmgrInfo *procinfo; - FmgrInfo *normprocinfo; - Oid collation; - - /* Lists */ - pairingheap *listQueue; - IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */ -} IvfflatScanOpaqueData; - -typedef IvfflatScanOpaqueData * IvfflatScanOpaque; - -#define VECTOR_ARRAY_SIZE(_length, _dim) \ - (sizeof(VectorArrayData) + ((_length) * VECTOR_SIZE(_dim))) - -#define VECTOR_ARRAY_OFFSET(_arr, _offset) \ - ((char*) (_arr)->items + ((_offset) * VECTOR_SIZE((_arr)->dim))) - -#define VectorArrayGet(_arr, _offset) \ - ((gtype *) VECTOR_ARRAY_OFFSET(_arr, _offset)) - -#define VectorArraySet(_arr, _offset, _val) \ - memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE((_arr)->dim)) - -/* Methods */ -VectorArray VectorArrayInit(int maxlen, int dimensions); -void VectorArrayFree(VectorArray arr); -void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers); -FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum); -bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, gtype *result); -int IvfflatGetLists(Relation index); -int IvfflatGetDimensions(Relation index); -void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, - BlockNumber startPage, ForkNumber forkNum); -void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state); -void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum); -Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum); -void IvfflatInitPage(Buffer buf, Page page); -void IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state); -void IvfflatInit(void); -PGDLLEXPORT void IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc); - -/* Index access methods */ -IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo); -void ivfflatbuildempty(Relation index); -bool ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique -#if PG_VERSION_NUM >= 140000 - , bool indexUnchanged -#endif - , IndexInfo *indexInfo); -IndexBulkDeleteResult *ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, - IndexBulkDeleteCallback callback, void *callback_state); -IndexBulkDeleteResult *ivfflatvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -IndexScanDesc ivfflatbeginscan(Relation index, int nkeys, int norderbys); -void ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys); -bool ivfflatgettuple(IndexScanDesc scan, ScanDirection dir); -void ivfflatendscan(IndexScanDesc scan); - -#endif diff --git a/src/include/access/vertex.h b/src/include/access/vertex.h new file mode 100644 index 00000000..ab6ef10b --- /dev/null +++ b/src/include/access/vertex.h @@ -0,0 +1,64 @@ +#ifndef ACCESS_VERTEX_H +#define ACCESS_VERTEX_H + +#include "access/amapi.h" +#include "access/itup.h" +#include "access/sdir.h" +#include "catalog/pg_am_d.h" +#include "common/hashfn.h" +#include "lib/stringinfo.h" +#include "storage/bufmgr.h" +#include "storage/lockdefs.h" +#include "utils/hsearch.h" +#include "utils/relcache.h" + +/* + * Descriptor for heap table scans. + */ +typedef struct VertexScanDescData +{ + + TableScanDescData rs_base; /* AM independent part of the descriptor */ + + /* Hash value of the scan key, ie, the hash key we seek */ + uint32 hashso_sk_hash; + + /* remember the buffer associated with primary bucket */ + Buffer hashso_bucket_buf; + + /* + * remember the buffer associated with primary bucket page of bucket being + * split. it is required during the scan of the bucket which is being + * populated during split operation. + */ + Buffer hashso_split_bucket_buf; + + /* Whether scan starts on bucket being populated due to split */ + bool hashso_buc_populated; + + /* + * Whether scanning bucket being split? The value of this parameter is + * referred only when hashso_buc_populated is true. + */ + bool hashso_buc_split; + /* info about killed items if any (killedItems is NULL if never used) */ + int *killedItems; /* currPos.items indexes of killed items */ + int numKilled; /* number of currently stored items */ + + /* + * Identify all the matching items on a page and save them in + * HashScanPosData + */ + HashScanPosData currPos; /* current position data */ + + +} VertexScanDescData; + +typedef struct VertexScanDescData *VertexHeapScanDesc; +bool +vertex_hash_next(TableScanDesc scan, ScanDirection dir); +bool +vertex_hash_first(TableScanDesc scan, ScanDirection dir); + + +#endif /* ACCESS_VERTEX_H */ diff --git a/src/include/executor/cypher_executor.h b/src/include/executor/cypher_executor.h index e3d5edb7..4343156f 100644 --- a/src/include/executor/cypher_executor.h +++ b/src/include/executor/cypher_executor.h @@ -25,21 +25,9 @@ #include "nodes/nodes.h" #include "nodes/plannodes.h" -#define DELETE_SCAN_STATE_NAME "Cypher Delete" -#define SET_SCAN_STATE_NAME "Cypher Set" #define CREATE_SCAN_STATE_NAME "Cypher Create" -#define MERGE_SCAN_STATE_NAME "Cypher Merge" Node *create_cypher_create_plan_state(CustomScan *cscan); extern const CustomExecMethods cypher_create_exec_methods; -Node *create_cypher_set_plan_state(CustomScan *cscan); -extern const CustomExecMethods cypher_set_exec_methods; - -Node *create_cypher_delete_plan_state(CustomScan *cscan); -extern const CustomExecMethods cypher_delete_exec_methods; - -Node *create_cypher_merge_plan_state(CustomScan *cscan); -extern const CustomExecMethods cypher_merge_exec_methods; - #endif diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h deleted file mode 100644 index 31427734..00000000 --- a/src/include/executor/cypher_utils.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * Portions Copyright (c) 2020-2023, Apache Software Foundation - * Portions Copyright (c) 1996-2010, Bitnine Global - */ -#ifndef AG_CYPHER_UTILS_H -#define AG_CYPHER_UTILS_H - -#include "access/heapam.h" -#include "access/table.h" -#include "access/tableam.h" -#include "nodes/execnodes.h" -#include "nodes/extensible.h" -#include "nodes/nodes.h" -#include "nodes/plannodes.h" - -#include "nodes/cypher_nodes.h" -#include "utils/gtype.h" - -// declaration of a useful postgres macro that isn't in a header file -#define DatumGetItemPointer(X) ((ItemPointer) DatumGetPointer(X)) -#define ItemPointerGetDatum(X) PointerGetDatum(X) - -/* - * When executing the children of the CREATE, SET, REMOVE, and - * DELETE clasues, we need to alter the command id in the estate - * and the snapshot. That way we can hide the modified tuples from - * the sub clauses that should not know what their parent clauses are - * doing. - */ -#define Increment_Estate_CommandId(estate) \ - estate->es_output_cid++; \ - estate->es_snapshot->curcid++; - -#define Decrement_Estate_CommandId(estate) \ - estate->es_output_cid--; \ - estate->es_snapshot->curcid--; - -typedef struct cypher_create_custom_scan_state -{ - CustomScanState css; - CustomScan *cs; - List *pattern; - List *path_values; - uint32 flags; - TupleTableSlot *slot; - Oid graph_oid; -} cypher_create_custom_scan_state; - -typedef struct cypher_set_custom_scan_state -{ - CustomScanState css; - CustomScan *cs; - cypher_update_information *set_list; - int flags; - Oid graph_oid; -} cypher_set_custom_scan_state; - -typedef struct cypher_delete_custom_scan_state -{ - CustomScanState css; - CustomScan *cs; - cypher_delete_information *delete_data; - int flags; - List *edge_labels; - Oid graph_oid; -} cypher_delete_custom_scan_state; - -typedef struct cypher_merge_custom_scan_state -{ - CustomScanState css; - CustomScan *cs; - cypher_merge_information *merge_information; - int flags; - cypher_create_path *path; - List *path_values; - Oid graph_oid; - AttrNumber merge_function_attr; - bool created_new_path; - bool found_a_path; - CommandId base_currentCommandId; -} cypher_merge_custom_scan_state; - -TupleTableSlot *populate_vertex_tts(TupleTableSlot *elemTupleSlot, - gtype_value *id, gtype_value *properties); -TupleTableSlot *populate_edge_tts( - TupleTableSlot *elemTupleSlot, gtype_value *id, gtype_value *startid, - gtype_value *endid, gtype_value *properties); - -ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, - char *label_name); -void destroy_entity_result_rel_info(ResultRelInfo *result_rel_info); - -bool entity_exists(EState *estate, Oid graph_oid, graphid id); -HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, - TupleTableSlot *elemTupleSlot, - EState *estate); -HeapTuple insert_entity_tuple_cid(ResultRelInfo *resultRelInfo, - TupleTableSlot *elemTupleSlot, - EState *estate, CommandId cid); - -#endif diff --git a/src/include/optimizer/cypher_createplan.h b/src/include/optimizer/cypher_createplan.h index e42e20b7..f27dd498 100644 --- a/src/include/optimizer/cypher_createplan.h +++ b/src/include/optimizer/cypher_createplan.h @@ -27,16 +27,6 @@ Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans); -Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans); -Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans); - -Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans); #endif diff --git a/src/include/parser/cypher_clause.h b/src/include/parser/cypher_clause.h index b23e22b3..47293465 100644 --- a/src/include/parser/cypher_clause.h +++ b/src/include/parser/cypher_clause.h @@ -37,7 +37,14 @@ struct cypher_clause Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause); + +Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause); +Query *transform_cypher_with(cypher_parsestate *cpstate, cypher_clause *clause); +List * +transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); +List * transform_group_clause(cypher_parsestate *cpstate, List *grouplist, List **groupingSets, + List **targetlist, List *sortClause, ParseExprKind exprKind); Query *cypher_parse_sub_analyze(Node *parseTree, cypher_parsestate *cpstate, CommonTableExpr *parentCTE, diff --git a/src/include/utils/global_graph.h b/src/include/utils/global_graph.h deleted file mode 100644 index bd42d9cc..00000000 --- a/src/include/utils/global_graph.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#ifndef POSTGRAPH_GLOBAL_GRAPH_H -#define POSTGRAPH_GLOBAL_GRAPH_H - -#include "utils/graphid.h" -#include "utils/queue.h" - -typedef struct vertex_entry vertex_entry; -typedef struct edge_entry edge_entry; -typedef struct graph_context graph_context; - -// GRAPH global context functions -graph_context *manage_graph_contexts(char *graph_name, Oid graph_oid); -graph_context *find_graph_context(Oid graph_oid); -bool is_ggctx_invalid(graph_context *ggctx); -// GRAPH retrieval functions -Queue *get_graph_vertices(graph_context *ggctx); -vertex_entry *get_vertex_entry(graph_context *ggctx, graphid vertex_id); -edge_entry *get_edge_entry(graph_context *ggctx, graphid edge_id); -// vertex entry accessor functions -graphid get_vertex_entry_id(vertex_entry *ve); -Queue *get_vertex_entry_edges_in(vertex_entry *ve); -Queue *get_vertex_entry_edges_out(vertex_entry *ve); -Queue *get_vertex_entry_edges_self(vertex_entry *ve); -Oid get_vertex_entry_label_table_oid(vertex_entry *ve); -Datum get_vertex_entry_properties(vertex_entry *ve); -// edge entry accessor functions -graphid get_edge_entry_id(edge_entry *ee); -Oid get_edge_entry_label_table_oid(edge_entry *ee); -Datum get_edge_entry_properties(edge_entry *ee); -graphid get_start_id(edge_entry *ee); -graphid get_end_id(edge_entry *ee); -#endif diff --git a/src/include/utils/path_finding.h b/src/include/utils/path_finding.h deleted file mode 100644 index f13312fd..00000000 --- a/src/include/utils/path_finding.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * Portions Copyright (c) 2020-2023, Apache Software Foundation - */ - -#ifndef AG_GTYPE_VLE_H -#define AG_GTYPE_VLE_H - -#include "utils/gtype.h" -#include "utils/global_graph.h" - -/* - * We declare the path_container here, and in this way, so that it may be - * used elsewhere. However, we keep the contents private by defining it in - * gtype_vle.c - */ -typedef struct path_container path_container; - -/* - * Function to take a AGTV_BINARY path_container and return a path as an - * gtype_value. - */ -gtype_value *agtv_materialize_vle_path(gtype *agt_arg_vpc); - -#endif diff --git a/src/include/utils/queue.h b/src/include/utils/queue.h deleted file mode 100644 index aa4d6d9a..00000000 --- a/src/include/utils/queue.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#ifndef POSTGRAPH_GRAPHID_DS_H -#define POSTGRAPH_GRAPHID_DS_H - -#include "utils/graphid.h" - -#define IS_GRAPHID_STACK_EMPTY(queue) \ - queue_size(queue) == 0 -#define PEEK_GRAPHID_STACK(queue) \ - (graphid) get_graphid(peek_queue_head(queue)) - -// declare the QueueNode -typedef struct QueueNode QueueNode; - -// declare the Queue container -typedef struct Queue Queue; - -// QueueNode access functions -QueueNode *next_queue_node(QueueNode *queuenode); -graphid get_graphid(QueueNode *queuenode); - -// graphid queue functions -// create a new Queue queue -Queue *new_graphid_queue(void); -// free a Queue queue -void free_graphid_queue(Queue *queue); -// push a graphid onto a Queue queue -void push_graphid_queue(Queue *queue, graphid id); -// pop (remove) a QueueNode from the top of the queue -graphid pop_graphid_queue(Queue *queue); -// peek (doesn't remove) at the head entry of a Queue queue -QueueNode *peek_queue_head(Queue *queue); -// peek (doesn't remove) at the tail entry of a Queue queue -QueueNode *peek_queue_tail(Queue *queue); -// return the size of a Queue queue -int64 queue_size(Queue *queue); - -// graphid list functions -/* - * Helper function to add a graphid to the end of a Queue container. - * If the container is NULL, it creates the container with the entry. - */ -Queue *append_graphid(Queue *container, graphid id); -// free a Queue container -void free_queue(Queue *container); -// return a reference to the head entry of a list -QueueNode *get_list_head(Queue *list); -// get the size of the passed list -int64 get_list_size(Queue *list); - -#endif From 574dbf7923ddc297dec7cdbaa302716e9a3ad7bd Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 12 Jul 2025 17:07:44 -0500 Subject: [PATCH 12/56] Put the sql folder in one file Yeah its big and cubersome, but its still easier than the divide it currently has --- .gitignore | 2 +- Makefile | 2 - postgraph--0.1.0.sql | 6775 +++++++++++++++++++++++++ sql/postgraph-aggregation.sql.in | 542 -- sql/postgraph-edge.sql.in | 260 - sql/postgraph-geometric.sql.in | 129 - sql/postgraph-graphid.sql.in | 210 - sql/postgraph-gtype-lists.sql.in | 102 - sql/postgraph-gtype.sql.in | 565 --- sql/postgraph-network.sql.in | 213 - sql/postgraph-number-functions.sql.in | 287 -- sql/postgraph-postgis.sql.in | 1364 ----- sql/postgraph-range.sql.in | 100 - sql/postgraph-string-functions.sql.in | 331 -- sql/postgraph-temporal.sql.in | 151 - sql/postgraph-traversal.sql.in | 195 - sql/postgraph-tsearch.sql.in | 102 - sql/postgraph-typecasting.sql.in | 1128 ---- sql/postgraph-variable_edge.sql.in | 246 - sql/postgraph-vertex.sql.in | 332 -- sql/postgraph.sql.in | 521 -- 21 files changed, 6776 insertions(+), 6781 deletions(-) create mode 100644 postgraph--0.1.0.sql delete mode 100644 sql/postgraph-aggregation.sql.in delete mode 100644 sql/postgraph-edge.sql.in delete mode 100644 sql/postgraph-geometric.sql.in delete mode 100644 sql/postgraph-graphid.sql.in delete mode 100644 sql/postgraph-gtype-lists.sql.in delete mode 100644 sql/postgraph-gtype.sql.in delete mode 100644 sql/postgraph-network.sql.in delete mode 100644 sql/postgraph-number-functions.sql.in delete mode 100644 sql/postgraph-postgis.sql.in delete mode 100644 sql/postgraph-range.sql.in delete mode 100644 sql/postgraph-string-functions.sql.in delete mode 100644 sql/postgraph-temporal.sql.in delete mode 100644 sql/postgraph-traversal.sql.in delete mode 100644 sql/postgraph-tsearch.sql.in delete mode 100644 sql/postgraph-typecasting.sql.in delete mode 100644 sql/postgraph-variable_edge.sql.in delete mode 100644 sql/postgraph-vertex.sql.in delete mode 100644 sql/postgraph.sql.in diff --git a/.gitignore b/.gitignore index 070f6df7..8536f05f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ build.sh .deps .DS_Store .vscode -postgraph--*.sql + src/backend/parser/temp_rules src/backend/parser/temp.text src/backend/parser/tmp_postgres diff --git a/Makefile b/Makefile index 67ea474b..597ab187 100644 --- a/Makefile +++ b/Makefile @@ -90,8 +90,6 @@ PG_LIBS += -ltree all: postgraph--0.1.0.sql -postgraph--0.1.0.sql: sql/postgraph.sql.in sql/postgraph-graphid.sql.in sql/postgraph-gtype.sql.in sql/postgraph-edge.sql.in sql/postgraph-vertex.sql.in sql/postgraph-variable_edge.sql.in sql/postgraph-traversal.sql.in sql/postgraph-typecasting.sql.in sql/postgraph-gtype-lists.sql.in sql/postgraph-string-functions.sql.in sql/postgraph-number-functions.sql.in sql/postgraph-temporal.sql.in sql/postgraph-network.sql.in sql/postgraph-tsearch.sql.in sql/postgraph-range.sql.in sql/postgraph-geometric.sql.in sql/postgraph-postgis.sql.in sql/postgraph-aggregation.sql.in - cat $^ > $@ ag_regress_dir = $(srcdir)/regress REGRESS_OPTS = --load-extension=postgis --load-extension=ltree --load-extension=postgraph --inputdir=$(ag_regress_dir) --outputdir=$(ag_regress_dir) --temp-instance=$(ag_regress_dir)/instance --port=61958 --encoding=UTF-8 diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql new file mode 100644 index 00000000..25ae65af --- /dev/null +++ b/postgraph--0.1.0.sql @@ -0,0 +1,6775 @@ +/* + * PostGraph + * Copyright (C) 2023 by PostGraph + * + * 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. + * + * Portions Copyright (c) 2020-2023, Apache Software Foundation + * Portions Copyright (c) 2019-2020, Bitnine Global + */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION PostGraph" to load this file. \quit + +-- +-- catalog tables +-- +CREATE TABLE ag_graph ( + graphid oid NOT NULL, + name name NOT NULL, + namespace regnamespace NOT NULL, + directed boolean NOT NULL +); + +CREATE UNIQUE INDEX ag_graph_graphid_index +ON ag_graph +USING btree (graphid); + +CREATE UNIQUE INDEX ag_graph_name_index +ON ag_graph +USING btree (name); + +CREATE UNIQUE INDEX ag_graph_namespace_index +ON ag_graph +USING btree (namespace); + +-- 0 is an invalid label ID +CREATE DOMAIN label_id +AS int +NOT NULL +CHECK (VALUE > 0 AND VALUE <= 65535); + +CREATE DOMAIN label_kind +AS "char" +NOT NULL +CHECK (VALUE = 'v' OR VALUE = 'e'); + +CREATE TABLE ag_label ( + name name NOT NULL, + graph oid NOT NULL, + id label_id, + kind label_kind, + relation regclass NOT NULL, + label_path public.ltree NULL, + CONSTRAINT fk_graph_oid FOREIGN KEY(graph) REFERENCES ag_graph(graphid) +); + +CREATE UNIQUE INDEX ag_label_name_graph_index +ON ag_label +USING btree (name, graph); + +CREATE UNIQUE INDEX ag_label_graph_oid_index +ON ag_label +USING btree (graph, id); + +CREATE UNIQUE INDEX ag_label_relation_index +ON ag_label +USING btree (relation); + +CREATE UNIQUE INDEX ag_label_path_graph_index +ON ag_label +USING btree (label_path, graph); + +CREATE INDEX ag_label_ltree_index +ON ag_label +USING gist (label_path public.gist_ltree_ops); + +CREATE TABLE session_graph_use ( + pid oid not null, + graph_oid oid not null +); + + +CREATE UNIQUE INDEX session_graph_use_index +ON session_graph_use +USING btree (pid, graph_oid); + + +------------------------------------- +-- Table AM interface functions +------------------------------------- +CREATE FUNCTION vertex_tableam_handler(internal) +RETURNS table_am_handler +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE ACCESS METHOD vertex TYPE TABLE +HANDLER vertex_tableam_handler; + +-- +-- catalog lookup functions +-- +CREATE FUNCTION _label_id(graph_name name, label_name name) +RETURNS label_id +LANGUAGE c +STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- utility functions +-- +CREATE FUNCTION create_graph(graph_name text) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION use_graph(graph_name text) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION create_graph_if_not_exists(graph_name name) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION drop_graph(graph_name name, cascade boolean = false) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION create_vlabel(graph_name name, label_name name) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION create_elabel(graph_name name, label_name name) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION alter_graph(graph_name name, operation cstring, new_value name) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION drop_label(graph_name name, label_name name, force boolean = false) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + + +CREATE FUNCTION create_unique_properties_constraint(graph_name name, label_name name) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + + +CREATE FUNCTION create_property_index(graph_name name, label_name name, property_name name, is_unique boolean = false) +RETURNS void +LANGUAGE c +AS 'MODULE_PATHNAME'; + +-- +-- graphid type +-- +CREATE TYPE graphid; + +CREATE FUNCTION graphid_in(cstring) +RETURNS graphid +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION graphid_out(graphid) +RETURNS cstring +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- binary I/O functions +CREATE FUNCTION graphid_send(graphid) +RETURNS bytea +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION graphid_recv(internal) +RETURNS graphid +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE TYPE graphid ( + INPUT = graphid_in, + OUTPUT = graphid_out, + SEND = graphid_send, + RECEIVE = graphid_recv, + INTERNALLENGTH = 8, + PASSEDBYVALUE, + ALIGNMENT = float8, + STORAGE = plain +); + +-- +-- gtype type and its I/O Routines +-- +CREATE TYPE gtype; + +CREATE FUNCTION gtype_in(cstring) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_out(gtype) +RETURNS cstring +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- binary I/O functions +CREATE FUNCTION gtype_send(gtype) +RETURNS bytea +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_recv(internal) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE TYPE gtype ( + INPUT = gtype_in, + OUTPUT = gtype_out, + SEND = gtype_send, + RECEIVE = gtype_recv, + LIKE = jsonb, + STORAGE = extended +); + +-- +-- vertex type and its I/O Routines +-- +CREATE TYPE vertex; + +-- throws an error +CREATE FUNCTION vertex_in(cstring) +RETURNS vertex +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION vertex_out(vertex) +RETURNS cstring +LANGUAGE c +STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION build_vertex(graphid, oid, gtype) +RETURNS vertex +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME'; + +CREATE TYPE vertex (INPUT = vertex_in, OUTPUT = vertex_out, LIKE = jsonb); + +-- +-- edge +-- +CREATE TYPE edge; + +CREATE FUNCTION edge_in(cstring) +RETURNS edge +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION edge_out(edge) +RETURNS cstring +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION build_edge(graphid, graphid, graphid, oid, gtype) +RETURNS edge +LANGUAGE c +STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE TYPE edge (INPUT = edge_in, OUTPUT = edge_out, LIKE = jsonb); + + +-- +-- path +-- +CREATE TYPE traversal; + +CREATE FUNCTION traversal_in(cstring) +RETURNS traversal +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION traversal_out(traversal) +RETURNS cstring +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION build_traversal(variadic "any") +RETURNS traversal +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME'; + +CREATE TYPE traversal (INPUT = traversal_in, OUTPUT = traversal_out, LIKE = jsonb); + +-- +-- partial traversal +-- +CREATE TYPE variable_edge; + +CREATE FUNCTION variable_edge_in(cstring) +RETURNS variable_edge +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION variable_edge_out(variable_edge) +RETURNS cstring +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION build_variable_edge(variadic "any") +RETURNS variable_edge +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME'; + +CREATE TYPE variable_edge (INPUT = variable_edge_in, OUTPUT = variable_edge_out, LIKE = jsonb); + +-- +-- MATCH edge uniqueness +-- +CREATE FUNCTION _ag_enforce_edge_uniqueness(VARIADIC "any") +RETURNS bool +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +as 'MODULE_PATHNAME'; + + +-- +-- There are times when the optimizer might eliminate +-- functions we need. Wrap the function with this to +-- prevent that from happening +-- +CREATE FUNCTION gtype_volatile_wrapper(agt gtype) RETURNS gtype AS $return_value$ +BEGIN RETURN agt; END; +$return_value$ LANGUAGE plpgsql +VOLATILE +CALLED ON NULL INPUT +PARALLEL SAFE; + +CREATE FUNCTION gtype_volatile_wrapper(agt edge) RETURNS edge AS $return_value$ +BEGIN RETURN agt; END; +$return_value$ LANGUAGE plpgsql +VOLATILE +CALLED ON NULL INPUT +PARALLEL SAFE; + +CREATE FUNCTION gtype_volatile_wrapper(agt vertex) RETURNS vertex AS $return_value$ +BEGIN RETURN agt; END; +$return_value$ LANGUAGE plpgsql +VOLATILE +CALLED ON NULL INPUT +PARALLEL SAFE; + +CREATE FUNCTION gtype_volatile_wrapper(agt variable_edge) RETURNS variable_edge AS $return_value$ +BEGIN RETURN agt; END; +$return_value$ LANGUAGE plpgsql +VOLATILE +CALLED ON NULL INPUT +PARALLEL SAFE; + +CREATE FUNCTION gtype_volatile_wrapper(agt traversal) RETURNS traversal +AS $return_value$ +BEGIN RETURN agt; END; +$return_value$ +LANGUAGE plpgsql +VOLATILE +CALLED ON NULL INPUT +PARALLEL SAFE; + +-- +-- functions for updating clauses +-- +-- This function is defined as a VOLATILE function to prevent the optimizer +-- from pulling up Query's for CREATE clauses. +CREATE FUNCTION _cypher_create_clause(internal) RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; +CREATE FUNCTION _cypher_set_clause(internal) RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; +CREATE FUNCTION _cypher_delete_clause(internal) RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; +CREATE FUNCTION _cypher_merge_clause(internal) RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; + +-- +-- query functions +-- +CREATE FUNCTION cypher(graph_name name, query_string cstring, params gtype = NULL) +RETURNS SETOF record +LANGUAGE c +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION get_cypher_keywords(OUT word text, OUT catcode "char", OUT catdesc text) +RETURNS SETOF record +LANGUAGE c +STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 10 ROWS 60 +AS 'MODULE_PATHNAME'; + +-- +-- Unnest (UNWIND Clause) Functions +-- +CREATE FUNCTION unnest (gtype, block_types boolean = false) +RETURNS SETOF gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_unnest'; + +CREATE FUNCTION unnest (vertex[], block_types boolean = false) +RETURNS SETOF vertex +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'vertex_unnest'; + +CREATE FUNCTION unnest (edge[], block_types boolean = false) +RETURNS SETOF edge +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_unnest'; + + +CREATE FUNCTION throw_error(gtype) +RETURNS gtype +LANGUAGE C +VOLATILE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- graphid - comparison operators (=, <>, <, >, <=, >=) +-- +CREATE FUNCTION graphid_eq(postgraph.graphid, postgraph.graphid) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR = ( + FUNCTION = graphid_eq, + LEFTARG = graphid, + RIGHTARG = graphid, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE FUNCTION graphid_ne(graphid, graphid) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <> ( + FUNCTION = graphid_ne, + LEFTARG = graphid, + RIGHTARG = graphid, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION graphid_lt(graphid, graphid) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION graphid_gt(graphid, graphid) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + + +CREATE FUNCTION graphid_le(graphid, graphid) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION graphid_ge(graphid, graphid) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + + +-- +-- graphid - B-tree support functions +-- +-- comparison support +CREATE FUNCTION graphid_btree_cmp(graphid, graphid) +RETURNS int +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- sort support +CREATE FUNCTION graphid_btree_sort(internal) +RETURNS void +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + + + +-- +-- graphid functions +-- +CREATE FUNCTION _graphid(label_id int, entry_id bigint) +RETURNS graphid +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION _label_name(graph_oid oid, graphid) +RETURNS cstring +LANGUAGE c +STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION _extract_label_id(graphid) +RETURNS label_id +LANGUAGE c +STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + + + +CREATE OPERATOR < ( + FUNCTION = graphid_lt, + LEFTARG = graphid, + RIGHTARG = graphid, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = graphid_gt, + LEFTARG = graphid, + RIGHTARG = graphid, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = graphid_le, + LEFTARG = graphid, + RIGHTARG = graphid, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +CREATE OPERATOR >= ( + FUNCTION = graphid_ge, + LEFTARG = graphid, + RIGHTARG = graphid, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); + +-- +-- graphid - hash operator class +-- +CREATE FUNCTION graphid_hash_cmp(graphid) +RETURNS INTEGER +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR CLASS graphid_ops_hash +DEFAULT FOR TYPE graphid +USING hash +AS + OPERATOR 1 =, + FUNCTION 1 graphid_hash_cmp(graphid); +-- +-- btree operator classes for graphid +-- +CREATE OPERATOR CLASS graphid_ops +DEFAULT FOR TYPE graphid +USING btree +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 graphid_btree_cmp (graphid, graphid), + FUNCTION 2 graphid_btree_sort (internal); + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- gtype - map literal (`{key: expr, ...}`) +-- +CREATE FUNCTION gtype_build_map(VARIADIC "any") +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_build_map() +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_build_map_noargs'; + +-- +-- gtype - access operators +-- +-- for series of `map.key` and `container[expr]` +CREATE FUNCTION gtype_field_access(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR -> ( + LEFTARG = gtype, + RIGHTARG = gtype, + FUNCTION = gtype_field_access +); + +-- +-- gtype - mathematical operators (+, -, *, /, %, ^) +-- +CREATE FUNCTION gtype_add(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR + (FUNCTION = gtype_add, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = +); + +CREATE FUNCTION gtype_sub(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR - (FUNCTION = gtype_sub, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_neg(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR - (FUNCTION = gtype_neg, RIGHTARG = gtype); + +CREATE FUNCTION gtype_mul(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR * (FUNCTION = gtype_mul, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = *); + +CREATE FUNCTION gtype_div(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR / (FUNCTION = gtype_div, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_mod(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR % (FUNCTION = gtype_mod, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_pow(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ^ (FUNCTION = gtype_pow, LEFTARG = gtype, RIGHTARG = gtype); + +-- +-- Vector Operators +-- +CREATE FUNCTION l2_distance(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <-> (FUNCTION = l2_distance, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = <->); + +CREATE FUNCTION inner_product(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_inner_product'; + +CREATE FUNCTION negative_inner_product(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE AS 'MODULE_PATHNAME', 'gtype_negative_inner_product'; + +CREATE OPERATOR <#> (FUNCTION = negative_inner_product, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = <#>); + +CREATE FUNCTION cosine_distance(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_cosine_distance'; + +CREATE OPERATOR <=> (FUNCTION = cosine_distance, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = <=>); + +CREATE FUNCTION l1_distance(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_l1_distance'; + +CREATE FUNCTION spherical_distance(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_spherical_distance'; + +CREATE FUNCTION dims(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_dims'; + +CREATE FUNCTION norm(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE AS 'MODULE_PATHNAME', 'gtype_norm'; + +CREATE FUNCTION l2_squared_distance(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_l2_squared_distance'; + +CREATE FUNCTION gtype_lt(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_gt(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_le(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_ge(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- gtype - comparison operators (=, <>, <, >, <=, >=) +-- +CREATE FUNCTION gtype_eq(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR = ( + FUNCTION = gtype_eq, + LEFTARG = gtype, + RIGHTARG = gtype, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE FUNCTION gtype_ne(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <> ( + FUNCTION = gtype_ne, + LEFTARG = gtype, + RIGHTARG = gtype, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + + +CREATE FUNCTION gtype_btree_cmp(gtype, gtype) +RETURNS INTEGER +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + + +CREATE OPERATOR < ( + FUNCTION = gtype_lt, + LEFTARG = gtype, + RIGHTARG = gtype, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = gtype_gt, + LEFTARG = gtype, + RIGHTARG = gtype, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = gtype_le, + LEFTARG = gtype, + RIGHTARG = gtype, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = gtype_ge, + LEFTARG = gtype, + RIGHTARG = gtype, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); + + +-- +-- gtype - btree operator class +-- +CREATE OPERATOR CLASS gtype_ops_btree +DEFAULT FOR TYPE gtype +USING btree +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >, + OPERATOR 5 >=, + FUNCTION 1 gtype_btree_cmp(gtype, gtype); + + + + +-- +-- gtype - hash operator class +-- +CREATE FUNCTION gtype_hash_cmp(gtype) +RETURNS INTEGER +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR CLASS gtype_ops_hash +DEFAULT FOR TYPE gtype +USING hash +AS + OPERATOR 1 =, + FUNCTION 1 gtype_hash_cmp(gtype); + +-- +-- gtype - access operators (->, ->>) +-- +CREATE FUNCTION gtype_object_field(gtype, text) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR -> (LEFTARG = gtype, RIGHTARG = text, FUNCTION = gtype_object_field); + +CREATE FUNCTION gtype_object_field_text(gtype, text) +RETURNS text +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ->> (LEFTARG = gtype, RIGHTARG = text, FUNCTION = gtype_object_field_text); + +CREATE FUNCTION gtype_array_element(gtype, int4) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR -> (LEFTARG = gtype, RIGHTARG = int4, FUNCTION = gtype_array_element); + +CREATE FUNCTION gtype_array_element_text(gtype, int4) +RETURNS text +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ->> (LEFTARG = gtype, RIGHTARG = int4, FUNCTION = gtype_array_element_text); + +-- +-- gtype - contains operators (@>, <@) +-- +CREATE FUNCTION gtype_contains(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR @> ( + LEFTARG = gtype, + RIGHTARG = gtype, + FUNCTION = gtype_contains, + COMMUTATOR = '<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION gtype_contained_by(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <@ ( + LEFTARG = gtype, + RIGHTARG = gtype, + FUNCTION = gtype_contained_by, + COMMUTATOR = '@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +-- +-- Key Existence Operators (?, ?|, ?&) +-- +CREATE FUNCTION gtype_exists(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ? ( + LEFTARG = gtype, + RIGHTARG = gtype, + FUNCTION = gtype_exists, + COMMUTATOR = '?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION gtype_exists_any(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ?| ( + LEFTARG = gtype, + RIGHTARG = gtype, + FUNCTION = gtype_exists_any, + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION gtype_exists_all(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ?& ( + LEFTARG = gtype, + RIGHTARG = gtype, + FUNCTION = gtype_exists_all, + RESTRICT = contsel, + JOIN = contjoinsel +); + +-- +-- gtype GIN support +-- +CREATE FUNCTION gin_compare_gtype(text, text) +RETURNS int +AS 'MODULE_PATHNAME' +LANGUAGE C +IMMUTABLE STRICT +PARALLEL SAFE; + +CREATE FUNCTION gin_extract_gtype(gtype, internal) RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C +IMMUTABLE +STRICT +PARALLEL SAFE; + +CREATE FUNCTION gin_extract_gtype_query(gtype, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C +IMMUTABLE +STRICT +PARALLEL SAFE; + +CREATE FUNCTION gin_consistent_gtype(internal, int2, gtype, int4, internal, internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C +IMMUTABLE +STRICT +PARALLEL SAFE; + +CREATE FUNCTION gin_triconsistent_gtype(internal, int2, gtype, int4, internal, internal, internal) +RETURNS bool +LANGUAGE C +IMMUTABLE +STRICT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR CLASS gin_gtype_ops +DEFAULT FOR TYPE gtype +USING gin +AS + OPERATOR 7 @>, + OPERATOR 9 ?(gtype, gtype), + /*OPERATOR 10 ?|(gtype, gtype), + OPERATOR 11 ?&(gtype, gtype),*/ + FUNCTION 1 gin_compare_gtype, + FUNCTION 2 gin_extract_gtype, + FUNCTION 3 gin_extract_gtype_query, + FUNCTION 4 gin_consistent_gtype, + FUNCTION 6 gin_triconsistent_gtype, +STORAGE text; + +CREATE FUNCTION generate_series (gtype, gtype) +RETURNS SETOF gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'generate_series_gtype'; + + +CREATE FUNCTION generate_series (gtype, gtype, gtype) +RETURNS SETOF gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'generate_series_step_gtype'; +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +-- +-- edge access opertators +-- +CREATE FUNCTION edge_property_access_gtype(edge, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR -> (LEFTARG = edge, RIGHTARG = gtype, FUNCTION = edge_property_access_gtype); + + +-- +-- edge equality operators +-- +CREATE FUNCTION edge_eq(edge, edge) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR = ( + FUNCTION = edge_eq, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE FUNCTION edge_ne(edge, edge) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <> ( + FUNCTION = edge_ne, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION edge_gt(edge, edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR > ( + FUNCTION = edge_gt, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = >, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE FUNCTION edge_ge(edge, edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR >= ( + FUNCTION = edge_ge, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = >=, + NEGATOR = <, + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); + + +CREATE FUNCTION edge_lt(edge, edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR < ( + FUNCTION = edge_lt, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = <, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE FUNCTION edge_le(edge, edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <= ( + FUNCTION = edge_le, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = <=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +-- +-- edge - B-tree support functions +-- +-- comparison support +CREATE FUNCTION edge_btree_cmp(edge, edge) +RETURNS int +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- sort support +CREATE FUNCTION edge_btree_sort(internal) +RETURNS void +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE AS 'MODULE_PATHNAME'; + +-- +-- define operator classes for graphid +-- +CREATE OPERATOR CLASS edge_ops +DEFAULT FOR TYPE edge +USING btree AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 edge_btree_cmp (edge, edge), + FUNCTION 2 edge_btree_sort (internal); + +-- +-- edge functions +-- +CREATE FUNCTION id(edge) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_id'; + +CREATE FUNCTION start_id(edge) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_start_id'; + +CREATE FUNCTION end_id(edge) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_end_id'; + +CREATE FUNCTION label(edge) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS +NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_label'; + +CREATE FUNCTION properties(edge) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_properties'; + +CREATE FUNCTION age_properties(edge) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_properties'; + +CREATE FUNCTION startnode(edge) +RETURNS vertex +LANGUAGE c +STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_startnode'; + +CREATE FUNCTION endnode(edge) +RETURNS vertex +LANGUAGE c +STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_endnode'; + + +CREATE FUNCTION type(edge) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME','edge_label'; + +CREATE FUNCTION keys(edge) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'edge_keys'; +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- vertex - equality operators (=, <>) +-- +CREATE FUNCTION vertex_eq(vertex, vertex) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR = ( + FUNCTION = vertex_eq, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE FUNCTION vertex_ne(vertex, vertex) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <> ( + FUNCTION = vertex_ne, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION vertex_lt(vertex, vertex) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR < ( + FUNCTION = vertex_lt, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE FUNCTION vertex_gt(vertex, vertex) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR > ( + FUNCTION = vertex_gt, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE FUNCTION vertex_le(vertex, vertex) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <= ( + FUNCTION = vertex_le, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE FUNCTION vertex_ge(vertex, vertex) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR >= ( + FUNCTION = vertex_ge, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); + +CREATE FUNCTION vertex_btree_cmp(vertex, vertex) +RETURNS INTEGER +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'vertex_btree_cmp'; + +CREATE OPERATOR CLASS vertex_ops_btree +DEFAULT FOR TYPE vertex +USING btree +AS +OPERATOR 1 <, +OPERATOR 2 <=, +OPERATOR 3 =, +OPERATOR 4 >, +OPERATOR 5 >=, +FUNCTION 1 vertex_btree_cmp(vertex, vertex); + +-- +-- vertex - access operators (->, ->> ) +-- +CREATE FUNCTION vertex_property_access(vertex, text) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR -> ( + LEFTARG = vertex, + RIGHTARG = text, + FUNCTION = vertex_property_access +); + +CREATE FUNCTION vertex_property_access_gtype(vertex, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR -> ( + LEFTARG = vertex, + RIGHTARG = gtype, + FUNCTION = vertex_property_access_gtype +); + +CREATE FUNCTION vertex_property_access_text(vertex, text) +RETURNS text +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ->> ( + LEFTARG = vertex, + RIGHTARG = text, + FUNCTION = vertex_property_access_text +); + + +-- +-- vertex - contains operators (@>, <@) +-- +CREATE FUNCTION vertex_contains(vertex, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR @> ( + LEFTARG = vertex, + RIGHTARG = gtype, + FUNCTION = vertex_contains, + COMMUTATOR = '<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION vertex_contained_by(gtype, vertex) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <@ ( + LEFTARG = gtype, + RIGHTARG = vertex, + FUNCTION = vertex_contained_by, + COMMUTATOR = '@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +-- +-- vertex - key existence operators (?, ?|, ?&) +-- +CREATE FUNCTION vertex_exists(vertex, text) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ? ( + LEFTARG = vertex, + RIGHTARG = text, + FUNCTION = vertex_exists, + COMMUTATOR = '?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION vertex_exists_any(vertex, text[]) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ?| ( + LEFTARG = vertex, + RIGHTARG = text[], + FUNCTION = vertex_exists_any, + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION vertex_exists_all(vertex, text[]) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ?& ( + LEFTARG = vertex, + RIGHTARG = text[], + FUNCTION = vertex_exists_all, + RESTRICT = contsel, + JOIN = contjoinsel +); + +-- +-- vertex functions +-- +CREATE FUNCTION id(vertex) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'vertex_id'; + +CREATE FUNCTION label(vertex) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'vertex_label'; + +CREATE FUNCTION properties(vertex) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'vertex_properties'; + +CREATE FUNCTION age_properties(vertex) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'vertex_properties'; + +CREATE FUNCTION type(vertex) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME','vertex_label'; + +CREATE FUNCTION keys(vertex) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE AS 'MODULE_PATHNAME', 'vertex_keys';/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- variable edge - equality operators (=, <>, >) +-- +CREATE FUNCTION variable_edge_eq(variable_edge, variable_edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR = ( + FUNCTION = variable_edge_eq, + LEFTARG = variable_edge, + RIGHTARG = variable_edge, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE FUNCTION variable_edge_ne(variable_edge, variable_edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <> ( + FUNCTION = variable_edge_ne, + LEFTARG = variable_edge, + RIGHTARG = variable_edge, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel, + HASHES +); + +CREATE FUNCTION variable_edge_gt(variable_edge, variable_edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR > ( + FUNCTION = variable_edge_gt, + LEFTARG = variable_edge, + RIGHTARG = variable_edge, + COMMUTATOR = >, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE FUNCTION variable_edge_ge(variable_edge, variable_edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR >= ( + FUNCTION = variable_edge_ge, + LEFTARG = variable_edge, + RIGHTARG = variable_edge, + COMMUTATOR = >=, + NEGATOR = <, + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); + + +CREATE FUNCTION variable_edge_lt(variable_edge, variable_edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR < ( + FUNCTION = variable_edge_lt, + LEFTARG = variable_edge, + RIGHTARG = variable_edge, + COMMUTATOR = <, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE FUNCTION variable_edge_le(variable_edge, variable_edge) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <= ( + FUNCTION = variable_edge_le, + LEFTARG = variable_edge, + RIGHTARG = variable_edge, + COMMUTATOR = <=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +-- +-- edge - B-tree support functions +-- +-- comparison support +CREATE FUNCTION variable_edge_btree_cmp(variable_edge, variable_edge) +RETURNS int +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- sort support +CREATE FUNCTION variable_edge_btree_sort(internal) +RETURNS void +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE AS 'MODULE_PATHNAME'; + +-- +-- define operator classes for graphid +-- +CREATE OPERATOR CLASS variable_edge_ops +DEFAULT FOR TYPE variable_edge +USING btree AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 variable_edge_btree_cmp (variable_edge, variable_edge), + FUNCTION 2 variable_edge_btree_sort (internal); + +-- +-- variable_edge containment operators (@>, @<, @@) +-- +CREATE FUNCTION edge_contained_in_variable_edge(edge, variable_edge) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR @> ( + FUNCTION = edge_contained_in_variable_edge, + LEFTARG = edge, + RIGHTARG = variable_edge, + --COMMUTATOR = '<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION variable_edge_contains_edge(variable_edge, edge) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <@ ( + FUNCTION = variable_edge_contains_edge, + LEFTARG = variable_edge, + RIGHTARG = edge, + COMMUTATOR = '@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION variable_edge_edges_overlap(variable_edge, variable_edge) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR && ( + FUNCTION = variable_edge_edges_overlap, + LEFTARG = variable_edge, + RIGHTARG = variable_edge +); + +-- +-- variable_edge functions +-- +CREATE FUNCTION nodes(variable_edge) RETURNS vertex[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'variable_edge_nodes'; + +CREATE FUNCTION relationships(variable_edge) RETURNS edge[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'variable_edge_edges'; + +CREATE FUNCTION edges(variable_edge) RETURNS edge[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'variable_edge_edges'; + +CREATE FUNCTION length(variable_edge) RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'variable_edge_length'; + +CREATE OPERATOR @-@ ( + FUNCTION = length, + RIGHTARG = variable_edge +); + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +-- +-- traversal - equality operators (=, <>, >) +-- +CREATE FUNCTION traversal_eq(traversal, traversal) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR = ( + FUNCTION = traversal_eq, + LEFTARG = traversal, + RIGHTARG = traversal, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE FUNCTION traversal_ne(traversal, traversal) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <> ( + FUNCTION = traversal_ne, + LEFTARG = traversal, + RIGHTARG = traversal, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel, + HASHES +); + +CREATE FUNCTION traversal_gt(traversal, traversal) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR > ( + FUNCTION = traversal_gt, + LEFTARG = traversal, + RIGHTARG = traversal, + COMMUTATOR = >, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE FUNCTION traversal_ge(traversal, traversal) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR >= ( + FUNCTION = traversal_ge, + LEFTARG = traversal, + RIGHTARG = traversal, + COMMUTATOR = >=, + NEGATOR = <, + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); + + +CREATE FUNCTION traversal_lt(traversal, traversal) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR < ( + FUNCTION = traversal_lt, + LEFTARG = traversal, + RIGHTARG = traversal, + COMMUTATOR = <, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE FUNCTION traversal_le(traversal, traversal) RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <= ( + FUNCTION = traversal_le, + LEFTARG = traversal, + RIGHTARG = traversal, + COMMUTATOR = <=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +-- +-- traversal - B-tree support functions +-- +-- comparison support +CREATE FUNCTION traversal_btree_cmp(traversal, traversal) +RETURNS int +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- sort support +CREATE FUNCTION traversal_btree_sort(internal) +RETURNS void +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE AS 'MODULE_PATHNAME'; + +-- +-- define operator classes for graphid +-- +CREATE OPERATOR CLASS traversal_ops +DEFAULT FOR TYPE traversal +USING btree AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 traversal_btree_cmp (traversal, traversal), + FUNCTION 2 traversal_btree_sort (internal); + +-- +-- traversal functions +-- +CREATE FUNCTION relationships(traversal) RETURNS edge[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'traversal_edges'; + +CREATE FUNCTION nodes(traversal) RETURNS vertex[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'traversal_nodes'; + +CREATE FUNCTION size(traversal) RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'traversal_size'; + +CREATE FUNCTION length(traversal) RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'traversal_length'; + +CREATE OPERATOR @-@ ( + FUNCTION = length, + RIGHTARG = traversal +); +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- graphid typecasting +-- +CREATE FUNCTION graphid_to_gtype(graphid) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (graphid AS gtype) WITH FUNCTION graphid_to_gtype(graphid); + +CREATE FUNCTION gtype_to_graphid(gtype) +RETURNS graphid +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS graphid) WITH FUNCTION gtype_to_graphid(gtype) AS IMPLICIT; + + +-- +-- gtype typecasting +-- +-- gtype -> text +CREATE FUNCTION gtype_to_text(gtype) +RETURNS text +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS text) +WITH FUNCTION gtype_to_text(gtype); + +-- gtype -> text[] +CREATE FUNCTION gtype_to_text_array(gtype) +RETURNS text[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS text[]) +WITH FUNCTION gtype_to_text_array(gtype); + +-- text[] -> gtype +CREATE FUNCTION text_array_to_gtype(text[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (text[] as gtype) +WITH FUNCTION text_array_to_gtype(text[]); + +-- text -> gtype +CREATE FUNCTION text_to_gtype(text) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (text AS gtype) +WITH FUNCTION text_to_gtype(text); + +-- gtype -> boolean +CREATE FUNCTION gtype_to_bool(gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS boolean) +WITH FUNCTION gtype_to_bool(gtype) +AS IMPLICIT; + +-- boolean -> gtype +CREATE FUNCTION bool_to_gtype(boolean) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (boolean AS gtype) +WITH FUNCTION bool_to_gtype(boolean); + +-- boolean[] -> gtype +CREATE FUNCTION bool_array_to_gtype(boolean[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (boolean[] AS gtype) +WITH FUNCTION bool_array_to_gtype(boolean[]); + + +-- float8 -> gtype +CREATE FUNCTION float8_to_gtype(float8) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (float8 AS gtype) +WITH FUNCTION float8_to_gtype(float8); + +-- gtype -> float8 +CREATE FUNCTION gtype_to_float8(gtype) +RETURNS float8 +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS float8) +WITH FUNCTION gtype_to_float8(gtype); + +-- float8[] -> gtype +CREATE FUNCTION float8_array_to_gtype(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (float8[] as gtype) +WITH FUNCTION float8_array_to_gtype(float8[]); + + +-- gtype -> float8[] +CREATE FUNCTION gtype_to_float8_array(gtype) +RETURNS float8[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS float8[]) +WITH FUNCTION gtype_to_float8_array(gtype); + +-- gtype -> float4[] +CREATE FUNCTION gtype_to_float4_array(gtype) +RETURNS float4[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS float4[]) +WITH FUNCTION gtype_to_float4_array(gtype); + +-- float4[] -> gtype +CREATE FUNCTION float4_array_to_gtype(float4[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (float4[] as gtype) +WITH FUNCTION float4_array_to_gtype(float4[]); + +-- gtype -> numeric +CREATE FUNCTION gtype_to_numeric(gtype) +RETURNS numeric +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS numeric) +WITH FUNCTION gtype_to_numeric(gtype); + +-- gtype -> numeric[] +CREATE FUNCTION gtype_to_numeric_array(gtype) +RETURNS numeric[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS numeric[]) +WITH FUNCTION gtype_to_numeric_array(gtype); + +-- numeric[] -> gtype +CREATE FUNCTION numeric_array_to_gtype(numeric[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (numeric[] as gtype) +WITH FUNCTION numeric_array_to_gtype(numeric[]); + +-- int8 -> gtype +CREATE FUNCTION int8_to_gtype(int8) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (int8 AS gtype) +WITH FUNCTION int8_to_gtype(int8); + +-- gtype -> int8 +CREATE FUNCTION gtype_to_int8(gtype) +RETURNS bigint +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS bigint) +WITH FUNCTION gtype_to_int8(gtype) +AS ASSIGNMENT; + +-- int4 -> gtype +CREATE FUNCTION int4_to_gtype(int4) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (int4 AS gtype) +WITH FUNCTION int4_to_gtype(int4); + +-- int8[] -> gtype +CREATE FUNCTION int8_array_to_gtype(int8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (int8[] as gtype) +WITH FUNCTION int8_array_to_gtype(int8[]); + +-- int4[] -> gtype +CREATE FUNCTION int4_array_to_gtype(int4[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (int4[] as gtype) +WITH FUNCTION int4_array_to_gtype(int4[]); + +-- int2[] -> gtype +CREATE FUNCTION int2_array_to_gtype(int2[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (int2[] as gtype) +WITH FUNCTION int2_array_to_gtype(int2[]); + + +-- timestamp -> gtype +CREATE FUNCTION timestamp_to_gtype(timestamp) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (timestamp AS gtype) +WITH FUNCTION timestamp_to_gtype(timestamp); + +-- timestamp[] -> gtype +CREATE FUNCTION timestamp_array_to_gtype(timestamp[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (timestamp[] as gtype) +WITH FUNCTION timestamp_array_to_gtype(timestamp[]); + +-- timestamptz -> gtype +CREATE FUNCTION timestamptz_to_gtype(timestamptz) +RETURNS gtype +LANGUAGE c +IMMUTABLE RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (timestamptz AS gtype) +WITH FUNCTION timestamptz_to_gtype(timestamptz); + +-- timestamptz[] -> gtype +CREATE FUNCTION timestamptz_array_to_gtype(timestamptz[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (timestamptz[] as gtype) +WITH FUNCTION timestamptz_array_to_gtype(timestamptz[]); + +-- date -> gtype +CREATE FUNCTION date_to_gtype(date) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (date AS gtype) +WITH FUNCTION date_to_gtype(date); + +-- date[] -> gtype +CREATE FUNCTION date_array_to_gtype(date[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (date[] as gtype) +WITH FUNCTION date_array_to_gtype(date[]); + + +-- time -> gtype +CREATE FUNCTION time_to_gtype(time) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (time AS gtype) +WITH FUNCTION time_to_gtype(time); + +-- time[] -> gtype +CREATE FUNCTION time_array_to_gtype(time[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (time[] as gtype) +WITH FUNCTION time_array_to_gtype(time[]); + +-- timetz -> gtype +CREATE FUNCTION timetz_to_gtype(timetz) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (timetz AS gtype) +WITH FUNCTION timetz_to_gtype(timetz); + +-- timetz[] -> gtype +CREATE FUNCTION timetz_array_to_gtype(timetz[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (timetz[] as gtype) +WITH FUNCTION timetz_array_to_gtype(timetz[]); + +-- gtype -> timestamp +CREATE FUNCTION gtype_to_timestamp(gtype) +RETURNS timestamp +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS timestamp) +WITH FUNCTION gtype_to_timestamp(gtype); + +-- gtype -> date +CREATE FUNCTION gtype_to_date(gtype) +RETURNS date +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS date) +WITH FUNCTION gtype_to_date(gtype); + +-- gtype -> timestamptz +CREATE FUNCTION gtype_to_timestamptz(gtype) +RETURNS timestamptz +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS timestamptz) +WITH FUNCTION gtype_to_timestamptz(gtype); + +-- gtype -> time +CREATE FUNCTION gtype_to_time(gtype) +RETURNS time +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS time) +WITH FUNCTION gtype_to_time(gtype); + +-- gtype -> timetz +CREATE FUNCTION gtype_to_timetz(gtype) +RETURNS timetz +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS timetz) +WITH FUNCTION gtype_to_timetz(gtype); + +-- interval -> gtype +CREATE FUNCTION interval_to_gtype(interval) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (interval AS gtype) +WITH FUNCTION interval_to_gtype(interval); + +-- gtype -> interval +CREATE FUNCTION gtype_to_interval(gtype) +RETURNS interval +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS interval) +WITH FUNCTION gtype_to_interval(gtype); + +-- interval[] -> gtype +CREATE FUNCTION interval_array_to_gtype(interval[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'array_to_gtype'; + +CREATE CAST (interval[] as gtype) +WITH FUNCTION interval_array_to_gtype(interval[]); + + + +-- gtype -> int8[] +CREATE FUNCTION gtype_to_int8_array(gtype) +RETURNS bigint[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS bigint[]) +WITH FUNCTION gtype_to_int8_array(gtype); + +-- gtype -> int4 +CREATE FUNCTION gtype_to_int4(gtype) +RETURNS int +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS int) WITH FUNCTION gtype_to_int4(gtype) AS IMPLICIT; + +-- gtype -> int2 +CREATE FUNCTION gtype_to_int2(gtype) +RETURNS smallint +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS smallint) +WITH FUNCTION gtype_to_int2(gtype); + +-- gtype -> int2[] +CREATE FUNCTION gtype_to_int2_array(gtype) +RETURNS smallint[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS smallint[]) WITH FUNCTION gtype_to_int2_array(gtype); + +-- gtype -> int4[] +CREATE FUNCTION gtype_to_int4_array(gtype) +RETURNS int[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype AS int[]) WITH FUNCTION gtype_to_int4_array(gtype); + +-- inet -> gtype +CREATE FUNCTION inet_to_gtype(inet) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (inet as gtype) WITH FUNCTION inet_to_gtype(inet); + +-- gtype -> inet +CREATE FUNCTION gtype_to_inet(gtype) +RETURNS inet +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as inet) WITH FUNCTION gtype_to_inet(gtype); + +-- gtype -> cidr +CREATE FUNCTION gtype_to_cidr(gtype) +RETURNS cidr +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as cidr) WITH FUNCTION gtype_to_cidr(gtype); + +-- point -> gtype +CREATE FUNCTION point_to_gtype(point) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (point AS gtype) +WITH FUNCTION point_to_gtype(point); + + +-- gtype -> point +CREATE FUNCTION gtype_to_point(gtype) +RETURNS point +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as point) WITH FUNCTION gtype_to_point(gtype); + +-- path -> gtype +CREATE FUNCTION path_to_gtype(path) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (path AS gtype) +WITH FUNCTION path_to_gtype(path); + +-- gtype -> path +CREATE FUNCTION gtype_to_path(gtype) +RETURNS path +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as path) WITH FUNCTION gtype_to_path(gtype); + +-- polygon -> gtype +CREATE FUNCTION polygon_to_gtype(polygon) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (polygon AS gtype) +WITH FUNCTION polygon_to_gtype(polygon); + +-- gtype -> polygon +CREATE FUNCTION gtype_to_polygon(gtype) +RETURNS polygon +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as polygon) WITH FUNCTION gtype_to_polygon(gtype); + + +-- geometry -> gtype +CREATE FUNCTION geometry_to_gtype(public.geometry) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (public.geometry AS gtype) +WITH FUNCTION geometry_to_gtype(public.geometry); + +-- gtype -> geometry +CREATE FUNCTION gtype_to_geometry(gtype) +RETURNS public.geometry +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as public.geometry) WITH FUNCTION gtype_to_geometry(gtype); + +-- gtype -> box3d +CREATE FUNCTION gtype_to_box3d(gtype) +RETURNS public.box3d +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as public.box3d) WITH FUNCTION gtype_to_box3d(gtype); + +-- gtype -> box2d +CREATE FUNCTION gtype_to_box2d(gtype) +RETURNS public.box2d +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as public.box2d) WITH FUNCTION gtype_to_box2d(gtype); + +-- box -> gtype +CREATE FUNCTION box_to_gtype(box) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (box AS gtype) +WITH FUNCTION box_to_gtype(box); + +-- gtype -> box +CREATE FUNCTION gtype_to_box(gtype) +RETURNS box +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as box) WITH FUNCTION gtype_to_box(gtype); + +-- tsvector -> gtype +CREATE FUNCTION tsvector_to_gtype(tsvector) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (tsvector as gtype) WITH FUNCTION tsvector_to_gtype(tsvector); + +-- gtype -> tsvector +CREATE FUNCTION gtype_to_tsvector(gtype) +RETURNS tsvector +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as tsvector) WITH FUNCTION gtype_to_tsvector(gtype); + +-- tsquery -> gtype +CREATE FUNCTION tsquery_to_gtype(tsquery) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (tsquery as gtype) WITH FUNCTION tsquery_to_gtype(tsquery); + +-- gtype -> tsquery +CREATE FUNCTION gtype_to_tsquery(gtype) +RETURNS tsquery +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (gtype as tsquery) WITH FUNCTION gtype_to_tsquery(gtype); + +-- +-- gtype - typecasting to other gtype types +-- +CREATE FUNCTION toboolean (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_toboolean'; + +CREATE FUNCTION tofloat (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tofloat'; + +CREATE FUNCTION tointeger(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tointeger'; + +CREATE FUNCTION tostring (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tostring'; + +CREATE FUNCTION tobytea (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tobytea'; + +CREATE FUNCTION tonumeric (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tonumeric'; + +CREATE FUNCTION totimestamp (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_totimestamp'; + +CREATE FUNCTION totimestamptz (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION totime(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION totimetz(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION tointerval(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION todate(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION tovector(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION toinet(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_toinet'; + +CREATE FUNCTION tocidr(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tocidr'; + +CREATE FUNCTION tomacaddr(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tomacaddr'; + +CREATE FUNCTION tomacaddr8(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tomacaddr8'; + +CREATE FUNCTION topoint(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_topoint'; + +CREATE FUNCTION tolseg(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tolseg'; + +CREATE FUNCTION toline(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_toline'; + +CREATE FUNCTION topath(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_topath'; + +CREATE FUNCTION topolygon(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_topolygon'; + +CREATE FUNCTION tocircle(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tocircle'; + +CREATE FUNCTION tobox(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tobox'; + +CREATE FUNCTION tobox2d(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tobox2d'; + +CREATE FUNCTION tobox3d(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tobox3d'; + +CREATE FUNCTION tospheroid(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tospheroid'; + +CREATE FUNCTION togeometry(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_togeometry'; + +CREATE FUNCTION totsvector(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_totsvector'; + +CREATE FUNCTION totsquery(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_totsquery'; + + +CREATE FUNCTION tointrange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tointrange'; + +CREATE FUNCTION tointmultirange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tointmultirange'; + +CREATE FUNCTION tonumrange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tonumrange'; + +CREATE FUNCTION tonummultirange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tonummultirange'; + +CREATE FUNCTION totsrange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_totsrange'; + +CREATE FUNCTION totsmultirange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_totsmultirange'; + +CREATE FUNCTION totstzrange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_totstzrange'; + +CREATE FUNCTION totstzmultirange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_totstzmultirange'; + +CREATE FUNCTION todaterange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_todaterange'; + +CREATE FUNCTION todatemultirange(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_todatemultirange'; + + + +-- +-- gtype - list literal (`[expr, ...]`) +-- + +CREATE FUNCTION gtype_build_list(VARIADIC "any") +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_build_list() +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_build_list_noargs'; + + +CREATE FUNCTION gtype_access_slice(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_in_operator(gtype, gtype) +RETURNS bool +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR @= ( + FUNCTION = gtype_in_operator, + LEFTARG = gtype, + RIGHTARG = gtype, + NEGATOR = !@=, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +-- +-- Scalar Functions +-- +CREATE FUNCTION head (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_head'; + +CREATE FUNCTION last (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_last'; + +CREATE FUNCTION size (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_size'; + +-- +-- list functions (reverse is defined in string functions) +-- + +-- keys +CREATE FUNCTION keys (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_keys'; + +--range +CREATE FUNCTION range (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_range'; + +CREATE FUNCTION range (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_range_w_skip';/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + -- +-- gtype - string matching (`STARTS WITH`, `ENDS WITH`, `CONTAINS`, & =~) +-- +-- TODO: Operators ^=, $=, @=, PostGres Regex +-- +CREATE FUNCTION gtype_string_match_starts_with(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_string_match_ends_with(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_string_match_contains(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- LIKE +-- +CREATE FUNCTION gtype_like(gtype, gtype) +RETURNS BOOLEAN +LANGUAGE C +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ~~~ ( + FUNCTION = gtype_like, + LEFTARG = gtype, + RIGHTARG = gtype, + RESTRICT = LIKESEL, + JOIN = LIKEJOINSEL, + NEGATOR = !~~ +); + +CREATE FUNCTION gtype_not_like(gtype, gtype) +RETURNS BOOLEAN +LANGUAGE C +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR !~~ ( + FUNCTION = gtype_not_like, + LEFTARG = gtype, + RIGHTARG = gtype, + RESTRICT = NLIKESEL, + JOIN = NLIKEJOINSEL, + NEGATOR = ~~~ +); + + +CREATE FUNCTION gtype_ilike(gtype, gtype) +RETURNS BOOLEAN +LANGUAGE C +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ~~* ( + FUNCTION = gtype_ilike, + LEFTARG = gtype, + RIGHTARG = gtype, + RESTRICT = LIKESEL, + JOIN = LIKEJOINSEL, + NEGATOR = !~~* +); + +CREATE FUNCTION gtype_not_ilike(gtype, gtype) +RETURNS BOOLEAN +LANGUAGE C +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR !~~* ( + FUNCTION = gtype_not_ilike, + LEFTARG = gtype, + RIGHTARG = gtype, + RESTRICT = NLIKESEL, + JOIN = NLIKEJOINSEL, + NEGATOR = ~~* +); + +-- +-- Regex +-- +CREATE FUNCTION regex_match_case_sensitive(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +COST 1 +AS 'MODULE_PATHNAME', 'gtype_eq_tilde'; + +CREATE OPERATOR ~ ( + FUNCTION = regex_match_case_sensitive, + LEFTARG = gtype, + RIGHTARG = gtype +); + +CREATE OPERATOR =~ ( + FUNCTION = regex_match_case_sensitive, + LEFTARG = gtype, + RIGHTARG = gtype +); + +CREATE FUNCTION regex_match_case_insensitive(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_match_case_insensitive'; + +CREATE OPERATOR ~* ( + FUNCTION = regex_match_case_insensitive, + LEFTARG = gtype, + RIGHTARG = gtype +); + +CREATE FUNCTION regex_not_cs(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_regex_not_cs'; + +CREATE OPERATOR !~ ( + FUNCTION = regex_not_cs, + LEFTARG = gtype, + RIGHTARG = gtype +); + +CREATE FUNCTION regex_not_ci(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_regex_not_ci'; + +CREATE OPERATOR !~* ( + FUNCTION = regex_not_ci, + LEFTARG = gtype, + RIGHTARG = gtype +); + +-- +-- String functions +-- +CREATE FUNCTION reverse (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_reverse'; + +CREATE FUNCTION toupper (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_toupper'; + +CREATE FUNCTION tolower (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tolower'; + +CREATE FUNCTION ltrim (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_ltrim'; + +CREATE FUNCTION rtrim (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_rtrim'; + +CREATE FUNCTION "trim" (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_trim'; + +CREATE FUNCTION right (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_right'; + +CREATE FUNCTION left (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_left'; + +CREATE FUNCTION initcap (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_initcap'; + +CREATE FUNCTION "substring" (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_substring_w_len'; + +CREATE FUNCTION "substring" (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_substring'; + +CREATE FUNCTION split (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_split'; + +CREATE FUNCTION replace (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_replace'; + +CREATE FUNCTION sha224 (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_sha224'; + +CREATE FUNCTION sha256 (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_sha256'; + +CREATE FUNCTION sha384 (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_sha384'; + +CREATE FUNCTION sha512 (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_sha512'; + +CREATE FUNCTION md5 (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_md5'; + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- Trig functions - radian input +-- +CREATE FUNCTION sin (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_sin'; + +CREATE FUNCTION cos (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_cos'; + +CREATE FUNCTION tan (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_tan'; + +CREATE FUNCTION cot (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_cot'; + +CREATE FUNCTION asin (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_asin'; + +CREATE FUNCTION acos (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_acos'; + +CREATE FUNCTION atan (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_atan'; + +CREATE FUNCTION atan2 (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_atan2'; + +CREATE FUNCTION sinh (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_sinh'; + +CREATE FUNCTION cosh (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_cosh'; + +CREATE FUNCTION tanh (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tanh'; + +CREATE FUNCTION asinh (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_asinh'; + +CREATE FUNCTION acosh (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_acosh'; + +CREATE FUNCTION atanh (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_atanh'; + +CREATE FUNCTION degrees (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_degrees'; + +CREATE FUNCTION radians (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_radians'; + +CREATE FUNCTION round (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_round'; + +CREATE FUNCTION round (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_round_w_precision'; + +CREATE FUNCTION ceil (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_ceil'; + +CREATE FUNCTION ceiling (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_ceil'; + +CREATE FUNCTION floor (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_floor'; + +CREATE FUNCTION abs (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_abs'; + +CREATE FUNCTION sign (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_sign'; + +CREATE FUNCTION log (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_log'; + +CREATE FUNCTION log10 (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_log10'; + +CREATE FUNCTION e () +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_e'; + +CREATE FUNCTION exp (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_exp'; + +CREATE FUNCTION sqrt (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_sqrt'; + +CREATE FUNCTION cbrt (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +RETURNS NULL ON NULL INPUT +AS 'MODULE_PATHNAME', 'gtype_cbrt'; + + +CREATE FUNCTION pi () +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_pi'; + +CREATE FUNCTION rand() +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_rand'; + +CREATE FUNCTION gcd (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_gcd'; + +CREATE FUNCTION lcm (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_lcm'; + +CREATE FUNCTION factorial (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_factorial'; + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- Temporal Functions +-- +CREATE FUNCTION age(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_age_w2args'; + +CREATE FUNCTION age(gtype) +RETURNS gtype +LANGUAGE c +STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_age_today'; + +CREATE FUNCTION "extract"(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_extract'; + +CREATE FUNCTION date_part(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_date_part'; + +CREATE FUNCTION date_bin(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_date_bin'; + +CREATE FUNCTION date_trunc(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_date_trunc'; + +CREATE FUNCTION date_trunc(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_date_trunc_zone'; + +CREATE FUNCTION overlaps(gtype, gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_overlaps'; + +CREATE FUNCTION isfinite(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_isfinite'; + +CREATE FUNCTION justify_hours(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_justify_hours'; + +CREATE FUNCTION justify_days(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_justify_days'; + +CREATE FUNCTION justify_interval(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_justify_interval'; + +CREATE FUNCTION make_date(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_make_date'; + +CREATE FUNCTION make_time(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_make_time'; + +CREATE FUNCTION make_timestamp(gtype, gtype, gtype, gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_make_timestamp'; + +CREATE FUNCTION make_timestamptz(gtype, gtype, gtype, gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_make_timestamptz'; + +CREATE FUNCTION make_timestamptz(gtype, gtype, gtype, gtype, gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME','gtype_make_timestamptz_wtimezone'; + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +CREATE FUNCTION gtype_bitwise_not(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ~ (FUNCTION = gtype_bitwise_not, RIGHTARG = gtype); + +CREATE FUNCTION gtype_bitwise_and(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR & (FUNCTION = gtype_bitwise_and, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_bitwise_or(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR | (FUNCTION = gtype_bitwise_or, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_inet_subnet_strict_contains(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR << ( + FUNCTION = gtype_inet_subnet_strict_contains, + LEFTARG = gtype, + RIGHTARG = gtype, + COMMUTATOR = '>>', + RESTRICT = positionsel, + JOIN = positionjoinsel +); + +CREATE FUNCTION gtype_inet_subnet_contains(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR <<= (FUNCTION = gtype_inet_subnet_contains, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_inet_subnet_strict_contained_by(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR >> (FUNCTION = gtype_inet_subnet_strict_contained_by, LEFTARG = gtype, RIGHTARG = gtype); + + +CREATE FUNCTION gtype_inet_subnet_contained_by(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR >>= (FUNCTION = gtype_inet_subnet_contained_by, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_inet_subnet_contain_both(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR && (FUNCTION = gtype_inet_subnet_contain_both, LEFTARG = gtype, RIGHTARG = gtype); + +-- +-- Network Functions +-- +CREATE FUNCTION abbrev(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_abbrev'; + +CREATE FUNCTION broadcast(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_broadcast'; + +CREATE FUNCTION family(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_family'; + +CREATE FUNCTION host(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_host'; + +CREATE FUNCTION hostmask(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_hostmask'; + +CREATE FUNCTION inet_merge(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_inet_merge'; + +CREATE FUNCTION inet_same_family(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_inet_same_family'; + +CREATE FUNCTION masklen(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_masklen'; + +CREATE FUNCTION netmask(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_netmask'; + +CREATE FUNCTION network(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_network'; + + +CREATE FUNCTION set_masklen(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_set_masklen'; + +CREATE FUNCTION trunc(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_trunc'; + +CREATE FUNCTION macaddr8_set7bit(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_macaddr8_set7bit'; +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- Text Search Operators +-- + +CREATE FUNCTION gtype_tsquery_or(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tsquery_or'; + +CREATE OPERATOR || (FUNCTION = gtype_tsquery_or, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_tsquery_not(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tsquery_not'; + +CREATE OPERATOR !! (FUNCTION = gtype_tsquery_not, RIGHTARG = gtype); + + +-- +-- Text Search Functions +-- +CREATE FUNCTION ts_delete(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_ts_delete'; + +CREATE FUNCTION strip(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_ts_strip'; + +CREATE FUNCTION tsquery_phrase(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tsquery_phrase'; + +CREATE FUNCTION tsquery_phrase(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tsquery_phrase_distance'; + +CREATE FUNCTION plainto_tsquery(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_plainto_tsquery'; + +CREATE FUNCTION phraseto_tsquery(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_phraseto_tsquery'; + +CREATE FUNCTION websearch_to_tsquery(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_websearch_to_tsquery'; + + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- Range Functions +-- +CREATE FUNCTION intrange(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_intrange'; + +CREATE FUNCTION intrange(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_intrange'; + +CREATE FUNCTION numrange(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_numrange'; + +CREATE FUNCTION numrange(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_numrange'; + +CREATE FUNCTION tsrange(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tsrange'; + +CREATE FUNCTION tsrange(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tsrange'; + +CREATE FUNCTION tstzrange(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tstzrange'; + +CREATE FUNCTION tstzrange(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_tstzrange'; + +CREATE FUNCTION daterange(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_daterange'; + +CREATE FUNCTION daterange(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_daterange'; + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +-- +-- Geometric Operators +-- +CREATE FUNCTION intersection_point(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_intersection_point'; + +CREATE OPERATOR # (FUNCTION = intersection_point, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_closest_point(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_closest_point'; + +CREATE OPERATOR ## (FUNCTION = gtype_closest_point, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_vertical(gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_vertical'; + +CREATE OPERATOR ?| (FUNCTION = gtype_vertical, RIGHTARG = gtype); + + +CREATE FUNCTION gtype_horizontal(gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_horizontal'; + +CREATE OPERATOR ?- (FUNCTION = gtype_horizontal, RIGHTARG = gtype); + + +CREATE FUNCTION gtype_center(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_center'; + +CREATE OPERATOR @@ (FUNCTION = gtype_center, RIGHTARG = gtype); + +CREATE FUNCTION gtype_distance(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_distance'; + +CREATE OPERATOR @-@ (FUNCTION = gtype_distance, RIGHTARG = gtype); + +CREATE FUNCTION gtype_perp(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_perp'; + +CREATE OPERATOR ?-| (FUNCTION = gtype_perp, LEFTARG = gtype, RIGHTARG = gtype); + +CREATE FUNCTION gtype_parallel(gtype, gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_parallel'; + +CREATE OPERATOR ?|| (FUNCTION = gtype_parallel, LEFTARG = gtype, RIGHTARG = gtype); + +-- +-- Geometric Functions +-- +CREATE FUNCTION height(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_height'; + +CREATE FUNCTION width(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_width'; + +CREATE FUNCTION bound_box(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_bound_box'; + +/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +----------------------------------------------------------------------------- +-- GEOMETRY Operators +----------------------------------------------------------------------------- + + +-- Availability: 2.2.0 +CREATE OR REPLACE FUNCTION ST_ShiftLongitude(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_longitude_shift'; + +-- TODO: ST_Wrapx + + + + +----------------------------------------------------------------------------- +-- BOX3D FUNCTIONS +----------------------------------------------------------------------------- + +CREATE FUNCTION ST_XMin (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_xmin'; + +CREATE FUNCTION ST_XMax (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_xmax'; + +CREATE FUNCTION ST_ZMin (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_zmin'; + +CREATE FUNCTION ST_YMin (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_ymin'; + +CREATE FUNCTION ST_YMax (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_ymax'; + +CREATE FUNCTION ST_ZMax (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_zmax'; + + + +----------------------------------------------------------------------------- +-- Overloaded ST_Expand FUNCTIONS +----------------------------------------------------------------------------- + +-- Availability: 1.2.2 +CREATE OR REPLACE FUNCTION ST_Expand(gtype,gtype) +RETURNS gtype +AS 'MODULE_PATHNAME', 'gtype_ST_Expand' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION ST_Expand(gtype, dx float8, dy float8) +RETURNS gtype +AS 'MODULE_PATHNAME', 'gtype_ST_Expand' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + +-- Availability: 2.3.0 +CREATE OR REPLACE FUNCTION ST_Expand(box gtype, dx float8, dy float8, dz float8 DEFAULT 0) +RETURNS gtype +AS 'MODULE_PATHNAME', 'gtype_ST_Expand' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + +-- Availability: 2.3.0 +CREATE OR REPLACE FUNCTION ST_Expand(geom gtype, dx gtype, dy gtype, dz gtype DEFAULT '0.0'::gtype, dm gtype DEFAULT '0.0'::gtype) +RETURNS gtype +AS 'MODULE_PATHNAME', 'gtype_ST_Expand' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + +----------------------------------------------------------------------------- +-- BOX2D FUNCTIONS +----------------------------------------------------------------------------- + +-- Availability: 1.5.0 +CREATE OR REPLACE FUNCTION postgis_getbbox(gtype) +RETURNS gtype +AS 'MODULE_PATHNAME','gtype_to_BOX2DF' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + +-- Availability: 1.2.2 +CREATE OR REPLACE FUNCTION ST_MakeBox2d(geom1 gtype, geom2 gtype) +RETURNS gtype +AS 'MODULE_PATHNAME', 'gtype_construct' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- PostGIS 2D Geometry Operators +-- +CREATE OR REPLACE FUNCTION gtype_geometry_same(geom1 gtype, geom2 gtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_same_2d'; + +CREATE OPERATOR ~= ( + LEFTARG = gtype, + RIGHTARG = gtype, + PROCEDURE = gtype_geometry_same, + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OR REPLACE FUNCTION gtype_geometry_within(geom1 gtype, geom2 gtype) +RETURNS bool +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_within_2d'; + +CREATE OPERATOR @ ( +LEFTARG = gtype, +RIGHTARG = gtype, +PROCEDURE = gtype_geometry_within--, +-- Updated: 3.4.0 to use selectivity estimator +--RESTRICT = gtype_gserialized_gist_sel_2d, +-- Updated: 3.4.0 to use join selectivity estimator +--JOIN = gtype_gserialized_gist_joinsel_2d +); + +CREATE OR REPLACE FUNCTION gtype_geometry_overleft(geom1 gtype, geom2 gtype) +RETURNS bool +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_overleft_2d'; + +CREATE OPERATOR &< ( +LEFTARG = gtype, +RIGHTARG = gtype, +PROCEDURE = gtype_geometry_overleft, +RESTRICT = positionsel, +JOIN = positionjoinsel +); + +CREATE OR REPLACE FUNCTION gtype_geometry_below(geom1 gtype, geom2 gtype) +RETURNS bool +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_below_2d'; + + +CREATE OPERATOR <<| ( +LEFTARG = gtype, +RIGHTARG = gtype, +PROCEDURE = gtype_geometry_below, +--COMMuTAOR = '<<|', +RESTRICT = positionsel, +JOIN = positionjoinsel +); + + +CREATE OR REPLACE FUNCTION gtype_geometry_overbelow(geom1 gtype, geom2 gtype) +RETURNS bool +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_overbelow_2d'; + +CREATE OPERATOR &<| ( +LEFTARG = gtype, +RIGHTARG = gtype, +PROCEDURE = gtype_geometry_overbelow, +RESTRICT = positionsel, +JOIN = positionjoinsel +); + + +CREATE OR REPLACE FUNCTION gtype_geometry_overright(geom1 gtype, geom2 gtype) +RETURNS bool +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_overright_2d'; + +CREATE OPERATOR &> ( +LEFTARG = gtype, +RIGHTARG = gtype, +PROCEDURE = gtype_geometry_overright, +RESTRICT = positionsel, +JOIN = positionjoinsel +); + +CREATE OR REPLACE FUNCTION gtype_geometry_overabove(geom1 gtype, geom2 gtype) +RETURNS bool +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_overabove_2d'; + +CREATE OPERATOR |&> ( +LEFTARG = gtype, +RIGHTARG = gtype, +PROCEDURE = gtype_geometry_overabove, +RESTRICT = positionsel, +JOIN = positionjoinsel +); + +CREATE OR REPLACE FUNCTION gtype_geometry_above(geom1 gtype, geom2 gtype) +RETURNS bool +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_above_2d'; + +CREATE OPERATOR |>> ( +LEFTARG = gtype, +RIGHTARG = gtype, +PROCEDURE = gtype_geometry_above, +--COMMuTAOR = '<<|', +RESTRICT = positionsel, +JOIN = positionjoinsel +); + +-- +-- 2D Gist Index +-- +CREATE OR REPLACE FUNCTION geometry_gist_distance_2d(internal, gtype, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_distance_2d' +LANGUAGE 'c' PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION geometry_gist_consistent_2d(internal,gtype,integer) +RETURNS bool +AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_consistent_2d' +LANGUAGE 'c' PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION geometry_gist_compress_2d(internal) +RETURNS internal +AS 'MODULE_PATHNAME','gtype_gserialized_gist_compress_2d' +LANGUAGE 'c' PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION geometry_gist_penalty_2d(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_penalty_2d' +LANGUAGE 'c' PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION geometry_gist_picksplit_2d(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_picksplit_2d' +LANGUAGE 'c' PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION geometry_gist_union_2d(bytea, internal) +RETURNS internal +AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_union_2d' +LANGUAGE 'c' PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION geometry_gist_same_2d(geom1 gtype, geom2 gtype, internal) +RETURNS internal +AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_same_2d' +LANGUAGE 'c' PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION geometry_gist_decompress_2d(internal) +RETURNS internal +AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_decompress_2d' +LANGUAGE 'c' PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION geometry_gist_sortsupport_2d(internal) +RETURNS void +AS 'MODULE_PATHNAME', 'gtype_gserialized_gist_sortsupport_2d' +LANGUAGE 'c' STRICT; + +CREATE OPERATOR CLASS gist_geometry_ops_2d + DEFAULT FOR TYPE gtype USING GIST AS + STORAGE public.box2df, + OPERATOR 1 << , + OPERATOR 2 &< , + OPERATOR 3 && , + OPERATOR 4 &> , + OPERATOR 5 >> , + OPERATOR 6 ~= , + OPERATOR 7 ~ , + OPERATOR 8 @ , + OPERATOR 9 &<| , + OPERATOR 10 <<| , + OPERATOR 11 |>> , + OPERATOR 12 |&> , + OPERATOR 13 <-> FOR ORDER BY gtype_ops_btree, + OPERATOR 14 <#> FOR ORDER BY gtype_ops_btree, +-- +-- Sort support in bulk indexing not included in the default +-- opclass for PostgreSQL versions <15 due to query performance +-- degradation caused by GiST index page overlap. +-- Since PostgreSQL 15 sorting build uses picksplit function to +-- find better partitioning for index records. This allows to +-- build indices performing similarly to those produced by default +-- method while still reducing index build time significantly. +-- +-- To enable sortsupport: +-- alter operator family gist_geometry_ops_2d using gist +-- add function 11 (geometry) +-- geometry_gist_sortsupport_2d (internal); +-- +-- To remove sortsupport: +-- alter operator family gist_geometry_ops_2d using gist +-- drop function 11 (geometry); +-- + FUNCTION 8 geometry_gist_distance_2d (internal, gtype, integer), + FUNCTION 1 geometry_gist_consistent_2d (internal, gtype, integer), + FUNCTION 2 geometry_gist_union_2d (bytea, internal), + FUNCTION 3 geometry_gist_compress_2d (internal), + FUNCTION 4 geometry_gist_decompress_2d (internal), + FUNCTION 5 geometry_gist_penalty_2d (internal, internal, internal), + FUNCTION 6 geometry_gist_picksplit_2d (internal, internal), + FUNCTION 7 geometry_gist_same_2d (geom1 gtype, geom2 gtype, internal)/*, + FUNCTION 11 geometry_gist_sortsupport_2d(gtype)*/; + +-- +-- PostGIS N-D Geometry Operators +-- +CREATE OR REPLACE FUNCTION geometry_overlaps_nd(gtype, gtype) +RETURNS boolean +AS 'MODULE_PATHNAME' ,'gtype_gserialized_overlaps' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE +COST 1; + +CREATE OPERATOR &&& ( + LEFTARG = gtype, RIGHTARG = gtype, PROCEDURE = geometry_overlaps_nd, + COMMUTATOR = '&&&'--, + --RESTRICT = gserialized_gist_sel_nd, + --JOIN = gserialized_gist_joinsel_nd +); + +CREATE OR REPLACE FUNCTION geometry_contains_nd(gtype, gtype) +RETURNS boolean +AS 'MODULE_PATHNAME' ,'gtype_gserialized_contains' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE +COST 1; + +CREATE OPERATOR ~~ ( + LEFTARG = gtype, RIGHTARG = gtype, PROCEDURE = geometry_contains_nd--, + --COMMUTATOR = '@@', + --RESTRICT = gserialized_gist_sel_nd, + --JOIN = gserialized_gist_joinsel_nd +); + +CREATE OR REPLACE FUNCTION geometry_within_nd(gtype, gtype) +RETURNS boolean +AS 'MODULE_PATHNAME' ,'gtype_gserialized_within' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE +COST 1; + +CREATE OPERATOR @@ ( + LEFTARG = gtype, RIGHTARG = gtype, PROCEDURE = geometry_within_nd, + COMMUTATOR = '~~'/*, + RESTRICT = gserialized_gist_sel_nd, + JOIN = gserialized_gist_joinsel_nd*/ +); + +CREATE OR REPLACE FUNCTION geometry_same_nd(gtype, gtype) +RETURNS boolean +AS 'MODULE_PATHNAME' ,'gtype_gserialized_same' +LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE +COST 1; + +CREATE OPERATOR ~~= ( + LEFTARG = gtype, RIGHTARG = gtype, PROCEDURE = geometry_same_nd, + COMMUTATOR = '~~='/*, + RESTRICT = gserialized_gist_sel_nd, + JOIN = gserialized_gist_joinsel_nd*/ +); + +CREATE FUNCTION ST_DistanceCPA (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_ST_DistanceCPA'; + +CREATE OPERATOR |=| ( + LEFTARG = gtype, RIGHTARG = gtype, + PROCEDURE = ST_DistanceCPA, + COMMUTATOR = '|=|' +); + +-- Availability: 2.2.0 +CREATE OR REPLACE FUNCTION gtype_distance_centroid_nd(gtype, gtype) +RETURNS gtype +IMMUTABLE +STRICT +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME', 'gtype_distance_nd'; + +-- Availability: 2.2.0 +CREATE OPERATOR <<->> ( + LEFTARG = gtype, + RIGHTARG = gtype, + PROCEDURE = gtype_distance_centroid_nd, + COMMUTATOR = '<<->>' +); + + +-- Availability: 2.2.0 +CREATE OR REPLACE FUNCTION gtype_gist_distance_nd(internal,gtype,integer) +RETURNS gtype +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME', 'gtype_gist_distance'; + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION gtype_gist_consistent_nd(internal,gtype,integer) +RETURNS gtype +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME', 'gtype_gist_consistent'; + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION gtype_gist_compress_nd(internal) +RETURNS internal +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME','gtype_gist_compress'; + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION gtype_gist_penalty_nd(internal,internal,internal) +RETURNS internal +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME' ,'gtype_gist_penalty'; + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION gtype_gist_picksplit_nd(internal, internal) +RETURNS internal +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME' ,'gtype_gist_picksplit'; + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION gtype_gist_union_nd(bytea, internal) +RETURNS internal +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME' ,'gtype_gist_union'; + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION gtype_gist_same_nd(gtype, gtype, internal) +RETURNS internal +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME' ,'gtype_gist_same'; + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION gtype_gist_decompress_nd(internal) +RETURNS internal +PARALLEL SAFE +LANGUAGE 'c' +AS 'MODULE_PATHNAME' ,'gtype_gist_decompress'; + + +-- Availability: 2.0.0 +CREATE OPERATOR CLASS gist_geometry_ops_nd + FOR TYPE gtype USING GIST AS + STORAGE public.gidx, + OPERATOR 3 &&& , + -- Availability: 3.0.0 + OPERATOR 6 ~~= , + -- Availability: 3.0.0 + OPERATOR 7 ~~ , + -- Availability: 3.0.0 + OPERATOR 8 @@ , + -- Availability: 2.2.0 + OPERATOR 13 <<->> FOR ORDER BY pg_catalog.float_ops, + -- Availability: 2.2.0 + OPERATOR 20 |=| FOR ORDER BY pg_catalog.float_ops, + -- Availability: 2.2.0 + FUNCTION 8 gtype_gist_distance_nd (internal, gtype, integer), + FUNCTION 1 gtype_gist_consistent_nd (internal, gtype, integer), + FUNCTION 2 gtype_gist_union_nd (bytea, internal), + FUNCTION 3 gtype_gist_compress_nd (internal), + FUNCTION 4 gtype_gist_decompress_nd (internal), + FUNCTION 5 gtype_gist_penalty_nd (internal, internal, internal), + FUNCTION 6 gtype_gist_picksplit_nd (internal, internal), + FUNCTION 7 gtype_gist_same_nd (gtype, gtype, internal); + +-- +-- PostGIS Functions +-- + +-- +-- PostGIS Constructors +-- +CREATE FUNCTION ST_MakePoint (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_makepoint'; + +CREATE FUNCTION ST_MakePoint (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_makepoint'; + +CREATE FUNCTION ST_MakePoint (gtype, gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_makepoint'; + +CREATE FUNCTION ST_MakePointM (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_makepoint3dm'; + + +CREATE FUNCTION ST_asEWKT (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME', 'gtype_asEWKT'; + +CREATE FUNCTION asEWKT (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME', 'gtype_asEWKT'; + +CREATE FUNCTION addbbox (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_addBBOX'; + +CREATE FUNCTION postgis_addbbox (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_addBBOX'; + +CREATE FUNCTION dropbbox (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_dropBBOX'; + +CREATE FUNCTION postgis_dropbbox (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_dropBBOX'; + + +CREATE FUNCTION ST_X (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_x_point'; + +CREATE FUNCTION X (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_x_point'; + +CREATE FUNCTION ST_Y (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_y_point'; + +CREATE FUNCTION Y (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_y_point'; + +CREATE FUNCTION ST_Z (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_z_point'; + +CREATE FUNCTION Z (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_z_point'; + +CREATE FUNCTION ST_M (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_m_point'; + +CREATE FUNCTION M (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 1 +AS 'MODULE_PATHNAME', 'gtype_m_point'; + +-- +-- PostGIS Functions - Measures +-- +CREATE FUNCTION ST_3DLength(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_length_linestring'; + +CREATE FUNCTION ST_Length2d(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_length2d_linestring'; + +CREATE FUNCTION ST_Length(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_length2d_linestring'; + +CREATE FUNCTION ST_LengthSpheroid(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME', 'gtype_length_ellipsoid_linestring'; + + + +CREATE FUNCTION ST_IsPolygonCW (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_ST_IsPolygonCW'; + +CREATE FUNCTION ST_IsPolygonCCW (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_ST_IsPolygonCCW'; + +CREATE FUNCTION ST_DistanceSpheroid (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME', 'gtype_distance_ellipsoid'; + +CREATE FUNCTION ST_DistanceSpheroid (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME', 'gtype_distance_ellipsoid'; + +CREATE FUNCTION ST_azimuth (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_azimuth'; + + +-- +-- PostGIS Functions - Temporal +-- +CREATE FUNCTION ST_ClosestPointOfApproach (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_ST_ClosestPointOfApproach'; + + + +CREATE FUNCTION ST_IsValidTrajectory (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_st_isvalidtrajectory'; + +CREATE FUNCTION ST_CPAWithin (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_ST_CPAWithin'; + + +-- +-- PostGIS Functions - GEOS +-- +CREATE FUNCTION ST_Intersection (gtype, gtype, gtype DEFAULT '-1.0'::gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_st_intersection'; + +CREATE FUNCTION ST_GeneratePoints (area gtype, npoints gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME', 'gtype_st_generatepoints'; + +CREATE FUNCTION ST_GeneratePoints (area gtype, npoints gtype, seed gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 250 +AS 'MODULE_PATHNAME', 'gtype_st_generatepoints'; + +CREATE FUNCTION ST_ConvexHull (gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_convexhull'; + +CREATE FUNCTION ST_SymDifference (gtype, gtype, gtype DEFAULT '-1.0'::gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_st_symdifference'; + +CREATE FUNCTION ST_SymDifference (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_st_symdifference'; + +CREATE FUNCTION ST_HausdorffDistance (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_hausdorffdistance'; + +CREATE FUNCTION ST_HausdorffDistance (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_hausdorffdistancedensify'; + +CREATE FUNCTION ST_FrechetDistance (gtype, gtype, gtype default '-1.0'::gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 5000 +AS 'MODULE_PATHNAME', 'gtype_st_frechet_distance'; + +-- +-- Algorithms +-- +CREATE FUNCTION ST_Simplify (gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_ST_Simplify'; + +CREATE FUNCTION ST_Simplify (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_ST_Simplify'; + +CREATE OR REPLACE FUNCTION ST_SnapToGrid(gtype, gtype, gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_snaptogrid'; + +CREATE OR REPLACE FUNCTION ST_SnapToGrid(gtype, gtype, gtype) +RETURNS gtype +AS $$ SELECT ST_SnapToGrid($1, '0'::gtype, '0'::gtype, $2, $3) $$ +LANGUAGE sql +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_SnapToGrid(gtype, gtype) +RETURNS gtype +AS $$ SELECT ST_SnapToGrid($1, '0'::gtype, '0'::gtype, $2, $2) $$ +LANGUAGE sql +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_SnapToGrid(gtype, gtype, gtype, gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_snaptogrid_pointoff'; + + +-- +-- PostGIS Affine Functions +-- +CREATE FUNCTION ST_Affine (gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_affine'; + +CREATE OR REPLACE FUNCTION ST_Affine(gtype, gtype, gtype, gtype, gtype, gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Affine($1, $2, $3, 0, $4, $5, 0, 0, 0, 1, $6, $7, 0)' +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_Rotate(gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Affine($1, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0, 1, 0, 0, 0)' +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_Rotate(gtype, gtype, gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Affine($1, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0, 1, $3 - cos($2) * $3 + sin($2) * $4, $4 - sin($2) * $3 - cos($2) * $4, 0)' +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_Rotate(gtype, gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Affine($1, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0, 1, ST_X($3) - cos($2) * ST_X($3) + sin($2) * ST_Y($3), ST_Y($3) - sin($2) * ST_X($3) - cos($2) * ST_Y($3), 0)' +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_RotateZ(gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Rotate($1, $2)' +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_RotateX(gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Affine($1, 1, 0, 0, 0, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0)' +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_RotateY(gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Affine($1, cos($2), 0, sin($2), 0, 1, 0, -sin($2), 0, cos($2), 0, 0, 0)' +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_Translate(gtype, gtype, gtype, gtype) +RETURNS gtype +AS $$ SELECT ST_Affine($1, '1'::gtype, '0'::gtype, '0'::gtype, '0'::gtype, '1'::gtype, '0'::gtype, '0'::gtype, '0'::gtype, '1'::gtype, $2, $3, $4) $$ +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_Translate(gtype, gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Translate($1, $2, $3, 0)' +LANGUAGE 'sql' +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; + +CREATE OR REPLACE FUNCTION ST_Scale(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_st_scale_2_args'; + +CREATE OR REPLACE FUNCTION ST_Scale(gtype, gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_st_scale'; + +CREATE OR REPLACE FUNCTION ST_Scale(gtype, gtype, gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Scale($1, ST_MakePoint($2, $3, $4))' +LANGUAGE sql +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; +/* XXX: Gonna Have to Get Creative With This One +CREATE OR REPLACE FUNCTION ST_Scale(gtype, gtype, gtype) +RETURNS gtype +AS 'SELECT ST_Scale($1, $2, $3, 1.0::float8::gtype)' +LANGUAGE sql +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50; +*/ +-- +-- MISC +-- + +CREATE OR REPLACE FUNCTION ST_Force2D(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_force_2d'; + +CREATE OR REPLACE FUNCTION ST_Force3DZ(gtype, gtype default '0.0') +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_force_3dz'; + +CREATE OR REPLACE FUNCTION ST_Force3DM(gtype, gtype default '0.0') +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_force_3dm'; + +CREATE OR REPLACE FUNCTION ST_Force4D(gtype, gtype default '0.0', gtype default '0.0') +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_force_4d'; + +CREATE OR REPLACE FUNCTION ST_ForceCollection(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_force_collection'; + +CREATE OR REPLACE FUNCTION ST_Multi(gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_force_multi'; + +--------------------------------------------------------------- +-- 3D-functions +--------------------------------------------------------------- + +CREATE OR REPLACE FUNCTION ST_3DDistance(geom1 gtype, geom2 gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_3DDistance'; + +CREATE OR REPLACE FUNCTION ST_3DMaxDistance(geom1 gtype, geom2 gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_maxdistance3d'; + +CREATE OR REPLACE FUNCTION ST_3DClosestPoint(geom1 gtype, geom2 gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_closestpoint3d'; + +CREATE OR REPLACE FUNCTION ST_3DShortestLine(geom1 gtype, geom2 gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_shortestline3d'; + +CREATE OR REPLACE FUNCTION ST_3DLongestLine(geom1 gtype, geom2 gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME', 'gtype_longestline3d'; + + +-- PostGIS equivalent function: AsText(geometry) +CREATE OR REPLACE FUNCTION ST_AsText(gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME','gtype_asText'; + +-- Availability: 2.5.0 +-- PostGIS equivalent function: AsText(gtype, gtype) +CREATE OR REPLACE FUNCTION ST_AsText(gtype, gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME','gtype_asText'; + + +CREATE OR REPLACE FUNCTION ST_3DDWithin(geom1 gtype, geom2 gtype,gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 500 +AS 'MODULE_PATHNAME', 'gtype_dwithin3d' +; + + +CREATE OR REPLACE FUNCTION ST_3DDFullyWithin(geom1 gtype, geom2 gtype,gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 500 +AS 'MODULE_PATHNAME', 'gtype_dfullywithin3d' +; + + +CREATE OR REPLACE FUNCTION ST_Dimension(geom gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 10 +AS 'MODULE_PATHNAME', 'gtype_dimension' +; + + +CREATE OR REPLACE FUNCTION ST_GeometryType(geom gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 10 +AS 'MODULE_PATHNAME', 'gtype_geometrytype'; + + +-- PostGIS equivalent function: NumGeometries(geometry) +CREATE OR REPLACE FUNCTION ST_NumGeometries(geom gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 10 +AS 'MODULE_PATHNAME', 'gtype_numgeometries_collection'; + +-- PostGIS equivalent function: ST_GeometryN(geometry) +CREATE OR REPLACE FUNCTION ST_GeometryN(geom gtype, gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 10 +AS 'MODULE_PATHNAME', 'gtype_geometryn_collection'; + + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION ST_NumPatches(gtype) +RETURNS gtype +LANGUAGE 'sql' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 10 +AS $$ + SELECT CASE WHEN @extschema@.ST_GeometryType($1) = ('"ST_PolyhedralSurface"'::gtype) + THEN @extschema@.ST_NumGeometries($1) + ELSE NULL END +$$; + + +-- Availability: 2.0.0 +CREATE OR REPLACE FUNCTION ST_PatchN(gtype, gtype) +RETURNS gtype +LANGUAGE 'sql' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 10 +AS $$ + SELECT CASE WHEN @extschema@.ST_GeometryType($1) = ('"ST_PolyhedralSurface"'::gtype) + THEN @extschema@.ST_GeometryN($1, $2) + ELSE NULL END +$$; + +-- PostGIS equivalent function: NumGeometries(geometry) +CREATE OR REPLACE FUNCTION ST_Reverse(geom gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 10 +AS 'MODULE_PATHNAME', 'gtype_st_reverse'; + +CREATE OR REPLACE FUNCTION ST_GeomFromText(gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME','gtype_from_text'; + +-- PostGIS equivalent function: ST_GeometryFromText(gtype, gtype) +CREATE OR REPLACE FUNCTION ST_GeomFromText(gtype, gtype) +RETURNS gtype +LANGUAGE 'c' +IMMUTABLE +STRICT +PARALLEL SAFE +COST 50 +AS 'MODULE_PATHNAME','gtype_from_text';/* + * Copyright (C) 2023 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +-- +-- Agreggation +-- + +-- accumlates floats into an array for aggregation +CREATE FUNCTION gtype_accum(float8[], gtype) +RETURNS float8[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- count +-- +CREATE AGGREGATE count(*) ( + stype = int8, + sfunc = int8inc, + finalfunc = int8_to_gtype, + combinefunc = int8pl, + finalfunc_modify = READ_ONLY, + initcond = 0, + parallel = SAFE +); + +CREATE AGGREGATE count(gtype) ( + stype = int8, + sfunc = int8inc_any, + finalfunc = int8_to_gtype, + combinefunc = int8pl, + finalfunc_modify = READ_ONLY, + initcond = 0, + parallel = SAFE +); + +CREATE AGGREGATE count(vertex) ( + stype = int8, + sfunc = int8inc_any, + finalfunc = int8_to_gtype, + combinefunc = int8pl, + finalfunc_modify = READ_ONLY, + initcond = 0, + parallel = SAFE +); + +CREATE AGGREGATE count(edge) ( + stype = int8, + sfunc = int8inc_any, + finalfunc = int8_to_gtype, + combinefunc = int8pl, + finalfunc_modify = READ_ONLY, + initcond = 0, parallel = SAFE +); + +CREATE AGGREGATE count(traversal) ( + stype = int8, + sfunc = int8inc_any, + finalfunc = int8_to_gtype, + combinefunc = int8pl, + finalfunc_modify = READ_ONLY, + initcond = 0, + parallel = SAFE +); + +CREATE AGGREGATE count(variable_edge) ( + stype = int8, + sfunc = int8inc_any, + finalfunc = int8_to_gtype, + combinefunc = int8pl, + finalfunc_modify = READ_ONLY, + initcond = 0, + parallel = SAFE +); + +-- +-- stdev +-- +CREATE FUNCTION gtype_stddev_samp(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE stdev(gtype) ( + stype = float8[], + sfunc = gtype_accum, + finalfunc = gtype_stddev_samp, + combinefunc = float8_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0}', + parallel = SAFE +); + +-- +-- stdevp +-- +CREATE FUNCTION gtype_stddev_pop(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE stdevp(gtype) ( + stype = float8[], + sfunc = gtype_accum, + finalfunc = gtype_stddev_pop, + combinefunc = float8_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0}', + parallel = SAFE +); + +-- +-- avg +-- +CREATE AGGREGATE avg(gtype) ( + stype = float8[], + sfunc = gtype_accum, + finalfunc = float8_avg, + combinefunc = float8_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0}', + parallel = SAFE +); + +-- +-- sum +-- +CREATE FUNCTION gtype_sum (gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +STRICT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_gtype_sum'; + +CREATE AGGREGATE sum(gtype) ( + stype = gtype, + sfunc = gtype_sum, + combinefunc = gtype_sum, + finalfunc_modify = READ_ONLY, + parallel = SAFE +); + +-- +-- max +-- +CREATE FUNCTION gtype_max_trans(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE max(gtype) ( + stype = gtype, + sfunc = gtype_max_trans, + combinefunc = gtype_max_trans, + finalfunc_modify = READ_ONLY, + parallel = SAFE +); + +-- +-- min +-- +CREATE FUNCTION gtype_min_trans(gtype, gtype) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE min(gtype) ( + stype = gtype, + sfunc = gtype_min_trans, + combinefunc = gtype_min_trans, + finalfunc_modify = READ_ONLY, + parallel = SAFE +); + +-- +-- percentileCont and percentileDisc +-- +CREATE FUNCTION percentile_aggtransfn (internal, gtype, gtype) +RETURNS internal +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_percentile_aggtransfn'; + +CREATE FUNCTION percentile_cont_aggfinalfn (internal) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_percentile_cont_aggfinalfn'; + +CREATE FUNCTION percentile_disc_aggfinalfn (internal) +RETURNS gtype +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_percentile_disc_aggfinalfn'; + +CREATE AGGREGATE percentilecont(gtype, gtype) ( + stype = internal, + sfunc = percentile_aggtransfn, + finalfunc = percentile_cont_aggfinalfn, + parallel = SAFE +); + +CREATE AGGREGATE percentiledisc(gtype, gtype) ( + stype = internal, + sfunc = percentile_aggtransfn, + finalfunc = percentile_disc_aggfinalfn, + parallel = SAFE +); + +-- +-- collect +-- +CREATE FUNCTION collect_aggtransfn (internal, gtype) +RETURNS internal +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_collect_aggtransfn'; + +CREATE FUNCTION collect_aggfinalfn (internal) +RETURNS gtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_collect_aggfinalfn'; + +CREATE AGGREGATE collect(gtype) ( + stype = internal, + sfunc = collect_aggtransfn, + finalfunc = collect_aggfinalfn, + parallel = safe +); + +CREATE FUNCTION vertex_collect_transfn(internal, vertex) +RETURNS internal +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + + +CREATE FUNCTION vertex_collect_finalfn(internal) +RETURNS vertex[] +LANGUAGE c +IMMUTABLE +PARALLEL SAFE AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE collect(vertex) ( + stype = internal, + sfunc = vertex_collect_transfn, + finalfunc = vertex_collect_finalfn, + parallel = safe +); + +CREATE FUNCTION vertex_collect_transfn_w_limit(internal, vertex, gtype) +RETURNS internal +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE collect(vertex, gtype) ( + stype = internal, + sfunc = vertex_collect_transfn_w_limit, + finalfunc = vertex_collect_finalfn, + parallel = safe +); + + +CREATE FUNCTION edge_collect_transfn(internal, edge) +RETURNS internal +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + + +CREATE FUNCTION edge_collect_finalfn(internal) +RETURNS edge[] +LANGUAGE c +IMMUTABLE +PARALLEL SAFE AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE collect(edge) ( + stype = internal, + sfunc = edge_collect_transfn, + finalfunc = edge_collect_finalfn, + parallel = safe +); + +CREATE FUNCTION edge_collect_transfn_w_limit(internal, edge, gtype) +RETURNS internal +LANGUAGE c +IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE collect(edge, gtype) ( + stype = internal, + sfunc = edge_collect_transfn_w_limit, + finalfunc = edge_collect_finalfn, + parallel = safe +); + +CREATE FUNCTION gtype_regr_accum(float8[], gtype, gtype) +RETURNS float8[] +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION gtype_corr(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME', 'gtype_corr'; + +CREATE AGGREGATE corr(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_corr, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_covar_pop(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE covar_pop(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_covar_pop, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_covar_samp(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE covar_samp(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_covar_samp, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_regr_sxx(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE regr_sxx(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_regr_sxx, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_regr_syy(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE regr_syy(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_regr_syy, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_regr_sxy(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE regr_sxy(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_regr_sxy, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_regr_slope(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE regr_slope(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_regr_slope, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_regr_intercept(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE regr_intercept(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_regr_intercept, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_regr_avgx(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE regr_avgx(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_regr_avgx, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_regr_avgy(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE regr_avgy(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_regr_avgy, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + +CREATE FUNCTION gtype_regr_r2(float8[]) +RETURNS gtype +LANGUAGE c +IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE AGGREGATE regr_r2(gtype, gtype) ( + stype = float8[], + sfunc = gtype_regr_accum, + finalfunc = gtype_regr_avgy, + combinefunc = float8_regr_combine, + finalfunc_modify = READ_ONLY, + initcond = '{0,0,0,0,0,0}', + parallel = SAFE +); + diff --git a/sql/postgraph-aggregation.sql.in b/sql/postgraph-aggregation.sql.in deleted file mode 100644 index 1afc4907..00000000 --- a/sql/postgraph-aggregation.sql.in +++ /dev/null @@ -1,542 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ --- --- Agreggation --- - --- accumlates floats into an array for aggregation -CREATE FUNCTION gtype_accum(float8[], gtype) -RETURNS float8[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- --- count --- -CREATE AGGREGATE count(*) ( - stype = int8, - sfunc = int8inc, - finalfunc = int8_to_gtype, - combinefunc = int8pl, - finalfunc_modify = READ_ONLY, - initcond = 0, - parallel = SAFE -); - -CREATE AGGREGATE count(gtype) ( - stype = int8, - sfunc = int8inc_any, - finalfunc = int8_to_gtype, - combinefunc = int8pl, - finalfunc_modify = READ_ONLY, - initcond = 0, - parallel = SAFE -); - -CREATE AGGREGATE count(vertex) ( - stype = int8, - sfunc = int8inc_any, - finalfunc = int8_to_gtype, - combinefunc = int8pl, - finalfunc_modify = READ_ONLY, - initcond = 0, - parallel = SAFE -); - -CREATE AGGREGATE count(edge) ( - stype = int8, - sfunc = int8inc_any, - finalfunc = int8_to_gtype, - combinefunc = int8pl, - finalfunc_modify = READ_ONLY, - initcond = 0, parallel = SAFE -); - -CREATE AGGREGATE count(traversal) ( - stype = int8, - sfunc = int8inc_any, - finalfunc = int8_to_gtype, - combinefunc = int8pl, - finalfunc_modify = READ_ONLY, - initcond = 0, - parallel = SAFE -); - -CREATE AGGREGATE count(variable_edge) ( - stype = int8, - sfunc = int8inc_any, - finalfunc = int8_to_gtype, - combinefunc = int8pl, - finalfunc_modify = READ_ONLY, - initcond = 0, - parallel = SAFE -); - --- --- stdev --- -CREATE FUNCTION gtype_stddev_samp(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE stdev(gtype) ( - stype = float8[], - sfunc = gtype_accum, - finalfunc = gtype_stddev_samp, - combinefunc = float8_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0}', - parallel = SAFE -); - --- --- stdevp --- -CREATE FUNCTION gtype_stddev_pop(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE stdevp(gtype) ( - stype = float8[], - sfunc = gtype_accum, - finalfunc = gtype_stddev_pop, - combinefunc = float8_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0}', - parallel = SAFE -); - --- --- avg --- -CREATE AGGREGATE avg(gtype) ( - stype = float8[], - sfunc = gtype_accum, - finalfunc = float8_avg, - combinefunc = float8_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0}', - parallel = SAFE -); - --- --- sum --- -CREATE FUNCTION gtype_sum (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -STRICT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_gtype_sum'; - -CREATE AGGREGATE sum(gtype) ( - stype = gtype, - sfunc = gtype_sum, - combinefunc = gtype_sum, - finalfunc_modify = READ_ONLY, - parallel = SAFE -); - --- --- max --- -CREATE FUNCTION gtype_max_trans(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE max(gtype) ( - stype = gtype, - sfunc = gtype_max_trans, - combinefunc = gtype_max_trans, - finalfunc_modify = READ_ONLY, - parallel = SAFE -); - --- --- min --- -CREATE FUNCTION gtype_min_trans(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE min(gtype) ( - stype = gtype, - sfunc = gtype_min_trans, - combinefunc = gtype_min_trans, - finalfunc_modify = READ_ONLY, - parallel = SAFE -); - --- --- percentileCont and percentileDisc --- -CREATE FUNCTION percentile_aggtransfn (internal, gtype, gtype) -RETURNS internal -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_percentile_aggtransfn'; - -CREATE FUNCTION percentile_cont_aggfinalfn (internal) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_percentile_cont_aggfinalfn'; - -CREATE FUNCTION percentile_disc_aggfinalfn (internal) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_percentile_disc_aggfinalfn'; - -CREATE AGGREGATE percentilecont(gtype, gtype) ( - stype = internal, - sfunc = percentile_aggtransfn, - finalfunc = percentile_cont_aggfinalfn, - parallel = SAFE -); - -CREATE AGGREGATE percentiledisc(gtype, gtype) ( - stype = internal, - sfunc = percentile_aggtransfn, - finalfunc = percentile_disc_aggfinalfn, - parallel = SAFE -); - --- --- collect --- -CREATE FUNCTION collect_aggtransfn (internal, gtype) -RETURNS internal -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_collect_aggtransfn'; - -CREATE FUNCTION collect_aggfinalfn (internal) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_collect_aggfinalfn'; - -CREATE AGGREGATE collect(gtype) ( - stype = internal, - sfunc = collect_aggtransfn, - finalfunc = collect_aggfinalfn, - parallel = safe -); - -CREATE FUNCTION vertex_collect_transfn(internal, vertex) -RETURNS internal -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - - -CREATE FUNCTION vertex_collect_finalfn(internal) -RETURNS vertex[] -LANGUAGE c -IMMUTABLE -PARALLEL SAFE AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE collect(vertex) ( - stype = internal, - sfunc = vertex_collect_transfn, - finalfunc = vertex_collect_finalfn, - parallel = safe -); - -CREATE FUNCTION vertex_collect_transfn_w_limit(internal, vertex, gtype) -RETURNS internal -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE collect(vertex, gtype) ( - stype = internal, - sfunc = vertex_collect_transfn_w_limit, - finalfunc = vertex_collect_finalfn, - parallel = safe -); - - -CREATE FUNCTION edge_collect_transfn(internal, edge) -RETURNS internal -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - - -CREATE FUNCTION edge_collect_finalfn(internal) -RETURNS edge[] -LANGUAGE c -IMMUTABLE -PARALLEL SAFE AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE collect(edge) ( - stype = internal, - sfunc = edge_collect_transfn, - finalfunc = edge_collect_finalfn, - parallel = safe -); - -CREATE FUNCTION edge_collect_transfn_w_limit(internal, edge, gtype) -RETURNS internal -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE collect(edge, gtype) ( - stype = internal, - sfunc = edge_collect_transfn_w_limit, - finalfunc = edge_collect_finalfn, - parallel = safe -); - -CREATE FUNCTION gtype_regr_accum(float8[], gtype, gtype) -RETURNS float8[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_corr(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_corr'; - -CREATE AGGREGATE corr(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_corr, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_covar_pop(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE covar_pop(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_covar_pop, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_covar_samp(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE covar_samp(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_covar_samp, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_regr_sxx(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE regr_sxx(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_regr_sxx, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_regr_syy(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE regr_syy(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_regr_syy, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_regr_sxy(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE regr_sxy(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_regr_sxy, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_regr_slope(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE regr_slope(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_regr_slope, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_regr_intercept(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE regr_intercept(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_regr_intercept, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_regr_avgx(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE regr_avgx(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_regr_avgx, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_regr_avgy(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE regr_avgy(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_regr_avgy, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - -CREATE FUNCTION gtype_regr_r2(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE AGGREGATE regr_r2(gtype, gtype) ( - stype = float8[], - sfunc = gtype_regr_accum, - finalfunc = gtype_regr_avgy, - combinefunc = float8_regr_combine, - finalfunc_modify = READ_ONLY, - initcond = '{0,0,0,0,0,0}', - parallel = SAFE -); - diff --git a/sql/postgraph-edge.sql.in b/sql/postgraph-edge.sql.in deleted file mode 100644 index d3435499..00000000 --- a/sql/postgraph-edge.sql.in +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - --- --- edge access opertators --- -CREATE FUNCTION edge_property_access_gtype(edge, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR -> (LEFTARG = edge, RIGHTARG = gtype, FUNCTION = edge_property_access_gtype); - - --- --- edge equality operators --- -CREATE FUNCTION edge_eq(edge, edge) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR = ( - FUNCTION = edge_eq, - LEFTARG = edge, - RIGHTARG = edge, - COMMUTATOR = =, - NEGATOR = <>, - RESTRICT = eqsel, - JOIN = eqjoinsel, - HASHES, - MERGES -); - -CREATE FUNCTION edge_ne(edge, edge) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <> ( - FUNCTION = edge_ne, - LEFTARG = edge, - RIGHTARG = edge, - COMMUTATOR = <>, - NEGATOR = =, - RESTRICT = neqsel, - JOIN = neqjoinsel -); - -CREATE FUNCTION edge_gt(edge, edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR > ( - FUNCTION = edge_gt, - LEFTARG = edge, - RIGHTARG = edge, - COMMUTATOR = >, - NEGATOR = <=, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel -); - -CREATE FUNCTION edge_ge(edge, edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR >= ( - FUNCTION = edge_ge, - LEFTARG = edge, - RIGHTARG = edge, - COMMUTATOR = >=, - NEGATOR = <, - RESTRICT = scalargesel, - JOIN = scalargejoinsel -); - - -CREATE FUNCTION edge_lt(edge, edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR < ( - FUNCTION = edge_lt, - LEFTARG = edge, - RIGHTARG = edge, - COMMUTATOR = <, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel -); - -CREATE FUNCTION edge_le(edge, edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <= ( - FUNCTION = edge_le, - LEFTARG = edge, - RIGHTARG = edge, - COMMUTATOR = <=, - NEGATOR = >, - RESTRICT = scalarlesel, - JOIN = scalarlejoinsel -); - --- --- edge - B-tree support functions --- --- comparison support -CREATE FUNCTION edge_btree_cmp(edge, edge) -RETURNS int -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- sort support -CREATE FUNCTION edge_btree_sort(internal) -RETURNS void -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE AS 'MODULE_PATHNAME'; - --- --- define operator classes for graphid --- -CREATE OPERATOR CLASS edge_ops -DEFAULT FOR TYPE edge -USING btree AS - OPERATOR 1 <, - OPERATOR 2 <=, - OPERATOR 3 =, - OPERATOR 4 >=, - OPERATOR 5 >, - FUNCTION 1 edge_btree_cmp (edge, edge), - FUNCTION 2 edge_btree_sort (internal); - --- --- edge functions --- -CREATE FUNCTION id(edge) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_id'; - -CREATE FUNCTION start_id(edge) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_start_id'; - -CREATE FUNCTION end_id(edge) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_end_id'; - -CREATE FUNCTION label(edge) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS -NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_label'; - -CREATE FUNCTION properties(edge) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_properties'; - -CREATE FUNCTION age_properties(edge) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_properties'; - -CREATE FUNCTION startnode(edge) -RETURNS vertex -LANGUAGE c -STABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_startnode'; - -CREATE FUNCTION endnode(edge) -RETURNS vertex -LANGUAGE c -STABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_endnode'; - - -CREATE FUNCTION type(edge) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME','edge_label'; - -CREATE FUNCTION keys(edge) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_keys'; diff --git a/sql/postgraph-geometric.sql.in b/sql/postgraph-geometric.sql.in deleted file mode 100644 index 81a55c25..00000000 --- a/sql/postgraph-geometric.sql.in +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- Geometric Operators --- -CREATE FUNCTION intersection_point(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_intersection_point'; - -CREATE OPERATOR # (FUNCTION = intersection_point, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_closest_point(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_closest_point'; - -CREATE OPERATOR ## (FUNCTION = gtype_closest_point, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_vertical(gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_vertical'; - -CREATE OPERATOR ?| (FUNCTION = gtype_vertical, RIGHTARG = gtype); - - -CREATE FUNCTION gtype_horizontal(gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_horizontal'; - -CREATE OPERATOR ?- (FUNCTION = gtype_horizontal, RIGHTARG = gtype); - - -CREATE FUNCTION gtype_center(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_center'; - -CREATE OPERATOR @@ (FUNCTION = gtype_center, RIGHTARG = gtype); - -CREATE FUNCTION gtype_distance(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_distance'; - -CREATE OPERATOR @-@ (FUNCTION = gtype_distance, RIGHTARG = gtype); - -CREATE FUNCTION gtype_perp(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_perp'; - -CREATE OPERATOR ?-| (FUNCTION = gtype_perp, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_parallel(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_parallel'; - -CREATE OPERATOR ?|| (FUNCTION = gtype_parallel, LEFTARG = gtype, RIGHTARG = gtype); - --- --- Geometric Functions --- -CREATE FUNCTION height(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_height'; - -CREATE FUNCTION width(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_width'; - -CREATE FUNCTION bound_box(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_bound_box'; - diff --git a/sql/postgraph-graphid.sql.in b/sql/postgraph-graphid.sql.in deleted file mode 100644 index ff6e650c..00000000 --- a/sql/postgraph-graphid.sql.in +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- graphid - comparison operators (=, <>, <, >, <=, >=) --- -CREATE FUNCTION graphid_eq(postgraph.graphid, postgraph.graphid) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR = ( - FUNCTION = graphid_eq, - LEFTARG = graphid, - RIGHTARG = graphid, - COMMUTATOR = =, - NEGATOR = <>, - RESTRICT = eqsel, - JOIN = eqjoinsel, - HASHES, - MERGES -); - -CREATE FUNCTION graphid_ne(graphid, graphid) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <> ( - FUNCTION = graphid_ne, - LEFTARG = graphid, - RIGHTARG = graphid, - COMMUTATOR = <>, - NEGATOR = =, - RESTRICT = neqsel, - JOIN = neqjoinsel -); - -CREATE FUNCTION graphid_lt(graphid, graphid) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION graphid_gt(graphid, graphid) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - - -CREATE FUNCTION graphid_le(graphid, graphid) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION graphid_ge(graphid, graphid) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - - --- --- graphid - B-tree support functions --- --- comparison support -CREATE FUNCTION graphid_btree_cmp(graphid, graphid) -RETURNS int -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- sort support -CREATE FUNCTION graphid_btree_sort(internal) -RETURNS void -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - - - --- --- graphid functions --- -CREATE FUNCTION _graphid(label_id int, entry_id bigint) -RETURNS graphid -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION _label_name(graph_oid oid, graphid) -RETURNS cstring -LANGUAGE c -STABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION _extract_label_id(graphid) -RETURNS label_id -LANGUAGE c -STABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - - - -CREATE OPERATOR < ( - FUNCTION = graphid_lt, - LEFTARG = graphid, - RIGHTARG = graphid, - COMMUTATOR = >, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel -); - -CREATE OPERATOR > ( - FUNCTION = graphid_gt, - LEFTARG = graphid, - RIGHTARG = graphid, - COMMUTATOR = <, - NEGATOR = <=, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel -); - -CREATE OPERATOR <= ( - FUNCTION = graphid_le, - LEFTARG = graphid, - RIGHTARG = graphid, - COMMUTATOR = >=, - NEGATOR = >, - RESTRICT = scalarlesel, - JOIN = scalarlejoinsel); - -CREATE OPERATOR >= ( - FUNCTION = graphid_ge, - LEFTARG = graphid, - RIGHTARG = graphid, - COMMUTATOR = <=, - NEGATOR = <, - RESTRICT = scalargesel, - JOIN = scalargejoinsel -); - --- --- graphid - hash operator class --- -CREATE FUNCTION graphid_hash_cmp(graphid) -RETURNS INTEGER -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR CLASS graphid_ops_hash -DEFAULT FOR TYPE graphid -USING hash -AS - OPERATOR 1 =, - FUNCTION 1 graphid_hash_cmp(graphid); --- --- btree operator classes for graphid --- -CREATE OPERATOR CLASS graphid_ops -DEFAULT FOR TYPE graphid -USING btree -AS - OPERATOR 1 <, - OPERATOR 2 <=, - OPERATOR 3 =, - OPERATOR 4 >=, - OPERATOR 5 >, - FUNCTION 1 graphid_btree_cmp (graphid, graphid), - FUNCTION 2 graphid_btree_sort (internal); diff --git a/sql/postgraph-gtype-lists.sql.in b/sql/postgraph-gtype-lists.sql.in deleted file mode 100644 index f618f4ba..00000000 --- a/sql/postgraph-gtype-lists.sql.in +++ /dev/null @@ -1,102 +0,0 @@ - - --- --- gtype - list literal (`[expr, ...]`) --- - -CREATE FUNCTION gtype_build_list(VARIADIC "any") -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_build_list() -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_build_list_noargs'; - - -CREATE FUNCTION gtype_access_slice(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_in_operator(gtype, gtype) -RETURNS bool -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR @= ( - FUNCTION = gtype_in_operator, - LEFTARG = gtype, - RIGHTARG = gtype, - NEGATOR = !@=, - RESTRICT = eqsel, - JOIN = eqjoinsel, - HASHES, - MERGES -); - --- --- Scalar Functions --- -CREATE FUNCTION head (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_head'; - -CREATE FUNCTION last (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_last'; - -CREATE FUNCTION size (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_size'; - --- --- list functions (reverse is defined in string functions) --- - --- keys -CREATE FUNCTION keys (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_keys'; - ---range -CREATE FUNCTION range (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_range'; - -CREATE FUNCTION range (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_range_w_skip'; \ No newline at end of file diff --git a/sql/postgraph-gtype.sql.in b/sql/postgraph-gtype.sql.in deleted file mode 100644 index 3268d464..00000000 --- a/sql/postgraph-gtype.sql.in +++ /dev/null @@ -1,565 +0,0 @@ - -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- gtype - map literal (`{key: expr, ...}`) --- -CREATE FUNCTION gtype_build_map(VARIADIC "any") -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_build_map() -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_build_map_noargs'; - --- --- gtype - access operators --- --- for series of `map.key` and `container[expr]` -CREATE FUNCTION gtype_field_access(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR -> ( - LEFTARG = gtype, - RIGHTARG = gtype, - FUNCTION = gtype_field_access -); - --- --- gtype - mathematical operators (+, -, *, /, %, ^) --- -CREATE FUNCTION gtype_add(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR + (FUNCTION = gtype_add, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = +); - -CREATE FUNCTION gtype_sub(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR - (FUNCTION = gtype_sub, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_neg(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR - (FUNCTION = gtype_neg, RIGHTARG = gtype); - -CREATE FUNCTION gtype_mul(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR * (FUNCTION = gtype_mul, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = *); - -CREATE FUNCTION gtype_div(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR / (FUNCTION = gtype_div, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_mod(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR % (FUNCTION = gtype_mod, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_pow(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ^ (FUNCTION = gtype_pow, LEFTARG = gtype, RIGHTARG = gtype); - --- --- Vector Operators --- -CREATE FUNCTION l2_distance(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <-> (FUNCTION = l2_distance, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = <->); - -CREATE FUNCTION inner_product(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_inner_product'; - -CREATE FUNCTION negative_inner_product(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE AS 'MODULE_PATHNAME', 'gtype_negative_inner_product'; - -CREATE OPERATOR <#> (FUNCTION = negative_inner_product, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = <#>); - -CREATE FUNCTION cosine_distance(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_cosine_distance'; - -CREATE OPERATOR <=> (FUNCTION = cosine_distance, LEFTARG = gtype, RIGHTARG = gtype, COMMUTATOR = <=>); - -CREATE FUNCTION l1_distance(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_l1_distance'; - -CREATE FUNCTION spherical_distance(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_spherical_distance'; - -CREATE FUNCTION dims(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_dims'; - -CREATE FUNCTION norm(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE AS 'MODULE_PATHNAME', 'gtype_norm'; - -CREATE FUNCTION l2_squared_distance(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_l2_squared_distance'; - -CREATE FUNCTION gtype_lt(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_gt(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_le(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_ge(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- --- gtype - comparison operators (=, <>, <, >, <=, >=) --- -CREATE FUNCTION gtype_eq(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR = ( - FUNCTION = gtype_eq, - LEFTARG = gtype, - RIGHTARG = gtype, - COMMUTATOR = =, - NEGATOR = <>, - RESTRICT = eqsel, - JOIN = eqjoinsel, - HASHES -); - -CREATE FUNCTION gtype_ne(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <> ( - FUNCTION = gtype_ne, - LEFTARG = gtype, - RIGHTARG = gtype, - COMMUTATOR = <>, - NEGATOR = =, - RESTRICT = neqsel, - JOIN = neqjoinsel -); - - -CREATE FUNCTION gtype_btree_cmp(gtype, gtype) -RETURNS INTEGER -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - - -CREATE OPERATOR < ( - FUNCTION = gtype_lt, - LEFTARG = gtype, - RIGHTARG = gtype, - COMMUTATOR = >, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel -); - -CREATE OPERATOR > ( - FUNCTION = gtype_gt, - LEFTARG = gtype, - RIGHTARG = gtype, - COMMUTATOR = <, - NEGATOR = <=, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel -); - -CREATE OPERATOR <= ( - FUNCTION = gtype_le, - LEFTARG = gtype, - RIGHTARG = gtype, - COMMUTATOR = >=, - NEGATOR = >, - RESTRICT = scalarlesel, - JOIN = scalarlejoinsel -); - -CREATE OPERATOR >= ( - FUNCTION = gtype_ge, - LEFTARG = gtype, - RIGHTARG = gtype, - COMMUTATOR = <=, - NEGATOR = <, - RESTRICT = scalargesel, - JOIN = scalargejoinsel -); - - --- --- gtype - btree operator class --- -CREATE OPERATOR CLASS gtype_ops_btree -DEFAULT FOR TYPE gtype -USING btree -AS - OPERATOR 1 <, - OPERATOR 2 <=, - OPERATOR 3 =, - OPERATOR 4 >, - OPERATOR 5 >=, - FUNCTION 1 gtype_btree_cmp(gtype, gtype); - - - - --- --- gtype - hash operator class --- -CREATE FUNCTION gtype_hash_cmp(gtype) -RETURNS INTEGER -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR CLASS gtype_ops_hash -DEFAULT FOR TYPE gtype -USING hash -AS - OPERATOR 1 =, - FUNCTION 1 gtype_hash_cmp(gtype); - --- --- gtype - access operators (->, ->>) --- -CREATE FUNCTION gtype_object_field(gtype, text) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR -> (LEFTARG = gtype, RIGHTARG = text, FUNCTION = gtype_object_field); - -CREATE FUNCTION gtype_object_field_text(gtype, text) -RETURNS text -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ->> (LEFTARG = gtype, RIGHTARG = text, FUNCTION = gtype_object_field_text); - -CREATE FUNCTION gtype_array_element(gtype, int4) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR -> (LEFTARG = gtype, RIGHTARG = int4, FUNCTION = gtype_array_element); - -CREATE FUNCTION gtype_array_element_text(gtype, int4) -RETURNS text -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ->> (LEFTARG = gtype, RIGHTARG = int4, FUNCTION = gtype_array_element_text); - --- --- gtype - contains operators (@>, <@) --- -CREATE FUNCTION gtype_contains(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR @> ( - LEFTARG = gtype, - RIGHTARG = gtype, - FUNCTION = gtype_contains, - COMMUTATOR = '<@', - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE FUNCTION gtype_contained_by(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <@ ( - LEFTARG = gtype, - RIGHTARG = gtype, - FUNCTION = gtype_contained_by, - COMMUTATOR = '@>', - RESTRICT = contsel, - JOIN = contjoinsel -); - --- --- Key Existence Operators (?, ?|, ?&) --- -CREATE FUNCTION gtype_exists(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ? ( - LEFTARG = gtype, - RIGHTARG = gtype, - FUNCTION = gtype_exists, - COMMUTATOR = '?', - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE FUNCTION gtype_exists_any(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ?| ( - LEFTARG = gtype, - RIGHTARG = gtype, - FUNCTION = gtype_exists_any, - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE FUNCTION gtype_exists_all(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ?& ( - LEFTARG = gtype, - RIGHTARG = gtype, - FUNCTION = gtype_exists_all, - RESTRICT = contsel, - JOIN = contjoinsel -); - --- --- gtype GIN support --- -CREATE FUNCTION gin_compare_gtype(text, text) -RETURNS int -AS 'MODULE_PATHNAME' -LANGUAGE C -IMMUTABLE STRICT -PARALLEL SAFE; - -CREATE FUNCTION gin_extract_gtype(gtype, internal) RETURNS internal -AS 'MODULE_PATHNAME' -LANGUAGE C -IMMUTABLE -STRICT -PARALLEL SAFE; - -CREATE FUNCTION gin_extract_gtype_query(gtype, internal, int2, internal, internal) -RETURNS internal -AS 'MODULE_PATHNAME' -LANGUAGE C -IMMUTABLE -STRICT -PARALLEL SAFE; - -CREATE FUNCTION gin_consistent_gtype(internal, int2, gtype, int4, internal, internal) -RETURNS bool -AS 'MODULE_PATHNAME' -LANGUAGE C -IMMUTABLE -STRICT -PARALLEL SAFE; - -CREATE FUNCTION gin_triconsistent_gtype(internal, int2, gtype, int4, internal, internal, internal) -RETURNS bool -LANGUAGE C -IMMUTABLE -STRICT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR CLASS gin_gtype_ops -DEFAULT FOR TYPE gtype -USING gin -AS - OPERATOR 7 @>, - OPERATOR 9 ?(gtype, gtype), - /*OPERATOR 10 ?|(gtype, gtype), - OPERATOR 11 ?&(gtype, gtype),*/ - FUNCTION 1 gin_compare_gtype, - FUNCTION 2 gin_extract_gtype, - FUNCTION 3 gin_extract_gtype_query, - FUNCTION 4 gin_consistent_gtype, - FUNCTION 6 gin_triconsistent_gtype, -STORAGE text; - -CREATE FUNCTION generate_series (gtype, gtype) -RETURNS SETOF gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'generate_series_gtype'; - - -CREATE FUNCTION generate_series (gtype, gtype, gtype) -RETURNS SETOF gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'generate_series_step_gtype'; diff --git a/sql/postgraph-network.sql.in b/sql/postgraph-network.sql.in deleted file mode 100644 index ec092e96..00000000 --- a/sql/postgraph-network.sql.in +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -CREATE FUNCTION gtype_bitwise_not(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ~ (FUNCTION = gtype_bitwise_not, RIGHTARG = gtype); - -CREATE FUNCTION gtype_bitwise_and(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR & (FUNCTION = gtype_bitwise_and, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_bitwise_or(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR | (FUNCTION = gtype_bitwise_or, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_inet_subnet_strict_contains(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR << ( - FUNCTION = gtype_inet_subnet_strict_contains, - LEFTARG = gtype, - RIGHTARG = gtype, - COMMUTATOR = '>>', - RESTRICT = positionsel, - JOIN = positionjoinsel -); - -CREATE FUNCTION gtype_inet_subnet_contains(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <<= (FUNCTION = gtype_inet_subnet_contains, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_inet_subnet_strict_contained_by(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR >> (FUNCTION = gtype_inet_subnet_strict_contained_by, LEFTARG = gtype, RIGHTARG = gtype); - - -CREATE FUNCTION gtype_inet_subnet_contained_by(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR >>= (FUNCTION = gtype_inet_subnet_contained_by, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_inet_subnet_contain_both(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR && (FUNCTION = gtype_inet_subnet_contain_both, LEFTARG = gtype, RIGHTARG = gtype); - --- --- Network Functions --- -CREATE FUNCTION abbrev(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_abbrev'; - -CREATE FUNCTION broadcast(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_broadcast'; - -CREATE FUNCTION family(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_family'; - -CREATE FUNCTION host(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_host'; - -CREATE FUNCTION hostmask(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_hostmask'; - -CREATE FUNCTION inet_merge(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_inet_merge'; - -CREATE FUNCTION inet_same_family(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_inet_same_family'; - -CREATE FUNCTION masklen(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_masklen'; - -CREATE FUNCTION netmask(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_netmask'; - -CREATE FUNCTION network(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_network'; - - -CREATE FUNCTION set_masklen(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_set_masklen'; - -CREATE FUNCTION trunc(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_trunc'; - -CREATE FUNCTION macaddr8_set7bit(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_macaddr8_set7bit'; diff --git a/sql/postgraph-number-functions.sql.in b/sql/postgraph-number-functions.sql.in deleted file mode 100644 index 0c5506be..00000000 --- a/sql/postgraph-number-functions.sql.in +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- Trig functions - radian input --- -CREATE FUNCTION sin (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_sin'; - -CREATE FUNCTION cos (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_cos'; - -CREATE FUNCTION tan (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tan'; - -CREATE FUNCTION cot (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_cot'; - -CREATE FUNCTION asin (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_asin'; - -CREATE FUNCTION acos (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_acos'; - -CREATE FUNCTION atan (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_atan'; - -CREATE FUNCTION atan2 (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_atan2'; - -CREATE FUNCTION sinh (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_sinh'; - -CREATE FUNCTION cosh (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_cosh'; - -CREATE FUNCTION tanh (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tanh'; - -CREATE FUNCTION asinh (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_asinh'; - -CREATE FUNCTION acosh (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_acosh'; - -CREATE FUNCTION atanh (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_atanh'; - -CREATE FUNCTION degrees (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_degrees'; - -CREATE FUNCTION radians (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_radians'; - -CREATE FUNCTION round (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_round'; - -CREATE FUNCTION round (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_round_w_precision'; - -CREATE FUNCTION ceil (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_ceil'; - -CREATE FUNCTION ceiling (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_ceil'; - -CREATE FUNCTION floor (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_floor'; - -CREATE FUNCTION abs (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_abs'; - -CREATE FUNCTION sign (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_sign'; - -CREATE FUNCTION log (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_log'; - -CREATE FUNCTION log10 (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_log10'; - -CREATE FUNCTION e () -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_e'; - -CREATE FUNCTION exp (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_exp'; - -CREATE FUNCTION sqrt (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_sqrt'; - -CREATE FUNCTION cbrt (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_cbrt'; - - -CREATE FUNCTION pi () -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_pi'; - -CREATE FUNCTION rand() -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_rand'; - -CREATE FUNCTION gcd (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_gcd'; - -CREATE FUNCTION lcm (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_lcm'; - -CREATE FUNCTION factorial (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_factorial'; - diff --git a/sql/postgraph-postgis.sql.in b/sql/postgraph-postgis.sql.in deleted file mode 100644 index e13517ad..00000000 --- a/sql/postgraph-postgis.sql.in +++ /dev/null @@ -1,1364 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - ------------------------------------------------------------------------------ --- GEOMETRY Operators ------------------------------------------------------------------------------ - - --- Availability: 2.2.0 -CREATE OR REPLACE FUNCTION ST_ShiftLongitude(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_longitude_shift'; - --- TODO: ST_Wrapx - - - - ------------------------------------------------------------------------------ --- BOX3D FUNCTIONS ------------------------------------------------------------------------------ - -CREATE FUNCTION ST_XMin (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_xmin'; - -CREATE FUNCTION ST_XMax (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_xmax'; - -CREATE FUNCTION ST_ZMin (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_zmin'; - -CREATE FUNCTION ST_YMin (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_ymin'; - -CREATE FUNCTION ST_YMax (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_ymax'; - -CREATE FUNCTION ST_ZMax (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_zmax'; - - - ------------------------------------------------------------------------------ --- Overloaded ST_Expand FUNCTIONS ------------------------------------------------------------------------------ - --- Availability: 1.2.2 -CREATE OR REPLACE FUNCTION ST_Expand(gtype,gtype) -RETURNS gtype -AS 'MODULE_PATHNAME', 'gtype_ST_Expand' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION ST_Expand(gtype, dx float8, dy float8) -RETURNS gtype -AS 'MODULE_PATHNAME', 'gtype_ST_Expand' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; - --- Availability: 2.3.0 -CREATE OR REPLACE FUNCTION ST_Expand(box gtype, dx float8, dy float8, dz float8 DEFAULT 0) -RETURNS gtype -AS 'MODULE_PATHNAME', 'gtype_ST_Expand' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; - --- Availability: 2.3.0 -CREATE OR REPLACE FUNCTION ST_Expand(geom gtype, dx gtype, dy gtype, dz gtype DEFAULT '0.0'::gtype, dm gtype DEFAULT '0.0'::gtype) -RETURNS gtype -AS 'MODULE_PATHNAME', 'gtype_ST_Expand' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; - - ------------------------------------------------------------------------------ --- BOX2D FUNCTIONS ------------------------------------------------------------------------------ - --- Availability: 1.5.0 -CREATE OR REPLACE FUNCTION postgis_getbbox(gtype) -RETURNS gtype -AS 'MODULE_PATHNAME','gtype_to_BOX2DF' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; - - --- Availability: 1.2.2 -CREATE OR REPLACE FUNCTION ST_MakeBox2d(geom1 gtype, geom2 gtype) -RETURNS gtype -AS 'MODULE_PATHNAME', 'gtype_construct' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; - --- --- PostGIS 2D Geometry Operators --- -CREATE OR REPLACE FUNCTION gtype_geometry_same(geom1 gtype, geom2 gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_same_2d'; - -CREATE OPERATOR ~= ( - LEFTARG = gtype, - RIGHTARG = gtype, - PROCEDURE = gtype_geometry_same, - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE OR REPLACE FUNCTION gtype_geometry_within(geom1 gtype, geom2 gtype) -RETURNS bool -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_within_2d'; - -CREATE OPERATOR @ ( -LEFTARG = gtype, -RIGHTARG = gtype, -PROCEDURE = gtype_geometry_within--, --- Updated: 3.4.0 to use selectivity estimator ---RESTRICT = gtype_gserialized_gist_sel_2d, --- Updated: 3.4.0 to use join selectivity estimator ---JOIN = gtype_gserialized_gist_joinsel_2d -); - -CREATE OR REPLACE FUNCTION gtype_geometry_overleft(geom1 gtype, geom2 gtype) -RETURNS bool -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_overleft_2d'; - -CREATE OPERATOR &< ( -LEFTARG = gtype, -RIGHTARG = gtype, -PROCEDURE = gtype_geometry_overleft, -RESTRICT = positionsel, -JOIN = positionjoinsel -); - -CREATE OR REPLACE FUNCTION gtype_geometry_below(geom1 gtype, geom2 gtype) -RETURNS bool -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_below_2d'; - - -CREATE OPERATOR <<| ( -LEFTARG = gtype, -RIGHTARG = gtype, -PROCEDURE = gtype_geometry_below, ---COMMuTAOR = '<<|', -RESTRICT = positionsel, -JOIN = positionjoinsel -); - - -CREATE OR REPLACE FUNCTION gtype_geometry_overbelow(geom1 gtype, geom2 gtype) -RETURNS bool -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_overbelow_2d'; - -CREATE OPERATOR &<| ( -LEFTARG = gtype, -RIGHTARG = gtype, -PROCEDURE = gtype_geometry_overbelow, -RESTRICT = positionsel, -JOIN = positionjoinsel -); - - -CREATE OR REPLACE FUNCTION gtype_geometry_overright(geom1 gtype, geom2 gtype) -RETURNS bool -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_overright_2d'; - -CREATE OPERATOR &> ( -LEFTARG = gtype, -RIGHTARG = gtype, -PROCEDURE = gtype_geometry_overright, -RESTRICT = positionsel, -JOIN = positionjoinsel -); - -CREATE OR REPLACE FUNCTION gtype_geometry_overabove(geom1 gtype, geom2 gtype) -RETURNS bool -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_overabove_2d'; - -CREATE OPERATOR |&> ( -LEFTARG = gtype, -RIGHTARG = gtype, -PROCEDURE = gtype_geometry_overabove, -RESTRICT = positionsel, -JOIN = positionjoinsel -); - -CREATE OR REPLACE FUNCTION gtype_geometry_above(geom1 gtype, geom2 gtype) -RETURNS bool -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_above_2d'; - -CREATE OPERATOR |>> ( -LEFTARG = gtype, -RIGHTARG = gtype, -PROCEDURE = gtype_geometry_above, ---COMMuTAOR = '<<|', -RESTRICT = positionsel, -JOIN = positionjoinsel -); - --- --- 2D Gist Index --- -CREATE OR REPLACE FUNCTION geometry_gist_distance_2d(internal, gtype, integer) -RETURNS float8 -AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_distance_2d' -LANGUAGE 'c' PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION geometry_gist_consistent_2d(internal,gtype,integer) -RETURNS bool -AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_consistent_2d' -LANGUAGE 'c' PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION geometry_gist_compress_2d(internal) -RETURNS internal -AS 'MODULE_PATHNAME','gtype_gserialized_gist_compress_2d' -LANGUAGE 'c' PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION geometry_gist_penalty_2d(internal,internal,internal) -RETURNS internal -AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_penalty_2d' -LANGUAGE 'c' PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION geometry_gist_picksplit_2d(internal, internal) -RETURNS internal -AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_picksplit_2d' -LANGUAGE 'c' PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION geometry_gist_union_2d(bytea, internal) -RETURNS internal -AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_union_2d' -LANGUAGE 'c' PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION geometry_gist_same_2d(geom1 gtype, geom2 gtype, internal) -RETURNS internal -AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_same_2d' -LANGUAGE 'c' PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION geometry_gist_decompress_2d(internal) -RETURNS internal -AS 'MODULE_PATHNAME' ,'gtype_gserialized_gist_decompress_2d' -LANGUAGE 'c' PARALLEL SAFE; - -CREATE OR REPLACE FUNCTION geometry_gist_sortsupport_2d(internal) -RETURNS void -AS 'MODULE_PATHNAME', 'gtype_gserialized_gist_sortsupport_2d' -LANGUAGE 'c' STRICT; - -CREATE OPERATOR CLASS gist_geometry_ops_2d - DEFAULT FOR TYPE gtype USING GIST AS - STORAGE public.box2df, - OPERATOR 1 << , - OPERATOR 2 &< , - OPERATOR 3 && , - OPERATOR 4 &> , - OPERATOR 5 >> , - OPERATOR 6 ~= , - OPERATOR 7 ~ , - OPERATOR 8 @ , - OPERATOR 9 &<| , - OPERATOR 10 <<| , - OPERATOR 11 |>> , - OPERATOR 12 |&> , - OPERATOR 13 <-> FOR ORDER BY gtype_ops_btree, - OPERATOR 14 <#> FOR ORDER BY gtype_ops_btree, --- --- Sort support in bulk indexing not included in the default --- opclass for PostgreSQL versions <15 due to query performance --- degradation caused by GiST index page overlap. --- Since PostgreSQL 15 sorting build uses picksplit function to --- find better partitioning for index records. This allows to --- build indices performing similarly to those produced by default --- method while still reducing index build time significantly. --- --- To enable sortsupport: --- alter operator family gist_geometry_ops_2d using gist --- add function 11 (geometry) --- geometry_gist_sortsupport_2d (internal); --- --- To remove sortsupport: --- alter operator family gist_geometry_ops_2d using gist --- drop function 11 (geometry); --- - FUNCTION 8 geometry_gist_distance_2d (internal, gtype, integer), - FUNCTION 1 geometry_gist_consistent_2d (internal, gtype, integer), - FUNCTION 2 geometry_gist_union_2d (bytea, internal), - FUNCTION 3 geometry_gist_compress_2d (internal), - FUNCTION 4 geometry_gist_decompress_2d (internal), - FUNCTION 5 geometry_gist_penalty_2d (internal, internal, internal), - FUNCTION 6 geometry_gist_picksplit_2d (internal, internal), - FUNCTION 7 geometry_gist_same_2d (geom1 gtype, geom2 gtype, internal)/*, - FUNCTION 11 geometry_gist_sortsupport_2d(gtype)*/; - --- --- PostGIS N-D Geometry Operators --- -CREATE OR REPLACE FUNCTION geometry_overlaps_nd(gtype, gtype) -RETURNS boolean -AS 'MODULE_PATHNAME' ,'gtype_gserialized_overlaps' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE -COST 1; - -CREATE OPERATOR &&& ( - LEFTARG = gtype, RIGHTARG = gtype, PROCEDURE = geometry_overlaps_nd, - COMMUTATOR = '&&&'--, - --RESTRICT = gserialized_gist_sel_nd, - --JOIN = gserialized_gist_joinsel_nd -); - -CREATE OR REPLACE FUNCTION geometry_contains_nd(gtype, gtype) -RETURNS boolean -AS 'MODULE_PATHNAME' ,'gtype_gserialized_contains' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE -COST 1; - -CREATE OPERATOR ~~ ( - LEFTARG = gtype, RIGHTARG = gtype, PROCEDURE = geometry_contains_nd--, - --COMMUTATOR = '@@', - --RESTRICT = gserialized_gist_sel_nd, - --JOIN = gserialized_gist_joinsel_nd -); - -CREATE OR REPLACE FUNCTION geometry_within_nd(gtype, gtype) -RETURNS boolean -AS 'MODULE_PATHNAME' ,'gtype_gserialized_within' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE -COST 1; - -CREATE OPERATOR @@ ( - LEFTARG = gtype, RIGHTARG = gtype, PROCEDURE = geometry_within_nd, - COMMUTATOR = '~~'/*, - RESTRICT = gserialized_gist_sel_nd, - JOIN = gserialized_gist_joinsel_nd*/ -); - -CREATE OR REPLACE FUNCTION geometry_same_nd(gtype, gtype) -RETURNS boolean -AS 'MODULE_PATHNAME' ,'gtype_gserialized_same' -LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE -COST 1; - -CREATE OPERATOR ~~= ( - LEFTARG = gtype, RIGHTARG = gtype, PROCEDURE = geometry_same_nd, - COMMUTATOR = '~~='/*, - RESTRICT = gserialized_gist_sel_nd, - JOIN = gserialized_gist_joinsel_nd*/ -); - -CREATE FUNCTION ST_DistanceCPA (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_ST_DistanceCPA'; - -CREATE OPERATOR |=| ( - LEFTARG = gtype, RIGHTARG = gtype, - PROCEDURE = ST_DistanceCPA, - COMMUTATOR = '|=|' -); - --- Availability: 2.2.0 -CREATE OR REPLACE FUNCTION gtype_distance_centroid_nd(gtype, gtype) -RETURNS gtype -IMMUTABLE -STRICT -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME', 'gtype_distance_nd'; - --- Availability: 2.2.0 -CREATE OPERATOR <<->> ( - LEFTARG = gtype, - RIGHTARG = gtype, - PROCEDURE = gtype_distance_centroid_nd, - COMMUTATOR = '<<->>' -); - - --- Availability: 2.2.0 -CREATE OR REPLACE FUNCTION gtype_gist_distance_nd(internal,gtype,integer) -RETURNS gtype -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME', 'gtype_gist_distance'; - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION gtype_gist_consistent_nd(internal,gtype,integer) -RETURNS gtype -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME', 'gtype_gist_consistent'; - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION gtype_gist_compress_nd(internal) -RETURNS internal -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME','gtype_gist_compress'; - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION gtype_gist_penalty_nd(internal,internal,internal) -RETURNS internal -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME' ,'gtype_gist_penalty'; - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION gtype_gist_picksplit_nd(internal, internal) -RETURNS internal -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME' ,'gtype_gist_picksplit'; - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION gtype_gist_union_nd(bytea, internal) -RETURNS internal -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME' ,'gtype_gist_union'; - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION gtype_gist_same_nd(gtype, gtype, internal) -RETURNS internal -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME' ,'gtype_gist_same'; - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION gtype_gist_decompress_nd(internal) -RETURNS internal -PARALLEL SAFE -LANGUAGE 'c' -AS 'MODULE_PATHNAME' ,'gtype_gist_decompress'; - - --- Availability: 2.0.0 -CREATE OPERATOR CLASS gist_geometry_ops_nd - FOR TYPE gtype USING GIST AS - STORAGE public.gidx, - OPERATOR 3 &&& , - -- Availability: 3.0.0 - OPERATOR 6 ~~= , - -- Availability: 3.0.0 - OPERATOR 7 ~~ , - -- Availability: 3.0.0 - OPERATOR 8 @@ , - -- Availability: 2.2.0 - OPERATOR 13 <<->> FOR ORDER BY pg_catalog.float_ops, - -- Availability: 2.2.0 - OPERATOR 20 |=| FOR ORDER BY pg_catalog.float_ops, - -- Availability: 2.2.0 - FUNCTION 8 gtype_gist_distance_nd (internal, gtype, integer), - FUNCTION 1 gtype_gist_consistent_nd (internal, gtype, integer), - FUNCTION 2 gtype_gist_union_nd (bytea, internal), - FUNCTION 3 gtype_gist_compress_nd (internal), - FUNCTION 4 gtype_gist_decompress_nd (internal), - FUNCTION 5 gtype_gist_penalty_nd (internal, internal, internal), - FUNCTION 6 gtype_gist_picksplit_nd (internal, internal), - FUNCTION 7 gtype_gist_same_nd (gtype, gtype, internal); - --- --- PostGIS Functions --- - --- --- PostGIS Constructors --- -CREATE FUNCTION ST_MakePoint (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_makepoint'; - -CREATE FUNCTION ST_MakePoint (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_makepoint'; - -CREATE FUNCTION ST_MakePoint (gtype, gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_makepoint'; - -CREATE FUNCTION ST_MakePointM (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_makepoint3dm'; - - -CREATE FUNCTION ST_asEWKT (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME', 'gtype_asEWKT'; - -CREATE FUNCTION asEWKT (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME', 'gtype_asEWKT'; - -CREATE FUNCTION addbbox (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_addBBOX'; - -CREATE FUNCTION postgis_addbbox (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_addBBOX'; - -CREATE FUNCTION dropbbox (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_dropBBOX'; - -CREATE FUNCTION postgis_dropbbox (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_dropBBOX'; - - -CREATE FUNCTION ST_X (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_x_point'; - -CREATE FUNCTION X (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_x_point'; - -CREATE FUNCTION ST_Y (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_y_point'; - -CREATE FUNCTION Y (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_y_point'; - -CREATE FUNCTION ST_Z (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_z_point'; - -CREATE FUNCTION Z (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_z_point'; - -CREATE FUNCTION ST_M (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_m_point'; - -CREATE FUNCTION M (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 1 -AS 'MODULE_PATHNAME', 'gtype_m_point'; - --- --- PostGIS Functions - Measures --- -CREATE FUNCTION ST_3DLength(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_length_linestring'; - -CREATE FUNCTION ST_Length2d(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_length2d_linestring'; - -CREATE FUNCTION ST_Length(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_length2d_linestring'; - -CREATE FUNCTION ST_LengthSpheroid(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME', 'gtype_length_ellipsoid_linestring'; - - - -CREATE FUNCTION ST_IsPolygonCW (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_ST_IsPolygonCW'; - -CREATE FUNCTION ST_IsPolygonCCW (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_ST_IsPolygonCCW'; - -CREATE FUNCTION ST_DistanceSpheroid (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME', 'gtype_distance_ellipsoid'; - -CREATE FUNCTION ST_DistanceSpheroid (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME', 'gtype_distance_ellipsoid'; - -CREATE FUNCTION ST_azimuth (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_azimuth'; - - --- --- PostGIS Functions - Temporal --- -CREATE FUNCTION ST_ClosestPointOfApproach (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_ST_ClosestPointOfApproach'; - - - -CREATE FUNCTION ST_IsValidTrajectory (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_st_isvalidtrajectory'; - -CREATE FUNCTION ST_CPAWithin (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_ST_CPAWithin'; - - --- --- PostGIS Functions - GEOS --- -CREATE FUNCTION ST_Intersection (gtype, gtype, gtype DEFAULT '-1.0'::gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_st_intersection'; - -CREATE FUNCTION ST_GeneratePoints (area gtype, npoints gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME', 'gtype_st_generatepoints'; - -CREATE FUNCTION ST_GeneratePoints (area gtype, npoints gtype, seed gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME', 'gtype_st_generatepoints'; - -CREATE FUNCTION ST_ConvexHull (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_convexhull'; - -CREATE FUNCTION ST_SymDifference (gtype, gtype, gtype DEFAULT '-1.0'::gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_st_symdifference'; - -CREATE FUNCTION ST_SymDifference (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_st_symdifference'; - -CREATE FUNCTION ST_HausdorffDistance (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_hausdorffdistance'; - -CREATE FUNCTION ST_HausdorffDistance (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_hausdorffdistancedensify'; - -CREATE FUNCTION ST_FrechetDistance (gtype, gtype, gtype default '-1.0'::gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 5000 -AS 'MODULE_PATHNAME', 'gtype_st_frechet_distance'; - --- --- Algorithms --- -CREATE FUNCTION ST_Simplify (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_ST_Simplify'; - -CREATE FUNCTION ST_Simplify (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_ST_Simplify'; - -CREATE OR REPLACE FUNCTION ST_SnapToGrid(gtype, gtype, gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_snaptogrid'; - -CREATE OR REPLACE FUNCTION ST_SnapToGrid(gtype, gtype, gtype) -RETURNS gtype -AS $$ SELECT ST_SnapToGrid($1, '0'::gtype, '0'::gtype, $2, $3) $$ -LANGUAGE sql -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_SnapToGrid(gtype, gtype) -RETURNS gtype -AS $$ SELECT ST_SnapToGrid($1, '0'::gtype, '0'::gtype, $2, $2) $$ -LANGUAGE sql -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_SnapToGrid(gtype, gtype, gtype, gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_snaptogrid_pointoff'; - - --- --- PostGIS Affine Functions --- -CREATE FUNCTION ST_Affine (gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_affine'; - -CREATE OR REPLACE FUNCTION ST_Affine(gtype, gtype, gtype, gtype, gtype, gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Affine($1, $2, $3, 0, $4, $5, 0, 0, 0, 1, $6, $7, 0)' -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_Rotate(gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Affine($1, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0, 1, 0, 0, 0)' -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_Rotate(gtype, gtype, gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Affine($1, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0, 1, $3 - cos($2) * $3 + sin($2) * $4, $4 - sin($2) * $3 - cos($2) * $4, 0)' -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_Rotate(gtype, gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Affine($1, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0, 1, ST_X($3) - cos($2) * ST_X($3) + sin($2) * ST_Y($3), ST_Y($3) - sin($2) * ST_X($3) - cos($2) * ST_Y($3), 0)' -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_RotateZ(gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Rotate($1, $2)' -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_RotateX(gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Affine($1, 1, 0, 0, 0, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0)' -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_RotateY(gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Affine($1, cos($2), 0, sin($2), 0, 1, 0, -sin($2), 0, cos($2), 0, 0, 0)' -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_Translate(gtype, gtype, gtype, gtype) -RETURNS gtype -AS $$ SELECT ST_Affine($1, '1'::gtype, '0'::gtype, '0'::gtype, '0'::gtype, '1'::gtype, '0'::gtype, '0'::gtype, '0'::gtype, '1'::gtype, $2, $3, $4) $$ -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_Translate(gtype, gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Translate($1, $2, $3, 0)' -LANGUAGE 'sql' -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; - -CREATE OR REPLACE FUNCTION ST_Scale(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_st_scale_2_args'; - -CREATE OR REPLACE FUNCTION ST_Scale(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_st_scale'; - -CREATE OR REPLACE FUNCTION ST_Scale(gtype, gtype, gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Scale($1, ST_MakePoint($2, $3, $4))' -LANGUAGE sql -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; -/* XXX: Gonna Have to Get Creative With This One -CREATE OR REPLACE FUNCTION ST_Scale(gtype, gtype, gtype) -RETURNS gtype -AS 'SELECT ST_Scale($1, $2, $3, 1.0::float8::gtype)' -LANGUAGE sql -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50; -*/ --- --- MISC --- - -CREATE OR REPLACE FUNCTION ST_Force2D(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_force_2d'; - -CREATE OR REPLACE FUNCTION ST_Force3DZ(gtype, gtype default '0.0') -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_force_3dz'; - -CREATE OR REPLACE FUNCTION ST_Force3DM(gtype, gtype default '0.0') -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_force_3dm'; - -CREATE OR REPLACE FUNCTION ST_Force4D(gtype, gtype default '0.0', gtype default '0.0') -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_force_4d'; - -CREATE OR REPLACE FUNCTION ST_ForceCollection(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_force_collection'; - -CREATE OR REPLACE FUNCTION ST_Multi(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_force_multi'; - ---------------------------------------------------------------- --- 3D-functions ---------------------------------------------------------------- - -CREATE OR REPLACE FUNCTION ST_3DDistance(geom1 gtype, geom2 gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_3DDistance'; - -CREATE OR REPLACE FUNCTION ST_3DMaxDistance(geom1 gtype, geom2 gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_maxdistance3d'; - -CREATE OR REPLACE FUNCTION ST_3DClosestPoint(geom1 gtype, geom2 gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_closestpoint3d'; - -CREATE OR REPLACE FUNCTION ST_3DShortestLine(geom1 gtype, geom2 gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_shortestline3d'; - -CREATE OR REPLACE FUNCTION ST_3DLongestLine(geom1 gtype, geom2 gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME', 'gtype_longestline3d'; - - --- PostGIS equivalent function: AsText(geometry) -CREATE OR REPLACE FUNCTION ST_AsText(gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME','gtype_asText'; - --- Availability: 2.5.0 --- PostGIS equivalent function: AsText(gtype, gtype) -CREATE OR REPLACE FUNCTION ST_AsText(gtype, gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME','gtype_asText'; - - -CREATE OR REPLACE FUNCTION ST_3DDWithin(geom1 gtype, geom2 gtype,gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 500 -AS 'MODULE_PATHNAME', 'gtype_dwithin3d' -; - - -CREATE OR REPLACE FUNCTION ST_3DDFullyWithin(geom1 gtype, geom2 gtype,gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 500 -AS 'MODULE_PATHNAME', 'gtype_dfullywithin3d' -; - - -CREATE OR REPLACE FUNCTION ST_Dimension(geom gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 10 -AS 'MODULE_PATHNAME', 'gtype_dimension' -; - - -CREATE OR REPLACE FUNCTION ST_GeometryType(geom gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 10 -AS 'MODULE_PATHNAME', 'gtype_geometrytype'; - - --- PostGIS equivalent function: NumGeometries(geometry) -CREATE OR REPLACE FUNCTION ST_NumGeometries(geom gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 10 -AS 'MODULE_PATHNAME', 'gtype_numgeometries_collection'; - --- PostGIS equivalent function: ST_GeometryN(geometry) -CREATE OR REPLACE FUNCTION ST_GeometryN(geom gtype, gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 10 -AS 'MODULE_PATHNAME', 'gtype_geometryn_collection'; - - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION ST_NumPatches(gtype) -RETURNS gtype -LANGUAGE 'sql' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 10 -AS $$ - SELECT CASE WHEN @extschema@.ST_GeometryType($1) = ('"ST_PolyhedralSurface"'::gtype) - THEN @extschema@.ST_NumGeometries($1) - ELSE NULL END -$$; - - --- Availability: 2.0.0 -CREATE OR REPLACE FUNCTION ST_PatchN(gtype, gtype) -RETURNS gtype -LANGUAGE 'sql' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 10 -AS $$ - SELECT CASE WHEN @extschema@.ST_GeometryType($1) = ('"ST_PolyhedralSurface"'::gtype) - THEN @extschema@.ST_GeometryN($1, $2) - ELSE NULL END -$$; - --- PostGIS equivalent function: NumGeometries(geometry) -CREATE OR REPLACE FUNCTION ST_Reverse(geom gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 10 -AS 'MODULE_PATHNAME', 'gtype_st_reverse'; - -CREATE OR REPLACE FUNCTION ST_GeomFromText(gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME','gtype_from_text'; - --- PostGIS equivalent function: ST_GeometryFromText(gtype, gtype) -CREATE OR REPLACE FUNCTION ST_GeomFromText(gtype, gtype) -RETURNS gtype -LANGUAGE 'c' -IMMUTABLE -STRICT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME','gtype_from_text'; \ No newline at end of file diff --git a/sql/postgraph-range.sql.in b/sql/postgraph-range.sql.in deleted file mode 100644 index 1d3ab186..00000000 --- a/sql/postgraph-range.sql.in +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- Range Functions --- -CREATE FUNCTION intrange(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_intrange'; - -CREATE FUNCTION intrange(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_intrange'; - -CREATE FUNCTION numrange(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_numrange'; - -CREATE FUNCTION numrange(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_numrange'; - -CREATE FUNCTION tsrange(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tsrange'; - -CREATE FUNCTION tsrange(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tsrange'; - -CREATE FUNCTION tstzrange(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tstzrange'; - -CREATE FUNCTION tstzrange(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tstzrange'; - -CREATE FUNCTION daterange(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_daterange'; - -CREATE FUNCTION daterange(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -CALLED ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_daterange'; - diff --git a/sql/postgraph-string-functions.sql.in b/sql/postgraph-string-functions.sql.in deleted file mode 100644 index aa4dbcdd..00000000 --- a/sql/postgraph-string-functions.sql.in +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - -- --- gtype - string matching (`STARTS WITH`, `ENDS WITH`, `CONTAINS`, & =~) --- --- TODO: Operators ^=, $=, @=, PostGres Regex --- -CREATE FUNCTION gtype_string_match_starts_with(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_string_match_ends_with(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_string_match_contains(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- --- LIKE --- -CREATE FUNCTION gtype_like(gtype, gtype) -RETURNS BOOLEAN -LANGUAGE C -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ~~~ ( - FUNCTION = gtype_like, - LEFTARG = gtype, - RIGHTARG = gtype, - RESTRICT = LIKESEL, - JOIN = LIKEJOINSEL, - NEGATOR = !~~ -); - -CREATE FUNCTION gtype_not_like(gtype, gtype) -RETURNS BOOLEAN -LANGUAGE C -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR !~~ ( - FUNCTION = gtype_not_like, - LEFTARG = gtype, - RIGHTARG = gtype, - RESTRICT = NLIKESEL, - JOIN = NLIKEJOINSEL, - NEGATOR = ~~~ -); - - -CREATE FUNCTION gtype_ilike(gtype, gtype) -RETURNS BOOLEAN -LANGUAGE C -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ~~* ( - FUNCTION = gtype_ilike, - LEFTARG = gtype, - RIGHTARG = gtype, - RESTRICT = LIKESEL, - JOIN = LIKEJOINSEL, - NEGATOR = !~~* -); - -CREATE FUNCTION gtype_not_ilike(gtype, gtype) -RETURNS BOOLEAN -LANGUAGE C -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR !~~* ( - FUNCTION = gtype_not_ilike, - LEFTARG = gtype, - RIGHTARG = gtype, - RESTRICT = NLIKESEL, - JOIN = NLIKEJOINSEL, - NEGATOR = ~~* -); - --- --- Regex --- -CREATE FUNCTION regex_match_case_sensitive(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -COST 1 -AS 'MODULE_PATHNAME', 'gtype_eq_tilde'; - -CREATE OPERATOR ~ ( - FUNCTION = regex_match_case_sensitive, - LEFTARG = gtype, - RIGHTARG = gtype -); - -CREATE OPERATOR =~ ( - FUNCTION = regex_match_case_sensitive, - LEFTARG = gtype, - RIGHTARG = gtype -); - -CREATE FUNCTION regex_match_case_insensitive(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_match_case_insensitive'; - -CREATE OPERATOR ~* ( - FUNCTION = regex_match_case_insensitive, - LEFTARG = gtype, - RIGHTARG = gtype -); - -CREATE FUNCTION regex_not_cs(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_regex_not_cs'; - -CREATE OPERATOR !~ ( - FUNCTION = regex_not_cs, - LEFTARG = gtype, - RIGHTARG = gtype -); - -CREATE FUNCTION regex_not_ci(gtype, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_regex_not_ci'; - -CREATE OPERATOR !~* ( - FUNCTION = regex_not_ci, - LEFTARG = gtype, - RIGHTARG = gtype -); - --- --- String functions --- -CREATE FUNCTION reverse (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_reverse'; - -CREATE FUNCTION toupper (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_toupper'; - -CREATE FUNCTION tolower (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tolower'; - -CREATE FUNCTION ltrim (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_ltrim'; - -CREATE FUNCTION rtrim (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_rtrim'; - -CREATE FUNCTION "trim" (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_trim'; - -CREATE FUNCTION right (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_right'; - -CREATE FUNCTION left (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_left'; - -CREATE FUNCTION initcap (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_initcap'; - -CREATE FUNCTION "substring" (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_substring_w_len'; - -CREATE FUNCTION "substring" (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_substring'; - -CREATE FUNCTION split (gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_split'; - -CREATE FUNCTION replace (gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_replace'; - -CREATE FUNCTION sha224 (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_sha224'; - -CREATE FUNCTION sha256 (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_sha256'; - -CREATE FUNCTION sha384 (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_sha384'; - -CREATE FUNCTION sha512 (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_sha512'; - -CREATE FUNCTION md5 (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_md5'; - diff --git a/sql/postgraph-temporal.sql.in b/sql/postgraph-temporal.sql.in deleted file mode 100644 index 42ad8390..00000000 --- a/sql/postgraph-temporal.sql.in +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- Temporal Functions --- -CREATE FUNCTION age(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_age_w2args'; - -CREATE FUNCTION age(gtype) -RETURNS gtype -LANGUAGE c -STABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_age_today'; - -CREATE FUNCTION "extract"(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_extract'; - -CREATE FUNCTION date_part(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_date_part'; - -CREATE FUNCTION date_bin(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_date_bin'; - -CREATE FUNCTION date_trunc(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_date_trunc'; - -CREATE FUNCTION date_trunc(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_date_trunc_zone'; - -CREATE FUNCTION overlaps(gtype, gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_overlaps'; - -CREATE FUNCTION isfinite(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_isfinite'; - -CREATE FUNCTION justify_hours(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_justify_hours'; - -CREATE FUNCTION justify_days(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_justify_days'; - -CREATE FUNCTION justify_interval(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_justify_interval'; - -CREATE FUNCTION make_date(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_make_date'; - -CREATE FUNCTION make_time(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_make_time'; - -CREATE FUNCTION make_timestamp(gtype, gtype, gtype, gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_make_timestamp'; - -CREATE FUNCTION make_timestamptz(gtype, gtype, gtype, gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -STABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_make_timestamptz'; - -CREATE FUNCTION make_timestamptz(gtype, gtype, gtype, gtype, gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -STABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME','gtype_make_timestamptz_wtimezone'; - diff --git a/sql/postgraph-traversal.sql.in b/sql/postgraph-traversal.sql.in deleted file mode 100644 index 473b95c4..00000000 --- a/sql/postgraph-traversal.sql.in +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - --- --- traversal - equality operators (=, <>, >) --- -CREATE FUNCTION traversal_eq(traversal, traversal) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR = ( - FUNCTION = traversal_eq, - LEFTARG = traversal, - RIGHTARG = traversal, - COMMUTATOR = =, - NEGATOR = <>, - RESTRICT = eqsel, - JOIN = eqjoinsel, - HASHES -); - -CREATE FUNCTION traversal_ne(traversal, traversal) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <> ( - FUNCTION = traversal_ne, - LEFTARG = traversal, - RIGHTARG = traversal, - COMMUTATOR = <>, - NEGATOR = =, - RESTRICT = neqsel, - JOIN = neqjoinsel, - HASHES -); - -CREATE FUNCTION traversal_gt(traversal, traversal) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR > ( - FUNCTION = traversal_gt, - LEFTARG = traversal, - RIGHTARG = traversal, - COMMUTATOR = >, - NEGATOR = <=, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel -); - -CREATE FUNCTION traversal_ge(traversal, traversal) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR >= ( - FUNCTION = traversal_ge, - LEFTARG = traversal, - RIGHTARG = traversal, - COMMUTATOR = >=, - NEGATOR = <, - RESTRICT = scalargesel, - JOIN = scalargejoinsel -); - - -CREATE FUNCTION traversal_lt(traversal, traversal) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR < ( - FUNCTION = traversal_lt, - LEFTARG = traversal, - RIGHTARG = traversal, - COMMUTATOR = <, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel -); - -CREATE FUNCTION traversal_le(traversal, traversal) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <= ( - FUNCTION = traversal_le, - LEFTARG = traversal, - RIGHTARG = traversal, - COMMUTATOR = <=, - NEGATOR = >, - RESTRICT = scalarlesel, - JOIN = scalarlejoinsel -); - --- --- traversal - B-tree support functions --- --- comparison support -CREATE FUNCTION traversal_btree_cmp(traversal, traversal) -RETURNS int -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- sort support -CREATE FUNCTION traversal_btree_sort(internal) -RETURNS void -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE AS 'MODULE_PATHNAME'; - --- --- define operator classes for graphid --- -CREATE OPERATOR CLASS traversal_ops -DEFAULT FOR TYPE traversal -USING btree AS - OPERATOR 1 <, - OPERATOR 2 <=, - OPERATOR 3 =, - OPERATOR 4 >=, - OPERATOR 5 >, - FUNCTION 1 traversal_btree_cmp (traversal, traversal), - FUNCTION 2 traversal_btree_sort (internal); - --- --- traversal functions --- -CREATE FUNCTION relationships(traversal) RETURNS edge[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'traversal_edges'; - -CREATE FUNCTION nodes(traversal) RETURNS vertex[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'traversal_nodes'; - -CREATE FUNCTION size(traversal) RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'traversal_size'; - -CREATE FUNCTION length(traversal) RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'traversal_length'; - -CREATE OPERATOR @-@ ( - FUNCTION = length, - RIGHTARG = traversal -); diff --git a/sql/postgraph-tsearch.sql.in b/sql/postgraph-tsearch.sql.in deleted file mode 100644 index 20967808..00000000 --- a/sql/postgraph-tsearch.sql.in +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- Text Search Operators --- - -CREATE FUNCTION gtype_tsquery_or(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tsquery_or'; - -CREATE OPERATOR || (FUNCTION = gtype_tsquery_or, LEFTARG = gtype, RIGHTARG = gtype); - -CREATE FUNCTION gtype_tsquery_not(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tsquery_not'; - -CREATE OPERATOR !! (FUNCTION = gtype_tsquery_not, RIGHTARG = gtype); - - --- --- Text Search Functions --- -CREATE FUNCTION ts_delete(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_ts_delete'; - -CREATE FUNCTION strip(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_ts_strip'; - -CREATE FUNCTION tsquery_phrase(gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tsquery_phrase'; - -CREATE FUNCTION tsquery_phrase(gtype, gtype, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tsquery_phrase_distance'; - -CREATE FUNCTION plainto_tsquery(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_plainto_tsquery'; - -CREATE FUNCTION phraseto_tsquery(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_phraseto_tsquery'; - -CREATE FUNCTION websearch_to_tsquery(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_websearch_to_tsquery'; - - diff --git a/sql/postgraph-typecasting.sql.in b/sql/postgraph-typecasting.sql.in deleted file mode 100644 index 5a82b0db..00000000 --- a/sql/postgraph-typecasting.sql.in +++ /dev/null @@ -1,1128 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- graphid typecasting --- -CREATE FUNCTION graphid_to_gtype(graphid) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (graphid AS gtype) WITH FUNCTION graphid_to_gtype(graphid); - -CREATE FUNCTION gtype_to_graphid(gtype) -RETURNS graphid -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS graphid) WITH FUNCTION gtype_to_graphid(gtype) AS IMPLICIT; - - --- --- gtype typecasting --- --- gtype -> text -CREATE FUNCTION gtype_to_text(gtype) -RETURNS text -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS text) -WITH FUNCTION gtype_to_text(gtype); - --- gtype -> text[] -CREATE FUNCTION gtype_to_text_array(gtype) -RETURNS text[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS text[]) -WITH FUNCTION gtype_to_text_array(gtype); - --- text[] -> gtype -CREATE FUNCTION text_array_to_gtype(text[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (text[] as gtype) -WITH FUNCTION text_array_to_gtype(text[]); - --- text -> gtype -CREATE FUNCTION text_to_gtype(text) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (text AS gtype) -WITH FUNCTION text_to_gtype(text); - --- gtype -> boolean -CREATE FUNCTION gtype_to_bool(gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS boolean) -WITH FUNCTION gtype_to_bool(gtype) -AS IMPLICIT; - --- boolean -> gtype -CREATE FUNCTION bool_to_gtype(boolean) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (boolean AS gtype) -WITH FUNCTION bool_to_gtype(boolean); - --- boolean[] -> gtype -CREATE FUNCTION bool_array_to_gtype(boolean[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (boolean[] AS gtype) -WITH FUNCTION bool_array_to_gtype(boolean[]); - - --- float8 -> gtype -CREATE FUNCTION float8_to_gtype(float8) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (float8 AS gtype) -WITH FUNCTION float8_to_gtype(float8); - --- gtype -> float8 -CREATE FUNCTION gtype_to_float8(gtype) -RETURNS float8 -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS float8) -WITH FUNCTION gtype_to_float8(gtype); - --- float8[] -> gtype -CREATE FUNCTION float8_array_to_gtype(float8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (float8[] as gtype) -WITH FUNCTION float8_array_to_gtype(float8[]); - - --- gtype -> float8[] -CREATE FUNCTION gtype_to_float8_array(gtype) -RETURNS float8[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS float8[]) -WITH FUNCTION gtype_to_float8_array(gtype); - --- gtype -> float4[] -CREATE FUNCTION gtype_to_float4_array(gtype) -RETURNS float4[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS float4[]) -WITH FUNCTION gtype_to_float4_array(gtype); - --- float4[] -> gtype -CREATE FUNCTION float4_array_to_gtype(float4[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (float4[] as gtype) -WITH FUNCTION float4_array_to_gtype(float4[]); - --- gtype -> numeric -CREATE FUNCTION gtype_to_numeric(gtype) -RETURNS numeric -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS numeric) -WITH FUNCTION gtype_to_numeric(gtype); - --- gtype -> numeric[] -CREATE FUNCTION gtype_to_numeric_array(gtype) -RETURNS numeric[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS numeric[]) -WITH FUNCTION gtype_to_numeric_array(gtype); - --- numeric[] -> gtype -CREATE FUNCTION numeric_array_to_gtype(numeric[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (numeric[] as gtype) -WITH FUNCTION numeric_array_to_gtype(numeric[]); - --- int8 -> gtype -CREATE FUNCTION int8_to_gtype(int8) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (int8 AS gtype) -WITH FUNCTION int8_to_gtype(int8); - --- gtype -> int8 -CREATE FUNCTION gtype_to_int8(gtype) -RETURNS bigint -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS bigint) -WITH FUNCTION gtype_to_int8(gtype) -AS ASSIGNMENT; - --- int4 -> gtype -CREATE FUNCTION int4_to_gtype(int4) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (int4 AS gtype) -WITH FUNCTION int4_to_gtype(int4); - --- int8[] -> gtype -CREATE FUNCTION int8_array_to_gtype(int8[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (int8[] as gtype) -WITH FUNCTION int8_array_to_gtype(int8[]); - --- int4[] -> gtype -CREATE FUNCTION int4_array_to_gtype(int4[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (int4[] as gtype) -WITH FUNCTION int4_array_to_gtype(int4[]); - --- int2[] -> gtype -CREATE FUNCTION int2_array_to_gtype(int2[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (int2[] as gtype) -WITH FUNCTION int2_array_to_gtype(int2[]); - - --- timestamp -> gtype -CREATE FUNCTION timestamp_to_gtype(timestamp) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (timestamp AS gtype) -WITH FUNCTION timestamp_to_gtype(timestamp); - --- timestamp[] -> gtype -CREATE FUNCTION timestamp_array_to_gtype(timestamp[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (timestamp[] as gtype) -WITH FUNCTION timestamp_array_to_gtype(timestamp[]); - --- timestamptz -> gtype -CREATE FUNCTION timestamptz_to_gtype(timestamptz) -RETURNS gtype -LANGUAGE c -IMMUTABLE RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (timestamptz AS gtype) -WITH FUNCTION timestamptz_to_gtype(timestamptz); - --- timestamptz[] -> gtype -CREATE FUNCTION timestamptz_array_to_gtype(timestamptz[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (timestamptz[] as gtype) -WITH FUNCTION timestamptz_array_to_gtype(timestamptz[]); - --- date -> gtype -CREATE FUNCTION date_to_gtype(date) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (date AS gtype) -WITH FUNCTION date_to_gtype(date); - --- date[] -> gtype -CREATE FUNCTION date_array_to_gtype(date[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (date[] as gtype) -WITH FUNCTION date_array_to_gtype(date[]); - - --- time -> gtype -CREATE FUNCTION time_to_gtype(time) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (time AS gtype) -WITH FUNCTION time_to_gtype(time); - --- time[] -> gtype -CREATE FUNCTION time_array_to_gtype(time[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (time[] as gtype) -WITH FUNCTION time_array_to_gtype(time[]); - --- timetz -> gtype -CREATE FUNCTION timetz_to_gtype(timetz) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (timetz AS gtype) -WITH FUNCTION timetz_to_gtype(timetz); - --- timetz[] -> gtype -CREATE FUNCTION timetz_array_to_gtype(timetz[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (timetz[] as gtype) -WITH FUNCTION timetz_array_to_gtype(timetz[]); - --- gtype -> timestamp -CREATE FUNCTION gtype_to_timestamp(gtype) -RETURNS timestamp -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS timestamp) -WITH FUNCTION gtype_to_timestamp(gtype); - --- gtype -> date -CREATE FUNCTION gtype_to_date(gtype) -RETURNS date -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS date) -WITH FUNCTION gtype_to_date(gtype); - --- gtype -> timestamptz -CREATE FUNCTION gtype_to_timestamptz(gtype) -RETURNS timestamptz -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS timestamptz) -WITH FUNCTION gtype_to_timestamptz(gtype); - --- gtype -> time -CREATE FUNCTION gtype_to_time(gtype) -RETURNS time -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS time) -WITH FUNCTION gtype_to_time(gtype); - --- gtype -> timetz -CREATE FUNCTION gtype_to_timetz(gtype) -RETURNS timetz -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS timetz) -WITH FUNCTION gtype_to_timetz(gtype); - --- interval -> gtype -CREATE FUNCTION interval_to_gtype(interval) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (interval AS gtype) -WITH FUNCTION interval_to_gtype(interval); - --- gtype -> interval -CREATE FUNCTION gtype_to_interval(gtype) -RETURNS interval -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS interval) -WITH FUNCTION gtype_to_interval(gtype); - --- interval[] -> gtype -CREATE FUNCTION interval_array_to_gtype(interval[]) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'array_to_gtype'; - -CREATE CAST (interval[] as gtype) -WITH FUNCTION interval_array_to_gtype(interval[]); - - - --- gtype -> int8[] -CREATE FUNCTION gtype_to_int8_array(gtype) -RETURNS bigint[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS bigint[]) -WITH FUNCTION gtype_to_int8_array(gtype); - --- gtype -> int4 -CREATE FUNCTION gtype_to_int4(gtype) -RETURNS int -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS int) WITH FUNCTION gtype_to_int4(gtype) AS IMPLICIT; - --- gtype -> int2 -CREATE FUNCTION gtype_to_int2(gtype) -RETURNS smallint -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS smallint) -WITH FUNCTION gtype_to_int2(gtype); - --- gtype -> int2[] -CREATE FUNCTION gtype_to_int2_array(gtype) -RETURNS smallint[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS smallint[]) WITH FUNCTION gtype_to_int2_array(gtype); - --- gtype -> int4[] -CREATE FUNCTION gtype_to_int4_array(gtype) -RETURNS int[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype AS int[]) WITH FUNCTION gtype_to_int4_array(gtype); - --- inet -> gtype -CREATE FUNCTION inet_to_gtype(inet) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (inet as gtype) WITH FUNCTION inet_to_gtype(inet); - --- gtype -> inet -CREATE FUNCTION gtype_to_inet(gtype) -RETURNS inet -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as inet) WITH FUNCTION gtype_to_inet(gtype); - --- gtype -> cidr -CREATE FUNCTION gtype_to_cidr(gtype) -RETURNS cidr -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as cidr) WITH FUNCTION gtype_to_cidr(gtype); - --- point -> gtype -CREATE FUNCTION point_to_gtype(point) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (point AS gtype) -WITH FUNCTION point_to_gtype(point); - - --- gtype -> point -CREATE FUNCTION gtype_to_point(gtype) -RETURNS point -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as point) WITH FUNCTION gtype_to_point(gtype); - --- path -> gtype -CREATE FUNCTION path_to_gtype(path) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (path AS gtype) -WITH FUNCTION path_to_gtype(path); - --- gtype -> path -CREATE FUNCTION gtype_to_path(gtype) -RETURNS path -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as path) WITH FUNCTION gtype_to_path(gtype); - --- polygon -> gtype -CREATE FUNCTION polygon_to_gtype(polygon) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (polygon AS gtype) -WITH FUNCTION polygon_to_gtype(polygon); - --- gtype -> polygon -CREATE FUNCTION gtype_to_polygon(gtype) -RETURNS polygon -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as polygon) WITH FUNCTION gtype_to_polygon(gtype); - - --- geometry -> gtype -CREATE FUNCTION geometry_to_gtype(public.geometry) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (public.geometry AS gtype) -WITH FUNCTION geometry_to_gtype(public.geometry); - --- gtype -> geometry -CREATE FUNCTION gtype_to_geometry(gtype) -RETURNS public.geometry -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as public.geometry) WITH FUNCTION gtype_to_geometry(gtype); - --- gtype -> box3d -CREATE FUNCTION gtype_to_box3d(gtype) -RETURNS public.box3d -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as public.box3d) WITH FUNCTION gtype_to_box3d(gtype); - --- gtype -> box2d -CREATE FUNCTION gtype_to_box2d(gtype) -RETURNS public.box2d -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as public.box2d) WITH FUNCTION gtype_to_box2d(gtype); - --- box -> gtype -CREATE FUNCTION box_to_gtype(box) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (box AS gtype) -WITH FUNCTION box_to_gtype(box); - --- gtype -> box -CREATE FUNCTION gtype_to_box(gtype) -RETURNS box -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as box) WITH FUNCTION gtype_to_box(gtype); - --- tsvector -> gtype -CREATE FUNCTION tsvector_to_gtype(tsvector) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (tsvector as gtype) WITH FUNCTION tsvector_to_gtype(tsvector); - --- gtype -> tsvector -CREATE FUNCTION gtype_to_tsvector(gtype) -RETURNS tsvector -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as tsvector) WITH FUNCTION gtype_to_tsvector(gtype); - --- tsquery -> gtype -CREATE FUNCTION tsquery_to_gtype(tsquery) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (tsquery as gtype) WITH FUNCTION tsquery_to_gtype(tsquery); - --- gtype -> tsquery -CREATE FUNCTION gtype_to_tsquery(gtype) -RETURNS tsquery -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE CAST (gtype as tsquery) WITH FUNCTION gtype_to_tsquery(gtype); - --- --- gtype - typecasting to other gtype types --- -CREATE FUNCTION toboolean (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_toboolean'; - -CREATE FUNCTION tofloat (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tofloat'; - -CREATE FUNCTION tointeger(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tointeger'; - -CREATE FUNCTION tostring (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tostring'; - -CREATE FUNCTION tobytea (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_tobytea'; - -CREATE FUNCTION tonumeric (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tonumeric'; - -CREATE FUNCTION totimestamp (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_totimestamp'; - -CREATE FUNCTION totimestamptz (gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION totime(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION totimetz(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION tointerval(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION todate(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION tovector(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION toinet(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_toinet'; - -CREATE FUNCTION tocidr(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tocidr'; - -CREATE FUNCTION tomacaddr(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tomacaddr'; - -CREATE FUNCTION tomacaddr8(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tomacaddr8'; - -CREATE FUNCTION topoint(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_topoint'; - -CREATE FUNCTION tolseg(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tolseg'; - -CREATE FUNCTION toline(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_toline'; - -CREATE FUNCTION topath(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_topath'; - -CREATE FUNCTION topolygon(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_topolygon'; - -CREATE FUNCTION tocircle(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tocircle'; - -CREATE FUNCTION tobox(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tobox'; - -CREATE FUNCTION tobox2d(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tobox2d'; - -CREATE FUNCTION tobox3d(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tobox3d'; - -CREATE FUNCTION tospheroid(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tospheroid'; - -CREATE FUNCTION togeometry(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_togeometry'; - -CREATE FUNCTION totsvector(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_totsvector'; - -CREATE FUNCTION totsquery(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_totsquery'; - - -CREATE FUNCTION tointrange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tointrange'; - -CREATE FUNCTION tointmultirange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tointmultirange'; - -CREATE FUNCTION tonumrange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tonumrange'; - -CREATE FUNCTION tonummultirange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_tonummultirange'; - -CREATE FUNCTION totsrange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_totsrange'; - -CREATE FUNCTION totsmultirange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_totsmultirange'; - -CREATE FUNCTION totstzrange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_totstzrange'; - -CREATE FUNCTION totstzmultirange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_totstzmultirange'; - -CREATE FUNCTION todaterange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_todaterange'; - -CREATE FUNCTION todatemultirange(gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -AS 'MODULE_PATHNAME', 'gtype_todatemultirange'; - diff --git a/sql/postgraph-variable_edge.sql.in b/sql/postgraph-variable_edge.sql.in deleted file mode 100644 index e95e6476..00000000 --- a/sql/postgraph-variable_edge.sql.in +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- variable edge - equality operators (=, <>, >) --- -CREATE FUNCTION variable_edge_eq(variable_edge, variable_edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR = ( - FUNCTION = variable_edge_eq, - LEFTARG = variable_edge, - RIGHTARG = variable_edge, - COMMUTATOR = =, - NEGATOR = <>, - RESTRICT = eqsel, - JOIN = eqjoinsel, - HASHES -); - -CREATE FUNCTION variable_edge_ne(variable_edge, variable_edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <> ( - FUNCTION = variable_edge_ne, - LEFTARG = variable_edge, - RIGHTARG = variable_edge, - COMMUTATOR = <>, - NEGATOR = =, - RESTRICT = neqsel, - JOIN = neqjoinsel, - HASHES -); - -CREATE FUNCTION variable_edge_gt(variable_edge, variable_edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR > ( - FUNCTION = variable_edge_gt, - LEFTARG = variable_edge, - RIGHTARG = variable_edge, - COMMUTATOR = >, - NEGATOR = <=, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel -); - -CREATE FUNCTION variable_edge_ge(variable_edge, variable_edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR >= ( - FUNCTION = variable_edge_ge, - LEFTARG = variable_edge, - RIGHTARG = variable_edge, - COMMUTATOR = >=, - NEGATOR = <, - RESTRICT = scalargesel, - JOIN = scalargejoinsel -); - - -CREATE FUNCTION variable_edge_lt(variable_edge, variable_edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR < ( - FUNCTION = variable_edge_lt, - LEFTARG = variable_edge, - RIGHTARG = variable_edge, - COMMUTATOR = <, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel -); - -CREATE FUNCTION variable_edge_le(variable_edge, variable_edge) RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <= ( - FUNCTION = variable_edge_le, - LEFTARG = variable_edge, - RIGHTARG = variable_edge, - COMMUTATOR = <=, - NEGATOR = >, - RESTRICT = scalarlesel, - JOIN = scalarlejoinsel -); - --- --- edge - B-tree support functions --- --- comparison support -CREATE FUNCTION variable_edge_btree_cmp(variable_edge, variable_edge) -RETURNS int -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- sort support -CREATE FUNCTION variable_edge_btree_sort(internal) -RETURNS void -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE AS 'MODULE_PATHNAME'; - --- --- define operator classes for graphid --- -CREATE OPERATOR CLASS variable_edge_ops -DEFAULT FOR TYPE variable_edge -USING btree AS - OPERATOR 1 <, - OPERATOR 2 <=, - OPERATOR 3 =, - OPERATOR 4 >=, - OPERATOR 5 >, - FUNCTION 1 variable_edge_btree_cmp (variable_edge, variable_edge), - FUNCTION 2 variable_edge_btree_sort (internal); - --- --- variable_edge containment operators (@>, @<, @@) --- -CREATE FUNCTION edge_contained_in_variable_edge(edge, variable_edge) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR @> ( - FUNCTION = edge_contained_in_variable_edge, - LEFTARG = edge, - RIGHTARG = variable_edge, - --COMMUTATOR = '<@', - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE FUNCTION variable_edge_contains_edge(variable_edge, edge) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <@ ( - FUNCTION = variable_edge_contains_edge, - LEFTARG = variable_edge, - RIGHTARG = edge, - COMMUTATOR = '@>', - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE FUNCTION variable_edge_edges_overlap(variable_edge, variable_edge) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR && ( - FUNCTION = variable_edge_edges_overlap, - LEFTARG = variable_edge, - RIGHTARG = variable_edge -); - --- --- variable_edge functions --- -CREATE FUNCTION nodes(variable_edge) RETURNS vertex[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'variable_edge_nodes'; - -CREATE FUNCTION relationships(variable_edge) RETURNS edge[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'variable_edge_edges'; - -CREATE FUNCTION edges(variable_edge) RETURNS edge[] -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'variable_edge_edges'; - -CREATE FUNCTION length(variable_edge) RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'variable_edge_length'; - -CREATE OPERATOR @-@ ( - FUNCTION = length, - RIGHTARG = variable_edge -); - diff --git a/sql/postgraph-vertex.sql.in b/sql/postgraph-vertex.sql.in deleted file mode 100644 index 07cd2307..00000000 --- a/sql/postgraph-vertex.sql.in +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - --- --- vertex - equality operators (=, <>) --- -CREATE FUNCTION vertex_eq(vertex, vertex) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR = ( - FUNCTION = vertex_eq, - LEFTARG = vertex, - RIGHTARG = vertex, - COMMUTATOR = =, - NEGATOR = <>, - RESTRICT = eqsel, - JOIN = eqjoinsel, - HASHES, - MERGES -); - -CREATE FUNCTION vertex_ne(vertex, vertex) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <> ( - FUNCTION = vertex_ne, - LEFTARG = vertex, - RIGHTARG = vertex, - COMMUTATOR = <>, - NEGATOR = =, - RESTRICT = neqsel, - JOIN = neqjoinsel -); - -CREATE FUNCTION vertex_lt(vertex, vertex) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR < ( - FUNCTION = vertex_lt, - LEFTARG = vertex, - RIGHTARG = vertex, - COMMUTATOR = >, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel -); - -CREATE FUNCTION vertex_gt(vertex, vertex) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR > ( - FUNCTION = vertex_gt, - LEFTARG = vertex, - RIGHTARG = vertex, - COMMUTATOR = <, - NEGATOR = <=, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel -); - -CREATE FUNCTION vertex_le(vertex, vertex) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <= ( - FUNCTION = vertex_le, - LEFTARG = vertex, - RIGHTARG = vertex, - COMMUTATOR = >=, - NEGATOR = >, - RESTRICT = scalarlesel, - JOIN = scalarlejoinsel -); - -CREATE FUNCTION vertex_ge(vertex, vertex) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR >= ( - FUNCTION = vertex_ge, - LEFTARG = vertex, - RIGHTARG = vertex, - COMMUTATOR = <=, - NEGATOR = <, - RESTRICT = scalargesel, - JOIN = scalargejoinsel -); - -CREATE FUNCTION vertex_btree_cmp(vertex, vertex) -RETURNS INTEGER -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'vertex_btree_cmp'; - -CREATE OPERATOR CLASS vertex_ops_btree -DEFAULT FOR TYPE vertex -USING btree -AS -OPERATOR 1 <, -OPERATOR 2 <=, -OPERATOR 3 =, -OPERATOR 4 >, -OPERATOR 5 >=, -FUNCTION 1 vertex_btree_cmp(vertex, vertex); - --- --- vertex - access operators (->, ->> ) --- -CREATE FUNCTION vertex_property_access(vertex, text) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR -> ( - LEFTARG = vertex, - RIGHTARG = text, - FUNCTION = vertex_property_access -); - -CREATE FUNCTION vertex_property_access_gtype(vertex, gtype) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR -> ( - LEFTARG = vertex, - RIGHTARG = gtype, - FUNCTION = vertex_property_access_gtype -); - -CREATE FUNCTION vertex_property_access_text(vertex, text) -RETURNS text -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ->> ( - LEFTARG = vertex, - RIGHTARG = text, - FUNCTION = vertex_property_access_text -); - - --- --- vertex - contains operators (@>, <@) --- -CREATE FUNCTION vertex_contains(vertex, gtype) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR @> ( - LEFTARG = vertex, - RIGHTARG = gtype, - FUNCTION = vertex_contains, - COMMUTATOR = '<@', - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE FUNCTION vertex_contained_by(gtype, vertex) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR <@ ( - LEFTARG = gtype, - RIGHTARG = vertex, - FUNCTION = vertex_contained_by, - COMMUTATOR = '@>', - RESTRICT = contsel, - JOIN = contjoinsel -); - --- --- vertex - key existence operators (?, ?|, ?&) --- -CREATE FUNCTION vertex_exists(vertex, text) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ? ( - LEFTARG = vertex, - RIGHTARG = text, - FUNCTION = vertex_exists, - COMMUTATOR = '?', - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE FUNCTION vertex_exists_any(vertex, text[]) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ?| ( - LEFTARG = vertex, - RIGHTARG = text[], - FUNCTION = vertex_exists_any, - RESTRICT = contsel, - JOIN = contjoinsel -); - -CREATE FUNCTION vertex_exists_all(vertex, text[]) -RETURNS boolean -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE OPERATOR ?& ( - LEFTARG = vertex, - RIGHTARG = text[], - FUNCTION = vertex_exists_all, - RESTRICT = contsel, - JOIN = contjoinsel -); - --- --- vertex functions --- -CREATE FUNCTION id(vertex) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'vertex_id'; - -CREATE FUNCTION label(vertex) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'vertex_label'; - -CREATE FUNCTION properties(vertex) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'vertex_properties'; - -CREATE FUNCTION age_properties(vertex) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'vertex_properties'; - -CREATE FUNCTION type(vertex) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME','vertex_label'; - -CREATE FUNCTION keys(vertex) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE AS 'MODULE_PATHNAME', 'vertex_keys'; \ No newline at end of file diff --git a/sql/postgraph.sql.in b/sql/postgraph.sql.in deleted file mode 100644 index bb384405..00000000 --- a/sql/postgraph.sql.in +++ /dev/null @@ -1,521 +0,0 @@ -/* - * PostGraph - * Copyright (C) 2023 by PostGraph - * - * 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. - * - * Portions Copyright (c) 2020-2023, Apache Software Foundation - * Portions Copyright (c) 2019-2020, Bitnine Global - */ - --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "CREATE EXTENSION PostGraph" to load this file. \quit - --- --- catalog tables --- -CREATE TABLE ag_graph ( - graphid oid NOT NULL, - name name NOT NULL, - namespace regnamespace NOT NULL, - directed boolean NOT NULL -); - -CREATE UNIQUE INDEX ag_graph_graphid_index -ON ag_graph -USING btree (graphid); - -CREATE UNIQUE INDEX ag_graph_name_index -ON ag_graph -USING btree (name); - -CREATE UNIQUE INDEX ag_graph_namespace_index -ON ag_graph -USING btree (namespace); - --- 0 is an invalid label ID -CREATE DOMAIN label_id -AS int -NOT NULL -CHECK (VALUE > 0 AND VALUE <= 65535); - -CREATE DOMAIN label_kind -AS "char" -NOT NULL -CHECK (VALUE = 'v' OR VALUE = 'e'); - -CREATE TABLE ag_label ( - name name NOT NULL, - graph oid NOT NULL, - id label_id, - kind label_kind, - relation regclass NOT NULL, - label_path public.ltree NULL, - CONSTRAINT fk_graph_oid FOREIGN KEY(graph) REFERENCES ag_graph(graphid) -); - -CREATE UNIQUE INDEX ag_label_name_graph_index -ON ag_label -USING btree (name, graph); - -CREATE UNIQUE INDEX ag_label_graph_oid_index -ON ag_label -USING btree (graph, id); - -CREATE UNIQUE INDEX ag_label_relation_index -ON ag_label -USING btree (relation); - -CREATE UNIQUE INDEX ag_label_path_graph_index -ON ag_label -USING btree (label_path, graph); - -CREATE INDEX ag_label_ltree_index -ON ag_label -USING gist (label_path public.gist_ltree_ops); - -CREATE TABLE session_graph_use ( - pid oid not null, - graph_oid oid not null -); - - -CREATE UNIQUE INDEX session_graph_use_index -ON session_graph_use -USING btree (pid, graph_oid); - - -------------------------------------- --- Table AM interface functions -------------------------------------- -CREATE FUNCTION vertex_tableam_handler(internal) -RETURNS table_am_handler -AS 'MODULE_PATHNAME' -LANGUAGE C STRICT; - -CREATE ACCESS METHOD vertex TYPE TABLE -HANDLER vertex_tableam_handler; - --- --- catalog lookup functions --- -CREATE FUNCTION _label_id(graph_name name, label_name name) -RETURNS label_id -LANGUAGE c -STABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- --- utility functions --- -CREATE FUNCTION create_graph(graph_name text) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION use_graph(graph_name text) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION create_graph_if_not_exists(graph_name name) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION drop_graph(graph_name name, cascade boolean = false) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION create_vlabel(graph_name name, label_name name) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION create_elabel(graph_name name, label_name name) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION alter_graph(graph_name name, operation cstring, new_value name) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION drop_label(graph_name name, label_name name, force boolean = false) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - - -CREATE FUNCTION create_unique_properties_constraint(graph_name name, label_name name) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - - -CREATE FUNCTION create_property_index(graph_name name, label_name name, property_name name, is_unique boolean = false) -RETURNS void -LANGUAGE c -AS 'MODULE_PATHNAME'; - --- --- graphid type --- -CREATE TYPE graphid; - -CREATE FUNCTION graphid_in(cstring) -RETURNS graphid -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION graphid_out(graphid) -RETURNS cstring -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- binary I/O functions -CREATE FUNCTION graphid_send(graphid) -RETURNS bytea -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION graphid_recv(internal) -RETURNS graphid -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE TYPE graphid ( - INPUT = graphid_in, - OUTPUT = graphid_out, - SEND = graphid_send, - RECEIVE = graphid_recv, - INTERNALLENGTH = 8, - PASSEDBYVALUE, - ALIGNMENT = float8, - STORAGE = plain -); - --- --- gtype type and its I/O Routines --- -CREATE TYPE gtype; - -CREATE FUNCTION gtype_in(cstring) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_out(gtype) -RETURNS cstring -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - --- binary I/O functions -CREATE FUNCTION gtype_send(gtype) -RETURNS bytea -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION gtype_recv(internal) -RETURNS gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE TYPE gtype ( - INPUT = gtype_in, - OUTPUT = gtype_out, - SEND = gtype_send, - RECEIVE = gtype_recv, - LIKE = jsonb, - STORAGE = extended -); - --- --- vertex type and its I/O Routines --- -CREATE TYPE vertex; - --- throws an error -CREATE FUNCTION vertex_in(cstring) -RETURNS vertex -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION vertex_out(vertex) -RETURNS cstring -LANGUAGE c -STABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION build_vertex(graphid, oid, gtype) -RETURNS vertex -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME'; - -CREATE TYPE vertex (INPUT = vertex_in, OUTPUT = vertex_out, LIKE = jsonb); - --- --- edge --- -CREATE TYPE edge; - -CREATE FUNCTION edge_in(cstring) -RETURNS edge -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 50 -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION edge_out(edge) -RETURNS cstring -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION build_edge(graphid, graphid, graphid, oid, gtype) -RETURNS edge -LANGUAGE c -STABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE TYPE edge (INPUT = edge_in, OUTPUT = edge_out, LIKE = jsonb); - - --- --- path --- -CREATE TYPE traversal; - -CREATE FUNCTION traversal_in(cstring) -RETURNS traversal -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION traversal_out(traversal) -RETURNS cstring -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION build_traversal(variadic "any") -RETURNS traversal -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME'; - -CREATE TYPE traversal (INPUT = traversal_in, OUTPUT = traversal_out, LIKE = jsonb); - --- --- partial traversal --- -CREATE TYPE variable_edge; - -CREATE FUNCTION variable_edge_in(cstring) -RETURNS variable_edge -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION variable_edge_out(variable_edge) -RETURNS cstring -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION build_variable_edge(variadic "any") -RETURNS variable_edge -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 250 -AS 'MODULE_PATHNAME'; - -CREATE TYPE variable_edge (INPUT = variable_edge_in, OUTPUT = variable_edge_out, LIKE = jsonb); - --- --- MATCH edge uniqueness --- -CREATE FUNCTION _ag_enforce_edge_uniqueness(VARIADIC "any") -RETURNS bool -LANGUAGE c -IMMUTABLE -PARALLEL SAFE -RETURNS NULL ON NULL INPUT -as 'MODULE_PATHNAME'; - - --- --- There are times when the optimizer might eliminate --- functions we need. Wrap the function with this to --- prevent that from happening --- -CREATE FUNCTION gtype_volatile_wrapper(agt gtype) RETURNS gtype AS $return_value$ -BEGIN RETURN agt; END; -$return_value$ LANGUAGE plpgsql -VOLATILE -CALLED ON NULL INPUT -PARALLEL SAFE; - -CREATE FUNCTION gtype_volatile_wrapper(agt edge) RETURNS edge AS $return_value$ -BEGIN RETURN agt; END; -$return_value$ LANGUAGE plpgsql -VOLATILE -CALLED ON NULL INPUT -PARALLEL SAFE; - -CREATE FUNCTION gtype_volatile_wrapper(agt vertex) RETURNS vertex AS $return_value$ -BEGIN RETURN agt; END; -$return_value$ LANGUAGE plpgsql -VOLATILE -CALLED ON NULL INPUT -PARALLEL SAFE; - -CREATE FUNCTION gtype_volatile_wrapper(agt variable_edge) RETURNS variable_edge AS $return_value$ -BEGIN RETURN agt; END; -$return_value$ LANGUAGE plpgsql -VOLATILE -CALLED ON NULL INPUT -PARALLEL SAFE; - -CREATE FUNCTION gtype_volatile_wrapper(agt traversal) RETURNS traversal -AS $return_value$ -BEGIN RETURN agt; END; -$return_value$ -LANGUAGE plpgsql -VOLATILE -CALLED ON NULL INPUT -PARALLEL SAFE; - --- --- functions for updating clauses --- --- This function is defined as a VOLATILE function to prevent the optimizer --- from pulling up Query's for CREATE clauses. -CREATE FUNCTION _cypher_create_clause(internal) RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; -CREATE FUNCTION _cypher_set_clause(internal) RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; -CREATE FUNCTION _cypher_delete_clause(internal) RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; -CREATE FUNCTION _cypher_merge_clause(internal) RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; - --- --- query functions --- -CREATE FUNCTION cypher(graph_name name, query_string cstring, params gtype = NULL) -RETURNS SETOF record -LANGUAGE c -AS 'MODULE_PATHNAME'; - -CREATE FUNCTION get_cypher_keywords(OUT word text, OUT catcode "char", OUT catdesc text) -RETURNS SETOF record -LANGUAGE c -STABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -COST 10 ROWS 60 -AS 'MODULE_PATHNAME'; - --- --- Unnest (UNWIND Clause) Functions --- -CREATE FUNCTION unnest (gtype, block_types boolean = false) -RETURNS SETOF gtype -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'gtype_unnest'; - -CREATE FUNCTION unnest (vertex[], block_types boolean = false) -RETURNS SETOF vertex -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'vertex_unnest'; - -CREATE FUNCTION unnest (edge[], block_types boolean = false) -RETURNS SETOF edge -LANGUAGE c -IMMUTABLE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME', 'edge_unnest'; - - -CREATE FUNCTION throw_error(gtype) -RETURNS gtype -LANGUAGE C -VOLATILE -RETURNS NULL ON NULL INPUT -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - From b96e3bbfe14e763da917b0f3223a5207fb031eb6 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 12 Jul 2025 18:03:56 -0500 Subject: [PATCH 13/56] Make vertex adjacency list table and cleanup code --- Makefile | 12 +- postgraph--0.1.0.sql | 32 +- regress/expected/cypher_create.out | 401 +-------------- regress/expected/new_cypher.out | 485 +----------------- regress/expected/vertex_am.out | 45 ++ regress/sql/cypher_create.sql | 153 +----- regress/sql/new_cypher.sql | 167 +----- regress/sql/vertex_am.sql | 2 +- .../vertex_heap/vertex_heapam_handler.c | 4 +- src/backend/catalog/ag_label.c | 22 +- src/backend/commands/label_commands.c | 99 +++- src/backend/optimizer/cypher_paths.c | 2 + src/backend/utils/adt/age_graphid_ds.c | 259 ---------- src/backend/utils/cache/ag_cache.c | 8 +- src/include/catalog/ag_label.h | 2 +- src/include/utils/ag_cache.h | 1 + src/include/utils/graphid_ds.h | 73 --- src/include/utils/gtype_temporal.h | 29 -- 18 files changed, 219 insertions(+), 1577 deletions(-) mode change 100644 => 100755 postgraph--0.1.0.sql delete mode 100644 src/backend/utils/adt/age_graphid_ds.c delete mode 100644 src/include/utils/graphid_ds.h delete mode 100644 src/include/utils/gtype_temporal.h diff --git a/Makefile b/Makefile index 597ab187..960b9d45 100644 --- a/Makefile +++ b/Makefile @@ -71,17 +71,7 @@ DATA = postgraph--0.1.0.sql REGRESS = new_cypher \ vertex_am \ - graphid \ - gtype \ - expr \ - regex \ - temporal \ - network \ - lists \ - tsearch \ - range \ - vector \ - cypher_create + cypher_create srcdir=`pwd` POSTGIS_DIR ?= postgis_dir diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql old mode 100644 new mode 100755 index 25ae65af..b7c05510 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -2,22 +2,15 @@ * PostGraph * Copyright (C) 2023 by PostGraph * - * 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 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * 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. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * * Portions Copyright (c) 2020-2023, Apache Software Foundation * Portions Copyright (c) 2019-2020, Bitnine Global @@ -64,7 +57,8 @@ CREATE TABLE ag_label ( graph oid NOT NULL, id label_id, kind label_kind, - relation regclass NOT NULL, + relation regclass NOT NULL, + vertex_adjlist regclass NULL, label_path public.ltree NULL, CONSTRAINT fk_graph_oid FOREIGN KEY(graph) REFERENCES ag_graph(graphid) ); @@ -103,13 +97,13 @@ USING btree (pid, graph_oid); ------------------------------------- -- Table AM interface functions ------------------------------------- -CREATE FUNCTION vertex_tableam_handler(internal) +CREATE FUNCTION vertex_adjlist_tableam_handler(internal) RETURNS table_am_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT; -CREATE ACCESS METHOD vertex TYPE TABLE -HANDLER vertex_tableam_handler; +CREATE ACCESS METHOD vertex_adjlist TYPE TABLE +HANDLER vertex_adjlist_tableam_handler; -- -- catalog lookup functions diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index be574513..8271bf75 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -32,408 +32,17 @@ USE GRAPH cypher_create; (1 row) CREATE (); --- -(0 rows) - -CREATE (:v); --- -(0 rows) - -CREATE (:v {}); --- -(0 rows) - -CREATE (:v {prop_key: 'value'}); --- -(0 rows) - -MATCH (n:v) RETURN n; - n ----------------------------------------------------------------------------- - {"id": 844424930131969, "label": "v", "properties": {}} - {"id": 844424930131970, "label": "v", "properties": {}} - {"id": 844424930131971, "label": "v", "properties": {"prop_key": "value"}} -(3 rows) - --- Left relationship -CREATE (:v {id:'right rel, initial node'})-[:e {id:'right rel'}]->(:v {id:'right rel, end node'}); --- -(0 rows) - --- Right relationship -CREATE (:v {id:'left rel, initial node'})<-[:e {id:'left rel'}]-(:v {id:'left rel, end node'}); --- -(0 rows) - --- Pattern creates a path from the initial node to the last node -CREATE (:v {id: 'path, initial node'})-[:e {id: 'path, edge one'}]->(:v {id:'path, middle node'})-[:e {id:'path, edge two'}]->(:v {id:'path, last node'}); --- -(0 rows) - --- middle vertex points to the initial and last vertex -CREATE (:v {id: 'divergent, initial node'})<-[:e {id: 'divergent, edge one'}]-(:v {id: 'divergent middle node'})-[:e {id: 'divergent, edge two'}]->(:v {id: 'divergent, end node'}); --- -(0 rows) - --- initial and last vertex point to the middle vertex -CREATE (:v {id: 'convergent, initial node'})-[:e {id: 'convergent, edge one'}]->(:v {id: 'convergent middle node'})<-[:e {id: 'convergent, edge two'}]-(:v {id: 'convergent, end node'}); --- -(0 rows) - --- Validate Paths work correctly -CREATE (:v {id: 'paths, vertex one'})-[:e {id: 'paths, edge one'}]->(:v {id: 'paths, vertex two'}), - (:v {id: 'paths, vertex three'})-[:e {id: 'paths, edge two'}]->(:v {id: 'paths, vertex four'}); --- -(0 rows) - ---edge with double relationship will throw an error -CREATE (:v)<-[:e]->(); -ERROR: syntax error at or near "->" -LINE 1: CREATE (:v)<-[:e]->(); - ^ ---edge with no relationship will throw an error -CREATE (:v)-[:e]-(); -ERROR: only directed relationships are allowed in CREATE -LINE 1: CREATE (:v)-[:e]-(); - ^ ---edges without labels are not supported -CREATE (:v)-[]->(:v); -ERROR: relationships must be specify a label in CREATE. -LINE 1: CREATE (:v)-[]->(:v); - ^ -MATCH (n) RETURN n; - n ------------------------------------------------------------------------------------------ - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 844424930131969, "label": "v", "properties": {}} - {"id": 844424930131970, "label": "v", "properties": {}} - {"id": 844424930131971, "label": "v", "properties": {"prop_key": "value"}} - {"id": 844424930131972, "label": "v", "properties": {"id": "right rel, initial node"}} - {"id": 844424930131973, "label": "v", "properties": {"id": "right rel, end node"}} - {"id": 844424930131974, "label": "v", "properties": {"id": "left rel, initial node"}} - {"id": 844424930131975, "label": "v", "properties": {"id": "left rel, end node"}} - {"id": 844424930131976, "label": "v", "properties": {"id": "path, initial node"}} - {"id": 844424930131977, "label": "v", "properties": {"id": "path, middle node"}} - {"id": 844424930131978, "label": "v", "properties": {"id": "path, last node"}} - {"id": 844424930131979, "label": "v", "properties": {"id": "divergent, initial node"}} - {"id": 844424930131980, "label": "v", "properties": {"id": "divergent middle node"}} - {"id": 844424930131981, "label": "v", "properties": {"id": "divergent, end node"}} - {"id": 844424930131982, "label": "v", "properties": {"id": "convergent, initial node"}} - {"id": 844424930131983, "label": "v", "properties": {"id": "convergent middle node"}} - {"id": 844424930131984, "label": "v", "properties": {"id": "convergent, end node"}} - {"id": 844424930131985, "label": "v", "properties": {"id": "paths, vertex one"}} - {"id": 844424930131986, "label": "v", "properties": {"id": "paths, vertex two"}} - {"id": 844424930131987, "label": "v", "properties": {"id": "paths, vertex three"}} - {"id": 844424930131988, "label": "v", "properties": {"id": "paths, vertex four"}} -(21 rows) - -MATCH ()-[e]-() RETURN e; - e ----------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 1125899906842625, "start_id": 844424930131972, "end_id": 844424930131973, "label": "e", "properties": {"id": "right rel"}} - {"id": 1125899906842625, "start_id": 844424930131972, "end_id": 844424930131973, "label": "e", "properties": {"id": "right rel"}} - {"id": 1125899906842626, "start_id": 844424930131975, "end_id": 844424930131974, "label": "e", "properties": {"id": "left rel"}} - {"id": 1125899906842626, "start_id": 844424930131975, "end_id": 844424930131974, "label": "e", "properties": {"id": "left rel"}} - {"id": 1125899906842627, "start_id": 844424930131977, "end_id": 844424930131978, "label": "e", "properties": {"id": "path, edge two"}} - {"id": 1125899906842627, "start_id": 844424930131977, "end_id": 844424930131978, "label": "e", "properties": {"id": "path, edge two"}} - {"id": 1125899906842628, "start_id": 844424930131976, "end_id": 844424930131977, "label": "e", "properties": {"id": "path, edge one"}} - {"id": 1125899906842628, "start_id": 844424930131976, "end_id": 844424930131977, "label": "e", "properties": {"id": "path, edge one"}} - {"id": 1125899906842629, "start_id": 844424930131980, "end_id": 844424930131981, "label": "e", "properties": {"id": "divergent, edge two"}} - {"id": 1125899906842629, "start_id": 844424930131980, "end_id": 844424930131981, "label": "e", "properties": {"id": "divergent, edge two"}} - {"id": 1125899906842630, "start_id": 844424930131980, "end_id": 844424930131979, "label": "e", "properties": {"id": "divergent, edge one"}} - {"id": 1125899906842630, "start_id": 844424930131980, "end_id": 844424930131979, "label": "e", "properties": {"id": "divergent, edge one"}} - {"id": 1125899906842631, "start_id": 844424930131984, "end_id": 844424930131983, "label": "e", "properties": {"id": "convergent, edge two"}} - {"id": 1125899906842631, "start_id": 844424930131984, "end_id": 844424930131983, "label": "e", "properties": {"id": "convergent, edge two"}} - {"id": 1125899906842632, "start_id": 844424930131982, "end_id": 844424930131983, "label": "e", "properties": {"id": "convergent, edge one"}} - {"id": 1125899906842632, "start_id": 844424930131982, "end_id": 844424930131983, "label": "e", "properties": {"id": "convergent, edge one"}} - {"id": 1125899906842633, "start_id": 844424930131985, "end_id": 844424930131986, "label": "e", "properties": {"id": "paths, edge one"}} - {"id": 1125899906842633, "start_id": 844424930131985, "end_id": 844424930131986, "label": "e", "properties": {"id": "paths, edge one"}} - {"id": 1125899906842634, "start_id": 844424930131987, "end_id": 844424930131988, "label": "e", "properties": {"id": "paths, edge two"}} - {"id": 1125899906842634, "start_id": 844424930131987, "end_id": 844424930131988, "label": "e", "properties": {"id": "paths, edge two"}} -(20 rows) - -CREATE (:n_var {var_name: 'Node A'}); --- -(0 rows) - -CREATE (:n_var {var_name: 'Node B'}); --- -(0 rows) - -CREATE (:n_var {var_name: 'Node C'}); --- -(0 rows) - -MATCH (a:n_var), (b:n_var) WHERE a.var_name <> b.var_name CREATE (a)-[:e_var {var_name: a.name + ' -> ' + b.name}]->(b); --- -(0 rows) - -MATCH (a:n_var) CREATE (a)-[:e_var {var_name: a.var_name + ' -> ' + a.var_name}]->(a); --- -(0 rows) - -MATCH (a:n_var) CREATE (a)-[:e_var {var_name: a.var_name + ' -> new node'}]->(:n_other_node); --- -(0 rows) - -MATCH (a:n_var) WHERE a.var_name = 'Node A' CREATE (a)-[b:e_var]->(); --- -(0 rows) - -CREATE (a)-[:b_var]->() RETURN a, id(a); - a | id ---------------------------------------------------------+----------------- - {"id": 281474976710659, "label": "", "properties": {}} | 281474976710659 -(1 row) - -CREATE ()-[b:e_var]->() RETURN b, id(b); - b | id -----------------------------------------------------------------------------------------------------------------------+------------------ - {"id": 1688849860263950, "start_id": 281474976710661, "end_id": 281474976710662, "label": "e_var", "properties": {}} | 1688849860263950 -(1 row) - -CREATE (a)-[b:e_var {id: 0}]->() RETURN a, b, b.id, b.id + 1; - a | b | id | ?column? ---------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+----+---------- - {"id": 281474976710663, "label": "", "properties": {}} | {"id": 1688849860263951, "start_id": 281474976710663, "end_id": 281474976710664, "label": "e_var", "properties": {"id": 0}} | 0 | 1 -(1 row) - -MATCH (a:n_var) CREATE (a)-[b:e_var]->(a) RETURN a, b; - a | b -----------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------ - {"id": 1407374883553281, "label": "n_var", "properties": {"var_name": "Node A"}} | {"id": 1688849860263952, "start_id": 1407374883553281, "end_id": 1407374883553281, "label": "e_var", "properties": {}} - {"id": 1407374883553282, "label": "n_var", "properties": {"var_name": "Node B"}} | {"id": 1688849860263953, "start_id": 1407374883553282, "end_id": 1407374883553282, "label": "e_var", "properties": {}} - {"id": 1407374883553283, "label": "n_var", "properties": {"var_name": "Node C"}} | {"id": 1688849860263954, "start_id": 1407374883553283, "end_id": 1407374883553283, "label": "e_var", "properties": {}} -(3 rows) - -MATCH (a:n_var) CREATE (a)-[b:e_var]->(c) RETURN a, b, c; - a | b | c -----------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------- - {"id": 1407374883553281, "label": "n_var", "properties": {"var_name": "Node A"}} | {"id": 1688849860263955, "start_id": 1407374883553281, "end_id": 281474976710665, "label": "e_var", "properties": {}} | {"id": 281474976710665, "label": "", "properties": {}} - {"id": 1407374883553282, "label": "n_var", "properties": {"var_name": "Node B"}} | {"id": 1688849860263956, "start_id": 1407374883553282, "end_id": 281474976710666, "label": "e_var", "properties": {}} | {"id": 281474976710666, "label": "", "properties": {}} - {"id": 1407374883553283, "label": "n_var", "properties": {"var_name": "Node C"}} | {"id": 1688849860263957, "start_id": 1407374883553283, "end_id": 281474976710667, "label": "e_var", "properties": {}} | {"id": 281474976710667, "label": "", "properties": {}} -(3 rows) - -CREATE (a)-[:e_var]->() RETURN a; - a --------------------------------------------------------- - {"id": 281474976710668, "label": "", "properties": {}} -(1 row) - -CREATE ()-[b:e_var]->() RETURN b; - b ----------------------------------------------------------------------------------------------------------------------- - {"id": 1688849860263959, "start_id": 281474976710670, "end_id": 281474976710671, "label": "e_var", "properties": {}} -(1 row) - -CREATE p=()-[:e_var]->() RETURN p; - p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710672, "label": "", "properties": {}}, {"id": 1688849860263960, "start_id": 281474976710672, "end_id": 281474976710673, "label": "e_var", "properties": {}}, {"id": 281474976710673, "label": "", "properties": {}}] -(1 row) - -CREATE p=(a {id:0})-[:e_var]->(a) RETURN p; - p ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710674, "label": "", "properties": {"id": 0}}, {"id": 1688849860263961, "start_id": 281474976710674, "end_id": 281474976710674, "label": "e_var", "properties": {}}, {"id": 281474976710674, "label": "", "properties": {"id": 0}}] -(1 row) - -MATCH (a:n_var) CREATE p=(a)-[:e_var]->(a) RETURN p; - p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 1407374883553281, "label": "n_var", "properties": {"var_name": "Node A"}}, {"id": 1688849860263962, "start_id": 1407374883553281, "end_id": 1407374883553281, "label": "e_var", "properties": {}}, {"id": 1407374883553281, "label": "n_var", "properties": {"var_name": "Node A"}}] - [{"id": 1407374883553282, "label": "n_var", "properties": {"var_name": "Node B"}}, {"id": 1688849860263963, "start_id": 1407374883553282, "end_id": 1407374883553282, "label": "e_var", "properties": {}}, {"id": 1407374883553282, "label": "n_var", "properties": {"var_name": "Node B"}}] - [{"id": 1407374883553283, "label": "n_var", "properties": {"var_name": "Node C"}}, {"id": 1688849860263964, "start_id": 1407374883553283, "end_id": 1407374883553283, "label": "e_var", "properties": {}}, {"id": 1407374883553283, "label": "n_var", "properties": {"var_name": "Node C"}}] -(3 rows) - -CREATE p=(a)-[:e_var]->(), (a)-[b:e_var]->(a) RETURN p, b; - p | b -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710675, "label": "", "properties": {}}, {"id": 1688849860263965, "start_id": 281474976710675, "end_id": 281474976710676, "label": "e_var", "properties": {}}, {"id": 281474976710676, "label": "", "properties": {}}] | {"id": 1688849860263966, "start_id": 281474976710675, "end_id": 281474976710675, "label": "e_var", "properties": {}} -(1 row) - -MATCH (a:n_var) WHERE a.var_name = 'Node Z' CREATE (a)-[:e_var {var_name: a.var_name + ' -> doesnt exist'}]->(:n_other_node) RETURN a; - a ---- -(0 rows) - -MATCH (n:n_var) RETURN n; - n ----------------------------------------------------------------------------------- - {"id": 1407374883553281, "label": "n_var", "properties": {"var_name": "Node A"}} - {"id": 1407374883553282, "label": "n_var", "properties": {"var_name": "Node B"}} - {"id": 1407374883553283, "label": "n_var", "properties": {"var_name": "Node C"}} -(3 rows) - -MATCH ()-[e:e_var]->() RETURN e; - e --------------------------------------------------------------------------------------------------------------------------------------------------------- - {"id": 1688849860263937, "start_id": 1407374883553281, "end_id": 1407374883553282, "label": "e_var", "properties": {"var_name": null}} - {"id": 1688849860263938, "start_id": 1407374883553281, "end_id": 1407374883553283, "label": "e_var", "properties": {"var_name": null}} - {"id": 1688849860263939, "start_id": 1407374883553282, "end_id": 1407374883553281, "label": "e_var", "properties": {"var_name": null}} - {"id": 1688849860263940, "start_id": 1407374883553282, "end_id": 1407374883553283, "label": "e_var", "properties": {"var_name": null}} - {"id": 1688849860263941, "start_id": 1407374883553283, "end_id": 1407374883553281, "label": "e_var", "properties": {"var_name": null}} - {"id": 1688849860263942, "start_id": 1407374883553283, "end_id": 1407374883553282, "label": "e_var", "properties": {"var_name": null}} - {"id": 1688849860263943, "start_id": 1407374883553281, "end_id": 1407374883553281, "label": "e_var", "properties": {"var_name": "Node A -> Node A"}} - {"id": 1688849860263944, "start_id": 1407374883553282, "end_id": 1407374883553282, "label": "e_var", "properties": {"var_name": "Node B -> Node B"}} - {"id": 1688849860263945, "start_id": 1407374883553283, "end_id": 1407374883553283, "label": "e_var", "properties": {"var_name": "Node C -> Node C"}} - {"id": 1688849860263946, "start_id": 1407374883553281, "end_id": 1970324836974593, "label": "e_var", "properties": {"var_name": "Node A -> new node"}} - {"id": 1688849860263947, "start_id": 1407374883553282, "end_id": 1970324836974594, "label": "e_var", "properties": {"var_name": "Node B -> new node"}} - {"id": 1688849860263948, "start_id": 1407374883553283, "end_id": 1970324836974595, "label": "e_var", "properties": {"var_name": "Node C -> new node"}} - {"id": 1688849860263949, "start_id": 1407374883553281, "end_id": 281474976710658, "label": "e_var", "properties": {}} - {"id": 1688849860263950, "start_id": 281474976710661, "end_id": 281474976710662, "label": "e_var", "properties": {}} - {"id": 1688849860263951, "start_id": 281474976710663, "end_id": 281474976710664, "label": "e_var", "properties": {"id": 0}} - {"id": 1688849860263952, "start_id": 1407374883553281, "end_id": 1407374883553281, "label": "e_var", "properties": {}} - {"id": 1688849860263953, "start_id": 1407374883553282, "end_id": 1407374883553282, "label": "e_var", "properties": {}} - {"id": 1688849860263954, "start_id": 1407374883553283, "end_id": 1407374883553283, "label": "e_var", "properties": {}} - {"id": 1688849860263955, "start_id": 1407374883553281, "end_id": 281474976710665, "label": "e_var", "properties": {}} - {"id": 1688849860263956, "start_id": 1407374883553282, "end_id": 281474976710666, "label": "e_var", "properties": {}} - {"id": 1688849860263957, "start_id": 1407374883553283, "end_id": 281474976710667, "label": "e_var", "properties": {}} - {"id": 1688849860263958, "start_id": 281474976710668, "end_id": 281474976710669, "label": "e_var", "properties": {}} - {"id": 1688849860263959, "start_id": 281474976710670, "end_id": 281474976710671, "label": "e_var", "properties": {}} - {"id": 1688849860263960, "start_id": 281474976710672, "end_id": 281474976710673, "label": "e_var", "properties": {}} - {"id": 1688849860263961, "start_id": 281474976710674, "end_id": 281474976710674, "label": "e_var", "properties": {}} - {"id": 1688849860263962, "start_id": 1407374883553281, "end_id": 1407374883553281, "label": "e_var", "properties": {}} - {"id": 1688849860263963, "start_id": 1407374883553282, "end_id": 1407374883553282, "label": "e_var", "properties": {}} - {"id": 1688849860263964, "start_id": 1407374883553283, "end_id": 1407374883553283, "label": "e_var", "properties": {}} - {"id": 1688849860263965, "start_id": 281474976710675, "end_id": 281474976710676, "label": "e_var", "properties": {}} - {"id": 1688849860263966, "start_id": 281474976710675, "end_id": 281474976710675, "label": "e_var", "properties": {}} -(30 rows) - ---Validate every vertex has the correct label -MATCH (n) RETURN n; - n ------------------------------------------------------------------------------------------ - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} - {"id": 281474976710659, "label": "", "properties": {}} - {"id": 281474976710660, "label": "", "properties": {}} - {"id": 281474976710661, "label": "", "properties": {}} - {"id": 281474976710662, "label": "", "properties": {}} - {"id": 281474976710663, "label": "", "properties": {}} - {"id": 281474976710664, "label": "", "properties": {}} - {"id": 281474976710665, "label": "", "properties": {}} - {"id": 281474976710666, "label": "", "properties": {}} - {"id": 281474976710667, "label": "", "properties": {}} - {"id": 281474976710668, "label": "", "properties": {}} - {"id": 281474976710669, "label": "", "properties": {}} - {"id": 281474976710670, "label": "", "properties": {}} - {"id": 281474976710671, "label": "", "properties": {}} - {"id": 281474976710672, "label": "", "properties": {}} - {"id": 281474976710673, "label": "", "properties": {}} - {"id": 281474976710674, "label": "", "properties": {"id": 0}} - {"id": 281474976710675, "label": "", "properties": {}} - {"id": 281474976710676, "label": "", "properties": {}} - {"id": 844424930131969, "label": "v", "properties": {}} - {"id": 844424930131970, "label": "v", "properties": {}} - {"id": 844424930131971, "label": "v", "properties": {"prop_key": "value"}} - {"id": 844424930131972, "label": "v", "properties": {"id": "right rel, initial node"}} - {"id": 844424930131973, "label": "v", "properties": {"id": "right rel, end node"}} - {"id": 844424930131974, "label": "v", "properties": {"id": "left rel, initial node"}} - {"id": 844424930131975, "label": "v", "properties": {"id": "left rel, end node"}} - {"id": 844424930131976, "label": "v", "properties": {"id": "path, initial node"}} - {"id": 844424930131977, "label": "v", "properties": {"id": "path, middle node"}} - {"id": 844424930131978, "label": "v", "properties": {"id": "path, last node"}} - {"id": 844424930131979, "label": "v", "properties": {"id": "divergent, initial node"}} - {"id": 844424930131980, "label": "v", "properties": {"id": "divergent middle node"}} - {"id": 844424930131981, "label": "v", "properties": {"id": "divergent, end node"}} - {"id": 844424930131982, "label": "v", "properties": {"id": "convergent, initial node"}} - {"id": 844424930131983, "label": "v", "properties": {"id": "convergent middle node"}} - {"id": 844424930131984, "label": "v", "properties": {"id": "convergent, end node"}} - {"id": 844424930131985, "label": "v", "properties": {"id": "paths, vertex one"}} - {"id": 844424930131986, "label": "v", "properties": {"id": "paths, vertex two"}} - {"id": 844424930131987, "label": "v", "properties": {"id": "paths, vertex three"}} - {"id": 844424930131988, "label": "v", "properties": {"id": "paths, vertex four"}} - {"id": 1407374883553281, "label": "n_var", "properties": {"var_name": "Node A"}} - {"id": 1407374883553282, "label": "n_var", "properties": {"var_name": "Node B"}} - {"id": 1407374883553283, "label": "n_var", "properties": {"var_name": "Node C"}} - {"id": 1970324836974593, "label": "n_other_node", "properties": {}} - {"id": 1970324836974594, "label": "n_other_node", "properties": {}} - {"id": 1970324836974595, "label": "n_other_node", "properties": {}} -(46 rows) - --- prepared statements -/* -PREPARE p_1 AS SELECT * FROM cypher('cypher_create', $$CREATE (v:new_vertex {key: 'value'}) RETURN v$$) AS (a vertex); -EXECUTE p_1; -EXECUTE p_1; - -PREPARE p_2 AS SELECT * FROM cypher('cypher_create', $$CREATE (v:new_vertex {key: $var_name}) RETURN v$$, $1) AS (a vertex); -EXECUTE p_2('{'var_name': 'Hello Prepared Statements'}'); -EXECUTE p_2('{'var_name': 'Hello Prepared Statements 2'}'); - --- pl/pgsql -CREATE FUNCTION create_test() -RETURNS TABLE(v vertex) -LANGUAGE plpgsql -VOLATILE -AS $BODY$ -BEGIN - RETURN QUERY SELECT * FROM cypher('cypher_create', $$CREATE (v:new_vertex {key: 'value'}) RETURN v$$) AS (a vertex); -END -$BODY$; - -SELECT create_test(); -SELECT create_test(); -*/ --- --- check the cypher CREATE clause inside an INSERT INTO --- -/* -CREATE TABLE simple_path (u vertex, e edge, v vertex); - -INSERT INTO simple_path(SELECT * FROM cypher('cypher_create', - $$CREATE (u)-[e:knows]->(v) return u, e, v - $$) AS (u vertex, e edge, v vertex)); - -SELECT count(*) FROM simple_path; -*/ --- --- check the cypher CREATE clause inside of a BEGIN/END/COMMIT block --- -/* -BEGIN; -SELECT * FROM cypher('cypher_create', $$ CREATE (a:Part {part_num: '670'}) $$) as (a gtype); -SELECT * FROM cypher('cypher_create', $$ MATCH (a:Part) RETURN a $$) as (a vertex); - -SELECT * FROM cypher('cypher_create', $$ CREATE (a:Part {part_num: '671'}) $$) as (a gtype); -SELECT * FROM cypher('cypher_create', $$ CREATE (a:Part {part_num: '672'}) $$) as (a gtype); -SELECT * FROM cypher('cypher_create', $$ MATCH (a:Part) RETURN a $$) as (a vertex); - -SELECT * FROM cypher('cypher_create', $$ CREATE (a:Part {part_num: '673'}) $$) as (a gtype); -SELECT * FROM cypher('cypher_create', $$ MATCH (a:Part) RETURN a $$) as (a vertex); -END; -*/ --- --- Errors --- --- Var 'a' cannot have properties in the create clause -MATCH (a:n_var) WHERE a.var_name = 'Node A' CREATE (a {test:1})-[:e_var]->(); -ERROR: previously declared nodes in a create clause cannot have properties --- Var 'a' cannot change labels -MATCH (a:n_var) WHERE a.var_name = 'Node A' CREATE (a:new_label)-[:e_var]->(); -ERROR: previously declared variables cannot have a label -MATCH (a:n_var)-[b]-() WHERE a.var_name = 'Node A' CREATE (a)-[b:e_var]->(); -ERROR: variable b already exists ---CREATE with joins -/* -SELECT * -FROM (CREATE (a) RETURN a) as q(a vertex), - (CREATE (b) RETURN b) as t(b vertex); -*/ +ERROR: unexpected Node for cypher_clause +CREATE ()-[]->(); +ERROR: unexpected Node for cypher_clause -- -- Clean up -- DROP GRAPH cypher_create CASCADE; -NOTICE: drop cascades to 8 other objects +NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table cypher_create._ag_label_vertex +drop cascades to table cypher_create._adj__ag_label_vertex drop cascades to table cypher_create._ag_label_edge -drop cascades to table cypher_create.v -drop cascades to table cypher_create.e -drop cascades to table cypher_create.n_var -drop cascades to table cypher_create.e_var -drop cascades to table cypher_create.n_other_node -drop cascades to table cypher_create.b_var NOTICE: graph "cypher_create" has been dropped drop_graph ------------ diff --git a/regress/expected/new_cypher.out b/regress/expected/new_cypher.out index 7f23169f..1dd89823 100644 --- a/regress/expected/new_cypher.out +++ b/regress/expected/new_cypher.out @@ -69,472 +69,34 @@ USE GRAPH new_cypher; (1 row) --- Old Grammar is at least partially plugged in. -RETURN 1 as a; - a ---- - 1 -(1 row) - -CREATE (n); --- -(0 rows) - -CREATE (n) RETURN n; - n --------------------------------------------------------- - {"id": 281474976710658, "label": "", "properties": {}} -(1 row) - ---CREATE (n) RETURN *; -MATCH (n) RETURN n; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} -(2 rows) - -MATCH (n) RETURN *; - n --------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}} - {"id": 281474976710658, "label": "", "properties": {}} -(2 rows) - -CREATE TABLE tst (i text); -CREATE TABLE tst3 () INHERITS (tst); -CREATE TEMPORARY TABLE tst2 (i text); -CREATE TABLE tst4 (i text) PARTITION BY LIST (i); -CREATE TABLE tst5 (i text) PARTITION BY LIST (i COLLATE "de_DE"); -CREATE TABLE tst6 (i text) PARTITION BY LIST (i COLLATE "de_DE" text_ops); -CREATE TABLE tst7 (i text) USING heap; -CREATE TABLE tst8 (i text) WITH (fillfactor=70); -SELECT; --- -(1 row) - -SELECT ALL; --- -(1 row) - -SELECT *; -ERROR: SELECT * with no tables specified is not valid -LINE 1: SELECT *; - ^ -SELECT * FROM tst; - i ---- -(0 rows) - -SELECT tst.i FROM tst; - i ---- -(0 rows) - -SELECT a.i FROM tst AS a; - i ---- -(0 rows) - -SELECT a.i FROM tst a; - i ---- -(0 rows) - -SELECT a.i as j FROM tst a; - j ---- -(0 rows) - -SELECT a.j FROM tst AS a(j); - j ---- -(0 rows) - -SELECT i FROM tst WHERE i = i; - i ---- -(0 rows) - -SELECT i FROM tst WHERE i > i; - i ---- -(0 rows) - ---SELECT a.j FROM tst a(j); -SELECT a.* FROM tst as a; - i ---- -(0 rows) - -SELECT a.* FROM tst as a ORDER BY a.i DESC; - i ---- -(0 rows) - -SELECT a.i as j FROM tst a GROUP BY a.i; - j ---- -(0 rows) - -SELECT a.i as j FROM tst a GROUP BY a.i HAVING a.i = a.i; - j ---- -(0 rows) - -SELECT sum(salary) OVER w, avg(salary) OVER w - FROM empsalary - WINDOW w AS (PARTITION BY i ORDER BY i DESC); -ERROR: relation "empsalary" does not exist -LINE 2: FROM empsalary - ^ -SELECT a.i FROM tst a -UNION -SELECT a.i FROM tst a; - i ---- -(0 rows) - -SELECT a.i FROM tst a -EXCEPT -SELECT a.i FROM tst a; - i ---- -(0 rows) - -SELECT a.i FROM tst a -INTERSECT -SELECT a.i FROM tst a; - i ---- -(0 rows) - -INSERT INTO tst SELECT 'Hello'; -TABLE tst; - i -------- - Hello -(1 row) - -SELECT * FROM tst; - i -------- - Hello -(1 row) - -SELECT '"Hello"'::gtype = '"Hello"'::gtype; - ?column? ----------- - t -(1 row) - -SELECT '{"Hello": "World"}'::gtype <@ '"Hello"'::gtype; - ?column? ----------- - f -(1 row) - -SELECT 'Hello' LIKE 'Hello'; - ?column? ----------- - t -(1 row) - -SELECT 'Hello' NOT LIKE 'Hello'; - ?column? ----------- - f -(1 row) - -SELECT 'Hello' ILIKE 'hello'; - ?column? ----------- - t -(1 row) - -SELECT 'Hello' NOT ILIKE 'hello'; - ?column? ----------- - f -(1 row) - -SELECT 'Hello' SIMILAR TO 'Hello'; - ?column? ----------- - t -(1 row) - -SELECT 'Hello' NOT SIMILAR TO 'Hello'; - ?column? ----------- - f -(1 row) - -SELECT true AND false; - ?column? ----------- - f -(1 row) - -SELECT true OR false; - ?column? ----------- - t -(1 row) - -SELECT NOT false; - ?column? ----------- - t -(1 row) - -SELECT 1 IS NULL; - ?column? ----------- - f -(1 row) - -SELECT 1 IS NOT NULL; - ?column? ----------- - t -(1 row) - -SELECT 1 ISNULL; - ?column? ----------- - f -(1 row) - -SELECT 1 NOTNULL; - ?column? ----------- - t -(1 row) - -UPDATE tst SET i = 'World'; -SELECT a.i FROM tst a; - i -------- - World -(1 row) - -DELETE FROM tst; -SELECT a.i FROM tst a; - i ---- -(0 rows) - -SELECT a.i FROM ONLY tst a; - i ---- -(0 rows) - -SELECT true is TRUE; - ?column? ----------- - t -(1 row) - -SELECT true is not TRUE; - ?column? ----------- - f -(1 row) - -SELECT true is false; - ?column? ----------- - f -(1 row) - -SELECT true is not false; - ?column? ----------- - t -(1 row) - -SELECT NULL is unknown; - ?column? ----------- - t -(1 row) - -SELECT NULL is not unknown; - ?column? ----------- - f -(1 row) - -SELECT 1 IS DISTINCT FROM 2; - ?column? ----------- - t -(1 row) - -SELECT 1 IS NOT DISTINCT FROM 2; - ?column? ----------- - f -(1 row) - -CREATE TABLE tbl1 (i int4) USING heap; -CREATE TABLE tbl2 (j int4) USING heap; -INSERT INTO tbl1 VALUES (1), (2); -INSERT INTO tbl2 VALUES (3), (2); -SELECT * FROM tbl1 AS t1 JOIN tbl2 AS t2 ON t1.i = t2.j; - i | j ----+--- - 2 | 2 -(1 row) - -SELECT * FROM tbl1 AS t1 LEFT JOIN tbl2 AS t2 ON t1.i = t2.j; - i | j ----+--- - 1 | - 2 | 2 +SELECT * FROM postgraph.ag_label WHERE name = '_ag_label_vertex'; + name | graph | id | kind | relation | vertex_adjlist | label_path +------------------+-------+----+------+-------------------------------+------------------------------------+------------------ + _ag_label_vertex | 18780 | 1 | v | new_cypher._ag_label_vertex | new_cypher._adj__ag_label_vertex | _ag_label_vertex + _ag_label_vertex | 18805 | 1 | v | new_cypher_2._ag_label_vertex | new_cypher_2._adj__ag_label_vertex | _ag_label_vertex (2 rows) -SELECT * FROM tbl1 AS t1 RIGHT JOIN tbl2 AS t2 ON t1.i = t2.j; - i | j ----+--- - 2 | 2 - | 3 +\d+ new_cypher._adj__ag_label_vertex + Table "new_cypher._adj__ag_label_vertex" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +---------+---------+-----------+----------+---------+----------+--------------+------------- + id | graphid | | | | plain | | + adjlist | gtype | | | | extended | | + +SELECT c.relname AS table_name,am.amname AS access_method +FROM pg_class c +JOIN pg_am am ON c.relam = am.oid +WHERE c.relkind = 'r' AND c.relname = '_adj__ag_label_vertex'; + table_name | access_method +-----------------------+---------------- + _adj__ag_label_vertex | vertex_adjlist + _adj__ag_label_vertex | vertex_adjlist (2 rows) -SELECT * FROM tbl1 AS t1 FULL JOIN tbl2 AS t2 ON t1.i = t2.j; - i | j ----+--- - 1 | - 2 | 2 - | 3 -(3 rows) - -SELECT * FROM tbl1 AS t1 WHERE t1.i IN (SELECT j FROM tbl2); - i ---- - 2 -(1 row) - -SELECT * FROM tbl1 AS t1 WHERE t1.i NOT IN (SELECT j FROM tbl2); - i ---- - 1 -(1 row) - -SELECT (SELECT * FROM tbl1 WHERE i = 1); - i ---- - 1 -(1 row) - -SELECT * FROM tbl1 AS t1 WHERE EXISTS (SELECT j FROM tbl2 AS t2 WHERE t1.i = t2.j); - i ---- - 2 -(1 row) - -SELECT ARRAY (SELECT * FROM tbl1); - array -------- - {1,2} -(1 row) - -SELECT 1 BETWEEN SYMMETRIC 0 AND 2; - ?column? ----------- - t -(1 row) - -SELECT 1 BETWEEN 0 AND 2; - ?column? ----------- - t -(1 row) - -SELECT 1 BETWEEN ASYMMETRIC 0 AND 2; - ?column? ----------- - t -(1 row) - -SELECT (true AND true) OR false; - ?column? ----------- - t -(1 row) - -SELECT false AND (true AND false); - ?column? ----------- - f -(1 row) - -SELECT CASE WHEN true THEN true ELSE false END; - case ------- - t -(1 row) - -SELECT CASE WHEN false THEN true ELSE false END; - case ------- - f -(1 row) - -SELECT sin(1); - sin --------------------- - 0.8414709848078965 -(1 row) - -SELECT * FROM ( - SELECT * FROM tbl1 -) as a; - i ---- - 1 - 2 -(2 rows) - -SELECT * FROM LATERAL ( - SELECT * FROM tbl1 -) as a; - i ---- - 1 - 2 -(2 rows) - -SELECT ARRAY [1, 2, 3, 4]; - array ------------ - {1,2,3,4} -(1 row) - -SELECT (1, 2, 3, 4); - row ------------ - (1,2,3,4) -(1 row) - -SELECT ROW (1, 2, 3, 4); - row ------------ - (1,2,3,4) -(1 row) - -SELECT * -FROM tbl1 -WHERE i = SOME (SELECT j FROM tbl2); - i ---- - 2 -(1 row) - DROP GRAPH new_cypher CASCADE; -NOTICE: drop cascades to 2 other objects +NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table new_cypher._ag_label_vertex +drop cascades to table new_cypher._adj__ag_label_vertex drop cascades to table new_cypher._ag_label_edge NOTICE: graph "new_cypher" has been dropped drop_graph @@ -543,8 +105,9 @@ NOTICE: graph "new_cypher" has been dropped (1 row) DROP GRAPH new_cypher_2 CASCADE; -NOTICE: drop cascades to 2 other objects +NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table new_cypher_2._ag_label_vertex +drop cascades to table new_cypher_2._adj__ag_label_vertex drop cascades to table new_cypher_2._ag_label_edge NOTICE: graph "new_cypher_2" has been dropped drop_graph diff --git a/regress/expected/vertex_am.out b/regress/expected/vertex_am.out index 8b137891..8b681140 100644 --- a/regress/expected/vertex_am.out +++ b/regress/expected/vertex_am.out @@ -1 +1,46 @@ +/* + * Copyright (C) 2024 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +-- Regression tests don't preload extensions, gotta load it first +LOAD 'postgraph'; +CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex_adjlist; +INSERT INTO vertex_am_tst (id, props) +VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); +SELECT * FROM vertex_am_tst; + id | props +----+----------- + 1 | {"id": 1} +(1 row) + +DELETE FROM vertex_am_tst; +INSERT INTO vertex_am_tst (id, props) +VALUES ('2'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); +SELECT * FROM vertex_am_tst; + id | props +----+----------- + 2 | {"id": 1} +(1 row) + +UPDATE vertex_am_tst SET id = '2'::postgraph.graphid; +INSERT INTO vertex_am_tst (id, props) +VALUES ('4'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); +SELECT * FROM vertex_am_tst; + id | props +----+----------- + 2 | {"id": 1} + 4 | {"id": 1} +(2 rows) diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 728a9ecf..df0736af 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -26,158 +26,7 @@ USE GRAPH cypher_create; CREATE (); -CREATE (:v); - -CREATE (:v {}); - -CREATE (:v {prop_key: 'value'}); - -MATCH (n:v) RETURN n; - --- Left relationship -CREATE (:v {id:'right rel, initial node'})-[:e {id:'right rel'}]->(:v {id:'right rel, end node'}); - --- Right relationship -CREATE (:v {id:'left rel, initial node'})<-[:e {id:'left rel'}]-(:v {id:'left rel, end node'}); - --- Pattern creates a path from the initial node to the last node -CREATE (:v {id: 'path, initial node'})-[:e {id: 'path, edge one'}]->(:v {id:'path, middle node'})-[:e {id:'path, edge two'}]->(:v {id:'path, last node'}); - --- middle vertex points to the initial and last vertex -CREATE (:v {id: 'divergent, initial node'})<-[:e {id: 'divergent, edge one'}]-(:v {id: 'divergent middle node'})-[:e {id: 'divergent, edge two'}]->(:v {id: 'divergent, end node'}); - --- initial and last vertex point to the middle vertex -CREATE (:v {id: 'convergent, initial node'})-[:e {id: 'convergent, edge one'}]->(:v {id: 'convergent middle node'})<-[:e {id: 'convergent, edge two'}]-(:v {id: 'convergent, end node'}); - --- Validate Paths work correctly -CREATE (:v {id: 'paths, vertex one'})-[:e {id: 'paths, edge one'}]->(:v {id: 'paths, vertex two'}), - (:v {id: 'paths, vertex three'})-[:e {id: 'paths, edge two'}]->(:v {id: 'paths, vertex four'}); - ---edge with double relationship will throw an error -CREATE (:v)<-[:e]->(); - ---edge with no relationship will throw an error -CREATE (:v)-[:e]-(); - ---edges without labels are not supported -CREATE (:v)-[]->(:v); - -MATCH (n) RETURN n; -MATCH ()-[e]-() RETURN e; - -CREATE (:n_var {var_name: 'Node A'}); -CREATE (:n_var {var_name: 'Node B'}); -CREATE (:n_var {var_name: 'Node C'}); - -MATCH (a:n_var), (b:n_var) WHERE a.var_name <> b.var_name CREATE (a)-[:e_var {var_name: a.name + ' -> ' + b.name}]->(b); - -MATCH (a:n_var) CREATE (a)-[:e_var {var_name: a.var_name + ' -> ' + a.var_name}]->(a); - -MATCH (a:n_var) CREATE (a)-[:e_var {var_name: a.var_name + ' -> new node'}]->(:n_other_node); - -MATCH (a:n_var) WHERE a.var_name = 'Node A' CREATE (a)-[b:e_var]->(); - -CREATE (a)-[:b_var]->() RETURN a, id(a); - -CREATE ()-[b:e_var]->() RETURN b, id(b); - -CREATE (a)-[b:e_var {id: 0}]->() RETURN a, b, b.id, b.id + 1; - -MATCH (a:n_var) CREATE (a)-[b:e_var]->(a) RETURN a, b; - -MATCH (a:n_var) CREATE (a)-[b:e_var]->(c) RETURN a, b, c; - -CREATE (a)-[:e_var]->() RETURN a; - -CREATE ()-[b:e_var]->() RETURN b; - -CREATE p=()-[:e_var]->() RETURN p; - -CREATE p=(a {id:0})-[:e_var]->(a) RETURN p; - -MATCH (a:n_var) CREATE p=(a)-[:e_var]->(a) RETURN p; - -CREATE p=(a)-[:e_var]->(), (a)-[b:e_var]->(a) RETURN p, b; - -MATCH (a:n_var) WHERE a.var_name = 'Node Z' CREATE (a)-[:e_var {var_name: a.var_name + ' -> doesnt exist'}]->(:n_other_node) RETURN a; - -MATCH (n:n_var) RETURN n; -MATCH ()-[e:e_var]->() RETURN e; - ---Validate every vertex has the correct label -MATCH (n) RETURN n; - --- prepared statements -/* -PREPARE p_1 AS SELECT * FROM cypher('cypher_create', $$CREATE (v:new_vertex {key: 'value'}) RETURN v$$) AS (a vertex); -EXECUTE p_1; -EXECUTE p_1; - -PREPARE p_2 AS SELECT * FROM cypher('cypher_create', $$CREATE (v:new_vertex {key: $var_name}) RETURN v$$, $1) AS (a vertex); -EXECUTE p_2('{'var_name': 'Hello Prepared Statements'}'); -EXECUTE p_2('{'var_name': 'Hello Prepared Statements 2'}'); - --- pl/pgsql -CREATE FUNCTION create_test() -RETURNS TABLE(v vertex) -LANGUAGE plpgsql -VOLATILE -AS $BODY$ -BEGIN - RETURN QUERY SELECT * FROM cypher('cypher_create', $$CREATE (v:new_vertex {key: 'value'}) RETURN v$$) AS (a vertex); -END -$BODY$; - -SELECT create_test(); -SELECT create_test(); -*/ - --- --- check the cypher CREATE clause inside an INSERT INTO --- -/* -CREATE TABLE simple_path (u vertex, e edge, v vertex); - -INSERT INTO simple_path(SELECT * FROM cypher('cypher_create', - $$CREATE (u)-[e:knows]->(v) return u, e, v - $$) AS (u vertex, e edge, v vertex)); - -SELECT count(*) FROM simple_path; -*/ --- --- check the cypher CREATE clause inside of a BEGIN/END/COMMIT block --- -/* -BEGIN; -SELECT * FROM cypher('cypher_create', $$ CREATE (a:Part {part_num: '670'}) $$) as (a gtype); -SELECT * FROM cypher('cypher_create', $$ MATCH (a:Part) RETURN a $$) as (a vertex); - -SELECT * FROM cypher('cypher_create', $$ CREATE (a:Part {part_num: '671'}) $$) as (a gtype); -SELECT * FROM cypher('cypher_create', $$ CREATE (a:Part {part_num: '672'}) $$) as (a gtype); -SELECT * FROM cypher('cypher_create', $$ MATCH (a:Part) RETURN a $$) as (a vertex); - -SELECT * FROM cypher('cypher_create', $$ CREATE (a:Part {part_num: '673'}) $$) as (a gtype); -SELECT * FROM cypher('cypher_create', $$ MATCH (a:Part) RETURN a $$) as (a vertex); -END; -*/ - --- --- Errors --- --- Var 'a' cannot have properties in the create clause -MATCH (a:n_var) WHERE a.var_name = 'Node A' CREATE (a {test:1})-[:e_var]->(); - --- Var 'a' cannot change labels -MATCH (a:n_var) WHERE a.var_name = 'Node A' CREATE (a:new_label)-[:e_var]->(); - -MATCH (a:n_var)-[b]-() WHERE a.var_name = 'Node A' CREATE (a)-[b:e_var]->(); - ---CREATE with joins -/* -SELECT * -FROM (CREATE (a) RETURN a) as q(a vertex), - (CREATE (b) RETURN b) as t(b vertex); -*/ +CREATE ()-[]->(); -- -- Clean up diff --git a/regress/sql/new_cypher.sql b/regress/sql/new_cypher.sql index 7218cf16..e75cdda5 100644 --- a/regress/sql/new_cypher.sql +++ b/regress/sql/new_cypher.sql @@ -43,168 +43,13 @@ CREATE GRAPH new_cypher_2; USE GRAPH new_cypher_2; USE GRAPH new_cypher; --- Old Grammar is at least partially plugged in. -RETURN 1 as a; +SELECT * FROM postgraph.ag_label WHERE name = '_ag_label_vertex'; +\d+ new_cypher._adj__ag_label_vertex -CREATE (n); - -CREATE (n) RETURN n; - ---CREATE (n) RETURN *; - -MATCH (n) RETURN n; - -MATCH (n) RETURN *; - -CREATE TABLE tst (i text); -CREATE TABLE tst3 () INHERITS (tst); -CREATE TEMPORARY TABLE tst2 (i text); - -CREATE TABLE tst4 (i text) PARTITION BY LIST (i); -CREATE TABLE tst5 (i text) PARTITION BY LIST (i COLLATE "de_DE"); -CREATE TABLE tst6 (i text) PARTITION BY LIST (i COLLATE "de_DE" text_ops); -CREATE TABLE tst7 (i text) USING heap; -CREATE TABLE tst8 (i text) WITH (fillfactor=70); - -SELECT; -SELECT ALL; - -SELECT *; -SELECT * FROM tst; -SELECT tst.i FROM tst; - -SELECT a.i FROM tst AS a; -SELECT a.i FROM tst a; - -SELECT a.i as j FROM tst a; - - -SELECT a.j FROM tst AS a(j); - -SELECT i FROM tst WHERE i = i; -SELECT i FROM tst WHERE i > i; ---SELECT a.j FROM tst a(j); -SELECT a.* FROM tst as a; - -SELECT a.* FROM tst as a ORDER BY a.i DESC; - -SELECT a.i as j FROM tst a GROUP BY a.i; - -SELECT a.i as j FROM tst a GROUP BY a.i HAVING a.i = a.i; - -SELECT sum(salary) OVER w, avg(salary) OVER w - FROM empsalary - WINDOW w AS (PARTITION BY i ORDER BY i DESC); - -SELECT a.i FROM tst a -UNION -SELECT a.i FROM tst a; - -SELECT a.i FROM tst a -EXCEPT -SELECT a.i FROM tst a; - -SELECT a.i FROM tst a -INTERSECT -SELECT a.i FROM tst a; - -INSERT INTO tst SELECT 'Hello'; - - -TABLE tst; -SELECT * FROM tst; - -SELECT '"Hello"'::gtype = '"Hello"'::gtype; - -SELECT '{"Hello": "World"}'::gtype <@ '"Hello"'::gtype; - -SELECT 'Hello' LIKE 'Hello'; - -SELECT 'Hello' NOT LIKE 'Hello'; - -SELECT 'Hello' ILIKE 'hello'; - -SELECT 'Hello' NOT ILIKE 'hello'; - -SELECT 'Hello' SIMILAR TO 'Hello'; - -SELECT 'Hello' NOT SIMILAR TO 'Hello'; - -SELECT true AND false; -SELECT true OR false; -SELECT NOT false; - -SELECT 1 IS NULL; -SELECT 1 IS NOT NULL; -SELECT 1 ISNULL; -SELECT 1 NOTNULL; - -UPDATE tst SET i = 'World'; -SELECT a.i FROM tst a; - -DELETE FROM tst; -SELECT a.i FROM tst a; - -SELECT a.i FROM ONLY tst a; - -SELECT true is TRUE; -SELECT true is not TRUE; -SELECT true is false; -SELECT true is not false; -SELECT NULL is unknown; -SELECT NULL is not unknown; - -SELECT 1 IS DISTINCT FROM 2; -SELECT 1 IS NOT DISTINCT FROM 2; - -CREATE TABLE tbl1 (i int4) USING heap; -CREATE TABLE tbl2 (j int4) USING heap; - -INSERT INTO tbl1 VALUES (1), (2); -INSERT INTO tbl2 VALUES (3), (2); - -SELECT * FROM tbl1 AS t1 JOIN tbl2 AS t2 ON t1.i = t2.j; -SELECT * FROM tbl1 AS t1 LEFT JOIN tbl2 AS t2 ON t1.i = t2.j; -SELECT * FROM tbl1 AS t1 RIGHT JOIN tbl2 AS t2 ON t1.i = t2.j; -SELECT * FROM tbl1 AS t1 FULL JOIN tbl2 AS t2 ON t1.i = t2.j; - -SELECT * FROM tbl1 AS t1 WHERE t1.i IN (SELECT j FROM tbl2); -SELECT * FROM tbl1 AS t1 WHERE t1.i NOT IN (SELECT j FROM tbl2); - -SELECT (SELECT * FROM tbl1 WHERE i = 1); - -SELECT * FROM tbl1 AS t1 WHERE EXISTS (SELECT j FROM tbl2 AS t2 WHERE t1.i = t2.j); - -SELECT ARRAY (SELECT * FROM tbl1); - -SELECT 1 BETWEEN SYMMETRIC 0 AND 2; -SELECT 1 BETWEEN 0 AND 2; -SELECT 1 BETWEEN ASYMMETRIC 0 AND 2; - -SELECT (true AND true) OR false; -SELECT false AND (true AND false); - -SELECT CASE WHEN true THEN true ELSE false END; -SELECT CASE WHEN false THEN true ELSE false END; - -SELECT sin(1); - -SELECT * FROM ( - SELECT * FROM tbl1 -) as a; - -SELECT * FROM LATERAL ( - SELECT * FROM tbl1 -) as a; - -SELECT ARRAY [1, 2, 3, 4]; - -SELECT (1, 2, 3, 4); -SELECT ROW (1, 2, 3, 4); - -SELECT * -FROM tbl1 -WHERE i = SOME (SELECT j FROM tbl2); +SELECT c.relname AS table_name,am.amname AS access_method +FROM pg_class c +JOIN pg_am am ON c.relam = am.oid +WHERE c.relkind = 'r' AND c.relname = '_adj__ag_label_vertex'; DROP GRAPH new_cypher CASCADE; DROP GRAPH new_cypher_2 CASCADE; \ No newline at end of file diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index 3d4d9b7d..c2ce6c0e 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -20,7 +20,7 @@ LOAD 'postgraph'; -CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex; +CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex_adjlist; INSERT INTO vertex_am_tst (id, props) VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index f88dae11..2beeff3e 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -3246,9 +3246,9 @@ GetVertexHeapamTableAmRoutine(void) return &vertex_heapam_methods; } -PG_FUNCTION_INFO_V1(vertex_tableam_handler); +PG_FUNCTION_INFO_V1(vertex_adjlist_tableam_handler); Datum -vertex_tableam_handler(PG_FUNCTION_ARGS) +vertex_adjlist_tableam_handler(PG_FUNCTION_ARGS) { PG_RETURN_POINTER(&vertex_heapam_methods); } diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index 4a0e9381..46f8ead9 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -57,7 +57,7 @@ PG_FUNCTION_INFO_V1(ltree_addltree); // INSERT INTO CATALOG_SCHEMA.ag_label // VALUES (label_name, label_graph, label_id, label_kind, label_relation) void insert_label(const char *label_name, Oid graph_oid, int32 label_id, - char label_kind, Oid label_relation, const char *ltree) + char label_kind, Oid label_relation, const char *ltree, Oid vertex_adjlist) { NameData label_name_data; Datum values[Natts_ag_label + 1]; @@ -93,20 +93,28 @@ void insert_label(const char *label_name, Oid graph_oid, int32 label_id, values[Anum_ag_label_relation - 1] = ObjectIdGetDatum(label_relation); nulls[Anum_ag_label_relation - 1] = false; - if (strcmp(label_name, "_ag_label_vertex") == 0 || strcmp(label_name, "_ag_label_edge") == 0) { - values[5] = DirectFunctionCall1(ltree_in, CStringGetDatum(label_name)); + if (vertex_adjlist != InvalidOid) { + values[5] = ObjectIdGetDatum(vertex_adjlist); nulls[5] = false; + } else { + values[5] = NULL; + nulls[5] = true; + } + + if (strcmp(label_name, "_ag_label_vertex") == 0 || strcmp(label_name, "_ag_label_edge") == 0) { + values[6] = DirectFunctionCall1(ltree_in, CStringGetDatum(label_name)); + nulls[6] = false; } else if (label_kind == 'v') { - values[5] = DirectFunctionCall2(ltree_addltree, + values[6] = DirectFunctionCall2(ltree_addltree, DirectFunctionCall1(ltree_in, CStringGetDatum("_ag_label_vertex")), DirectFunctionCall1(ltree_in, CStringGetDatum(label_name))); - nulls[5] = false; + nulls[6] = false; } else { - values[5] = DirectFunctionCall2(ltree_addltree, + values[6] = DirectFunctionCall2(ltree_addltree, DirectFunctionCall1(ltree_in, CStringGetDatum("_ag_label_edge")), DirectFunctionCall1(ltree_in, CStringGetDatum(label_name))); - nulls[5] = false; + nulls[6] = false; } diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index cb3f4d24..de5e1d86 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -78,6 +78,7 @@ static Constraint *build_id_default(char *graph_name, char *label_name, static FuncCall *build_id_default_func_expr(char *graph_name, char *label_name, char *schema_name, char *seq_name); static Constraint *build_not_null_constraint(void); +static Constraint *build_null_constraint(void); static Constraint *build_properties_default(void); static void alter_sequence_owned_by_for_label(RangeVar *seq_range_var, char *rel_name); @@ -85,14 +86,14 @@ static int32 get_new_label_id(Oid graph_oid, Oid nsp_id); static void change_label_id_default(char *graph_name, char *label_name, char *schema_name, char *seq_name, Oid relid); - +static List *create_vertex_adjlist_table_elements(void); // drop static void remove_relation(List *qname); static void range_var_callback_for_remove_relation(const RangeVar *rel, Oid rel_oid, Oid odl_rel_oid, void *arg); - +static char *make_vertex_adjlist_alias(char *var_name); PG_FUNCTION_INFO_V1(create_vlabel); @@ -531,6 +532,78 @@ Datum create_property_index(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +static char *make_vertex_adjlist_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'a'; + str[2] = 'd'; + str[3] = 'j'; + str[4] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 5] = var_name[i]; + str[i + 5] = '\0'; + + return str; +} +void create_vertex_adjlist(char *graph_name, char *label_name) { + CreateStmt *create_stmt; + PlannedStmt *wrapper; + + create_stmt = makeNode(CreateStmt); + char *rel_name = make_vertex_adjlist_alias(label_name); + + // relpersistence is set to RELPERSISTENCE_PERMANENT by makeRangeVar() + create_stmt->relation = makeRangeVar(graph_name, rel_name, -1); + + + create_stmt->tableElts = create_vertex_adjlist_table_elements(); + + + create_stmt->inhRelations = NIL; + create_stmt->partbound = NULL; + create_stmt->ofTypename = NULL; + create_stmt->accessMethod = "vertex_adjlist"; + create_stmt->constraints = NIL; + create_stmt->options = NIL; + create_stmt->oncommit = ONCOMMIT_NOOP; + create_stmt->tablespacename = NULL; + create_stmt->if_not_exists = false; + + wrapper = makeNode(PlannedStmt); + wrapper->commandType = CMD_UTILITY; + wrapper->canSetTag = false; + wrapper->utilityStmt = (Node *)create_stmt; + wrapper->stmt_location = -1; + wrapper->stmt_len = 0; + + ProcessUtility(wrapper, "(generated CREATE TABLE command)", false, + PROCESS_UTILITY_SUBCOMMAND, NULL, NULL, None_Receiver, + NULL); + // CommandCounterIncrement() is called in ProcessUtility() +} + +// CREATE TABLE `schema_name`.`rel_name` ( +// "id" graphid PRIMARY KEY DEFAULT CATALOG_SCHEMA."_graphid"(...), +// "properties" gtype NOT NULL DEFAULT CATALOG_SCHEMA."gtype_build_map"() +// ) +static List *create_vertex_adjlist_table_elements(void) +{ + ColumnDef *id; + ColumnDef *props; + + // "id" graphid PRIMARY KEY DEFAULT CATALOG_SCHEMA."_graphid"(...) + id = makeColumnDef(AG_VERTEX_COLNAME_ID, GRAPHIDOID, -1, InvalidOid); + id->constraints = NIL; + + // "properties" gtype NOT NULL DEFAULT CATALOG_SCHEMA."gtype_build_map"() + props = makeColumnDef("adjlist", GTYPEOID, -1, InvalidOid); + props->constraints = list_make1(build_null_constraint()); + + return list_make2(id, props); +} /* @@ -585,8 +658,16 @@ void create_label(char *graph_name, char *label_name, char label_type, // get a new "id" for the new label label_id = get_new_label_id(graph_oid, nsp_id); - insert_label(label_name, graph_oid, label_id, label_type, relation_id, NULL); + if (label_type == 'v') { + create_vertex_adjlist(graph_name, label_name); + Oid adj_oid = get_relname_relid(make_vertex_adjlist_alias(label_name), nsp_id); + insert_label(label_name, graph_oid, label_id, label_type, relation_id, NULL, adj_oid); + }else { + insert_label(label_name, graph_oid, label_id, label_type, relation_id, NULL, InvalidOid); + + } + CommandCounterIncrement(); } @@ -850,6 +931,18 @@ static Constraint *build_not_null_constraint(void) return not_null; } + +// NULL +static Constraint *build_null_constraint(void) +{ + Constraint *null = makeNode(Constraint); + null->contype = CONSTR_NULL; + null->location = -1; + + return null; +} + + // DEFAULT CATALOG_SCHEMA."gtype_build_map"() static Constraint *build_properties_default(void) { diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index b52db827..ff0020eb 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -67,6 +67,8 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, case CYPHER_CLAUSE_CREATE: handle_cypher_create_clause(root, rel, rti, rte); break; + case CYPHER_CLAUSE_NONE: + break; default: ereport(ERROR, (errmsg_internal("invalid cypher_clause_kind"))); } diff --git a/src/backend/utils/adt/age_graphid_ds.c b/src/backend/utils/adt/age_graphid_ds.c deleted file mode 100644 index bea273df..00000000 --- a/src/backend/utils/adt/age_graphid_ds.c +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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. - */ - -#include "postgres.h" - -#include "utils/graphid.h" -#include "utils/graphid_ds.h" - -/* defines */ -/* - * A simple linked list node for graphid lists (int64). PG's implementation - * has too much overhead for this type of list as it only directly supports - * regular ints, not int64s, of which a graphid currently is. - */ -typedef struct GraphIdNode -{ - graphid id; - struct GraphIdNode *next; -} GraphIdNode; - -/* a container for a linked list of GraphIdNodes */ -typedef struct ListGraphId -{ - GraphIdNode *head; - GraphIdNode *tail; - int64 size; -} ListGraphId; - -/* declarations */ - -/* definitions */ -/* return the next GraphIdNode */ -GraphIdNode *next_GraphIdNode(GraphIdNode *node) -{ - return node->next; -} - -/* return the graphid */ -graphid get_graphid(GraphIdNode *node) -{ - return node->id; -} - -/* get the size of the passed queue */ -int64 get_queue_size(ListGraphId *queue) -{ - return queue->size; -} - -/* return a reference to the head entry in the queue, or NULL if empty */ -GraphIdNode *peek_queue_head(ListGraphId *queue) -{ - if (queue == NULL) - { - return NULL; - } - - return queue->head; -} - -/* return a reference to the tail entry in the queue */ -GraphIdNode *peek_queue_tail(ListGraphId *queue) -{ - return queue->tail; -} - -/* return a reference to the head entry of a list */ -GraphIdNode *get_list_head(ListGraphId *list) -{ - return list->head; -} - -/* get the size of the passed list */ -int64 get_list_size(ListGraphId *list) -{ - return list->size; -} - -/* - * Helper function to add a graphid to the end of a ListGraphId container. - * If the container is NULL, it creates the container with the entry. - */ -ListGraphId *append_graphid(ListGraphId *container, graphid id) -{ - GraphIdNode *new_node = NULL; - - /* create the new link */ - new_node = palloc0(sizeof(GraphIdNode)); - new_node->id = id; - new_node->next = NULL; - - /* - * If the container is NULL then this is a new list. So, create the - * container and add in the new link. - */ - if (container == NULL) - { - container = palloc0(sizeof(ListGraphId)); - container->head = new_node; - container->tail = new_node; - container->size = 1; - } - /* otherwise, this is an existing list, append id */ - else - { - container->tail->next = new_node; - container->tail = new_node; - container->size++; - } - - return container; -} - -/* free (delete) a ListGraphId list */ -void free_ListGraphId(ListGraphId *container) -{ - GraphIdNode *curr_node = NULL; - GraphIdNode *next_node = NULL; - - /* if the container is NULL we don't need to delete anything */ - if (container == NULL) - { - return; - } - - /* otherwise, start from the head, free, and delete the links */ - curr_node = container->head; - while (curr_node != NULL) - { - next_node = curr_node->next; - /* we can do this because this is just a list of ints */ - pfree(curr_node); - curr_node = next_node; - } - - /* free the container */ - pfree(container); -} - -/* helper function to create a new, empty, graphid queue */ -ListGraphId *new_graphid_queue(void) -{ - ListGraphId *queue = NULL; - - /* allocate the container for the queue */ - queue = palloc0(sizeof(ListGraphId)); - - /* set it to its initial values */ - queue->head = NULL; - queue->tail = NULL; - queue->size = 0; - - /* return the new queue */ - return queue; -} - -/* helper function to free a graphid queue's contents but, not the container */ -void free_graphid_queue(ListGraphId *queue) -{ - Assert(queue != NULL); - - if (queue == NULL) - { - elog(ERROR, "free_graphid_queue: NULL queue"); - } - - /* while there are entries */ - while (queue->head != NULL) - { - /* get the next element in the queue */ - GraphIdNode *next = queue->head->next; - - /* free the head element */ - pfree(queue->head); - /* move the head to the next */ - queue->head = next; - } - - /* reset the tail and size */ - queue->tail = NULL; - queue->size = 0; -} - -/* - * Helper function for a generic push graphid (int64) to a queue. If the queue - * is NULL, it will error out. - */ -void push_graphid_queue(ListGraphId *queue, graphid id) -{ - GraphIdNode *new_node = NULL; - - Assert(queue != NULL); - - if (queue == NULL) - { - elog(ERROR, "push_graphid_queue: NULL queue"); - } - - /* create the new element */ - new_node = palloc0(sizeof(GraphIdNode)); - new_node->id = id; - - /* insert (push) the new element on the top */ - new_node->next = queue->head; - queue->head = new_node; - queue->size++; -} - -/* - * Helper function for a generic pop graphid (int64) from a queue. If the queue - * is empty, it will error out. You should verify that the queue isn't empty - * prior to calling. - */ -graphid pop_graphid_queue(ListGraphId *queue) -{ - GraphIdNode *node = NULL; - graphid id; - - Assert(queue != NULL); - Assert(queue->size != 0); - - if (queue == NULL) - { - elog(ERROR, "pop_graphid_queue: NULL queue"); - } - - if (queue->size <= 0) - { - elog(ERROR, "pop_graphid_queue: empty queue"); - } - - - /* remove the element from the top of the queue */ - node = queue->head; - id = node->id; - queue->head = queue->head->next; - queue->size--; - /* free the element */ - pfree(node); - - /* return the id */ - return id; -} diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index 1d7366c9..820d5fc5 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -967,9 +967,13 @@ static void fill_label_cache_data(label_cache_data *cache_data, value = heap_getattr(tuple, Anum_ag_label_relation, tuple_desc, &is_null); Assert(!is_null); cache_data->relation = DatumGetObjectId(value); + + value = heap_getattr(tuple, 6, tuple_desc, &is_null); + cache_data->vertex_adjlist = DatumGetObjectId(value); + // ag_label.label_path - //value = heap_getattr(tuple, 6, tuple_desc, &is_null); + value = heap_getattr(tuple, 7, tuple_desc, &is_null); //Assert(!is_null); - //cache_data->label_tree = DatumGetPointer(value); + cache_data->label_tree = DatumGetPointer(value); } diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h index 33d712fc..b40414fa 100644 --- a/src/include/catalog/ag_label.h +++ b/src/include/catalog/ag_label.h @@ -69,7 +69,7 @@ #define LABEL_KIND_EDGE 'e' void insert_label(const char *label_name, Oid graph_oid, int32 label_id, - char label_kind, Oid label_relation, const char *ltree); + char label_kind, Oid label_relation, const char *ltree, Oid vertex_adjlist); void delete_label(Oid relation); int32 get_label_id(const char *label_name, Oid graph_oid); diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index 16a3e368..1c7a3989 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -41,6 +41,7 @@ typedef struct label_cache_data int32 id; char kind; Oid relation; + Oid vertex_adjlist; ltree *label_tree; } label_cache_data; diff --git a/src/include/utils/graphid_ds.h b/src/include/utils/graphid_ds.h deleted file mode 100644 index 75061f4c..00000000 --- a/src/include/utils/graphid_ds.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#ifndef POSTGRAPH_GRAPHID_DS_H -#define POSTGRAPH_GRAPHID_DS_H - -#include "utils/graphid.h" - -#define IS_GRAPHID_STACK_EMPTY(queue) \ - get_queue_size(queue) == 0 -#define PEEK_GRAPHID_STACK(queue) \ - (graphid) get_graphid(peek_queue_head(queue)) - -/* - * We declare the GRAPHID data structures here, and in this way, so that they - * may be used elsewhere. However, we keep the contents private by defining them - * in graphid_ds.c - */ - -/* declare the GraphIdNode */ -typedef struct GraphIdNode GraphIdNode; - -/* declare the ListGraphId container */ -typedef struct ListGraphId ListGraphId; - -/* GraphIdNode access functions */ -GraphIdNode *next_GraphIdNode(GraphIdNode *node); -graphid get_graphid(GraphIdNode *node); - -/* graphid queue functions */ -/* create a new ListGraphId queue */ -ListGraphId *new_graphid_queue(void); -/* free a ListGraphId queue */ -void free_graphid_queue(ListGraphId *queue); -/* push a graphid onto a ListGraphId queue */ -void push_graphid_queue(ListGraphId *queue, graphid id); -/* pop (remove) a GraphIdNode from the top of the queue */ -graphid pop_graphid_queue(ListGraphId *queue); -/* peek (doesn't remove) at the head entry of a ListGraphId queue */ -GraphIdNode *peek_queue_head(ListGraphId *queue); -/* peek (doesn't remove) at the tail entry of a ListGraphId queue */ -GraphIdNode *peek_queue_tail(ListGraphId *queue); -/* return the size of a ListGraphId queue */ -int64 get_queue_size(ListGraphId *queue); - -/* graphid list functions */ -/* - * Helper function to add a graphid to the end of a ListGraphId container. - * If the container is NULL, it creates the container with the entry. - */ -ListGraphId *append_graphid(ListGraphId *container, graphid id); -/* free a ListGraphId container */ -void free_ListGraphId(ListGraphId *container); -/* return a reference to the head entry of a list */ -GraphIdNode *get_list_head(ListGraphId *list); -/* get the size of the passed list */ -int64 get_list_size(ListGraphId *list); - -#endif diff --git a/src/include/utils/gtype_temporal.h b/src/include/utils/gtype_temporal.h deleted file mode 100644 index d07ad78b..00000000 --- a/src/include/utils/gtype_temporal.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Used to support gtype temporal values. - * - * Copyright (C) 2023 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#ifndef PGRAPH_GTYPE_TEMPORAL_H -#define PGRAPH_GTYPE_TEMPORAL_H - -#include "utils/timestamp.h" - -int timetz_cmp_internal(TimeTzADT *time1, TimeTzADT *time2); -int interval_cmp_internal(Interval *interval1, Interval *interval2); -//TimestampTz timestamp2timestamptz(Timestamp timestamp); - -#endif From 8ca54ec1f2e1c061d4d707d46b8a557c8299daa7 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sun, 13 Jul 2025 21:12:04 -0500 Subject: [PATCH 14/56] Support one vertex in match with no variable name or label --- regress/expected/cypher_create.out | 24 +- regress/sql/cypher_create.sql | 8 + src/backend/catalog/ag_label.c | 113 --------- src/backend/commands/graph_commands.c | 4 - src/backend/commands/label_commands.c | 2 +- src/backend/executor/cypher_create.c | 172 ++++++------- src/backend/optimizer/cypher_paths.c | 2 +- src/backend/parser/cypher_clause.c | 343 +++++++++++++++++++++++++- src/include/catalog/ag_label.h | 4 +- 9 files changed, 440 insertions(+), 232 deletions(-) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 8271bf75..3b10396a 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -32,9 +32,29 @@ USE GRAPH cypher_create; (1 row) CREATE (); -ERROR: unexpected Node for cypher_clause +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +-- +(0 rows) + +SELECT * FROM cypher_create._ag_label_vertex; + id | properties +-----------------+------------ + 281474976710657 | +(1 row) + +MATCH () RETURN 1; + ?column? +---------- + 1 +(1 row) + CREATE ()-[]->(); -ERROR: unexpected Node for cypher_clause +ERROR: CREATE doesn't work with paths +CYPHER WITH 1 as a +CREATE (); +ERROR: CREATE doesn't work with previous clauses +CREATE () RETURN 1 as a; +ERROR: CREATE doesn't work with next clauses -- -- Clean up -- diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index df0736af..478f4bfe 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -25,9 +25,17 @@ CREATE GRAPH cypher_create; USE GRAPH cypher_create; CREATE (); +SELECT * FROM cypher_create._ag_label_vertex; + +MATCH () RETURN 1; CREATE ()-[]->(); +CYPHER WITH 1 as a +CREATE (); + +CREATE () RETURN 1 as a; + -- -- Clean up -- diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index 46f8ead9..627535b7 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -45,12 +45,6 @@ #include "utils/ag_cache.h" #include "utils/graphid.h" - - - -static ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, - char *label_name); - PG_FUNCTION_INFO_V1(ltree_in); PG_FUNCTION_INFO_V1(ltree_addltree); @@ -282,111 +276,4 @@ RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, return makeRangeVar(graph_name, relname, 2); } -#include "parser/parse_node.h" -/* - * Given the graph name and the label name, create a ResultRelInfo for the table - * those to variables represent. Open the Indices too. - */ -ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, - char *label_name) -{ - RangeVar *rv; - Relation label_relation; - ResultRelInfo *resultRelInfo; - - ParseState *pstate = make_parsestate(NULL); - - resultRelInfo = palloc(sizeof(ResultRelInfo)); - - if (strlen(label_name) == 0) - { - rv = makeRangeVar(graph_name, AG_DEFAULT_LABEL_VERTEX, -1); - } - else - { - rv = makeRangeVar(graph_name, label_name, -1); - } - - label_relation = parserOpenTable(pstate, rv, RowExclusiveLock); - - // initialize the resultRelInfo - InitResultRelInfo(resultRelInfo, label_relation, - list_length(estate->es_range_table), NULL, - estate->es_instrument); - - // open the parse state - ExecOpenIndices(resultRelInfo, false); - - free_parsestate(pstate); - - return resultRelInfo; -} - -/* - * Retrieves a list of all the names of a graph. - * - * XXX: We may want to use the cache system for this function, - * however the cache system currently requires us to know the - * name of the label we want. - */ -List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid) -{ - List *labels = NIL; - ScanKeyData scan_keys[2]; - Relation ag_label; - TableScanDesc scan_desc; - HeapTuple tuple; - TupleTableSlot *slot; - ResultRelInfo *resultRelInfo; - - // setup scan keys to get all edges for the given graph oid - ScanKeyInit(&scan_keys[1], Anum_ag_label_graph, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - ScanKeyInit(&scan_keys[0], Anum_ag_label_kind, BTEqualStrategyNumber, - F_CHAREQ, CharGetDatum(LABEL_TYPE_EDGE)); - - // setup the table to be scanned - ag_label = table_open(ag_label_relation_id(), RowExclusiveLock); - scan_desc = table_beginscan(ag_label, estate->es_snapshot, 2, scan_keys); - - resultRelInfo = create_entity_result_rel_info(estate, CATALOG_SCHEMA, - "ag_label"); - - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - - // scan through the results and get all the label names. - while(true) - { - Name label; - bool isNull; - Datum datum; - - tuple = heap_getnext(scan_desc, ForwardScanDirection); - - // no more labels to process - if (!HeapTupleIsValid(tuple)) - break; - - ExecStoreHeapTuple(tuple, slot, false); - - datum = slot_getattr(slot, Anum_ag_label_name, &isNull); - label = DatumGetName(datum); - - labels = lappend(labels, label); - } - - table_endscan(scan_desc); - - // close the indices - ExecCloseIndices(resultRelInfo); - - // close the rel - table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); - - table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); - - return labels; -} diff --git a/src/backend/commands/graph_commands.c b/src/backend/commands/graph_commands.c index 51ecf997..d93247a4 100644 --- a/src/backend/commands/graph_commands.c +++ b/src/backend/commands/graph_commands.c @@ -188,10 +188,6 @@ Datum create_graph(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("graph name must not be NULL"))); - /* - graph_name = PG_GETARG_NAME(0); - - graph_name_str = NameStr(*graph_name);*/ graph_name_str = TextDatumGetCString(PG_GETARG_DATUM(0)); if (graph_exists(graph_name_str)) ereport(ERROR, diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index de5e1d86..8e890bf4 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -788,7 +788,7 @@ static List *create_vertex_table_elements(char *graph_name, char *label_name, // "properties" gtype NOT NULL DEFAULT CATALOG_SCHEMA."gtype_build_map"() props = makeColumnDef(AG_VERTEX_COLNAME_PROPERTIES, GTYPEOID, -1, InvalidOid); - props->constraints = list_make2(build_not_null_constraint(), + props->constraints = list_make2(build_null_constraint(), build_properties_default()); return list_make2(id, props); diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 16521496..a4fd573a 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -66,8 +66,6 @@ typedef struct cypher_create_custom_scan_state Oid graph_oid; } cypher_create_custom_scan_state; - - static HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate); @@ -132,6 +130,8 @@ static Datum create_traversal(List *entities) { return TRAVERSAL_GET_DATUM(p); } +#include "commands/label_commands.h" + static void begin_cypher_create(CustomScanState *node, EState *estate, int eflags) { cypher_create_custom_scan_state *css = (cypher_create_custom_scan_state *)node; ListCell *lc; @@ -148,48 +148,38 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag ExecGetResultType(node->ss.ps.lefttree), &TTSOpsHeapTuple); + /* if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) { TupleDesc tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } - - foreach (lc, css->pattern) { - cypher_create_path *path = lfirst(lc); - ListCell *lc2; - foreach (lc2, path->target_nodes) { - cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc2); - Relation rel; - - if (!CYPHER_TARGET_NODE_INSERT_ENTITY(cypher_node->flags)) - continue; - - // Open relation and aquire a row exclusive lock. - rel = table_open(cypher_node->relid, RowExclusiveLock); - - // Initialize resultRelInfo for the vertex - cypher_node->resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, rel, - list_length(estate->es_range_table), NULL, - estate->es_instrument); - - // Open all indexes for the relation - ExecOpenIndices(cypher_node->resultRelInfo, false); - - // Setup the relation's tuple slot - cypher_node->elemTupleSlot = table_slot_create(rel, &estate->es_tupleTable); - - if (cypher_node->id_expr != NULL) - cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); - } - } - /* - * Postgres does not assign the es_output_cid in queries that do - * not write to disk, ie: SELECT commands. We need the command id - * for our clauses, and we may need to initialize it. We cannot use - * GetCurrentCommandId because there may be other cypher clauses - * that have modified the command id. - */ + */ + + if (list_length(css->pattern) != 1) + ereport(ERROR, (errmsg_internal("executor create found a multi pattern"))); + + cypher_create_path *path = linitial(css->pattern); + + if (list_length(path->target_nodes) != 1) + ereport(ERROR, (errmsg_internal("executor create found a traversal"))); + + cypher_target_node *cypher_node = (cypher_target_node *)linitial(path->target_nodes); + label_cache_data *lcd = search_label_name_graph_cache(AG_DEFAULT_LABEL_VERTEX, css->graph_oid); + + Relation rel = table_open(lcd->relation, RowExclusiveLock); + cypher_node->resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(cypher_node->resultRelInfo, rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + + // Open all indexes for the relation + ExecOpenIndices(cypher_node->resultRelInfo, false); + + // Setup the relation's tuple slot + cypher_node->elemTupleSlot = table_slot_create(rel, &estate->es_tupleTable); + + Assert(cypher_node->id_expr); + cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); + if (estate->es_output_cid == 0) estate->es_output_cid = estate->es_snapshot->curcid; @@ -238,57 +228,51 @@ static void process_pattern(cypher_create_custom_scan_state *css) } } -static TupleTableSlot *exec_cypher_create(CustomScanState *node) +static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) { - cypher_create_custom_scan_state *css = - (cypher_create_custom_scan_state *)node; + cypher_create_custom_scan_state *css = (cypher_create_custom_scan_state *)csnode; + EState *estate = css->css.ss.ps.state; ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *slot; - bool terminal = CYPHER_CLAUSE_IS_TERMINAL(css->flags); - bool used = false; - /* - * If the CREATE clause was the final cypher clause written then we aren't - * returning anything from this result node. So the exec_cypher_create - * function will only be called once. Therefore we will process all tuples - * from the subtree at once. - */ - do - { - /*Process the subtree first */ - Decrement_Estate_CommandId(estate) - slot = ExecProcNode(node->ss.ps.lefttree); - Increment_Estate_CommandId(estate) - /* break when there are no tuples */ - if (TupIsNull(slot)) - break; - - /* setup the scantuple that the process_pattern needs */ - econtext->ecxt_scantuple = - node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - process_pattern(css); - /* - * This may not be necessary. If we have an empty pattern, nothing was - * inserted and the current command Id was not used. So, only flag it - * if there is a non empty pattern. - */ - if (list_length(css->pattern) > 0) - { - /* the current command Id has been used */ - used = true; - } - } while (terminal); + TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; + + if (list_length(css->pattern) != 1) + ereport(ERROR, (errmsg_internal("executor create found a multi pattern"))); - if (!used) - return NULL; + cypher_create_path *path = linitial(css->pattern); - /* if this was a terminal CREATE just return NULL */ - if (terminal) - return NULL; + if (list_length(path->target_nodes) != 1) + ereport(ERROR, (errmsg_internal("executor create found a traversal"))); - econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); - return ExecProject(node->ss.ps.ps_ProjInfo); + cypher_target_node *node = (cypher_target_node *)linitial(path->target_nodes); + ResultRelInfo *resultRelInfo = node->resultRelInfo; + TupleTableSlot *elemTupleSlot = node->elemTupleSlot; + + ResultRelInfo **old_estate_es_result_relations_info = NULL; + + /* save the old result relation info */ + old_estate_es_result_relations_info = estate->es_result_relations; + + estate->es_result_relations = &resultRelInfo; + + ExecClearTuple(elemTupleSlot); + + // get the next graphid for this vertex. + bool isNull; + elemTupleSlot->tts_values[0] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + elemTupleSlot->tts_isnull[0] = isNull; + + // get the properties for this vertex + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + + /* restore the old result relation info */ + estate->es_result_relations = old_estate_es_result_relations_info; + + return NULL; } static void end_cypher_create(CustomScanState *node) @@ -306,18 +290,11 @@ static void end_cypher_create(CustomScanState *node) ListCell *lc2; foreach (lc2, path->target_nodes) { - cypher_target_node *cypher_node = - (cypher_target_node *)lfirst(lc2); - - if (!CYPHER_TARGET_NODE_INSERT_ENTITY(cypher_node->flags)) - continue; + cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc2); - // close all indices for the node ExecCloseIndices(cypher_node->resultRelInfo); - // close the relation itself - table_close(cypher_node->resultRelInfo->ri_RelationDesc, - RowExclusiveLock); + table_close(cypher_node->resultRelInfo->ri_RelationDesc, RowExclusiveLock); } } } @@ -508,13 +485,13 @@ static Datum insert_vertex(cypher_create_custom_scan_state *css, // get the next graphid for this vertex. id = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - elemTupleSlot->tts_values[vertex_tuple_id] = id; - elemTupleSlot->tts_isnull[vertex_tuple_id] = isNull; + elemTupleSlot->tts_values[0] = id; + elemTupleSlot->tts_isnull[0] = isNull; // get the properties for this vertex - elemTupleSlot->tts_values[vertex_tuple_properties] = + elemTupleSlot->tts_values[1] = scanTupleSlot->tts_values[node->prop_attr_num]; - elemTupleSlot->tts_isnull[vertex_tuple_properties] = + elemTupleSlot->tts_isnull[1] = scanTupleSlot->tts_isnull[node->prop_attr_num]; // Insert the new vertex @@ -625,8 +602,7 @@ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, } // Insert the tuple normally - table_tuple_insert(resultRelInfo->ri_RelationDesc, elemTupleSlot, - GetCurrentCommandId(true), 0, NULL); + table_tuple_insert(resultRelInfo->ri_RelationDesc, elemTupleSlot, estate->es_output_cid, 0, NULL); // Insert index entries for the tuple if (resultRelInfo->ri_NumIndices > 0) diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index ff0020eb..a71c6ccf 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -101,7 +101,7 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte) fe = (FuncExpr *)te->expr; - if (is_oid_ag_func(fe->funcid, CREATE_CLAUSE_FUNCTION_NAME)) + if (is_oid_ag_func(fe->funcid, CREATE_CLAUSE_FUNCTION_NAME)) return CYPHER_CLAUSE_CREATE; else return CYPHER_CLAUSE_NONE; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index c2c1837c..8a1b6bb1 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -81,6 +81,11 @@ static ParseNamespaceItem *get_namespace_item(ParseState *pstate, RangeTblEntry static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); +static char *make_id_alias(char *var_name); +static char *make_property_alias(char *var_name); + +static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); + List * transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); @@ -114,11 +119,226 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause return result; } -static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause) { - ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); + + + + +static FuncExpr *make_write_clause_function_placeholder(char *function_name, Node *clause_information) { + StringInfo str = makeStringInfo(); + + outNode(str, clause_information); + + Const *c = makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false); + + Oid func_oid = get_ag_func_oid(function_name, 1, INTERNALOID); + + return makeFuncExpr(func_oid, GTYPEOID, list_make1(c), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} + +static char *make_id_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'i'; + str[2] = 'd'; + str[3] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; + str[i + 4] = '_'; + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\0'; + + return str; +} + + +static char *make_property_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'p'; + str[2] = 'r'; + str[3] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; + + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\0'; + + return str; +} + +static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { + ParseState *pstate = (ParseState *)cpstate; + + Oid func_oid = get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID); + + Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + + Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), + ObjectIdGetDatum(cpstate->graph_oid), false, true); + + Node * props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); + + List *args = list_make3(id, graph_oid_const, props); + + FuncExpr *func_expr = makeFuncExpr(func_oid, VERTEXOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + func_expr->location = -1; + + return (Node *)func_expr; +} + + +static Node * +make_int_placeholder(cypher_parsestate *cpstate) { + + Datum agt = integer_to_gtype(0); + + // typtypmod, typcollation, typlen, and typbyval of gtype are hard-coded. + Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, agt, false, false); + c->location = -1; + + return (Node *)c; +} +/* + * This function is similar to transformFromClause() that is called with a + * single RangeSubselect. + */ +static ParseNamespaceItem * +transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, cypher_clause *clause, Alias *alias, bool add_rte_to_query, Query *query) { + ParseState *pstate = (ParseState *)cpstate; + ParseExprKind old_expr_kind = pstate->p_expr_kind; + bool lateral = pstate->p_lateral_active; + + + if (!alias) + alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); + + ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); + + /* + * NOTE: skip namespace conflicts check if the rte will be the only + * RangeTblEntry in pstate + */ + if (list_length(pstate->p_rtable) > 1) { + List *namespace = NULL; + int rtindex = 0; + + rtindex = list_length(pstate->p_rtable); + + if (pnsi->p_rte != rt_fetch(rtindex, pstate->p_rtable)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rte must be last entry in p_rtable"))); + + namespace = list_make1(pnsi); + + checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + } + + //if (add_rte_to_query) + addNSItemToQuery(pstate, pnsi, true, false, true); + + return pnsi; } +#include "rewrite/rewriteHandler.h" +static Expr *add_volatile_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, GTYPEOID); + + return (Expr *)makeFuncExpr(oid, GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} +static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause) { + ParseState *pstate = (ParseState *)cpstate; + cypher_create *self = (cypher_create *)clause->self; + + Query *query = makeNode(Query); + query->commandType = CMD_SELECT; + query->targetList = NIL; + + if (clause->prev != NULL) + ereport(ERROR, (errmsg_internal("CREATE doesn't work with previous clauses"))); + + if (clause->next) + ereport(ERROR, (errmsg_internal("CREATE doesn't work with next clauses"))); + + if (list_length(self->pattern) != 1) + ereport(ERROR, (errmsg_internal("CREATE doesn't work with patterns"))); + cypher_create_target_nodes *target_nodes; + target_nodes = make_ag_node(cypher_create_target_nodes); + target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE; + target_nodes->graph_oid = cpstate->graph_oid; + + + + ListCell *lc; + foreach (lc, self->pattern) { + cypher_path *path = lfirst(lc); + cypher_create_path *ccp = make_ag_node(cypher_create_path); + + if (list_length(path->path) != 1) + ereport(ERROR, (errmsg_internal("CREATE doesn't work with paths"))); + + if (path->var_name) + ereport(ERROR, (errmsg_internal("CREATE doesn't work with traversals"))); + + cypher_node *node = (cypher_node *)linitial(path->path); + + if (node->label) + ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have labels"))); + + if (node->name) + ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have variable names"))); + + if (node->props) + ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have properties"))); + + cypher_target_node *target = make_ag_node(cypher_target_node); + + label_cache_data *lcd = search_label_name_graph_cache(AG_DEFAULT_LABEL_VERTEX, cpstate->graph_oid); + + target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); + + TargetEntry *te = makeTargetEntry(make_int_placeholder(cpstate), pstate->p_next_resno++, make_id_alias(get_next_default_alias(cpstate)), false); + + ccp->target_nodes = list_make1(target); + target_nodes->paths = list_make1(ccp); + query->targetList = list_make1(te); + + } + + // Function for the set_rel_pathlist to capture + FuncExpr *func_expr = make_write_clause_function_placeholder(CREATE_CLAUSE_FUNCTION_NAME, target_nodes); + TargetEntry *te = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, "_create_clause", false); + query->targetList = lappend(query->targetList, te); + + query->rtable = pstate->p_rtable; + query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + + { + Query *topquery; + cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); + topquery = makeNode(Query); + topquery->commandType = CMD_SELECT; + topquery->targetList = NIL; + + ParseState *pstate = (ParseState *) cpstate; + int rtindex; + + ParseNamespaceItem *pnsi = transform_cypher_clause_as_subquery_2(new_cpstate, clause, NULL, false, query); + topquery->rtable = new_cpstate->pstate.p_rtable; + topquery->jointree = makeFromExpr(new_cpstate->pstate.p_joinlist, NULL); + + return topquery; + } +} Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause) { ParseState *pstate = (ParseState *)cpstate; @@ -395,11 +615,6 @@ static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, *res_colvars = list_concat(*res_colvars, colvars); } -/* - * transform_cypher_match_clause - * Transform the previous clauses and OPTIONAL MATCH clauses to be LATERAL LEFT JOIN - * transform_cypher_match_clause to construct a result value. - */ static RangeTblEntry *transform_cypher_match_clause(cypher_parsestate *cpstate, cypher_clause *clause) { cypher_clause *prevclause; RangeTblEntry *l_rte, *r_rte; @@ -518,12 +733,122 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li Expr *expr = NULL; + + if (list_length(pattern) !=1 ) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH found more than one pattern"))); + foreach (lc, pattern) { List *qual = NULL; cypher_path *path = (cypher_path *) lfirst(lc); // TODO: implement the new match logic //qual = transform_match_path(cpstate, query, path); + if (list_length(path->path) != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH paths only support 1 vertex"))); + + cypher_node *node = linitial(path->path); + + if (node->name) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH variable names are not supported"))); + else + node->name = get_next_default_alias(cpstate); + + bool is_default_label = true; + if (node->label) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH labels are not supported"))); + else + node->label = AG_DEFAULT_LABEL_VERTEX; + + char *schema_name; + char *rel_name; + int resno; + TargetEntry *te; + Expr *expr; + ParseNamespaceItem *pnsi; + RangeVar *label_range_var; + Alias *alias; + + schema_name = get_graph_namespace_name(cpstate->graph_name); + + // XXX: LTree Labeling Project, first code is here + Datum label_lquery; + if (is_default_label) + label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(node->label)); + else + label_lquery = DirectFunctionCall2(ltree_addltree, + DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), + DirectFunctionCall1(ltree_in, CStringGetDatum(node->label))); + + int ltq_query_args[2]; + ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + + Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); + + ScanKeyData scan_keys[1]; + ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); + + Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); + SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); + + HeapTuple tuple = systable_getnext(scan_desc); + + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("not found %s", node->label))); + + bool is_null; + rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); + + + systable_endscan(scan_desc); + table_close(label_catalog, AccessShareLock); + + + //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); + label_range_var = makeRangeVar(schema_name, rel_name, -1); + alias = makeAlias(node->name, NIL); + + pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); + Assert(pnsi != NULL); + + addNSItemToQuery(pstate, pnsi, true, true, true); + + resno = pstate->p_next_resno++; + + // XXX: End LTree Code here + + expr = (Expr *)make_vertex_expr(cpstate, pnsi); + + // make target entry and add it + te = makeTargetEntry(expr, resno, node->name, false); + query->targetList = lappend(query->targetList, te); + + // id field + Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + resno = pstate->p_next_resno++; + + te = makeTargetEntry(id, resno, make_id_alias(node->name), false); + query->targetList = lappend(query->targetList, te); + + /* + * properties field + */ + Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); + resno = pstate->p_next_resno++; + + te = makeTargetEntry(props, resno, make_property_alias(node->name), false); + query->targetList = lappend(query->targetList, te); + + quals = list_concat(quals, NULL); } @@ -596,8 +921,6 @@ static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte) return targetlist; } - - /* * This function is similar to transformFromClause() that is called with a * single RangeSubselect. @@ -632,7 +955,7 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method * needs to see what comes before it. */ Query *query = analyze_cypher_clause(transform, clause, cpstate); - + pstate->p_expr_kind = old_expr_kind; if (!alias) diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h index b40414fa..4163e30b 100644 --- a/src/include/catalog/ag_label.h +++ b/src/include/catalog/ag_label.h @@ -49,7 +49,7 @@ #define Anum_ag_label_id 3 #define Anum_ag_label_kind 4 #define Anum_ag_label_relation 5 -#define Anum_ag_label_label_path 6 +#define Anum_ag_label_label_path 7 #define Natts_ag_label 6 @@ -80,8 +80,6 @@ bool label_id_exists(Oid graph_oid, int32 label_id); RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, char *label_name); -List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid); - #define label_exists(label_name, label_graph) \ OidIsValid(get_label_id(label_name, label_graph)) From 391f621c60143745458728b2653098aa0be2a5ce Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sun, 13 Jul 2025 22:32:27 -0500 Subject: [PATCH 15/56] Support CREATE VLABEL Command with no hierarchy --- postgraph--0.1.0.sql | 3 +- regress/expected/cypher_create.out | 23 +++++++++- regress/sql/cypher_create.sql | 3 ++ src/backend/commands/label_commands.c | 62 ++++++--------------------- src/backend/nodes/ag_nodes.c | 6 ++- src/backend/nodes/cypher_outfuncs.c | 8 ++++ src/backend/nodes/cypher_readfuncs.c | 8 ++++ src/backend/parser/cypher_analyze.c | 48 +++++++++++++++++++++ src/backend/parser/cypher_clause.c | 7 +-- src/backend/parser/cypher_gram.y | 16 ++++++- src/include/nodes/ag_nodes.h | 3 +- src/include/nodes/cypher_nodes.h | 7 +++ src/include/nodes/cypher_outfuncs.h | 2 + src/include/nodes/cypher_readfuncs.h | 2 + src/include/parser/cypher_kwlist.h | 1 + 15 files changed, 137 insertions(+), 62 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index b7c05510..a9b8f97c 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -138,8 +138,9 @@ RETURNS void LANGUAGE c AS 'MODULE_PATHNAME'; -CREATE FUNCTION create_vlabel(graph_name name, label_name name) +CREATE FUNCTION create_vlabel(graph_name text, label_name text) RETURNS void +RETURNS NULL ON NULL INPUT LANGUAGE c AS 'MODULE_PATHNAME'; diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 3b10396a..4fc944eb 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -48,6 +48,25 @@ MATCH () RETURN 1; 1 (1 row) +CREATE VLABEL test; +NOTICE: VLabel "test" has been created + create_vlabel +--------------- + +(1 row) + +SELECT * FROM postgraph.ag_label; + name | graph | id | kind | relation | vertex_adjlist | label_path +------------------+-------+----+------+--------------------------------+-------------------------------------+----------------------- + _ag_label_vertex | 18780 | 1 | v | 18783 | 18792 | _ag_label_vertex + _ag_label_edge | 18780 | 2 | e | 18796 | | _ag_label_edge + _ag_label_vertex | 18805 | 1 | v | 18808 | 18817 | _ag_label_vertex + _ag_label_edge | 18805 | 2 | e | 18821 | | _ag_label_edge + _ag_label_vertex | 18833 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex + _ag_label_edge | 18833 | 2 | e | cypher_create._ag_label_edge | | _ag_label_edge + test | 18833 | 3 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test +(7 rows) + CREATE ()-[]->(); ERROR: CREATE doesn't work with paths CYPHER WITH 1 as a @@ -59,10 +78,12 @@ ERROR: CREATE doesn't work with next clauses -- Clean up -- DROP GRAPH cypher_create CASCADE; -NOTICE: drop cascades to 3 other objects +NOTICE: drop cascades to 5 other objects DETAIL: drop cascades to table cypher_create._ag_label_vertex drop cascades to table cypher_create._adj__ag_label_vertex drop cascades to table cypher_create._ag_label_edge +drop cascades to table cypher_create.test +drop cascades to table cypher_create._adj_test NOTICE: graph "cypher_create" has been dropped drop_graph ------------ diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 478f4bfe..edfe199e 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -29,6 +29,9 @@ SELECT * FROM cypher_create._ag_label_vertex; MATCH () RETURN 1; +CREATE VLABEL test; +SELECT * FROM postgraph.ag_label; + CREATE ()-[]->(); CYPHER WITH 1 as a diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index 8e890bf4..c1f0fa29 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -97,21 +97,10 @@ static char *make_vertex_adjlist_alias(char *var_name); PG_FUNCTION_INFO_V1(create_vlabel); - -/* - * This is a callback function - * This function will be called when the user will call SELECT create_vlabel. - * The function takes two parameters - * 1. Graph name - * 2. Label Name - * Function will create a vertex label - * Function returns an error if graph or label names or not provided -*/ - Datum create_vlabel(PG_FUNCTION_ARGS) { char *graph; - Name graph_name; + text * graph_name; char *graph_name_str; Oid graph_oid; List *parent; @@ -119,59 +108,34 @@ Datum create_vlabel(PG_FUNCTION_ARGS) RangeVar *rv; char *label; - Name label_name; + text * label_name; char *label_name_str; - // checking if user has not provided the graph name - if (PG_ARGISNULL(0)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("graph name must not be NULL"))); - } - - // checking if user has not provided the label name - if (PG_ARGISNULL(1)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("label name must not be NULL"))); - } - - graph_name = PG_GETARG_NAME(0); - label_name = PG_GETARG_NAME(1); + graph_name = PG_GETARG_TEXT_PP(0); + label_name = PG_GETARG_TEXT_PP(1); - graph_name_str = NameStr(*graph_name); - label_name_str = NameStr(*label_name); + graph_name_str = text_to_cstring(graph_name); + label_name_str = text_to_cstring(label_name); // Check if graph does not exist if (!graph_exists(graph_name_str)) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("graph \"%s\" does not exist.", graph_name_str))); - } + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist.", graph_name_str))); graph_oid = get_graph_oid(graph_name_str); // Check if label with the input name already exists if (label_exists(label_name_str, graph_oid)) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("label \"%s\" already exists", label_name_str))); - } - - //Create the default label tables - graph = graph_name->data; - label = label_name->data; + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("label \"%s\" already exists", label_name_str))); - rv = get_label_range_var(graph, graph_oid, AG_DEFAULT_LABEL_VERTEX); + rv = get_label_range_var(graph_name_str, graph_oid, AG_DEFAULT_LABEL_VERTEX); parent = list_make1(rv); - create_label(graph, label, LABEL_TYPE_VERTEX, parent, NULL); + create_label(graph_name_str, label_name_str, LABEL_TYPE_VERTEX, parent, NULL); - ereport(NOTICE, - (errmsg("VLabel \"%s\" has been created", NameStr(*label_name)))); + ereport(NOTICE, (errmsg("VLabel \"%s\" has been created", label_name_str))); PG_RETURN_VOID(); } diff --git a/src/backend/nodes/ag_nodes.c b/src/backend/nodes/ag_nodes.c index 7754408c..1d24b825 100644 --- a/src/backend/nodes/ag_nodes.c +++ b/src/backend/nodes/ag_nodes.c @@ -67,7 +67,8 @@ const char *node_names[] = { "cypher_create_graph", "cypher_use_graph", "cypher_drop_graph", - "cypher_label_tree_node" + "cypher_label_tree_node", + "cypher_create_vlabel" }; /* @@ -137,7 +138,8 @@ const ExtensibleNodeMethods node_methods[] = { DEFINE_NODE_METHODS(cypher_create_graph), DEFINE_NODE_METHODS(cypher_use_graph), DEFINE_NODE_METHODS(cypher_drop_graph), - DEFINE_NODE_METHODS(cypher_label_tree_node) + DEFINE_NODE_METHODS(cypher_label_tree_node), + DEFINE_NODE_METHODS(cypher_create_vlabel) }; static bool equal_ag_node(const ExtensibleNode *a, const ExtensibleNode *b) diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c index a62ba7eb..e965fee7 100644 --- a/src/backend/nodes/cypher_outfuncs.c +++ b/src/backend/nodes/cypher_outfuncs.c @@ -468,6 +468,14 @@ void out_cypher_drop_graph(StringInfo str, const ExtensibleNode *node) WRITE_BOOL_FIELD(cascade); } +void out_cypher_create_vlabel(StringInfo str, const ExtensibleNode *node) +{ + DEFINE_AG_NODE(cypher_create_vlabel); + + WRITE_STRING_FIELD(label_name); +} + + /* * Copied from Postgres * diff --git a/src/backend/nodes/cypher_readfuncs.c b/src/backend/nodes/cypher_readfuncs.c index 22e7a94d..eefd266d 100644 --- a/src/backend/nodes/cypher_readfuncs.c +++ b/src/backend/nodes/cypher_readfuncs.c @@ -322,4 +322,12 @@ void read_cypher_drop_graph(struct ExtensibleNode *node) READ_STRING_FIELD(graph_name); READ_BOOL_FIELD(cascade); +} + + +void read_cypher_create_vlabel(struct ExtensibleNode *node) +{ + READ_LOCALS(cypher_create_vlabel); + + READ_STRING_FIELD(label_name); } \ No newline at end of file diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index e131b317..c3eba450 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -78,6 +78,9 @@ static FuncExpr *make_clause_create_graph_func_expr(char *graph_name); static Query *cypher_create_graph_utility(ParseState *pstate, const char *graph_name); static FuncExpr *make_clause_use_graph_func_expr(char *graph_name); static Query *cypher_use_graph_utility(ParseState *pstate, const char *graph_name); +static FuncExpr *make_clause_create_vlabel_func_expr(char *label_name); +static Query *cypher_create_vlabel_utility(ParseState *pstate, const char *label_name); + static Oid get_session_graph_oid(); static CommandTag CypherCreateCommandTag(Node *parsetree); @@ -1223,6 +1226,10 @@ cypher_parse_analyze(RawStmt *parseTree, const char *sourceText, cypher_drop_graph *ccg = n; query = cypher_drop_graph_utility(pstate, ccg->graph_name); + } else if (is_ag_node(n, cypher_create_vlabel)) { + cypher_create_vlabel *ccg = n; + + query = cypher_create_vlabel_utility(pstate, ccg->label_name); } else { query = parse_analyze((makeRawStmt(n, 0)), sourceText, paramTypes, numParams, queryEnv); @@ -1452,6 +1459,47 @@ cypher_use_graph_utility(ParseState *pstate, const char *graph_name) { return query; } + + +/* + * Creates the function expression that represents the clause. Adds the + * extensible node that represents the metadata that the clause needs to + * handle the clause in the execution phase. + */ +static FuncExpr *make_clause_create_vlabel_func_expr(char *label_name) { + + graph_cache_data *gcd = search_graph_namespace_cache(get_session_graph_oid()); + + Const *c = makeConst(TEXTOID, -1, InvalidOid, strlen(label_name), CStringGetTextDatum(label_name), false, false); + Const *c1 = makeConst(TEXTOID, -1, InvalidOid, strlen(gcd->name.data), CStringGetTextDatum(gcd->name.data), false, false); + + + Oid func_oid = get_ag_func_oid("create_vlabel", 2, TEXTOID, TEXTOID); + + return makeFuncExpr(func_oid, VOIDOID, list_make2(c1, c), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} + +static Query * +cypher_create_vlabel_utility(ParseState *pstate, const char *label_name) { + Query *query; + TargetEntry *tle; + FuncExpr *func_expr; + + query = makeNode(Query); + query->commandType = CMD_SELECT; + query->targetList = NIL; + + func_expr = make_clause_create_vlabel_func_expr(label_name); + + // Create the target entry + tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, "create_vlabel", false); + query->targetList = lappend(query->targetList, tle); + + query->rtable = pstate->p_rtable; + query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + return query; +} + Oid get_session_graph_oid() { ScanKeyData scan_keys[1]; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 8a1b6bb1..8c02ed9e 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -808,11 +808,9 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li bool is_null; rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); - systable_endscan(scan_desc); table_close(label_catalog, AccessShareLock); - //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); label_range_var = makeRangeVar(schema_name, rel_name, -1); alias = makeAlias(node->name, NIL); @@ -823,7 +821,6 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li addNSItemToQuery(pstate, pnsi, true, true, true); resno = pstate->p_next_resno++; - // XXX: End LTree Code here expr = (Expr *)make_vertex_expr(cpstate, pnsi); @@ -839,9 +836,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li te = makeTargetEntry(id, resno, make_id_alias(node->name), false); query->targetList = lappend(query->targetList, te); - /* - * properties field - */ + // properties field Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); resno = pstate->p_next_resno++; diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index ddf92009..8329d501 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -291,7 +291,7 @@ makeSimpleCypherA_Expr(A_Expr_Kind kind, char *name, UNLISTEN UNLOGGED UNTIL UNWIND UPDATE USE USER USING VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING - VERBOSE VERSION_P VIEW VIEWS VOLATILE + VERBOSE VERSION_P VIEW VIEWS VLABEL VOLATILE WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE @@ -345,7 +345,7 @@ makeSimpleCypherA_Expr(A_Expr_Kind kind, char *name, CreateMatViewStmt RefreshMatViewStmt CreateAmStmt CreatePublicationStmt AlterPublicationStmt CreateSubscriptionStmt AlterSubscriptionStmt DropSubscriptionStmt - CreateGraphStmt UseGraphStmt DropGraphStmt + CreateGraphStmt CreateVLabelStmt UseGraphStmt DropGraphStmt %type select_no_parens select_with_parens select_clause simple_select @@ -936,6 +936,7 @@ toplevel_stmt: stmt: CYPHER cypher_stmt { $$ = (Node *)$2; } | CreateGraphStmt + | CreateVLabelStmt | DropGraphStmt | UseGraphStmt | AlterEventTrigStmt @@ -11431,7 +11432,18 @@ CreateGraphStmt: $$ = (Node *)n; } ; + +CreateVLabelStmt: + CREATE VLABEL BareColLabel + { + + cypher_create_vlabel *n; + n = make_ag_node(cypher_create_vlabel); + n->label_name = $3; + $$ = (Node *)n; + } + ; /***************************************************************************** * diff --git a/src/include/nodes/ag_nodes.h b/src/include/nodes/ag_nodes.h index fab6996a..9a1bc3e1 100644 --- a/src/include/nodes/ag_nodes.h +++ b/src/include/nodes/ag_nodes.h @@ -81,7 +81,8 @@ typedef enum ag_node_tag cypher_use_graph_t, // DROP GRAPH ident cypher_drop_graph_t, - cypher_label_tree_node_t + cypher_label_tree_node_t, + cypher_create_vlabel_t } ag_node_tag; void register_ag_nodes(void); diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index a72d5952..f7acc94d 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -222,6 +222,7 @@ typedef struct cypher_create_graph char *graph_name; } cypher_create_graph; + typedef struct cypher_use_graph { ExtensibleNode extensible; @@ -235,6 +236,12 @@ typedef struct cypher_drop_graph bool cascade; } cypher_drop_graph; +typedef struct cypher_create_vlabel +{ + ExtensibleNode extensible; + char *label_name; +} cypher_create_vlabel; + /* * expression */ diff --git a/src/include/nodes/cypher_outfuncs.h b/src/include/nodes/cypher_outfuncs.h index e4e101e2..beb6ce0b 100644 --- a/src/include/nodes/cypher_outfuncs.h +++ b/src/include/nodes/cypher_outfuncs.h @@ -85,6 +85,8 @@ void out_cypher_create_graph(StringInfo str, const ExtensibleNode *node); void out_cypher_use_graph(StringInfo str, const ExtensibleNode *node); void out_cypher_drop_graph(StringInfo str, const ExtensibleNode *node); +void out_cypher_create_vlabel(StringInfo str, const ExtensibleNode *node); + // internal label tree nodes void out_cypher_label_tree_node(StringInfo str, const ExtensibleNode *node); diff --git a/src/include/nodes/cypher_readfuncs.h b/src/include/nodes/cypher_readfuncs.h index 305bf072..910509d9 100644 --- a/src/include/nodes/cypher_readfuncs.h +++ b/src/include/nodes/cypher_readfuncs.h @@ -58,4 +58,6 @@ void read_cypher_create_graph(struct ExtensibleNode *node); void read_cypher_use_graph(struct ExtensibleNode *node); void read_cypher_drop_graph(struct ExtensibleNode *node); +void read_cypher_create_vlabel(struct ExtensibleNode *node); + #endif diff --git a/src/include/parser/cypher_kwlist.h b/src/include/parser/cypher_kwlist.h index 5553dc4c..f81db593 100644 --- a/src/include/parser/cypher_kwlist.h +++ b/src/include/parser/cypher_kwlist.h @@ -441,6 +441,7 @@ PG_KEYWORD("verbose", VERBOSE, RESERVED_KEYWORD) PG_KEYWORD("version", VERSION_P, RESERVED_KEYWORD) PG_KEYWORD("view", VIEW, RESERVED_KEYWORD) PG_KEYWORD("views", VIEWS, RESERVED_KEYWORD) +PG_KEYWORD("vlabel", VLABEL, RESERVED_KEYWORD) PG_KEYWORD("volatile", VOLATILE, RESERVED_KEYWORD) PG_KEYWORD("when", WHEN, RESERVED_KEYWORD) PG_KEYWORD("where", WHERE, RESERVED_KEYWORD) From fe35cf9ecd1ec2178cc5cf32073f26013f71f564 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sun, 13 Jul 2025 23:04:26 -0500 Subject: [PATCH 16/56] Support Labels for vertices in CREATE clause --- regress/sql/cypher_create.sql | 12 +++++++++++ src/backend/executor/cypher_create.c | 5 ++--- src/backend/parser/cypher_clause.c | 32 ++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index edfe199e..29d7a08b 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -32,6 +32,18 @@ MATCH () RETURN 1; CREATE VLABEL test; SELECT * FROM postgraph.ag_label; +CREATE (:test); +SELECT * FROM postgraph.ag_label; +SELECT * FROM cypher_create.test; + + +CREATE (:test2); +SELECT * FROM postgraph.ag_label; +SELECT * FROM cypher_create.test2; + + +MATCH () RETURN 1; + CREATE ()-[]->(); CYPHER WITH 1 as a diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index a4fd573a..3ae907bd 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -165,9 +165,8 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag ereport(ERROR, (errmsg_internal("executor create found a traversal"))); cypher_target_node *cypher_node = (cypher_target_node *)linitial(path->target_nodes); - label_cache_data *lcd = search_label_name_graph_cache(AG_DEFAULT_LABEL_VERTEX, css->graph_oid); - - Relation rel = table_open(lcd->relation, RowExclusiveLock); + + Relation rel = table_open(cypher_node->relid, RowExclusiveLock); cypher_node->resultRelInfo = makeNode(ResultRelInfo); InitResultRelInfo(cypher_node->resultRelInfo, rel, list_length(estate->es_range_table), NULL, estate->es_instrument); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 8c02ed9e..54aab7fc 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -253,6 +253,27 @@ static Expr *add_volatile_wrapper(Expr *node) { return (Expr *)makeFuncExpr(oid, GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } + +static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *node) { + ParseState *pstate = (ParseState *)cpstate; + label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); + + if (lcd && lcd->kind != LABEL_KIND_VERTEX) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("label %s is for edges, not vertices", node->label), + parser_errposition(pstate, node->location))); + } else if (!lcd) { + List *parent; + RangeVar *rv; + + rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_VERTEX); + + parent = list_make1(rv); + + create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, parent, NULL); + } +} + static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause) { ParseState *pstate = (ParseState *)cpstate; cypher_create *self = (cypher_create *)clause->self; @@ -291,8 +312,10 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause cypher_node *node = (cypher_node *)linitial(path->path); - if (node->label) - ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have labels"))); + if (node->label) + validate_or_create_vlabel(cpstate, node); + else + node->label = AG_DEFAULT_LABEL_VERTEX; if (node->name) ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have variable names"))); @@ -302,10 +325,11 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause cypher_target_node *target = make_ag_node(cypher_target_node); - label_cache_data *lcd = search_label_name_graph_cache(AG_DEFAULT_LABEL_VERTEX, cpstate->graph_oid); + label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); - + target->relid = lcd->relation; + TargetEntry *te = makeTargetEntry(make_int_placeholder(cpstate), pstate->p_next_resno++, make_id_alias(get_next_default_alias(cpstate)), false); ccp->target_nodes = list_make1(target); From d9cbfbb5235b3f6885705e8a627dd4a7d451afd6 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Mon, 14 Jul 2025 20:02:40 -0500 Subject: [PATCH 17/56] Get setup for adjacency list --- src/backend/executor/cypher_create.c | 391 ++++++++++----------------- src/backend/nodes/cypher_copyfuncs.c | 5 + src/backend/nodes/cypher_outfuncs.c | 3 + src/backend/nodes/cypher_readfuncs.c | 3 + src/backend/parser/cypher_clause.c | 77 ++++-- src/include/nodes/cypher_nodes.h | 5 + 6 files changed, 211 insertions(+), 273 deletions(-) diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 3ae907bd..3a8e6cc2 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -63,7 +63,10 @@ typedef struct cypher_create_custom_scan_state List *path_values; uint32 flags; TupleTableSlot *slot; + TupleTableSlot *slot_adj_relid; Oid graph_oid; + graphid **vertex_ids; + graphid **edge_ids; } cypher_create_custom_scan_state; static HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, @@ -79,7 +82,7 @@ static void insert_edge(cypher_create_custom_scan_state *css, cypher_target_node *node, Datum prev_vertex_id, ListCell *next, List *list); -static Datum insert_vertex(cypher_create_custom_scan_state *css, +static void insert_vertex(cypher_create_custom_scan_state *css, cypher_target_node *node, ListCell *next, List *list); static void process_pattern(cypher_create_custom_scan_state *css); @@ -164,21 +167,47 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag if (list_length(path->target_nodes) != 1) ereport(ERROR, (errmsg_internal("executor create found a traversal"))); - cypher_target_node *cypher_node = (cypher_target_node *)linitial(path->target_nodes); - - Relation rel = table_open(cypher_node->relid, RowExclusiveLock); - cypher_node->resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, rel, list_length(estate->es_range_table), NULL, estate->es_instrument); - - // Open all indexes for the relation - ExecOpenIndices(cypher_node->resultRelInfo, false); - - // Setup the relation's tuple slot - cypher_node->elemTupleSlot = table_slot_create(rel, &estate->es_tupleTable); + + css->vertex_ids = palloc(sizeof(graphid *) * list_length(css->pattern)); + css->vertex_ids[0] = palloc(sizeof(graphid) * ((list_length(path->target_nodes)/ 2) + 1) ); + + if (path->target_nodes) { + css->edge_ids = palloc(sizeof(graphid *) * list_length(css->pattern)); + css->edge_ids[0] = palloc(sizeof(graphid) * ((list_length(path->target_nodes)/ 2) ) ); + } + + ListCell *lc1; + foreach (lc1, path->target_nodes) { + cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc1); + + Relation rel = table_open(cypher_node->relid, RowExclusiveLock); + cypher_node->resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(cypher_node->resultRelInfo, rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + + // Open all indexes for the relation + ExecOpenIndices(cypher_node->resultRelInfo, false); - Assert(cypher_node->id_expr); - cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); + + // Setup the relation's tuple slot + cypher_node->elemTupleSlot = table_slot_create(rel, &estate->es_tupleTable); + + + Relation adj_rel = table_open(cypher_node->adj_relid, RowExclusiveLock); + cypher_node->adj_resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(cypher_node->resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + + // Open all indexes for the relation + //ExecOpenIndices(cypher_node->adj_resultRelInfo, false); + + // Setup the relation's tuple slot + cypher_node->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); + + + Assert(cypher_node->id_expr); + cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); + } + if (estate->es_output_cid == 0) estate->es_output_cid = estate->es_snapshot->curcid; @@ -241,35 +270,96 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) cypher_create_path *path = linitial(css->pattern); + ListCell *lc; + int i = 1; + foreach(lc, path->target_nodes) { + cypher_target_node *node = (cypher_target_node *)linitial(path->target_nodes); + bool isNull; + if(i % 2 == 1) + css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + else + css->edge_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + + } if (list_length(path->target_nodes) != 1) ereport(ERROR, (errmsg_internal("executor create found a traversal"))); - cypher_target_node *node = (cypher_target_node *)linitial(path->target_nodes); - ResultRelInfo *resultRelInfo = node->resultRelInfo; - TupleTableSlot *elemTupleSlot = node->elemTupleSlot; + for (int i = 0; i < 1 + (list_length(path->target_nodes)/2); i++) { + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i /2); + ResultRelInfo *resultRelInfo = node->resultRelInfo; + TupleTableSlot *elemTupleSlot = node->elemTupleSlot; - ResultRelInfo **old_estate_es_result_relations_info = NULL; + ResultRelInfo **old_estate_es_result_relations_info = NULL; - /* save the old result relation info */ - old_estate_es_result_relations_info = estate->es_result_relations; + /* save the old result relation info */ + old_estate_es_result_relations_info = estate->es_result_relations; - estate->es_result_relations = &resultRelInfo; + estate->es_result_relations = &resultRelInfo; - ExecClearTuple(elemTupleSlot); + ExecClearTuple(elemTupleSlot); - // get the next graphid for this vertex. - bool isNull; - elemTupleSlot->tts_values[0] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - elemTupleSlot->tts_isnull[0] = isNull; + // get the next graphid for this vertex. + bool isNull; + elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; + elemTupleSlot->tts_isnull[0] = isNull; - // get the properties for this vertex - elemTupleSlot->tts_values[1] = NULL; - elemTupleSlot->tts_isnull[1] = true; - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + // get the properties for this vertex + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - /* restore the old result relation info */ - estate->es_result_relations = old_estate_es_result_relations_info; + /* restore the old result relation info */ + estate->es_result_relations = old_estate_es_result_relations_info; + } + + //TODO: the edges aren't setup to use this. I gotta get the correct relid in cypher_target_node. + // Then I gotta get any existing tuple, and update it if it exists, and insert if it doesn't + for (int i = 0; i < (list_length(path->target_nodes)/2); i++) { + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i /2); + ResultRelInfo *resultRelInfo = node->resultRelInfo; + TupleTableSlot *elemTupleSlot = node->elemTupleSlot; + + ResultRelInfo **old_estate_es_result_relations_info = NULL; + + /* save the old result relation info */ + old_estate_es_result_relations_info = estate->es_result_relations; + + estate->es_result_relations = &resultRelInfo; + + ExecClearTuple(elemTupleSlot); + + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; + + // get the properties for this vertex + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + + if (list_length(path->target_nodes) > 1) { + resultRelInfo = node->adj_resultRelInfo; + elemTupleSlot = node->adj_elemTupleSlot; + + estate->es_result_relations = &resultRelInfo; + + ExecClearTuple(elemTupleSlot); + + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; + + // get the properties for this vertex + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + } + /* restore the old result relation info */ + estate->es_result_relations = old_estate_es_result_relations_info; + } return NULL; } @@ -333,120 +423,12 @@ Node *create_cypher_create_plan_state(CustomScan *cscan) return (Node *)cypher_css; } -/* - * Create the edge entity. - */ -static void insert_edge(cypher_create_custom_scan_state *css, - cypher_target_node *node, Datum prev_vertex_id, - ListCell *next, List *list) -{ - bool isNull; - EState *estate = css->css.ss.ps.state; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - ResultRelInfo *resultRelInfo = node->resultRelInfo; - ResultRelInfo **old_estate_es_result_relations_info = NULL; - TupleTableSlot *elemTupleSlot = node->elemTupleSlot; - TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - Datum id; - Datum start_id, end_id, next_vertex_id; - List *prev_path = css->path_values; - - Assert(node->type == LABEL_KIND_EDGE); - Assert(lfirst(next) != NULL); - - /* - * Create the next vertex before creating the edge. We need the - * next vertex's id. - */ - css->path_values = NIL; - next_vertex_id = insert_vertex(css, lfirst(next), lnext(list, next), list); - - /* - * Set the start and end vertex ids - */ - if (node->dir == CYPHER_REL_DIR_RIGHT) { - start_id = prev_vertex_id; - end_id = next_vertex_id; - } else if (node->dir == CYPHER_REL_DIR_LEFT) { - start_id = next_vertex_id; - end_id = prev_vertex_id; - } else { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("edge direction must be specified in a CREATE clause"))); - } - - /* - * Set estate's result relation to the vertex's result - * relation. - * - * Note: This obliterates what was their previously - */ - - /* save the old result relation info */ - old_estate_es_result_relations_info = estate->es_result_relations; - - estate->es_result_relations = &resultRelInfo; - - ExecClearTuple(elemTupleSlot); - - // Graph Id for the edge - id = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - elemTupleSlot->tts_values[edge_tuple_id] = id; - elemTupleSlot->tts_isnull[edge_tuple_id] = isNull; - - // Graph id for the starting vertex - elemTupleSlot->tts_values[edge_tuple_start_id] = start_id; - elemTupleSlot->tts_isnull[edge_tuple_start_id] = false; - - // Graph id for the ending vertex - elemTupleSlot->tts_values[edge_tuple_end_id] = end_id; - elemTupleSlot->tts_isnull[edge_tuple_end_id] = false; - - // Edge's properties map - elemTupleSlot->tts_values[edge_tuple_properties] = - scanTupleSlot->tts_values[node->prop_attr_num]; - elemTupleSlot->tts_isnull[edge_tuple_properties] = - scanTupleSlot->tts_isnull[node->prop_attr_num]; - - // Insert the new edge - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - - /* restore the old result relation info */ - estate->es_result_relations = old_estate_es_result_relations_info; - - /* - * When the edge is used by clauses higher in the execution tree - * we need to create an edge datum. When the edge is a variable, - * add to the scantuple slot. When the edge is part of a path - * variable, add to the list. - */ - if (CYPHER_TARGET_NODE_OUTPUT(node->flags)) - { - PlanState *ps = css->css.ss.ps.lefttree; - TupleTableSlot *scantuple = ps->ps_ExprContext->ecxt_scantuple; - - edge *e = create_edge(id, start_id, end_id, css->graph_oid, DATUM_GET_GTYPE_P(scanTupleSlot->tts_values[node->prop_attr_num])); - - if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) - { - prev_path = lappend(prev_path, e); - css->path_values = list_concat(prev_path, css->path_values); - } - - if (CYPHER_TARGET_NODE_IS_VARIABLE(node->flags)) - { - scantuple->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(e); - scantuple->tts_isnull[node->tuple_position - 1] = false; - } - } -} /* * Creates the vertex entity, returns the vertex's id in case the caller is * the create_edge function. */ -static Datum insert_vertex(cypher_create_custom_scan_state *css, +static void insert_vertex(cypher_create_custom_scan_state *css, cypher_target_node *node, ListCell *next, List *list) { bool isNull; @@ -457,124 +439,33 @@ static Datum insert_vertex(cypher_create_custom_scan_state *css, TupleTableSlot *elemTupleSlot = node->elemTupleSlot; TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - Assert(node->type == LABEL_KIND_VERTEX); - /* - * Vertices in a patEDGE_GET_DATUMh might already exists. If they do get the id + * Vertices in a path might already exists. If they do get the id * to pass to the edges before and after it. Otherwise, insert the * new vertex into it's table and then pass the id along. */ - if (CYPHER_TARGET_NODE_INSERT_ENTITY(node->flags)) - { - ResultRelInfo **old_estate_es_result_relations_info = NULL; - - /* - * Set estate's result relation to the vertex's result - * relation. - * - * Note: This obliterates what was their previously - */ - - /* save the old result relation info */ - old_estate_es_result_relations_info = estate->es_result_relations; - - estate->es_result_relations = &resultRelInfo; - - ExecClearTuple(elemTupleSlot); - - // get the next graphid for this vertex. - id = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - elemTupleSlot->tts_values[0] = id; - elemTupleSlot->tts_isnull[0] = isNull; - - // get the properties for this vertex - elemTupleSlot->tts_values[1] = - scanTupleSlot->tts_values[node->prop_attr_num]; - elemTupleSlot->tts_isnull[1] = - scanTupleSlot->tts_isnull[node->prop_attr_num]; - - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - - /* restore the old result relation info */ - estate->es_result_relations = old_estate_es_result_relations_info; - - /* - * When the vertex is used by clauses higher in the execution tree - * we need to create a vertex datum. When the vertex is a variable, - * add to the scantuple slot. When the vertex is part of a path - * variable, add to the list. - */ - if (CYPHER_TARGET_NODE_OUTPUT(node->flags)) - { - TupleTableSlot *scantuple; - PlanState *ps; - Datum result; - - ps = css->css.ss.ps.lefttree; - scantuple = ps->ps_ExprContext->ecxt_scantuple; - - // make the vertex gtype - vertex *v = create_vertex(id, css->graph_oid, - DATUM_GET_GTYPE_P(scanTupleSlot->tts_values[node->prop_attr_num])); - // append to the path list - if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) - css->path_values = lappend(css->path_values, v); - - /* - * Put the vertex in the correct spot in the scantuple, so parent - * execution nodes can reference the newly created variable. - */ - if (CYPHER_TARGET_NODE_IS_VARIABLE(node->flags)) - { - scantuple->tts_values[node->tuple_position - 1] = VERTEX_GET_DATUM(v); - scantuple->tts_isnull[node->tuple_position - 1] = false; - } - } - } - else - { - TupleTableSlot *scantuple; - PlanState *ps; + ResultRelInfo **old_estate_es_result_relations_info = NULL; - ps = css->css.ss.ps.lefttree; - scantuple = ps->ps_ExprContext->ecxt_scantuple; + old_estate_es_result_relations_info = estate->es_result_relations; - vertex *v = DATUM_GET_VERTEX(scantuple->tts_values[node->tuple_position - 1]); - id = GRAPHID_GET_DATUM(*((int64 *)(&v->children[0]))); + estate->es_result_relations = &resultRelInfo; - /* - * Its possible the variable has already been deleted. There are two - * ways this can happen. One is the query explicitly deleted the - * variable, the is_deleted flag will catch that. However, it is - * possible the user deleted the vertex using another variable name. We - * need to scan the table to find the vertex's current status relative - * to this CREATE clause. If the variable was initially created in this - * clause, we can skip this check, because the transaction system - * guarantees that nothing can happen to that tuple, as far as we are - * concerned with at this time. - */ - if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) - { - if (!entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id))) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("vertex assigned to variable %s was deleted", - node->variable_name))); - } + ExecClearTuple(elemTupleSlot); - if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) - { - Datum vertex = scanTupleSlot->tts_values[node->tuple_position - 1]; - css->path_values = lappend(css->path_values, vertex); - } - } + // get the next graphid for this vertex. + id = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + elemTupleSlot->tts_values[0] = id; + elemTupleSlot->tts_isnull[0] = isNull; - // If the path continues, create the next edge, passing the vertex's id. - if (next != NULL) - insert_edge(css, lfirst(next), id, lnext(list, next), list); + // get the properties for this vertex + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - return id; + /* restore the old result relation info */ + estate->es_result_relations = old_estate_es_result_relations_info; } diff --git a/src/backend/nodes/cypher_copyfuncs.c b/src/backend/nodes/cypher_copyfuncs.c index 3bb46798..b8498383 100644 --- a/src/backend/nodes/cypher_copyfuncs.c +++ b/src/backend/nodes/cypher_copyfuncs.c @@ -102,6 +102,8 @@ void copy_cypher_target_node(ExtensibleNode *newnode, const ExtensibleNode *from COPY_SCALAR_FIELD(dir); COPY_SCALAR_FIELD(prop_attr_num); COPY_SCALAR_FIELD(relid); + COPY_SCALAR_FIELD(adj_relid); + COPY_SCALAR_FIELD(tuple_position); COPY_STRING_FIELD(label_name); @@ -113,6 +115,9 @@ void copy_cypher_target_node(ExtensibleNode *newnode, const ExtensibleNode *from COPY_NODE_FIELD(prop_expr_state); COPY_NODE_FIELD(resultRelInfo); COPY_NODE_FIELD(elemTupleSlot); + COPY_NODE_FIELD(adj_resultRelInfo); + COPY_NODE_FIELD(adj_elemTupleSlot); + } // copy function for cypher_update_information diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c index e965fee7..72a23ec3 100644 --- a/src/backend/nodes/cypher_outfuncs.c +++ b/src/backend/nodes/cypher_outfuncs.c @@ -373,6 +373,9 @@ void out_cypher_target_node(StringInfo str, const ExtensibleNode *node) WRITE_NODE_FIELD(resultRelInfo); WRITE_NODE_FIELD(elemTupleSlot); WRITE_OID_FIELD(relid); + WRITE_OID_FIELD(adj_relid); + WRITE_NODE_FIELD(adj_resultRelInfo); + WRITE_NODE_FIELD(adj_elemTupleSlot); WRITE_STRING_FIELD(label_name); WRITE_STRING_FIELD(variable_name); WRITE_INT32_FIELD(tuple_position); diff --git a/src/backend/nodes/cypher_readfuncs.c b/src/backend/nodes/cypher_readfuncs.c index eefd266d..2eec5b3f 100644 --- a/src/backend/nodes/cypher_readfuncs.c +++ b/src/backend/nodes/cypher_readfuncs.c @@ -222,6 +222,9 @@ void read_cypher_target_node(struct ExtensibleNode *node) READ_NODE_FIELD(resultRelInfo); READ_NODE_FIELD(elemTupleSlot); READ_OID_FIELD(relid); + READ_OID_FIELD(adj_relid); + READ_NODE_FIELD(adj_resultRelInfo); + READ_NODE_FIELD(adj_elemTupleSlot); READ_STRING_FIELD(label_name); READ_STRING_FIELD(variable_name); READ_INT_FIELD(tuple_position); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 54aab7fc..3f8ceda1 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -304,38 +304,69 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause cypher_path *path = lfirst(lc); cypher_create_path *ccp = make_ag_node(cypher_create_path); - if (list_length(path->path) != 1) - ereport(ERROR, (errmsg_internal("CREATE doesn't work with paths"))); - if (path->var_name) ereport(ERROR, (errmsg_internal("CREATE doesn't work with traversals"))); - cypher_node *node = (cypher_node *)linitial(path->path); + int i = 1; + ListCell *lc2; + foreach (lc2, path->path) { + if (i % 2 == 1) { + cypher_node *node = (cypher_node *)lfirst(lc2); - if (node->label) - validate_or_create_vlabel(cpstate, node); - else - node->label = AG_DEFAULT_LABEL_VERTEX; - - if (node->name) - ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have variable names"))); + if (node->label) + validate_or_create_vlabel(cpstate, node); + else + node->label = AG_DEFAULT_LABEL_VERTEX; + + if (node->name) + ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have variable names"))); - if (node->props) - ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have properties"))); + if (node->props) + ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have properties"))); - cypher_target_node *target = make_ag_node(cypher_target_node); - - label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); + cypher_target_node *target = make_ag_node(cypher_target_node); - target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); - target->relid = lcd->relation; - - TargetEntry *te = makeTargetEntry(make_int_placeholder(cpstate), pstate->p_next_resno++, make_id_alias(get_next_default_alias(cpstate)), false); + label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); + + target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); + target->relid = lcd->relation; + target->adj_relid = lcd->vertex_adjlist; + TargetEntry *te = makeTargetEntry(make_int_placeholder(cpstate), pstate->p_next_resno++, make_id_alias(get_next_default_alias(cpstate)), false); + + ccp->target_nodes = lappend(ccp->target_nodes, target); + + query->targetList = lappend(query->targetList, te); + } else { + cypher_relationship *edge = lfirst(lc2); + + if (edge->label) + ereport(ERROR, (errmsg_internal("edges in CREATE cannot have labels"))); + else + edge->label = AG_DEFAULT_LABEL_EDGE; - ccp->target_nodes = list_make1(target); - target_nodes->paths = list_make1(ccp); - query->targetList = list_make1(te); + if (edge->name) + ereport(ERROR, (errmsg_internal("edges in CREATE cannot have variable names"))); + + if (edge->props) + ereport(ERROR, (errmsg_internal("edges in CREATE cannot have properties"))); + + if (edge->dir != CYPHER_REL_DIR_RIGHT) + ereport(ERROR, (errmsg_internal("edges CREATE are right only right now"))); + + cypher_target_node *target = make_ag_node(cypher_target_node); + label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); + target->relid = lcd->relation; + + target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); + + ccp->target_nodes = lappend(ccp->target_nodes, target); + + } + + i++; + } + target_nodes->paths = lappend(target_nodes->paths, ccp); } // Function for the set_rel_pathlist to capture diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index f7acc94d..ca6dcef5 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -374,6 +374,11 @@ typedef struct cypher_target_node TupleTableSlot *elemTupleSlot; // relid that the label stores its entity Oid relid; + + Oid adj_relid; + ResultRelInfo *adj_resultRelInfo; + // elemTupleSlot used to insert the entity into its table + TupleTableSlot *adj_elemTupleSlot; // label this entity belongs to. char *label_name; // variable name for this entity From 532800eed6de2d4bd7800c4fdfad26041fb48a86 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Mon, 14 Jul 2025 20:04:15 -0500 Subject: [PATCH 18/56] merge changes --- Makefile | 1 + regress/sql/vertex_am.sql | 6 +- .../vertex_heap/vertex_heapam_handler.c | 1490 ++++++++--------- src/backend/parser/cypher_clause.c | 3 + 4 files changed, 685 insertions(+), 815 deletions(-) diff --git a/Makefile b/Makefile index 960b9d45..2e7c26b3 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ MODULE_big = postgraph OBJS = src/backend/postgraph.o \ src/backend/access/vertex_heap/vertex_heapam_handler.o \ + src/backend/access/vertex_heap/vertex_hash_search.o \ src/backend/catalog/ag_catalog.o \ src/backend/catalog/ag_graph.o \ src/backend/catalog/ag_label.o \ diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index c2ce6c0e..a4be8d94 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -19,9 +19,9 @@ LOAD 'postgraph'; - CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex_adjlist; + INSERT INTO vertex_am_tst (id, props) VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); @@ -41,4 +41,8 @@ UPDATE vertex_am_tst SET id = '2'::postgraph.graphid; INSERT INTO vertex_am_tst (id, props) VALUES ('4'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); + +INSERT INTO vertex_am_tst (id, props) +VALUES ('5'::postgraph.graphid, NULL); + SELECT * FROM vertex_am_tst; diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 2beeff3e..e5713efb 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -21,6 +21,7 @@ #include "postgres.h" #include "access/genam.h" +#include "access/hash.h" #include "access/heapam.h" #include "access/heaptoast.h" #include "access/multixact.h" @@ -49,6 +50,7 @@ #include "utils/builtins.h" #include "utils/rel.h" +#include "access/vertex.h" #include "utils/graphid.h" #include "utils/gtype.h" @@ -66,17 +68,89 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update); static bool heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode, LockWaitPolicy wait_policy, bool *have_tuple_lock); +uint32 +vertex_hash_init(Relation rel, double num_tuples, ForkNumber forkNum); +void +vertex_hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel); typedef struct vertex_hash_struct { graphid id; // hash key + ItemPointerData itemPointer; gtype *properties; + Page page; + ItemId itemId; + Buffer buffer; } vertex_hash_struct; static vertex_hash_struct *iterator = NULL; static HTAB *vertex_hash = NULL; static HASH_SEQ_STATUS scanStatus; + +#include "access/parallel.h" +/* + * Subroutine for heap_insert(). Prepares a tuple for insertion. This sets the + * tuple header fields and toasts the tuple if necessary. Returns a toasted + * version of the tuple if it was toasted, or the original tuple if not. Note + * that in any case, the header fields are also set in the original tuple. + */ +static HeapTuple +heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid, + CommandId cid, int options) +{ + /* + * To allow parallel inserts, we need to ensure that they are safe to be + * performed in workers. We have the infrastructure to allow parallel + * inserts in general except for the cases where inserts generate a new + * CommandId (eg. inserts into a table having a foreign key column). + */ + if (IsParallelWorker()) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TRANSACTION_STATE), + errmsg("cannot insert tuples in a parallel worker"))); + + tup->t_data->t_infomask &= ~(HEAP_XACT_MASK); + tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK); + tup->t_data->t_infomask |= HEAP_XMAX_INVALID; + HeapTupleHeaderSetXmin(tup->t_data, xid); + if (options & HEAP_INSERT_FROZEN) + HeapTupleHeaderSetXminFrozen(tup->t_data); + + HeapTupleHeaderSetCmin(tup->t_data, cid); + HeapTupleHeaderSetXmax(tup->t_data, 0); /* for cleanliness */ + tup->t_tableOid = RelationGetRelid(relation); + + /* + * If the new tuple is too big for storage or contains already toasted + * out-of-line attributes from some other relation, invoke the toaster. + */ + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_MATVIEW) + { + /* toast table entries should never be recursively toasted */ + Assert(!HeapTupleHasExternal(tup)); + return tup; + } + else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD) + return heap_toast_insert_or_update(relation, tup, NULL, options); + else + return tup; +} + + + + + + + + + + + + + + //BlackPink /* * * Given infomask/infomask2, compute the bits that must be saved in the @@ -1124,647 +1198,6 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock) } - -/* ---------------- - * heapgettup - fetch next heap tuple - * - * Initialize the scan if not already done; then advance to the next - * tuple as indicated by "dir"; return the next tuple in scan->rs_ctup, - * or set scan->rs_ctup.t_data = NULL if no more tuples. - * - * dir == NoMovementScanDirection means "re-fetch the tuple indicated - * by scan->rs_ctup". - * - * Note: the reason nkeys/key are passed separately, even though they are - * kept in the scan descriptor, is that the caller may not want us to check - * the scankeys. - * - * Note: when we fall off the end of the scan in either direction, we - * reset rs_inited. This means that a further request with the same - * scan direction will restart the scan, which is a bit odd, but a - * request with the opposite scan direction will start a fresh scan - * in the proper direction. The latter is required behavior for cursors, - * while the former case is generally undefined behavior in Postgres - * so we don't care too much. - * ---------------- - */ -static void -heapgettup(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key) -{ - HeapTuple tuple = &(scan->rs_ctup); - Snapshot snapshot = scan->rs_base.rs_snapshot; - bool backward = ScanDirectionIsBackward(dir); - BlockNumber page; - bool finished; - Page dp; - int lines; - OffsetNumber lineoff; - int linesleft; - ItemId lpp; - - /* - * calculate next starting lineoff, given scan direction - */ - if (ScanDirectionIsForward(dir)) - { - if (!scan->rs_inited) - { - /* - * return null immediately if relation is empty - */ - if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - if (scan->rs_base.rs_parallel != NULL) - { - ParallelBlockTableScanDesc pbscan = - (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; - ParallelBlockTableScanWorker pbscanwork = - scan->rs_parallelworkerdata; - table_block_parallelscan_startblock_init(scan->rs_base.rs_rd, pbscanwork, pbscan); - - page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, pbscanwork, pbscan); - - /* Other processes might have already finished the scan. */ - if (page == InvalidBlockNumber) { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - } - else - page = scan->rs_startblock; /* first page */ - - heapgetpage((TableScanDesc) scan, page); - - lineoff = FirstOffsetNumber; /* first offnum */ - scan->rs_inited = true; - } - else - { - /* continue from previously returned page/tuple */ - page = scan->rs_cblock; /* current page */ - lineoff = /* next offnum */ - OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self))); - } - - LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(snapshot, scan->rs_base.rs_rd, dp); - lines = PageGetMaxOffsetNumber(dp); - /* page and lineoff now reference the physically next tid */ - - linesleft = lines - lineoff + 1; - } - else if (backward) - { - /* backward parallel scan not supported */ - Assert(scan->rs_base.rs_parallel == NULL); - - if (!scan->rs_inited) - { - /* - * return null immediately if relation is empty - */ - if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - - /* - * Disable reporting to syncscan logic in a backwards scan; it's - * not very likely anyone else is doing the same thing at the same - * time, and much more likely that we'll just bollix things for - * forward scanners. - */ - scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; - - /* - * Start from last page of the scan. Ensure we take into account - * rs_numblocks if it's been adjusted by heap_setscanlimits(). - */ - if (scan->rs_numblocks != InvalidBlockNumber) - page = (scan->rs_startblock + scan->rs_numblocks - 1) % scan->rs_nblocks; - else if (scan->rs_startblock > 0) - page = scan->rs_startblock - 1; - else - page = scan->rs_nblocks - 1; - heapgetpage((TableScanDesc) scan, page); - } - else - { - /* continue from previously returned page/tuple */ - page = scan->rs_cblock; /* current page */ - } - - LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(snapshot, scan->rs_base.rs_rd, dp); - lines = PageGetMaxOffsetNumber(dp); - - if (!scan->rs_inited) - { - lineoff = lines; /* final offnum */ - scan->rs_inited = true; - } - else - { - /* - * The previous returned tuple may have been vacuumed since the - * previous scan when we use a non-MVCC snapshot, so we must - * re-establish the lineoff <= PageGetMaxOffsetNumber(dp) - * invariant - */ - lineoff = /* previous offnum */ - Min(lines, - OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)))); - } - /* page and lineoff now reference the physically previous tid */ - - linesleft = lineoff; - } - else - { - /* - * ``no movement'' scan direction: refetch prior tuple - */ - if (!scan->rs_inited) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - - page = ItemPointerGetBlockNumber(&(tuple->t_self)); - if (page != scan->rs_cblock) - heapgetpage((TableScanDesc) scan, page); - - /* Since the tuple was previously fetched, needn't lock page here */ - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(snapshot, scan->rs_base.rs_rd, dp); - lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self)); - lpp = PageGetItemId(dp, lineoff); - Assert(ItemIdIsNormal(lpp)); - - tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); - tuple->t_len = ItemIdGetLength(lpp); - - return; - } - - /* - * advance the scan until we find a qualifying tuple or run out of stuff - * to scan - */ - lpp = PageGetItemId(dp, lineoff); - for (;;) - { - /* - * Only continue scanning the page while we have lines left. - * - * Note that this protects us from accessing line pointers past - * PageGetMaxOffsetNumber(); both for forward scans when we resume the - * table scan, and for when we start scanning a new page. - */ - while (linesleft > 0) - { - if (ItemIdIsNormal(lpp)) - { - bool valid; - - tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); - tuple->t_len = ItemIdGetLength(lpp); - ItemPointerSet(&(tuple->t_self), page, lineoff); - - /* - * if current tuple qualifies, return it. - */ - valid = HeapTupleSatisfiesVisibility(tuple, - snapshot, - scan->rs_cbuf); - - HeapCheckForSerializableConflictOut(valid, scan->rs_base.rs_rd, - tuple, scan->rs_cbuf, - snapshot); - - if (valid && key != NULL) - HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd), - nkeys, key, valid); - - if (valid) - { - LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); - return; - } - } - - /* - * otherwise move to the next item on the page - */ - --linesleft; - if (backward) - { - --lpp; /* move back in this page's ItemId array */ - --lineoff; - } - else - { - ++lpp; /* move forward in this page's ItemId array */ - ++lineoff; - } - } - - /* - * if we get here, it means we've exhausted the items on this page and - * it's time to move to the next. - */ - LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); - - /* - * advance to next/prior page and detect end of scan - */ - if (backward) - { - finished = (page == scan->rs_startblock) || - (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); - if (page == 0) - page = scan->rs_nblocks; - page--; - } - else if (scan->rs_base.rs_parallel != NULL) - { - ParallelBlockTableScanDesc pbscan = - (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; - ParallelBlockTableScanWorker pbscanwork = - scan->rs_parallelworkerdata; - - page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, - pbscanwork, pbscan); - finished = (page == InvalidBlockNumber); - } - else - { - page++; - if (page >= scan->rs_nblocks) - page = 0; - finished = (page == scan->rs_startblock) || - (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); - - /* - * Report our new scan position for synchronization purposes. We - * don't do that when moving backwards, however. That would just - * mess up any other forward-moving scanners. - * - * Note: we do this before checking for end of scan so that the - * final state of the position hint is back at the start of the - * rel. That's not strictly necessary, but otherwise when you run - * the same query multiple times the starting position would shift - * a little bit backwards on every invocation, which is confusing. - * We don't guarantee any specific ordering in general, though. - */ - if (scan->rs_base.rs_flags & SO_ALLOW_SYNC) - ss_report_location(scan->rs_base.rs_rd, page); - } - - /* - * return NULL if we've exhausted all the pages - */ - if (finished) - { - if (BufferIsValid(scan->rs_cbuf)) - ReleaseBuffer(scan->rs_cbuf); - scan->rs_cbuf = InvalidBuffer; - scan->rs_cblock = InvalidBlockNumber; - tuple->t_data = NULL; - scan->rs_inited = false; - return; - } - - heapgetpage((TableScanDesc) scan, page); - - LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(snapshot, scan->rs_base.rs_rd, dp); - lines = PageGetMaxOffsetNumber((Page) dp); - linesleft = lines; - if (backward) - { - lineoff = lines; - lpp = PageGetItemId(dp, lines); - } - else - { - lineoff = FirstOffsetNumber; - lpp = PageGetItemId(dp, FirstOffsetNumber); - } - } -} - -/* ---------------- - * heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode - * - * Same API as heapgettup, but used in page-at-a-time mode - * - * The internal logic is much the same as heapgettup's too, but there are some - * differences: we do not take the buffer content lock (that only needs to - * happen inside heapgetpage), and we iterate through just the tuples listed - * in rs_vistuples[] rather than all tuples on the page. Notice that - * lineindex is 0-based, where the corresponding loop variable lineoff in - * heapgettup is 1-based. - * ---------------- - */ -static void -heapgettup_pagemode(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key) -{ - HeapTuple tuple = &(scan->rs_ctup); - bool backward = ScanDirectionIsBackward(dir); - BlockNumber page; - bool finished; - Page dp; - int lines; - int lineindex; - OffsetNumber lineoff; - int linesleft; - ItemId lpp; - - /* - * calculate next starting lineindex, given scan direction - */ - if (ScanDirectionIsForward(dir)) - { - if (!scan->rs_inited) - { - /* - * return null immediately if relation is empty - */ - if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - if (scan->rs_base.rs_parallel != NULL) - { - ParallelBlockTableScanDesc pbscan = - (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; - ParallelBlockTableScanWorker pbscanwork = - scan->rs_parallelworkerdata; - table_block_parallelscan_startblock_init(scan->rs_base.rs_rd, pbscanwork, pbscan); - - page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, - pbscanwork, pbscan); - - /* Other processes might have already finished the scan. */ - if (page == InvalidBlockNumber) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - } - else - page = scan->rs_startblock; /* first page */ - heapgetpage((TableScanDesc) scan, page); - lineindex = 0; - scan->rs_inited = true; - } - else - { - /* continue from previously returned page/tuple */ - page = scan->rs_cblock; /* current page */ - lineindex = scan->rs_cindex + 1; - } - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); - lines = scan->rs_ntuples; - /* page and lineindex now reference the next visible tid */ - - linesleft = lines - lineindex; - } - else if (backward) - { - /* backward parallel scan not supported */ - Assert(scan->rs_base.rs_parallel == NULL); - - if (!scan->rs_inited) - { - /* - * return null immediately if relation is empty - */ - if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - - /* - * Disable reporting to syncscan logic in a backwards scan; it's - * not very likely anyone else is doing the same thing at the same - * time, and much more likely that we'll just bollix things for - * forward scanners. - */ - scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; - - /* - * Start from last page of the scan. Ensure we take into account - * rs_numblocks if it's been adjusted by heap_setscanlimits(). - */ - if (scan->rs_numblocks != InvalidBlockNumber) - page = (scan->rs_startblock + scan->rs_numblocks - 1) % scan->rs_nblocks; - else if (scan->rs_startblock > 0) - page = scan->rs_startblock - 1; - else - page = scan->rs_nblocks - 1; - heapgetpage((TableScanDesc) scan, page); - } - else - { - /* continue from previously returned page/tuple */ - page = scan->rs_cblock; /* current page */ - } - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); - lines = scan->rs_ntuples; - - if (!scan->rs_inited) - { - lineindex = lines - 1; - scan->rs_inited = true; - } - else - { - lineindex = scan->rs_cindex - 1; - } - /* page and lineindex now reference the previous visible tid */ - - linesleft = lineindex + 1; - } - else - { - /* - * ``no movement'' scan direction: refetch prior tuple - */ - if (!scan->rs_inited) - { - Assert(!BufferIsValid(scan->rs_cbuf)); - tuple->t_data = NULL; - return; - } - - page = ItemPointerGetBlockNumber(&(tuple->t_self)); - if (page != scan->rs_cblock) - heapgetpage((TableScanDesc) scan, page); - - /* Since the tuple was previously fetched, needn't lock page here */ - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); - lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self)); - lpp = PageGetItemId(dp, lineoff); - Assert(ItemIdIsNormal(lpp)); - - tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); - tuple->t_len = ItemIdGetLength(lpp); - /* check that rs_cindex is in sync */ - Assert(scan->rs_cindex < scan->rs_ntuples); - Assert(lineoff == scan->rs_vistuples[scan->rs_cindex]); - - return; - } - - /* - * advance the scan until we find a qualifying tuple or run out of stuff - * to scan - */ - for (;;) - { - while (linesleft > 0) - { - lineoff = scan->rs_vistuples[lineindex]; - lpp = PageGetItemId(dp, lineoff); - Assert(ItemIdIsNormal(lpp)); - - tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); - tuple->t_len = ItemIdGetLength(lpp); - ItemPointerSet(&(tuple->t_self), page, lineoff); - - /* - * if current tuple qualifies, return it. - */ - if (key != NULL) - { - bool valid; - - HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd), - nkeys, key, valid); - if (valid) - { - scan->rs_cindex = lineindex; - return; - } - } - else - { - scan->rs_cindex = lineindex; - return; - } - - /* - * otherwise move to the next item on the page - */ - --linesleft; - if (backward) - --lineindex; - else - ++lineindex; - } - - /* - * if we get here, it means we've exhausted the items on this page and - * it's time to move to the next. - */ - if (backward) - { - finished = (page == scan->rs_startblock) || - (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); - if (page == 0) - page = scan->rs_nblocks; - page--; - } - else if (scan->rs_base.rs_parallel != NULL) - { - ParallelBlockTableScanDesc pbscan = - (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; - ParallelBlockTableScanWorker pbscanwork = - scan->rs_parallelworkerdata; - - page = table_block_parallelscan_nextpage(scan->rs_base.rs_rd, - pbscanwork, pbscan); - finished = (page == InvalidBlockNumber); - } - else - { - page++; - if (page >= scan->rs_nblocks) - page = 0; - finished = (page == scan->rs_startblock) || - (scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false); - - /* - * Report our new scan position for synchronization purposes. We - * don't do that when moving backwards, however. That would just - * mess up any other forward-moving scanners. - * - * Note: we do this before checking for end of scan so that the - * final state of the position hint is back at the start of the - * rel. That's not strictly necessary, but otherwise when you run - * the same query multiple times the starting position would shift - * a little bit backwards on every invocation, which is confusing. - * We don't guarantee any specific ordering in general, though. - */ - if (scan->rs_base.rs_flags & SO_ALLOW_SYNC) - ss_report_location(scan->rs_base.rs_rd, page); - } - - /* - * return NULL if we've exhausted all the pages - */ - if (finished) - { - if (BufferIsValid(scan->rs_cbuf)) - ReleaseBuffer(scan->rs_cbuf); - scan->rs_cbuf = InvalidBuffer; - scan->rs_cblock = InvalidBlockNumber; - tuple->t_data = NULL; - scan->rs_inited = false; - return; - } - - heapgetpage((TableScanDesc) scan, page); - - dp = BufferGetPage(scan->rs_cbuf); - TestForOldSnapshot(scan->rs_base.rs_snapshot, scan->rs_base.rs_rd, dp); - lines = scan->rs_ntuples; - linesleft = lines; - if (backward) - lineindex = lines - 1; - else - lineindex = 0; - } -} - - - /* ------------------------------------------------------------------------ * Slot related callbacks for vertex heap AM * ------------------------------------------------------------------------ @@ -1776,7 +1209,6 @@ vertex_heapam_slot_callbacks(Relation relation) return &TTSOpsBufferHeapTuple; } - /* ------------------------------------------------------------------------ * Functions related to scaning * ------------------------------------------------------------------------ @@ -1784,102 +1216,49 @@ vertex_heapam_slot_callbacks(Relation relation) TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, struct ScanKeyData *key, ParallelTableScanDesc parallel_scan, uint32 flags) { - HeapScanDesc scan; - /* - * increment relation ref count while scanning relation - * - * This is just to make really sure the relcache entry won't go away while - * the scan has a pointer to it. Caller should be holding the rel open - * anyway, so this is redundant in all normal scenarios... - */ - HASHCTL hash_ctl; -//BlackPink - MemSet(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = sizeof(graphid); - hash_ctl.entrysize = sizeof(vertex_hash_struct); - vertex_hash = ShmemInitHash(RelationGetRelationName(relation),16, 1000, &hash_ctl, - HASH_ELEM | HASH_BLOBS | HASH_COMPARE); - hash_seq_init(&scanStatus, vertex_hash); - + VertexHeapScanDesc scan; + VertexHeapScanDesc so; - RelationIncrementReferenceCount(relation); - /* - * allocate and initialize scan descriptor - */ - scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData)); + //scan->rs_strategy = NULL; /* set in initscan */ - scan->rs_base.rs_rd = relation; - scan->rs_base.rs_snapshot = snapshot; - scan->rs_base.rs_nkeys = nkeys; - scan->rs_base.rs_flags = flags; - scan->rs_base.rs_parallel = parallel_scan; - scan->rs_strategy = NULL; /* set in initscan */ - /* - * Disable page-at-a-time mode if it's not a MVCC-safe snapshot. - * TODO: Research Non MVCC Snapshots and Page-at-a-time mode - */ - if (!(snapshot && IsMVCCSnapshot(snapshot))) - scan->rs_base.rs_flags &= ~SO_ALLOW_PAGEMODE; + //scan = RelationGetIndexScan(relation, nkeys, norderbys); - /* - * For seqscan and sample scans in a serializable transaction, acquire a - * predicate lock on the entire relation. This is required not only to - * lock all the matching tuples, but also to conflict with new insertions - * into the table. In an indexscan, we take page locks on the index pages - * covering the range specified in the scan qual, but in a heap scan there - * is nothing more fine-grained to lock. A bitmap scan is a different - * story, there we have already scanned the index and locked the index - * pages covering the predicate. But in that case we still have to lock - * any matching heap tuples. For sample scan we could optimize the locking - * to be at least page-level granularity, but we'd need to add per-tuple - * locking for that. - */ - if (scan->rs_base.rs_flags & (SO_TYPE_SEQSCAN | SO_TYPE_SAMPLESCAN)) - { - /* - * Ensure a missing snapshot is noticed reliably, even if the - * isolation mode means predicate locking isn't performed (and - * therefore the snapshot isn't used here). - */ - Assert(snapshot); - PredicateLockRelation(relation, snapshot); - } + so = (VertexHeapScanDesc) palloc(sizeof(VertexScanDescData)); - /* we only need to set this up once */ - scan->rs_ctup.t_tableOid = RelationGetRelid(relation); - /* - * Allocate memory to keep track of page allocation for parallel workers - * when doing a parallel scan. - */ - if (parallel_scan != NULL) - scan->rs_parallelworkerdata = palloc(sizeof(ParallelBlockTableScanWorkerData)); - else - scan->rs_parallelworkerdata = NULL; + so->rs_base.rs_rd = relation; + so->rs_base.rs_snapshot = snapshot; + so->rs_base.rs_nkeys = nkeys; + so->rs_base.rs_flags = flags; + so->rs_base.rs_parallel = parallel_scan; - /* - * we do this here instead of in initscan() because heap_rescan also calls - * initscan() and we don't want to allocate memory again - */ - if (nkeys > 0) - scan->rs_base.rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys); - else - scan->rs_base.rs_key = NULL; + HashScanPosInvalidate(so->currPos); + so->hashso_bucket_buf = InvalidBuffer; + so->hashso_split_bucket_buf = InvalidBuffer; - initscan(scan, key, false); + so->hashso_buc_populated = false; + so->hashso_buc_split = false; - return (TableScanDesc) scan; + so->killedItems = NULL; + so->numKilled = 0; + + //scan->opaque = so; + + return so; } void vertex_scan_end(TableScanDesc sscan) { + + + return; HeapScanDesc scan = (HeapScanDesc) sscan; - + + hash_seq_term(&scanStatus); + /* Note: no locking manipulations needed */ -hash_seq_term(&scanStatus); -//BlackPink /* * unpin scan buffers */ @@ -1914,7 +1293,8 @@ void vertex_scan_rescan(TableScanDesc scan, struct ScanKeyData *key, bool set_params, bool allow_strat, bool allow_sync, bool allow_pagemode) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_rescan not implemented"))); + errmsg_internal("vertex_parallelscan_estimate not implemented"))); + } /* @@ -1922,39 +1302,26 @@ void vertex_scan_rescan(TableScanDesc scan, struct ScanKeyData *key, */ bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot) { - HeapScanDesc scan = (HeapScanDesc) sscan; - - /* Note: no locking manipulations needed */ - if (sscan->rs_flags & SO_ALLOW_PAGEMODE) - heapgettup_pagemode(scan, direction, sscan->rs_nkeys, sscan->rs_key); - else - heapgettup(scan, direction, sscan->rs_nkeys, sscan->rs_key); - - if (scan->rs_ctup.t_data == NULL) { - ExecClearTuple(slot); - return false; - } - - /* - * if we get here it means we have a new current scan tuple, so point to - * the proper return buffer and return the tuple. - */ - - pgstat_count_heap_getnext(scan->rs_base.rs_rd); - - ExecStoreBufferHeapTuple(&scan->rs_ctup, slot, scan->rs_cbuf); - -//BlackPink - if ((iterator = hash_seq_search(&scanStatus)) != NULL) { - slot->tts_values[0] = GRAPHID_GET_DATUM(iterator->id); - slot->tts_values[1] = GTYPE_P_GET_DATUM(iterator->properties); - } else { - return false; - } + VertexHeapScanDesc so = (VertexHeapScanDesc) sscan; + bool res; + /* + * If we've already initialized this scan, we can just advance it in the + * appropriate direction. If we haven't done so yet, we call a routine to + * get the first item in the scan. + */ + if (!HashScanPosIsValid(so->currPos)) + res = _hash_first(sscan, direction); + else + { + /* + * Now continue the scan. + */ + res = _hash_next(sscan, direction); + } - return true; + return res; } /* ------------------------------------------------------------------------ @@ -2142,6 +1509,113 @@ TransactionId vertex_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstat return NULL; } +Page +vertex_RelationPutHeapTuple(Relation relation, + Buffer buffer, + HeapTuple tuple, + bool token); + +/* + * heap_insert - insert tuple into a heap + * + * The new tuple is stamped with current transaction ID and the specified + * command ID. + * + * See table_tuple_insert for comments about most of the input flags, except + * that this routine directly takes a tuple rather than a slot. + * + * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_ + * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to + * implement table_tuple_insert_speculative(). + * + * On return the header fields of *tup are updated to match the stored tuple; + * in particular tup->t_self receives the actual TID where the tuple was + * stored. But note that any toasting of fields within the tuple data is NOT + * reflected into *tup. + */ +Page +vertex_heap_insert(Relation relation, HeapTuple tup, CommandId cid, + int options, BulkInsertState bistate) +{ + if (relation->rd_indexcxt == NULL) { + relation->rd_indexcxt= AllocSetContextCreate(CacheMemoryContext, + "index info", + ALLOCSET_SMALL_SIZES); + MemoryContextCopyAndSetIdentifier(relation->rd_indexcxt, + RelationGetRelationName(relation)); + + + } + + vertex_hash_doinsert(relation, tup, relation); + + return false; + +} + + + + +/* + * RelationPutHeapTuple - place tuple at specified page + * + * !!! EREPORT(ERROR) IS DISALLOWED HERE !!! Must PANIC on failure!!! + * + * Note - caller must hold BUFFER_LOCK_EXCLUSIVE on the buffer. + */ +Page +vertex_RelationPutHeapTuple(Relation relation, + Buffer buffer, + HeapTuple tuple, + bool token) +{ + Page pageHeader; + OffsetNumber offnum; + + /* + * A tuple that's being inserted speculatively should already have its + * token set. + */ + Assert(!token || HeapTupleHeaderIsSpeculative(tuple->t_data)); + + /* + * Do not allow tuples with invalid combinations of hint bits to be placed + * on a page. This combination is detected as corruption by the + * contrib/amcheck logic, so if you disable this assertion, make + * corresponding changes there. + */ + Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) && + (tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI))); + + /* Add the tuple to the page */ + pageHeader = BufferGetPage(buffer); + + offnum = PageAddItem(pageHeader, (Item) tuple->t_data, + tuple->t_len, InvalidOffsetNumber, false, true); + + if (offnum == InvalidOffsetNumber) + elog(PANIC, "failed to add tuple to page"); + + /* Update tuple->t_self to the actual position where it was stored */ + ItemPointerSet(&(tuple->t_self), BufferGetBlockNumber(buffer), offnum); + + /* + * Insert the correct position into CTID of the stored tuple, too (unless + * this is a speculative insertion, in which case the token is held in + * CTID field instead) + */ + if (!token) + { + ItemId itemId = PageGetItemId(pageHeader, offnum); + HeapTupleHeader item = (HeapTupleHeader) PageGetItem(pageHeader, itemId); + + item->t_ctid = tuple->t_self; + } + return pageHeader; +} + + + /* ------------------------------------------------------------------------ * Manipulations of physical tuples. * ------------------------------------------------------------------------ @@ -2151,43 +1625,30 @@ TransactionId vertex_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstat void vertex_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid, int options, struct BulkInsertStateData *bistate) { - // TODO, this is not implemented, merely the framework of whats to come - bool shouldFree = true; - HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + if (relation->rd_indexcxt == NULL) { + relation->rd_indexcxt= AllocSetContextCreate(CacheMemoryContext, + "index info", + ALLOCSET_SMALL_SIZES); + MemoryContextCopyAndSetIdentifier(relation->rd_indexcxt, + RelationGetRelationName(relation)); + } - /* Update the tuple with table oid */ - slot->tts_tableOid = RelationGetRelid(relation); + Datum index_values[1]; + bool index_isnull[1]; + IndexTuple itup; + bool shouldFree; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); tuple->t_tableOid = slot->tts_tableOid; + /* form an index tuple and point it at the heap tuple */ + //itup = index_form_tuple(RelationGetDescr(relation), index_values, index_isnull); + //itup->t_tid = *ht_ctid; - graphid id = DATUM_GET_GRAPHID(slot->tts_values[0]); - gtype *properties = DATUM_GET_GTYPE_P(slot->tts_values[1]); - + vertex_hash_doinsert(relation, tuple, relation); + if(shouldFree) + pfree(tuple); - gtype *shmem_properties = ShmemAlloc(VARSIZE(properties)); - memcpy(shmem_properties, properties, VARSIZE(properties)); - vertex_hash_struct *ctl = ShmemAlloc(sizeof(vertex_hash_struct)); - ctl->id = id, - ctl->properties = shmem_properties; - - HASHCTL hash_ctl; - - MemSet(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = sizeof(graphid); - hash_ctl.entrysize = sizeof(vertex_hash_struct); -//BlackPink - HTAB *rel_hash_table = ShmemInitHash(RelationGetRelationName(relation),16, 1000, &hash_ctl, - HASH_ELEM | HASH_BLOBS | HASH_COMPARE); - bool found; - hash_search(rel_hash_table, ctl, HASH_ENTER, &found); - - /* Perform the insertion, and copy the resulting ItemPointer */ - heap_insert(relation, tuple, cid, options, bistate); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); - - if (shouldFree) - pfree(tuple); } /* see table_tuple_insert_speculative() for reference about parameters */ @@ -2197,9 +1658,6 @@ void vertex_tuple_insert_speculative(Relation rel, TupleTableSlot *slot, uint32 specToken) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_tuple_insert_speculative not implemented"))); - - - } /* see table_tuple_complete_speculative() for reference about parameters */ @@ -2207,29 +1665,21 @@ void vertex_tuple_complete_speculative(Relation rel, TupleTableSlot *slot, uint32 specToken, bool succeeded) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - } /* see table_multi_insert() for reference about parameters */ void vertex_multi_insert(Relation rel, TupleTableSlot **slots, int nslots, CommandId cid, int options, struct BulkInsertStateData *bistate) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - } /* see table_tuple_delete() for reference about parameters */ TM_Result vertex_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, bool changingPart) { - TM_Result result; - TransactionId xid = GetCurrentTransactionId(); + TransactionId xid = GetCurrentTransactionId(); ItemId lp; HeapTupleData tp; Page page; @@ -2710,30 +2160,19 @@ void vertex_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, char persistence, TransactionId *freezeXid, MultiXactId *minmulti) { - SMgrRelation srel; - if(persistence != RELPERSISTENCE_PERMANENT || rel->rd_rel->relkind != RELKIND_RELATION) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex am can only work on permenant tables"))); - /* - * Initialize to the minimum XID that could put tuples in the table. We - * know that no xacts older than RecentXmin are still running, so that - * will do. - */ - *freezeXid = RecentXmin; - - /* - * Similarly, initialize the minimum Multixact to the first value that - * could possibly be stored in tuples in the table. Running transactions - * could reuse values from their local cache, so we are careful to - * consider all currently running multis. - * - * XXX this could be refined further, but is it worth the hassle? - */ + + + SMgrRelation srel; *minmulti = GetOldestMultiXactId(); srel = RelationCreateStorage(*newrnode, persistence); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(newrnode, INIT_FORKNUM); + smgrimmedsync(srel, INIT_FORKNUM); smgrclose(srel); HASHCTL hash_ctl; @@ -2744,7 +2183,7 @@ void vertex_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, HTAB *rel_hash_table = ShmemInitHash(RelationGetRelationName(rel),16, 1000, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_COMPARE); - + vertex_hash_init(rel, 0, INIT_FORKNUM); } /* @@ -2899,6 +2338,9 @@ void vertex_index_validate_scan(Relation table_rel, Relation index_rel, * point. */ uint64 vertex_relation_size(Relation rel, ForkNumber forkNumber) { + // if (forkNumber == INIT_FORKNUM) + // return 0; + uint64 nblocks = 0; /* InvalidForkNumber indicates returning the size for all forks */ @@ -3396,3 +2838,423 @@ heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode, return true; } + +#define VertexHashGetFillFactor(relation) \ + (relation)->rd_options ? \ + ((HashOptions *) (relation)->rd_options)->fillfactor : \ + HASH_DEFAULT_FILLFACTOR +#define VertexHashGetTargetPageUsage(relation) \ + (BLCKSZ * VertexHashGetFillFactor(relation) / 100) + + +#include "access/hash_xlog.h" + +/* + * _hash_init() -- Initialize the metadata page of a hash index, + * the initial buckets, and the initial bitmap page. + * + * The initial number of buckets is dependent on num_tuples, an estimate + * of the number of tuples to be loaded into the index initially. The + * chosen number of buckets is returned. + * + * We are fairly cavalier about locking here, since we know that no one else + * could be accessing this index. In particular the rule about not holding + * multiple buffer locks is ignored. + */ +uint32 +vertex_hash_init(Relation rel, double num_tuples, ForkNumber forkNum) +{ + Buffer metabuf; + Buffer buf; + Buffer bitmapbuf; + Page pg; + HashMetaPage metap; + RegProcedure procid; + int32 data_width; + int32 item_width; + int32 ffactor; + uint32 num_buckets; + uint32 i; + bool use_wal; + zero_damaged_pages = true; + /* safety check */ + if (RelationGetNumberOfBlocksInFork(rel, forkNum) != 0) + elog(ERROR, "cannot initialize non-empty hash index \"%s\"", + RelationGetRelationName(rel)); + + /* + * WAL log creation of pages if the relation is persistent, or this is the + * init fork. Init forks for unlogged relations always need to be WAL + * logged. + */ + use_wal = RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM; + + /* + * Determine the target fill factor (in tuples per bucket) for this index. + * The idea is to make the fill factor correspond to pages about as full + * as the user-settable fillfactor parameter says. We can compute it + * exactly since the index datatype (i.e. uint32 hash key) is fixed-width. + */ + data_width = sizeof(uint32); + item_width = MAXALIGN(sizeof(IndexTupleData)) + MAXALIGN(data_width) + + sizeof(ItemIdData); /* include the line pointer */ + //TODO FillFactor is a configuration setting, don't hard coded this forever + ffactor = 75 / item_width; + /* keep to a sane range */ + if (ffactor < 10) + ffactor = 10; + + + /* + * We initialize the metapage, the first N bucket pages, and the first + * bitmap page in sequence, using _hash_getnewbuf to cause smgrextend() + * calls to occur. This ensures that the smgr level has the right idea of + * the physical index length. + * + * Critical section not required, because on error the creation of the + * whole relation will be rolled back. + */ + metabuf = _hash_getnewbuf(rel, HASH_METAPAGE, MAIN_FORKNUM); + _hash_init_metabuffer(metabuf, num_tuples, 0, ffactor, false); + MarkBufferDirty(metabuf); + + pg = BufferGetPage(metabuf); + metap = HashPageGetMeta(pg); + + /* XLOG stuff */ + if (use_wal) + { + xl_hash_init_meta_page xlrec; + XLogRecPtr recptr; + + xlrec.num_tuples = num_tuples; + xlrec.procid = metap->hashm_procid; + xlrec.ffactor = metap->hashm_ffactor; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, SizeOfHashInitMetaPage); + XLogRegisterBuffer(0, metabuf, REGBUF_WILL_INIT | REGBUF_STANDARD); + + recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_INIT_META_PAGE); + + PageSetLSN(BufferGetPage(metabuf), recptr); + } + + num_buckets = metap->hashm_maxbucket + 1; + + /* + * Release buffer lock on the metapage while we initialize buckets. + * Otherwise, we'll be in interrupt holdoff and the CHECK_FOR_INTERRUPTS + * won't accomplish anything. It's a bad idea to hold buffer locks for + * long intervals in any case, since that can block the bgwriter. + */ + LockBuffer(metabuf, BUFFER_LOCK_UNLOCK); + + /* + * Initialize and WAL Log the first N buckets + */ + for (i = 0; i < num_buckets; i++) + { + BlockNumber blkno; + + /* Allow interrupts, in case N is huge */ + CHECK_FOR_INTERRUPTS(); + + blkno = BUCKET_TO_BLKNO(metap, i); + buf = _hash_getnewbuf(rel, blkno, MAIN_FORKNUM); + _hash_initbuf(buf, metap->hashm_maxbucket, i, LH_BUCKET_PAGE, false); + MarkBufferDirty(buf); + + if (use_wal) + log_newpage(&rel->rd_node, + forkNum, + blkno, + BufferGetPage(buf), + true); + _hash_relbuf(rel, buf); + } + + /* Now reacquire buffer lock on metapage */ + LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE); + + /* + * Initialize bitmap page + */ + bitmapbuf = _hash_getnewbuf(rel, num_buckets + 1, MAIN_FORKNUM); + _hash_initbitmapbuffer(bitmapbuf, metap->hashm_bmsize, false); + MarkBufferDirty(bitmapbuf); + + /* add the new bitmap page to the metapage's list of bitmaps */ + /* metapage already has a write lock */ + if (metap->hashm_nmaps >= HASH_MAX_BITMAPS) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("out of overflow pages in hash index \"%s\"", + RelationGetRelationName(rel)))); + + metap->hashm_mapp[metap->hashm_nmaps] = num_buckets + 1; + + metap->hashm_nmaps++; + MarkBufferDirty(metabuf); + + /* XLOG stuff */ + if (use_wal) + { + xl_hash_init_bitmap_page xlrec; + XLogRecPtr recptr; + + xlrec.bmsize = metap->hashm_bmsize; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, SizeOfHashInitBitmapPage); + XLogRegisterBuffer(0, bitmapbuf, REGBUF_WILL_INIT); + + /* + * This is safe only because nobody else can be modifying the index at + * this stage; it's only visible to the transaction that is creating + * it. + */ + XLogRegisterBuffer(1, metabuf, REGBUF_STANDARD); + + recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_INIT_BITMAP_PAGE); + + PageSetLSN(BufferGetPage(bitmapbuf), recptr); + PageSetLSN(BufferGetPage(metabuf), recptr); + } + + /* all done */ + _hash_relbuf(rel, bitmapbuf); + _hash_relbuf(rel, metabuf); + + return num_buckets; +} + + + +/* + * _hash_doinsert() -- Handle insertion of a single index tuple. + * + * This routine is called by the public interface routines, hashbuild + * and hashinsert. By here, itup is completely filled in. + */ +void +vertex_hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel) +{ + HeapTuple tuple = itup; + Buffer buf = InvalidBuffer; + Buffer bucket_buf; + Buffer metabuf; + HashMetaPage metap; + HashMetaPage usedmetap = NULL; + Page metapage; + Page page; + HashPageOpaque pageopaque; + Size itemsz; + bool do_expand; + uint32 hashkey; + Bucket bucket; + OffsetNumber itup_off; + + /* + * Get the hash key for the item (it's stored in the index tuple itself). + */ + hashkey = _hash_get_indextuple_hashkey(itup); + + /* compute item size too */ + itemsz = ((HeapTuple)itup)->t_len; + itemsz = MAXALIGN(itemsz); /* be safe, PageAddItem will do this but we + * need to be consistent */ + +restart_insert: + + /* + * Read the metapage. We don't lock it yet; HashMaxItemSize() will + * examine pd_pagesize_version, but that can't change so we can examine it + * without a lock. + */ + metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_NOLOCK, LH_META_PAGE); + metapage = BufferGetPage(metabuf); + + /* + * Check whether the item can fit on a hash page at all. (Eventually, we + * ought to try to apply TOAST methods if not.) Note that at this point, + * itemsz doesn't include the ItemId. + * + * XXX this is useless code if we are only storing hash keys. + */ + if (itemsz > HashMaxItemSize(metapage)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("index row size %zu exceeds hash maximum %zu", + itemsz, HashMaxItemSize(metapage)), + errhint("Values larger than a buffer page cannot be indexed."))); + + /* Lock the primary bucket page for the target bucket. */ + buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_WRITE, + &usedmetap); + Assert(usedmetap != NULL); + + CheckForSerializableConflictIn(rel, NULL, BufferGetBlockNumber(buf)); + + /* remember the primary bucket buffer to release the pin on it at end. */ + bucket_buf = buf; + + page = BufferGetPage(buf); + pageopaque = (HashPageOpaque) PageGetSpecialPointer(page); + bucket = pageopaque->hasho_bucket; + + /* + * If this bucket is in the process of being split, try to finish the + * split before inserting, because that might create room for the + * insertion to proceed without allocating an additional overflow page. + * It's only interesting to finish the split if we're trying to insert + * into the bucket from which we're removing tuples (the "old" bucket), + * not if we're trying to insert into the bucket into which tuples are + * being moved (the "new" bucket). + */ + if (H_BUCKET_BEING_SPLIT(pageopaque) && IsBufferCleanupOK(buf)) + { + /* release the lock on bucket buffer, before completing the split. */ + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + + _hash_finish_split(rel, metabuf, buf, bucket, + usedmetap->hashm_maxbucket, + usedmetap->hashm_highmask, + usedmetap->hashm_lowmask); + + /* release the pin on old and meta buffer. retry for insert. */ + _hash_dropbuf(rel, buf); + _hash_dropbuf(rel, metabuf); + goto restart_insert; + } + + /* Do the insertion */ + while (PageGetFreeSpace(page) < itemsz) + { + BlockNumber nextblkno; + + /* + * Check if current page has any DEAD tuples. If yes, delete these + * tuples and see if we can get a space for the new item to be + * inserted before moving to the next page in the bucket chain. + */ + if (H_HAS_DEAD_TUPLES(pageopaque)) + { + + /*if (IsBufferCleanupOK(buf)) + { + _hash_vacuum_one_page(rel, heapRel, metabuf, buf); + + if (PageGetFreeSpace(page) >= itemsz) + break; // OK, now we have enough space + }*/ + } + + /* + * no space on this page; check for an overflow page + */ + nextblkno = pageopaque->hasho_nextblkno; + + if (BlockNumberIsValid(nextblkno)) + { + /* + * ovfl page exists; go get it. if it doesn't have room, we'll + * find out next pass through the loop test above. we always + * release both the lock and pin if this is an overflow page, but + * only the lock if this is the primary bucket page, since the pin + * on the primary bucket must be retained throughout the scan. + */ + if (buf != bucket_buf) + _hash_relbuf(rel, buf); + else + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + buf = _hash_getbuf(rel, nextblkno, HASH_WRITE, LH_OVERFLOW_PAGE); + page = BufferGetPage(buf); + } + else + { + /* + * we're at the end of the bucket chain and we haven't found a + * page with enough room. allocate a new overflow page. + */ + + /* release our write lock without modifying buffer */ + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + + /* chain to a new overflow page */ + buf = _hash_addovflpage(rel, metabuf, buf, (buf == bucket_buf) ? true : false); + page = BufferGetPage(buf); + + /* should fit now, given test above */ + Assert(PageGetFreeSpace(page) >= itemsz); + } + pageopaque = (HashPageOpaque) PageGetSpecialPointer(page); + Assert((pageopaque->hasho_flag & LH_PAGE_TYPE) == LH_OVERFLOW_PAGE); + Assert(pageopaque->hasho_bucket == bucket); + } + + /* + * Write-lock the metapage so we can increment the tuple count. After + * incrementing it, check to see if it's time for a split. + */ + LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE); + + /* Do the update. No ereport(ERROR) until changes are logged */ + START_CRIT_SECTION(); + + /* found page with enough space, so add the item here */ + itup_off = _hash_pgaddtup(rel, buf, itemsz, itup); + MarkBufferDirty(buf); + + /* metapage operations */ + metap = HashPageGetMeta(metapage); + metap->hashm_ntuples += 1; + + /* Make sure this stays in sync with _hash_expandtable() */ + do_expand = metap->hashm_ntuples > + (double) metap->hashm_ffactor * (metap->hashm_maxbucket + 1); + + MarkBufferDirty(metabuf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_hash_insert xlrec; + XLogRecPtr recptr; + + xlrec.offnum = itup_off; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, SizeOfHashInsert); + + XLogRegisterBuffer(1, metabuf, REGBUF_STANDARD); + + XLogRegisterBuffer(0, buf, REGBUF_STANDARD); + XLogRegisterBufData(0, (char *) itup, IndexTupleSize(itup)); + + recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_INSERT); + + PageSetLSN(BufferGetPage(buf), recptr); + PageSetLSN(BufferGetPage(metabuf), recptr); + } + + END_CRIT_SECTION(); + + /* drop lock on metapage, but keep pin */ + LockBuffer(metabuf, BUFFER_LOCK_UNLOCK); + + /* + * Release the modified page and ensure to release the pin on primary + * page. + */ + _hash_relbuf(rel, buf); + if (buf != bucket_buf) + _hash_dropbuf(rel, bucket_buf); + + /* Attempt to split if a split is needed */ + if (do_expand) + _hash_expandtable(rel, metabuf); + + /* Finally drop our pin on the metapage */ + _hash_dropbuf(rel, metabuf); +} diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 3f8ceda1..bf6ee9c5 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -113,6 +113,7 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause } else { ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); } + result->querySource = QSRC_ORIGINAL; result->canSetTag = true; @@ -191,10 +192,12 @@ static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pn FuncExpr *func_expr = makeFuncExpr(func_oid, VERTEXOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); func_expr->location = -1; + return (Node *)func_expr; } + static Node * make_int_placeholder(cypher_parsestate *cpstate) { From aba288eef481a38f4f5e47b5a5cae424782abe10 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Mon, 14 Jul 2025 20:52:16 -0500 Subject: [PATCH 19/56] Support simple CREATE Path no variables, no labels, completely useless, but important for everything that comes next --- postgraph--0.1.0.sql | 8 +- regress/expected/cypher_create.out | 81 +- regress/expected/vertex_am.out | 39 +- regress/sql/cypher_create.sql | 2 + regress/sql/vertex_am.sql | 14 +- .../access/vertex_heap/vertex_hash_search.c | 723 ++++++++++++++++++ src/backend/executor/cypher_create.c | 82 +- src/backend/parser/cypher_clause.c | 1 + 8 files changed, 875 insertions(+), 75 deletions(-) create mode 100644 src/backend/access/vertex_heap/vertex_hash_search.c diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index a9b8f97c..d3e7be1c 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -534,7 +534,7 @@ AS 'MODULE_PATHNAME'; -- -- graphid - comparison operators (=, <>, <, >, <=, >=) -- -CREATE FUNCTION graphid_eq(postgraph.graphid, postgraph.graphid) +CREATE FUNCTION graphid_eq(graphid, graphid) RETURNS boolean LANGUAGE c IMMUTABLE @@ -549,9 +549,9 @@ CREATE OPERATOR = ( COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, - JOIN = eqjoinsel, - HASHES, - MERGES + JOIN = eqjoinsel--, + --HASHES, + --MERGES ); CREATE FUNCTION graphid_ne(graphid, graphid) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 4fc944eb..361bfb67 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -33,6 +33,7 @@ USE GRAPH cypher_create; CREATE (); WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -- (0 rows) @@ -67,8 +68,82 @@ SELECT * FROM postgraph.ag_label; test | 18833 | 3 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test (7 rows) +CREATE (:test); +WARNING: relcache reference leak: relation "test" not closed +WARNING: relcache reference leak: relation "test" not closed +-- +(0 rows) + +SELECT * FROM postgraph.ag_label; + name | graph | id | kind | relation | vertex_adjlist | label_path +------------------+-------+----+------+--------------------------------+-------------------------------------+----------------------- + _ag_label_vertex | 18780 | 1 | v | 18783 | 18792 | _ag_label_vertex + _ag_label_edge | 18780 | 2 | e | 18796 | | _ag_label_edge + _ag_label_vertex | 18805 | 1 | v | 18808 | 18817 | _ag_label_vertex + _ag_label_edge | 18805 | 2 | e | 18821 | | _ag_label_edge + _ag_label_vertex | 18833 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex + _ag_label_edge | 18833 | 2 | e | cypher_create._ag_label_edge | | _ag_label_edge + test | 18833 | 3 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test +(7 rows) + +SELECT * FROM cypher_create.test; + id | properties +-----------------+------------ + 844424930131969 | +(1 row) + +CREATE (:test2); +WARNING: relcache reference leak: relation "test2" not closed +WARNING: relcache reference leak: relation "test2" not closed +-- +(0 rows) + +SELECT * FROM postgraph.ag_label; + name | graph | id | kind | relation | vertex_adjlist | label_path +------------------+-------+----+------+--------------------------------+-------------------------------------+------------------------ + _ag_label_vertex | 18780 | 1 | v | 18783 | 18792 | _ag_label_vertex + _ag_label_edge | 18780 | 2 | e | 18796 | | _ag_label_edge + _ag_label_vertex | 18805 | 1 | v | 18808 | 18817 | _ag_label_vertex + _ag_label_edge | 18805 | 2 | e | 18821 | | _ag_label_edge + _ag_label_vertex | 18833 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex + _ag_label_edge | 18833 | 2 | e | cypher_create._ag_label_edge | | _ag_label_edge + test | 18833 | 3 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test + test2 | 18833 | 4 | v | cypher_create.test2 | cypher_create._adj_test2 | _ag_label_vertex.test2 +(8 rows) + +SELECT * FROM cypher_create.test2; + id | properties +------------------+------------ + 1125899906842625 | +(1 row) + +MATCH () RETURN 1; + ?column? +---------- + 1 + 1 + 1 +(3 rows) + CREATE ()-[]->(); -ERROR: CREATE doesn't work with paths +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed +-- +(0 rows) + +MATCH () RETURN 1; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 +(5 rows) + CYPHER WITH 1 as a CREATE (); ERROR: CREATE doesn't work with previous clauses @@ -78,12 +153,14 @@ ERROR: CREATE doesn't work with next clauses -- Clean up -- DROP GRAPH cypher_create CASCADE; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 7 other objects DETAIL: drop cascades to table cypher_create._ag_label_vertex drop cascades to table cypher_create._adj__ag_label_vertex drop cascades to table cypher_create._ag_label_edge drop cascades to table cypher_create.test drop cascades to table cypher_create._adj_test +drop cascades to table cypher_create.test2 +drop cascades to table cypher_create._adj_test2 NOTICE: graph "cypher_create" has been dropped drop_graph ------------ diff --git a/regress/expected/vertex_am.out b/regress/expected/vertex_am.out index 8b681140..20b3aa31 100644 --- a/regress/expected/vertex_am.out +++ b/regress/expected/vertex_am.out @@ -19,28 +19,35 @@ LOAD 'postgraph'; CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex_adjlist; INSERT INTO vertex_am_tst (id, props) VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); -SELECT * FROM vertex_am_tst; - id | props -----+----------- - 1 | {"id": 1} -(1 row) - +SELECT * FROM vertex_am_tst WHERE id = '1'::postgraph.graphid; +ERROR: operator does not exist: postgraph.graphid = postgraph.graphid +LINE 1: SELECT * FROM vertex_am_tst WHERE id = '1'::postgraph.graphi... + ^ +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +/* DELETE FROM vertex_am_tst; + + +SELECT * FROM vertex_am_tst WHERE id = '1'::postgraph.graphid; + + INSERT INTO vertex_am_tst (id, props) VALUES ('2'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); + + +SELECT * FROM vertex_am_tst WHERE id = '2'::postgraph.graphid; + + SELECT * FROM vertex_am_tst; - id | props -----+----------- - 2 | {"id": 1} -(1 row) UPDATE vertex_am_tst SET id = '2'::postgraph.graphid; + INSERT INTO vertex_am_tst (id, props) VALUES ('4'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); -SELECT * FROM vertex_am_tst; - id | props -----+----------- - 2 | {"id": 1} - 4 | {"id": 1} -(2 rows) + +INSERT INTO vertex_am_tst (id, props) +VALUES ('5'::postgraph.graphid, NULL); + +SELECT * FROM vertex_am_tst WHERE id = '5'::postgraph.graphid; + */ diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 29d7a08b..f1caae19 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -46,6 +46,8 @@ MATCH () RETURN 1; CREATE ()-[]->(); +MATCH () RETURN 1; + CYPHER WITH 1 as a CREATE (); diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index a4be8d94..cf2d0c9b 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -26,14 +26,21 @@ INSERT INTO vertex_am_tst (id, props) VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); -SELECT * FROM vertex_am_tst; - +SELECT * FROM vertex_am_tst WHERE id = '1'::postgraph.graphid; +/* DELETE FROM vertex_am_tst; + +SELECT * FROM vertex_am_tst WHERE id = '1'::postgraph.graphid; + + INSERT INTO vertex_am_tst (id, props) VALUES ('2'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); +SELECT * FROM vertex_am_tst WHERE id = '2'::postgraph.graphid; + + SELECT * FROM vertex_am_tst; UPDATE vertex_am_tst SET id = '2'::postgraph.graphid; @@ -45,4 +52,5 @@ VALUES ('4'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); INSERT INTO vertex_am_tst (id, props) VALUES ('5'::postgraph.graphid, NULL); -SELECT * FROM vertex_am_tst; +SELECT * FROM vertex_am_tst WHERE id = '5'::postgraph.graphid; + */ \ No newline at end of file diff --git a/src/backend/access/vertex_heap/vertex_hash_search.c b/src/backend/access/vertex_heap/vertex_hash_search.c new file mode 100644 index 00000000..54df245b --- /dev/null +++ b/src/backend/access/vertex_heap/vertex_hash_search.c @@ -0,0 +1,723 @@ +/*------------------------------------------------------------------------- + * + * hashsearch.c + * search code for postgres hash tables + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/hash/hashsearch.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/hash.h" +#include "access/relscan.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "storage/predicate.h" +#include "utils/rel.h" + +#include "access/vertex.h" + +static bool vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, + ScanDirection dir); +static int vertex_hash_load_qualified_items(TableScanDesc scan, Page page, + OffsetNumber offnum, ScanDirection dir); +static inline void vertex_hash_saveitem(VertexHeapScanDesc so, int itemIndex, + OffsetNumber offnum, IndexTuple itup); +static void vertex_hash_readnext(TableScanDesc scan, Buffer *bufp, + Page *pagep, HashPageOpaque *opaquep); + +/* + * vertex_hash_next() -- Get the next item in a scan. + * + * On entry, so->currPos describes the current page, which may + * be pinned but not locked, and so->currPos.itemIndex identifies + * which item was previously returned. + * + * On successful exit, scan->xs_ctup.t_self is set to the TID + * of the next heap tuple. so->currPos is updated as needed. + * + * On failure exit (no more tuples), we return false with pin + * held on bucket page but no pins or locks held on overflow + * page. + */ +bool +vertex_hash_next(TableScanDesc scan, ScanDirection dir) +{ + Relation rel = scan->rs_rd; + VertexHeapScanDesc so = (VertexHeapScanDesc) scan; + HashScanPosItem *currItem; + BlockNumber blkno; + Buffer buf; + bool end_of_scan = false; + + /* + * Advance to the next tuple on the current page; or if done, try to read + * data from the next or previous page based on the scan direction. Before + * moving to the next or previous page make sure that we deal with all the + * killed items. + */ + if (ScanDirectionIsForward(dir)) + { + if (++so->currPos.itemIndex > so->currPos.lastItem) + { + if (so->numKilled > 0) + _hash_kill_items(scan); + + blkno = so->currPos.nextPage; + if (BlockNumberIsValid(blkno)) + { + buf = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE); + TestForOldSnapshot(GetSnapshotData(), rel, BufferGetPage(buf)); + if (!vertex_hash_readpage(scan, &buf, dir)) + end_of_scan = true; + } + else + end_of_scan = true; + } + } + else + { + if (--so->currPos.itemIndex < so->currPos.firstItem) + { + if (so->numKilled > 0) + _hash_kill_items(scan); + + blkno = so->currPos.prevPage; + if (BlockNumberIsValid(blkno)) + { + buf = _hash_getbuf(rel, blkno, HASH_READ, + LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); + TestForOldSnapshot(GetSnapshotData(), rel, BufferGetPage(buf)); + + /* + * We always maintain the pin on bucket page for whole scan + * operation, so releasing the additional pin we have acquired + * here. + */ + if (buf == so->hashso_bucket_buf || + buf == so->hashso_split_bucket_buf) + _hash_dropbuf(rel, buf); + + if (!vertex_hash_readpage(scan, &buf, dir)) + end_of_scan = true; + } + else + end_of_scan = true; + } + } + + if (end_of_scan) + { + _hash_dropscanbuf(rel, so); + HashScanPosInvalidate(so->currPos); + return false; + } + + /* OK, itemIndex says what to return */ + currItem = &so->currPos.items[so->currPos.itemIndex]; + //scan->xs_heaptid = currItem->heapTid; + + return true; +} + +/* + * Advance to next page in a bucket, if any. If we are scanning the bucket + * being populated during split operation then this function advances to the + * bucket being split after the last bucket page of bucket being populated. + */ +static void +vertex_hash_readnext(TableScanDesc scan, + Buffer *bufp, Page *pagep, HashPageOpaque *opaquep) +{ + BlockNumber blkno; + Relation rel = scan->rs_rd; + VertexHeapScanDesc so = (VertexHeapScanDesc) scan; + bool block_found = false; + + blkno = (*opaquep)->hasho_nextblkno; + + /* + * Retain the pin on primary bucket page till the end of scan. Refer the + * comments in vertex_hash_first to know the reason of retaining pin. + */ + if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf) + LockBuffer(*bufp, BUFFER_LOCK_UNLOCK); + else + _hash_relbuf(rel, *bufp); + + *bufp = InvalidBuffer; + /* check for interrupts while we're not holding any buffer lock */ + CHECK_FOR_INTERRUPTS(); + if (BlockNumberIsValid(blkno)) + { + *bufp = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE); + block_found = true; + } + else if (so->hashso_buc_populated && !so->hashso_buc_split) + { + /* + * end of bucket, scan bucket being split if there was a split in + * progress at the start of scan. + */ + *bufp = so->hashso_split_bucket_buf; + + /* + * buffer for bucket being split must be valid as we acquire the pin + * on it before the start of scan and retain it till end of scan. + */ + Assert(BufferIsValid(*bufp)); + + LockBuffer(*bufp, BUFFER_LOCK_SHARE); + PredicateLockPage(rel, BufferGetBlockNumber(*bufp), GetSnapshotData()); + + /* + * setting hashso_buc_split to true indicates that we are scanning + * bucket being split. + */ + so->hashso_buc_split = true; + + block_found = true; + } + + if (block_found) + { + *pagep = BufferGetPage(*bufp); + TestForOldSnapshot(GetSnapshotData(), rel, *pagep); + *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); + } +} + +/* + * Advance to previous page in a bucket, if any. If the current scan has + * started during split operation then this function advances to bucket + * being populated after the first bucket page of bucket being split. + */ +static void +vertex__hash_readprev(TableScanDesc scan, + Buffer *bufp, Page *pagep, HashPageOpaque *opaquep) +{ + BlockNumber blkno; + Relation rel = scan->rs_rd; + VertexHeapScanDesc so = (VertexHeapScanDesc) scan; + bool haveprevblk; + + blkno = (*opaquep)->hasho_prevblkno; + + /* + * Retain the pin on primary bucket page till the end of scan. Refer the + * comments in vertex_hash_first to know the reason of retaining pin. + */ + if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf) + { + LockBuffer(*bufp, BUFFER_LOCK_UNLOCK); + haveprevblk = false; + } + else + { + _hash_relbuf(rel, *bufp); + haveprevblk = true; + } + + *bufp = InvalidBuffer; + /* check for interrupts while we're not holding any buffer lock */ + CHECK_FOR_INTERRUPTS(); + + if (haveprevblk) + { + Assert(BlockNumberIsValid(blkno)); + *bufp = _hash_getbuf(rel, blkno, HASH_READ, + LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); + *pagep = BufferGetPage(*bufp); + TestForOldSnapshot(GetSnapshotData(), rel, *pagep); + *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); + + /* + * We always maintain the pin on bucket page for whole scan operation, + * so releasing the additional pin we have acquired here. + */ + if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf) + _hash_dropbuf(rel, *bufp); + } + else if (so->hashso_buc_populated && so->hashso_buc_split) + { + /* + * end of bucket, scan bucket being populated if there was a split in + * progress at the start of scan. + */ + *bufp = so->hashso_bucket_buf; + + /* + * buffer for bucket being populated must be valid as we acquire the + * pin on it before the start of scan and retain it till end of scan. + */ + Assert(BufferIsValid(*bufp)); + + LockBuffer(*bufp, BUFFER_LOCK_SHARE); + *pagep = BufferGetPage(*bufp); + *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); + + /* move to the end of bucket chain */ + while (BlockNumberIsValid((*opaquep)->hasho_nextblkno)) + vertex_hash_readnext(scan, bufp, pagep, opaquep); + + /* + * setting hashso_buc_split to false indicates that we are scanning + * bucket being populated. + */ + so->hashso_buc_split = false; + } +} + +/* + * vertex_hash_first() -- Find the first item in a scan. + * + * We find the first item (or, if backward scan, the last item) in the + * index that satisfies the qualification associated with the scan + * descriptor. + * + * On successful exit, if the page containing current index tuple is an + * overflow page, both pin and lock are released whereas if it is a bucket + * page then it is pinned but not locked and data about the matching + * tuple(s) on the page has been loaded into so->currPos, + * scan->xs_ctup.t_self is set to the heap TID of the current tuple. + * + * On failure exit (no more tuples), we return false, with pin held on + * bucket page but no pins or locks held on overflow page. + */ +bool +vertex_hash_first(TableScanDesc scan, ScanDirection dir) +{ + Relation rel = scan->rs_rd; + VertexHeapScanDesc so = (VertexHeapScanDesc) scan; + ScanKey cur; + uint32 hashkey; + Bucket bucket; + Buffer buf; + Page page; + HashPageOpaque opaque; + HashScanPosItem *currItem; + + pgstat_count_index_scan(rel); + + /* + * We do not support hash scans with no index qualification, because we + * would have to read the whole index rather than just one bucket. That + * creates a whole raft of problems, since we haven't got a practical way + * to lock all the buckets against splits or compactions. + */ + // XXX: This is a major problem and must be solved + // if (scan->numberOfKeys < 1) + // ereport(ERROR, +//(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + // errmsg("hash indexes do not support whole-index scans"))); + + /* There may be more than one index qual, but we hash only the first */ + //cur = &scan->keyData[0]; + + /* We support only single-column hash indexes */ + //Assert(cur->sk_attno == 1); + /* And there's only one operator strategy, too */ + //Assert(cur->sk_strategy == HTEqualStrategyNumber); + + /* + * If the constant in the index qual is NULL, assume it cannot match any + * items in the index. + */ + if (cur->sk_flags & SK_ISNULL) + return false; + + /* + * Okay to compute the hash key. We want to do this before acquiring any + * locks, in case a user-defined hash function happens to be slow. + * + * If scankey operator is not a cross-type comparison, we can use the + * cached hash function; otherwise gotta look it up in the catalogs. + * + * We support the convention that sk_subtype == InvalidOid means the + * opclass input type; this is a hack to simplify life for ScanKeyInit(). + */ + if (cur->sk_subtype == rel->rd_opcintype[0] || + cur->sk_subtype == InvalidOid) + hashkey = _hash_datum2hashkey(rel, cur->sk_argument); + else + hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument, + cur->sk_subtype); + + so->hashso_sk_hash = hashkey; + + buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_READ, NULL); + PredicateLockPage(rel, BufferGetBlockNumber(buf), GetSnapshotData()); + page = BufferGetPage(buf); + TestForOldSnapshot(GetSnapshotData(), rel, page); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + bucket = opaque->hasho_bucket; + + so->hashso_bucket_buf = buf; + + /* + * If a bucket split is in progress, then while scanning the bucket being + * populated, we need to skip tuples that were copied from bucket being + * split. We also need to maintain a pin on the bucket being split to + * ensure that split-cleanup work done by vacuum doesn't remove tuples + * from it till this scan is done. We need to maintain a pin on the + * bucket being populated to ensure that vacuum doesn't squeeze that + * bucket till this scan is complete; otherwise, the ordering of tuples + * can't be maintained during forward and backward scans. Here, we have + * to be cautious about locking order: first, acquire the lock on bucket + * being split; then, release the lock on it but not the pin; then, + * acquire a lock on bucket being populated and again re-verify whether + * the bucket split is still in progress. Acquiring the lock on bucket + * being split first ensures that the vacuum waits for this scan to + * finish. + */ + if (H_BUCKET_BEING_POPULATED(opaque)) + { + BlockNumber old_blkno; + Buffer old_buf; + + old_blkno = _hash_get_oldblock_from_newbucket(rel, bucket); + + /* + * release the lock on new bucket and re-acquire it after acquiring + * the lock on old bucket. + */ + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + + old_buf = _hash_getbuf(rel, old_blkno, HASH_READ, LH_BUCKET_PAGE); + TestForOldSnapshot(GetSnapshotData(), rel, BufferGetPage(old_buf)); + + /* + * remember the split bucket buffer so as to use it later for + * scanning. + */ + so->hashso_split_bucket_buf = old_buf; + LockBuffer(old_buf, BUFFER_LOCK_UNLOCK); + + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + Assert(opaque->hasho_bucket == bucket); + + if (H_BUCKET_BEING_POPULATED(opaque)) + so->hashso_buc_populated = true; + else + { + _hash_dropbuf(rel, so->hashso_split_bucket_buf); + so->hashso_split_bucket_buf = InvalidBuffer; + } + } + + /* If a backwards scan is requested, move to the end of the chain */ + if (ScanDirectionIsBackward(dir)) + { + /* + * Backward scans that start during split needs to start from end of + * bucket being split. + */ + while (BlockNumberIsValid(opaque->hasho_nextblkno) || + (so->hashso_buc_populated && !so->hashso_buc_split)) + vertex_hash_readnext(scan, &buf, &page, &opaque); + } + + /* remember which buffer we have pinned, if any */ + Assert(BufferIsInvalid(so->currPos.buf)); + so->currPos.buf = buf; + + /* Now find all the tuples satisfying the qualification from a page */ + if (!vertex_hash_readpage(scan, &buf, dir)) + return false; + + /* OK, itemIndex says what to return */ + currItem = &so->currPos.items[so->currPos.itemIndex]; + //scan->xs_heaptid = currItem->heapTid; + + /* if we're here, vertex_hash_readpage found a valid tuples */ + return true; +} + +/* + * vertex_hash_readpage() -- Load data from current index page into so->currPos + * + * We scan all the items in the current index page and save them into + * so->currPos if it satisfies the qualification. If no matching items + * are found in the current page, we move to the next or previous page + * in a bucket chain as indicated by the direction. + * + * Return true if any matching items are found else return false. + */ +static bool +vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, ScanDirection dir) +{ + Relation rel = scan->rs_rd; + VertexHeapScanDesc so = (VertexHeapScanDesc) scan; + Buffer buf; + Page page; + HashPageOpaque opaque; + OffsetNumber offnum; + uint16 itemIndex; + + buf = *bufP; + Assert(BufferIsValid(buf)); + _hash_checkpage(rel, buf, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); + page = BufferGetPage(buf); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + + so->currPos.buf = buf; + so->currPos.currPage = BufferGetBlockNumber(buf); + + if (ScanDirectionIsForward(dir)) + { + BlockNumber prev_blkno = InvalidBlockNumber; + + for (;;) + { + /* new page, locate starting position by binary search */ + offnum = _hash_binsearch(page, so->hashso_sk_hash); + + itemIndex = vertex_hash_load_qualified_items(scan, page, offnum, dir); + + if (itemIndex != 0) + break; + + /* + * Could not find any matching tuples in the current page, move to + * the next page. Before leaving the current page, deal with any + * killed items. + */ + if (so->numKilled > 0) + _hash_kill_items(scan); + + /* + * If this is a primary bucket page, hasho_prevblkno is not a real + * block number. + */ + if (so->currPos.buf == so->hashso_bucket_buf || + so->currPos.buf == so->hashso_split_bucket_buf) + prev_blkno = InvalidBlockNumber; + else + prev_blkno = opaque->hasho_prevblkno; + + vertex_hash_readnext(scan, &buf, &page, &opaque); + if (BufferIsValid(buf)) + { + so->currPos.buf = buf; + so->currPos.currPage = BufferGetBlockNumber(buf); + } + else + { + /* + * Remember next and previous block numbers for scrollable + * cursors to know the start position and return false + * indicating that no more matching tuples were found. Also, + * don't reset currPage or lsn, because we expect + * _hash_kill_items to be called for the old page after this + * function returns. + */ + so->currPos.prevPage = prev_blkno; + so->currPos.nextPage = InvalidBlockNumber; + so->currPos.buf = buf; + return false; + } + } + + so->currPos.firstItem = 0; + so->currPos.lastItem = itemIndex - 1; + so->currPos.itemIndex = 0; + } + else + { + BlockNumber next_blkno = InvalidBlockNumber; + + for (;;) + { + /* new page, locate starting position by binary search */ + offnum = _hash_binsearch_last(page, so->hashso_sk_hash); + + itemIndex = vertex_hash_load_qualified_items(scan, page, offnum, dir); + + if (itemIndex != MaxIndexTuplesPerPage) + break; + + /* + * Could not find any matching tuples in the current page, move to + * the previous page. Before leaving the current page, deal with + * any killed items. + */ + if (so->numKilled > 0) + _hash_kill_items(scan); + + if (so->currPos.buf == so->hashso_bucket_buf || + so->currPos.buf == so->hashso_split_bucket_buf) + next_blkno = opaque->hasho_nextblkno; + + vertex__hash_readprev(scan, &buf, &page, &opaque); + if (BufferIsValid(buf)) + { + so->currPos.buf = buf; + so->currPos.currPage = BufferGetBlockNumber(buf); + } + else + { + /* + * Remember next and previous block numbers for scrollable + * cursors to know the start position and return false + * indicating that no more matching tuples were found. Also, + * don't reset currPage or lsn, because we expect + * _hash_kill_items to be called for the old page after this + * function returns. + */ + so->currPos.prevPage = InvalidBlockNumber; + so->currPos.nextPage = next_blkno; + so->currPos.buf = buf; + return false; + } + } + + so->currPos.firstItem = itemIndex; + so->currPos.lastItem = MaxIndexTuplesPerPage - 1; + so->currPos.itemIndex = MaxIndexTuplesPerPage - 1; + } + + if (so->currPos.buf == so->hashso_bucket_buf || + so->currPos.buf == so->hashso_split_bucket_buf) + { + so->currPos.prevPage = InvalidBlockNumber; + so->currPos.nextPage = opaque->hasho_nextblkno; + LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK); + } + else + { + so->currPos.prevPage = opaque->hasho_prevblkno; + so->currPos.nextPage = opaque->hasho_nextblkno; + _hash_relbuf(rel, so->currPos.buf); + so->currPos.buf = InvalidBuffer; + } + + Assert(so->currPos.firstItem <= so->currPos.lastItem); + return true; +} + +/* + * Load all the qualified items from a current index page + * into so->currPos. Helper function for vertex_hash_readpage. + */ +static int +vertex_hash_load_qualified_items(TableScanDesc scan, Page page, + OffsetNumber offnum, ScanDirection dir) +{ + VertexHeapScanDesc so = scan; + IndexTuple itup; + int itemIndex; + OffsetNumber maxoff; + + maxoff = PageGetMaxOffsetNumber(page); + + if (ScanDirectionIsForward(dir)) + { + /* load items[] in ascending order */ + itemIndex = 0; + + while (offnum <= maxoff) + { + Assert(offnum >= FirstOffsetNumber); + itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); + + /* + * skip the tuples that are moved by split operation for the scan + * that has started when split was in progress. Also, skip the + * tuples that are marked as dead. + */ + if ((so->hashso_buc_populated && !so->hashso_buc_split && + (itup->t_info & INDEX_MOVED_BY_SPLIT_MASK)) || + ((ItemIdIsDead(PageGetItemId(page, offnum))))) + { + offnum = OffsetNumberNext(offnum); /* move forward */ + continue; + } + + if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) && + _hash_checkqual(scan, itup)) + { + /* tuple is qualified, so remember it */ + vertex_hash_saveitem(so, itemIndex, offnum, itup); + itemIndex++; + } + else + { + /* + * No more matching tuples exist in this page. so, exit while + * loop. + */ + break; + } + + offnum = OffsetNumberNext(offnum); + } + + Assert(itemIndex <= MaxIndexTuplesPerPage); + return itemIndex; + } + else + { + /* load items[] in descending order */ + itemIndex = MaxIndexTuplesPerPage; + + while (offnum >= FirstOffsetNumber) + { + Assert(offnum <= maxoff); + itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); + + /* + * skip the tuples that are moved by split operation for the scan + * that has started when split was in progress. Also, skip the + * tuples that are marked as dead. + */ + if ((so->hashso_buc_populated && !so->hashso_buc_split && + (itup->t_info & INDEX_MOVED_BY_SPLIT_MASK)) || + (true && + (ItemIdIsDead(PageGetItemId(page, offnum))))) + { + offnum = OffsetNumberPrev(offnum); /* move back */ + continue; + } + + if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) && + _hash_checkqual(scan, itup)) + { + itemIndex--; + /* tuple is qualified, so remember it */ + vertex_hash_saveitem(so, itemIndex, offnum, itup); + } + else + { + /* + * No more matching tuples exist in this page. so, exit while + * loop. + */ + break; + } + + offnum = OffsetNumberPrev(offnum); + } + + Assert(itemIndex >= 0); + return itemIndex; + } +} + +/* Save an index item into so->currPos.items[itemIndex] */ +static inline void +vertex_hash_saveitem(VertexHeapScanDesc so, int itemIndex, + OffsetNumber offnum, IndexTuple itup) +{ + HashScanPosItem *currItem = &so->currPos.items[itemIndex]; + + currItem->heapTid = itup->t_tid; + currItem->indexOffset = offnum; +} diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 3a8e6cc2..e553b43e 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -164,8 +164,6 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag cypher_create_path *path = linitial(css->pattern); - if (list_length(path->target_nodes) != 1) - ereport(ERROR, (errmsg_internal("executor create found a traversal"))); css->vertex_ids = palloc(sizeof(graphid *) * list_length(css->pattern)); @@ -177,8 +175,17 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag } ListCell *lc1; + int i = 0; foreach (lc1, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc1); + + Assert(cypher_node->id_expr); + cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); + + if (i % 2 == 1) { + i++; + continue; + } Relation rel = table_open(cypher_node->relid, RowExclusiveLock); cypher_node->resultRelInfo = makeNode(ResultRelInfo); @@ -190,22 +197,20 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag // Setup the relation's tuple slot cypher_node->elemTupleSlot = table_slot_create(rel, &estate->es_tupleTable); - - + Relation adj_rel = table_open(cypher_node->adj_relid, RowExclusiveLock); cypher_node->adj_resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + InitResultRelInfo(cypher_node->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); // Open all indexes for the relation - //ExecOpenIndices(cypher_node->adj_resultRelInfo, false); + ExecOpenIndices(cypher_node->adj_resultRelInfo, false); // Setup the relation's tuple slot cypher_node->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); + i++; - Assert(cypher_node->id_expr); - cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); } if (estate->es_output_cid == 0) @@ -273,50 +278,24 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) ListCell *lc; int i = 1; foreach(lc, path->target_nodes) { - cypher_target_node *node = (cypher_target_node *)linitial(path->target_nodes); + cypher_target_node *node = (cypher_target_node *)lfirst(lc); bool isNull; if(i % 2 == 1) css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - else + else { css->edge_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + i++; + } + } - if (list_length(path->target_nodes) != 1) - ereport(ERROR, (errmsg_internal("executor create found a traversal"))); - - for (int i = 0; i < 1 + (list_length(path->target_nodes)/2); i++) { - cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i /2); - ResultRelInfo *resultRelInfo = node->resultRelInfo; - TupleTableSlot *elemTupleSlot = node->elemTupleSlot; - - ResultRelInfo **old_estate_es_result_relations_info = NULL; - - /* save the old result relation info */ - old_estate_es_result_relations_info = estate->es_result_relations; - - estate->es_result_relations = &resultRelInfo; - - ExecClearTuple(elemTupleSlot); - // get the next graphid for this vertex. - bool isNull; - elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; - elemTupleSlot->tts_isnull[0] = isNull; - - // get the properties for this vertex - elemTupleSlot->tts_values[1] = NULL; - elemTupleSlot->tts_isnull[1] = true; - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - - /* restore the old result relation info */ - estate->es_result_relations = old_estate_es_result_relations_info; - } //TODO: the edges aren't setup to use this. I gotta get the correct relid in cypher_target_node. // Then I gotta get any existing tuple, and update it if it exists, and insert if it doesn't - for (int i = 0; i < (list_length(path->target_nodes)/2); i++) { - cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i /2); + for (int i = 0; i < list_length(path->target_nodes)/2 + 1; i++) { + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); + ResultRelInfo *resultRelInfo = node->resultRelInfo; TupleTableSlot *elemTupleSlot = node->elemTupleSlot; @@ -339,7 +318,7 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) // Insert the new vertex insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - if (list_length(path->target_nodes) > 1) { + if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { resultRelInfo = node->adj_resultRelInfo; elemTupleSlot = node->adj_elemTupleSlot; @@ -368,22 +347,25 @@ static void end_cypher_create(CustomScanState *node) { cypher_create_custom_scan_state *css = (cypher_create_custom_scan_state *)node; - ListCell *lc; + CommandCounterIncrement(); ExecEndNode(node->ss.ps.lefttree); - foreach (lc, css->pattern) - { + ListCell *lc; + foreach (lc, css->pattern) { cypher_create_path *path = lfirst(lc); ListCell *lc2; - foreach (lc2, path->target_nodes) - { + + foreach (lc2, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc2); - ExecCloseIndices(cypher_node->resultRelInfo); + if (!cypher_node->resultRelInfo) + continue; - table_close(cypher_node->resultRelInfo->ri_RelationDesc, RowExclusiveLock); + ExecCloseIndices(cypher_node->resultRelInfo); + ExecCloseIndices(cypher_node->adj_resultRelInfo); + table_close(cypher_node->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); } } } diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index bf6ee9c5..6fa120c5 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -334,6 +334,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); target->relid = lcd->relation; target->adj_relid = lcd->vertex_adjlist; + TargetEntry *te = makeTargetEntry(make_int_placeholder(cpstate), pstate->p_next_resno++, make_id_alias(get_next_default_alias(cpstate)), false); ccp->target_nodes = lappend(ccp->target_nodes, target); From 2a107d5f8d34377a30c3f63dc2c62bbbe8be7a73 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 2 Aug 2025 12:45:28 -0500 Subject: [PATCH 20/56] Checkin WIP --- postgraph--0.1.0.sql | 4 +- regress/sql/vertex_am.sql | 6 +- .../access/vertex_heap/vertex_hash_search.c | 188 +++++++++++++++--- .../vertex_heap/vertex_heapam_handler.c | 102 ++++------ src/backend/catalog/ag_label.c | 2 +- src/backend/postgraph.c | 3 + src/include/access/vertex.h | 5 +- 7 files changed, 219 insertions(+), 91 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index d3e7be1c..0358ac64 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -535,8 +535,8 @@ AS 'MODULE_PATHNAME'; -- graphid - comparison operators (=, <>, <, >, <=, >=) -- CREATE FUNCTION graphid_eq(graphid, graphid) -RETURNS boolean -LANGUAGE c +RETURNS BOOLEAN +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT PARALLEL SAFE diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index cf2d0c9b..b72ab3d3 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -17,7 +17,7 @@ -- Regression tests don't preload extensions, gotta load it first LOAD 'postgraph'; - +set search_path 'postgraph'; CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex_adjlist; @@ -26,7 +26,9 @@ INSERT INTO vertex_am_tst (id, props) VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); -SELECT * FROM vertex_am_tst WHERE id = '1'::postgraph.graphid; +SELECT '1'::postgraph.graphid OPERATOR(postgraph.=) '1'::postgraph.graphid; +EXPLAIN SELECT * FROM vertex_am_tst WHERE id OPERATOR(postgraph.=) '1'::postgraph.graphid; +SELECT * FROM vertex_am_tst WHERE id OPERATOR(postgraph.=) '1'::postgraph.graphid; /* DELETE FROM vertex_am_tst; diff --git a/src/backend/access/vertex_heap/vertex_hash_search.c b/src/backend/access/vertex_heap/vertex_hash_search.c index 54df245b..af4437f8 100644 --- a/src/backend/access/vertex_heap/vertex_hash_search.c +++ b/src/backend/access/vertex_heap/vertex_hash_search.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "storage/predicate.h" #include "utils/rel.h" +#include "storage/procarray.h" #include "access/vertex.h" @@ -31,6 +32,7 @@ static inline void vertex_hash_saveitem(VertexHeapScanDesc so, int itemIndex, OffsetNumber offnum, IndexTuple itup); static void vertex_hash_readnext(TableScanDesc scan, Buffer *bufp, Page *pagep, HashPageOpaque *opaquep); +static void vertex_hash_kill_items(TableScanDesc scan); /* * vertex_hash_next() -- Get the next item in a scan. @@ -67,13 +69,13 @@ vertex_hash_next(TableScanDesc scan, ScanDirection dir) if (++so->currPos.itemIndex > so->currPos.lastItem) { if (so->numKilled > 0) - _hash_kill_items(scan); + vertex_hash_kill_items(scan); blkno = so->currPos.nextPage; if (BlockNumberIsValid(blkno)) { buf = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE); - TestForOldSnapshot(GetSnapshotData(), rel, BufferGetPage(buf)); + TestForOldSnapshot(scan->rs_snapshot, rel, BufferGetPage(buf)); if (!vertex_hash_readpage(scan, &buf, dir)) end_of_scan = true; } @@ -86,14 +88,14 @@ vertex_hash_next(TableScanDesc scan, ScanDirection dir) if (--so->currPos.itemIndex < so->currPos.firstItem) { if (so->numKilled > 0) - _hash_kill_items(scan); + vertex_hash_kill_items(scan); blkno = so->currPos.prevPage; if (BlockNumberIsValid(blkno)) { buf = _hash_getbuf(rel, blkno, HASH_READ, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); - TestForOldSnapshot(GetSnapshotData(), rel, BufferGetPage(buf)); + TestForOldSnapshot(scan->rs_snapshot, rel, BufferGetPage(buf)); /* * We always maintain the pin on bucket page for whole scan @@ -114,7 +116,7 @@ vertex_hash_next(TableScanDesc scan, ScanDirection dir) if (end_of_scan) { - _hash_dropscanbuf(rel, so); + //_hash_dropscanbuf(rel,( TableScanDesc)so); TODO HashScanPosInvalidate(so->currPos); return false; } @@ -174,7 +176,7 @@ vertex_hash_readnext(TableScanDesc scan, Assert(BufferIsValid(*bufp)); LockBuffer(*bufp, BUFFER_LOCK_SHARE); - PredicateLockPage(rel, BufferGetBlockNumber(*bufp), GetSnapshotData()); + PredicateLockPage(rel, BufferGetBlockNumber(*bufp), scan->rs_snapshot); /* * setting hashso_buc_split to true indicates that we are scanning @@ -188,7 +190,7 @@ vertex_hash_readnext(TableScanDesc scan, if (block_found) { *pagep = BufferGetPage(*bufp); - TestForOldSnapshot(GetSnapshotData(), rel, *pagep); + TestForOldSnapshot(scan->rs_snapshot, rel, *pagep); *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); } } @@ -234,7 +236,7 @@ vertex__hash_readprev(TableScanDesc scan, *bufp = _hash_getbuf(rel, blkno, HASH_READ, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); *pagep = BufferGetPage(*bufp); - TestForOldSnapshot(GetSnapshotData(), rel, *pagep); + TestForOldSnapshot(scan->rs_snapshot, rel, *pagep); *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); /* @@ -312,13 +314,13 @@ vertex_hash_first(TableScanDesc scan, ScanDirection dir) * to lock all the buckets against splits or compactions. */ // XXX: This is a major problem and must be solved - // if (scan->numberOfKeys < 1) - // ereport(ERROR, -//(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - // errmsg("hash indexes do not support whole-index scans"))); + if (scan->rs_nkeys < 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("hash indexes do not support whole-index scans"))); /* There may be more than one index qual, but we hash only the first */ - //cur = &scan->keyData[0]; + cur = &scan->rs_key[0]; /* We support only single-column hash indexes */ //Assert(cur->sk_attno == 1); @@ -346,15 +348,14 @@ vertex_hash_first(TableScanDesc scan, ScanDirection dir) cur->sk_subtype == InvalidOid) hashkey = _hash_datum2hashkey(rel, cur->sk_argument); else - hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument, - cur->sk_subtype); + hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument, cur->sk_subtype); so->hashso_sk_hash = hashkey; buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_READ, NULL); - PredicateLockPage(rel, BufferGetBlockNumber(buf), GetSnapshotData()); + PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->rs_snapshot); page = BufferGetPage(buf); - TestForOldSnapshot(GetSnapshotData(), rel, page); + //TestForOldSnapshot(scan->rs_snapshot, rel, page); opaque = (HashPageOpaque) PageGetSpecialPointer(page); bucket = opaque->hasho_bucket; @@ -390,7 +391,7 @@ vertex_hash_first(TableScanDesc scan, ScanDirection dir) LockBuffer(buf, BUFFER_LOCK_UNLOCK); old_buf = _hash_getbuf(rel, old_blkno, HASH_READ, LH_BUCKET_PAGE); - TestForOldSnapshot(GetSnapshotData(), rel, BufferGetPage(old_buf)); + TestForOldSnapshot(scan->rs_snapshot, rel, BufferGetPage(old_buf)); /* * remember the split bucket buffer so as to use it later for @@ -441,6 +442,95 @@ vertex_hash_first(TableScanDesc scan, ScanDirection dir) return true; } +void +vertex_hash_kill_items(TableScanDesc scan) +{ + HashScanOpaque so = (HashScanOpaque) scan; + Relation rel = scan->rs_rd; + BlockNumber blkno; + Buffer buf; + Page page; + HashPageOpaque opaque; + OffsetNumber offnum, + maxoff; + int numKilled = so->numKilled; + int i; + bool killedsomething = false; + bool havePin = false; + + Assert(so->numKilled > 0); + Assert(so->killedItems != NULL); + Assert(HashScanPosIsValid(so->currPos)); + + /* + * Always reset the scan state, so we don't look for same items on other + * pages. + */ + so->numKilled = 0; + + blkno = so->currPos.currPage; + if (HashScanPosIsPinned(so->currPos)) + { + /* + * We already have pin on this buffer, so, all we need to do is + * acquire lock on it. + */ + havePin = true; + buf = so->currPos.buf; + LockBuffer(buf, BUFFER_LOCK_SHARE); + } + else + buf = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE); + + page = BufferGetPage(buf); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + maxoff = PageGetMaxOffsetNumber(page); + + for (i = 0; i < numKilled; i++) + { + int itemIndex = so->killedItems[i]; + HashScanPosItem *currItem = &so->currPos.items[itemIndex]; + + offnum = currItem->indexOffset; + + Assert(itemIndex >= so->currPos.firstItem && + itemIndex <= so->currPos.lastItem); + + while (offnum <= maxoff) + { + ItemId iid = PageGetItemId(page, offnum); + IndexTuple ituple = (IndexTuple) PageGetItem(page, iid); + + if (ItemPointerEquals(&ituple->t_tid, &currItem->heapTid)) + { + /* found the item */ + ItemIdMarkDead(iid); + killedsomething = true; + break; /* out of inner search loop */ + } + offnum = OffsetNumberNext(offnum); + } + } + + /* + * Since this can be redone later if needed, mark as dirty hint. Whenever + * we mark anything LP_DEAD, we also set the page's + * LH_PAGE_HAS_DEAD_TUPLES flag, which is likewise just a hint. + */ + if (killedsomething) + { + opaque->hasho_flag |= LH_PAGE_HAS_DEAD_TUPLES; + MarkBufferDirtyHint(buf, true); + } + + if (so->hashso_bucket_buf == so->currPos.buf || + havePin) + LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK); + else + _hash_relbuf(rel, buf); +} + + /* * vertex_hash_readpage() -- Load data from current index page into so->currPos * @@ -491,7 +581,7 @@ vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, ScanDirection dir) * killed items. */ if (so->numKilled > 0) - _hash_kill_items(scan); + vertex_hash_kill_items(scan); /* * If this is a primary bucket page, hasho_prevblkno is not a real @@ -516,7 +606,7 @@ vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, ScanDirection dir) * cursors to know the start position and return false * indicating that no more matching tuples were found. Also, * don't reset currPage or lsn, because we expect - * _hash_kill_items to be called for the old page after this + * vertex_hash_kill_items to be called for the old page after this * function returns. */ so->currPos.prevPage = prev_blkno; @@ -550,7 +640,7 @@ vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, ScanDirection dir) * any killed items. */ if (so->numKilled > 0) - _hash_kill_items(scan); + vertex_hash_kill_items(scan); if (so->currPos.buf == so->hashso_bucket_buf || so->currPos.buf == so->hashso_split_bucket_buf) @@ -569,7 +659,7 @@ vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, ScanDirection dir) * cursors to know the start position and return false * indicating that no more matching tuples were found. Also, * don't reset currPage or lsn, because we expect - * _hash_kill_items to be called for the old page after this + * vertex_hash_kill_items to be called for the old page after this * function returns. */ so->currPos.prevPage = InvalidBlockNumber; @@ -603,6 +693,54 @@ vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, ScanDirection dir) return true; } +/* + * vertex_hash_checkqual -- does the index tuple satisfy the scan conditions? + */ +bool +vertex_hash_checkqual(TableScanDesc scan, IndexTuple itup) +{ + /* + * Currently, we can't check any of the scan conditions since we do not + * have the original index entry value to supply to the sk_func. Always + * return true; we expect that hashgettuple already set the recheck flag + * to make the main indexscan code do it. + */ +#ifdef NOT_USED + TupleDesc tupdesc = RelationGetDescr(scan->indexRelation); + ScanKey key = scan->keyData; + int scanKeySize = scan->numberOfKeys; + + while (scanKeySize > 0) + { + Datum datum; + bool isNull; + Datum test; + + datum = index_getattr(itup, + key->sk_attno, + tupdesc, + &isNull); + + /* assume sk_func is strict */ + if (isNull) + return false; + if (key->sk_flags & SK_ISNULL) + return false; + + test = FunctionCall2Coll(&key->sk_func, key->sk_collation, + datum, key->sk_argument); + + if (!DatumGetBool(test)) + return false; + + key++; + scanKeySize--; + } +#endif + + return true; +} + /* * Load all the qualified items from a current index page * into so->currPos. Helper function for vertex_hash_readpage. @@ -611,7 +749,7 @@ static int vertex_hash_load_qualified_items(TableScanDesc scan, Page page, OffsetNumber offnum, ScanDirection dir) { - VertexHeapScanDesc so = scan; + VertexHeapScanDesc so = (VertexHeapScanDesc)scan; IndexTuple itup; int itemIndex; OffsetNumber maxoff; @@ -642,7 +780,7 @@ vertex_hash_load_qualified_items(TableScanDesc scan, Page page, } if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) && - _hash_checkqual(scan, itup)) + vertex_hash_checkqual(scan, itup)) { /* tuple is qualified, so remember it */ vertex_hash_saveitem(so, itemIndex, offnum, itup); @@ -688,7 +826,7 @@ vertex_hash_load_qualified_items(TableScanDesc scan, Page page, } if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) && - _hash_checkqual(scan, itup)) + vertex_hash_checkqual(scan, itup)) { itemIndex--; /* tuple is qualified, so remember it */ diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index e5713efb..2aae4f98 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -50,6 +50,12 @@ #include "utils/builtins.h" #include "utils/rel.h" +#include "optimizer/plancat.h" +#include "utils/inval.h" +#include "access/visibilitymap.h" + + + #include "access/vertex.h" #include "utils/graphid.h" #include "utils/gtype.h" @@ -71,7 +77,7 @@ static bool heap_acquire_tuplock(Relation relation, ItemPointer tid, uint32 vertex_hash_init(Relation rel, double num_tuples, ForkNumber forkNum); void -vertex_hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel); +vertex_hash_doinsert(Relation rel, HeapTuple itup, Relation heapRel); typedef struct vertex_hash_struct { @@ -1232,6 +1238,7 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, so->rs_base.rs_rd = relation; so->rs_base.rs_snapshot = snapshot; so->rs_base.rs_nkeys = nkeys; + so->rs_base.rs_key = key; so->rs_base.rs_flags = flags; so->rs_base.rs_parallel = parallel_scan; @@ -1247,7 +1254,7 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, //scan->opaque = so; - return so; + return (TableScanDesc)so; } void vertex_scan_end(TableScanDesc sscan) { @@ -1312,16 +1319,9 @@ bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, * get the first item in the scan. */ if (!HashScanPosIsValid(so->currPos)) - res = _hash_first(sscan, direction); - else - { - /* - * Now continue the scan. - */ - res = _hash_next(sscan, direction); - } + return vertex_hash_first(sscan, direction); - return res; + return vertex_hash_next(sscan, direction); } /* ------------------------------------------------------------------------ @@ -1337,7 +1337,7 @@ Size vertex_parallelscan_estimate(Relation rel) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_estimate not implemented"))); - return NULL; + return -1; } /* @@ -1349,7 +1349,7 @@ Size vertex_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_initialize not implemented"))); - return NULL; + return -1; } /* @@ -1506,7 +1506,7 @@ TransactionId vertex_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstat errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return NULL; + return InvalidTransactionId; } Page @@ -1515,44 +1515,6 @@ vertex_RelationPutHeapTuple(Relation relation, HeapTuple tuple, bool token); -/* - * heap_insert - insert tuple into a heap - * - * The new tuple is stamped with current transaction ID and the specified - * command ID. - * - * See table_tuple_insert for comments about most of the input flags, except - * that this routine directly takes a tuple rather than a slot. - * - * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_ - * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to - * implement table_tuple_insert_speculative(). - * - * On return the header fields of *tup are updated to match the stored tuple; - * in particular tup->t_self receives the actual TID where the tuple was - * stored. But note that any toasting of fields within the tuple data is NOT - * reflected into *tup. - */ -Page -vertex_heap_insert(Relation relation, HeapTuple tup, CommandId cid, - int options, BulkInsertState bistate) -{ - if (relation->rd_indexcxt == NULL) { - relation->rd_indexcxt= AllocSetContextCreate(CacheMemoryContext, - "index info", - ALLOCSET_SMALL_SIZES); - MemoryContextCopyAndSetIdentifier(relation->rd_indexcxt, - RelationGetRelationName(relation)); - - - } - - vertex_hash_doinsert(relation, tup, relation); - - return false; - -} - @@ -2659,13 +2621,13 @@ static const TableAmRoutine vertex_heapam_methods = { .index_delete_tuples = vertex_index_delete_tuples, .relation_set_new_filenode = vertex_relation_set_new_filenode, - .relation_nontransactional_truncate = vertex_relation_nontransactional_truncate, + .relation_nontransactional_truncate = NULL, .relation_copy_data = vertex_relation_copy_data, - .relation_copy_for_cluster = vertex_relation_nontransactional_truncate, + .relation_copy_for_cluster = NULL, .relation_vacuum = vertex_relation_vacuum, .scan_analyze_next_block = vertex_scan_analyze_next_block, .scan_analyze_next_tuple = vertex_scan_analyze_next_tuple, - .index_build_range_scan = vertex_index_validate_scan, + .index_build_range_scan = NULL, .index_validate_scan = vertex_index_validate_scan, .relation_size = vertex_relation_size, @@ -3038,7 +3000,7 @@ vertex_hash_init(Relation rel, double num_tuples, ForkNumber forkNum) * and hashinsert. By here, itup is completely filled in. */ void -vertex_hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel) +vertex_hash_doinsert(Relation rel, HeapTuple itup, Relation heapRel) { HeapTuple tuple = itup; Buffer buf = InvalidBuffer; @@ -3058,10 +3020,12 @@ vertex_hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel) /* * Get the hash key for the item (it's stored in the index tuple itself). */ - hashkey = _hash_get_indextuple_hashkey(itup); + bool isnull; + hashkey = heap_getattr(itup, 1, RelationGetDescr(rel), &isnull); + //hashkey = vertex_hash_get_indextuple_hashkey(itup); //TODO /* compute item size too */ - itemsz = ((HeapTuple)itup)->t_len; + itemsz = (itup)->t_len; itemsz = MAXALIGN(itemsz); /* be safe, PageAddItem will do this but we * need to be consistent */ @@ -3203,7 +3167,7 @@ vertex_hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel) START_CRIT_SECTION(); /* found page with enough space, so add the item here */ - itup_off = _hash_pgaddtup(rel, buf, itemsz, itup); + itup_off = _hash_pgaddtup(rel, buf, itemsz, (IndexTuple)itup); MarkBufferDirty(buf); /* metapage operations */ @@ -3230,7 +3194,7 @@ vertex_hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel) XLogRegisterBuffer(1, metabuf, REGBUF_STANDARD); XLogRegisterBuffer(0, buf, REGBUF_STANDARD); - XLogRegisterBufData(0, (char *) itup, IndexTupleSize(itup)); + XLogRegisterBufData(0, (char *) itup, itemsz); recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_INSERT); @@ -3258,3 +3222,21 @@ vertex_hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel) /* Finally drop our pin on the metapage */ _hash_dropbuf(rel, metabuf); } + +#include "executor/nodeSeqscan.h" + +static exec_seq_scan_scan_key_hook_type prev_exec_seq_scan_scan_key_hook = NULL; + +void postgraph_seq_scan_key_hook (SeqScanState *node, + int *numScanKeys, ScanKey scanKeys) { + return; +} + +void register_seq_scan_hook(void){ + prev_exec_seq_scan_scan_key_hook = exec_seq_scan_scan_key_hook; + exec_seq_scan_scan_key_hook = postgraph_seq_scan_key_hook; +} + +void unregister_seq_scan_hook(void){ + exec_seq_scan_scan_key_hook = prev_exec_seq_scan_scan_key_hook; +} \ No newline at end of file diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index 627535b7..44f5156b 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -91,7 +91,7 @@ void insert_label(const char *label_name, Oid graph_oid, int32 label_id, values[5] = ObjectIdGetDatum(vertex_adjlist); nulls[5] = false; } else { - values[5] = NULL; + values[5] = 0; nulls[5] = true; } diff --git a/src/backend/postgraph.c b/src/backend/postgraph.c index 335daaac..8ffb77b7 100644 --- a/src/backend/postgraph.c +++ b/src/backend/postgraph.c @@ -26,6 +26,7 @@ #include "nodes/ag_nodes.h" #include "optimizer/cypher_paths.h" #include "parser/cypher_analyze.h" +#include "access/vertex.h" PG_MODULE_MAGIC; @@ -37,6 +38,7 @@ void _PG_init(void) set_rel_pathlist_init(); parse_analyze_init(); parse_init(); + register_seq_scan_hook(); } void _PG_fini(void); @@ -46,4 +48,5 @@ void _PG_fini(void) set_rel_pathlist_fini(); parse_analyze_fini(); parse_fini(); + unregister_seq_scan_hook(); } diff --git a/src/include/access/vertex.h b/src/include/access/vertex.h index ab6ef10b..2505cb61 100644 --- a/src/include/access/vertex.h +++ b/src/include/access/vertex.h @@ -11,7 +11,8 @@ #include "storage/lockdefs.h" #include "utils/hsearch.h" #include "utils/relcache.h" - +#include "access/relscan.h" +#include "access/hash.h" /* * Descriptor for heap table scans. */ @@ -60,5 +61,7 @@ vertex_hash_next(TableScanDesc scan, ScanDirection dir); bool vertex_hash_first(TableScanDesc scan, ScanDirection dir); +void unregister_seq_scan_hook(void); +void register_seq_scan_hook(void); #endif /* ACCESS_VERTEX_H */ From 1a2213489257a78cf58506f7657ca79ebc79b8c9 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 2 Aug 2025 21:56:58 -0500 Subject: [PATCH 21/56] update --- postgraph--0.1.0.sql | 16 + regress/sql/cypher_create.sql | 4 + regress/sql/vertex_am.sql | 4 +- .../vertex_heap/vertex_heapam_handler.c | 579 ++---------------- src/backend/commands/label_commands.c | 47 +- src/backend/executor/cypher_create.c | 30 +- src/backend/postgraph.c | 1 + src/backend/utils/cache/ag_cache.c | 1 + src/include/access/vertex.h | 4 + src/include/catalog/ag_label.h | 5 + src/include/utils/ag_cache.h | 1 + 11 files changed, 164 insertions(+), 528 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 0358ac64..249120b8 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -83,6 +83,22 @@ CREATE INDEX ag_label_ltree_index ON ag_label USING gist (label_path public.gist_ltree_ops); +CREATE TABLE edge_metatable ( + id label_id NOT NULL, + level int NOT NULL, + base_rel regclass NOT NULL, + sub_rel regclass NOT NULL--, + --CONSTRAINT fk_label_id FOREIGN KEY(id) REFERENCES ag_label(id) +); + +CREATE INDEX edge_metatable_id +ON edge_metatable +USING btree (id); + +CREATE INDEX edge_metatable_id_level +ON edge_metatable +USING btree (id, level); + CREATE TABLE session_graph_use ( pid oid not null, graph_oid oid not null diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index f1caae19..024e8d99 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -46,6 +46,10 @@ MATCH () RETURN 1; CREATE ()-[]->(); +SELECT * FROM cypher_create._ag_label_vertex; + +SELECT * FROM cypher_create._adj__adj__ag_label_vertex; +SELECT * FROM cypher_create._adj__ag_label_vertex; MATCH () RETURN 1; CYPHER WITH 1 as a diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index b72ab3d3..4a90193d 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -18,7 +18,7 @@ LOAD 'postgraph'; set search_path 'postgraph'; - +/* CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex_adjlist; @@ -29,7 +29,7 @@ VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); SELECT '1'::postgraph.graphid OPERATOR(postgraph.=) '1'::postgraph.graphid; EXPLAIN SELECT * FROM vertex_am_tst WHERE id OPERATOR(postgraph.=) '1'::postgraph.graphid; SELECT * FROM vertex_am_tst WHERE id OPERATOR(postgraph.=) '1'::postgraph.graphid; -/* + DELETE FROM vertex_am_tst; diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 2aae4f98..e9554589 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -53,7 +53,8 @@ #include "optimizer/plancat.h" #include "utils/inval.h" #include "access/visibilitymap.h" - +#include "utils/lsyscache.h" +#include "access/table.h" #include "access/vertex.h" @@ -1219,77 +1220,61 @@ vertex_heapam_slot_callbacks(Relation relation) * Functions related to scaning * ------------------------------------------------------------------------ */ -TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, - struct ScanKeyData *key, - ParallelTableScanDesc parallel_scan, uint32 flags) { - - VertexHeapScanDesc scan; - VertexHeapScanDesc so; - - - //scan->rs_strategy = NULL; /* set in initscan */ - - - //scan = RelationGetIndexScan(relation, nkeys, norderbys); - - so = (VertexHeapScanDesc) palloc(sizeof(VertexScanDescData)); - - - so->rs_base.rs_rd = relation; - so->rs_base.rs_snapshot = snapshot; - so->rs_base.rs_nkeys = nkeys; - so->rs_base.rs_key = key; - so->rs_base.rs_flags = flags; - so->rs_base.rs_parallel = parallel_scan; - HashScanPosInvalidate(so->currPos); - so->hashso_bucket_buf = InvalidBuffer; - so->hashso_split_bucket_buf = InvalidBuffer; +static char *make_vertex_adjlist_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); - so->hashso_buc_populated = false; - so->hashso_buc_split = false; + str[0] = '_'; + str[1] = 'a'; + str[2] = 'd'; + str[3] = 'j'; + str[4] = '_'; - so->killedItems = NULL; - so->numKilled = 0; + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 5] = var_name[i]; + str[i + 5] = '\0'; - //scan->opaque = so; + return str; +} - return (TableScanDesc)so; +TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, + struct ScanKeyData *key, + ParallelTableScanDesc parallel_scan, uint32 flags) { + VertexScanDescData *vertex_desc = palloc(sizeof(VertexScanDescData)); + vertex_desc->rs_base.rs_rd = relation; + vertex_desc->rs_base.rs_snapshot = snapshot; + vertex_desc->rs_base.rs_nkeys = nkeys; + vertex_desc->rs_base.rs_key = key; + //vertex_desc->rs_base.rs_mintid = NULL; + //vertex_desc->rs_base.rs_maxtid = NULL; + vertex_desc->rs_base.rs_flags = flags; + vertex_desc->rs_base.rs_parallel = NULL; + + vertex_desc->ndesc = 1; + //Relation rel = table_open(RelnameGetRelid(make_vertex_adjlist_alias(RelationGetRelationName(relation))), AccessShareLock); + TableAmRoutine *tableam = GetHeapamTableAmRoutine(); + Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(RelationGetRelid(relation))), get_rel_namespace(RelationGetRelid(relation))); + Relation rel = RelationIdGetRelation(oid); + + TableScanDesc *desc = tableam->scan_begin(rel, + snapshot, 0, NULL, parallel_scan, flags); + vertex_desc->desc = palloc(sizeof (TableScanDesc *) * vertex_desc->ndesc); + vertex_desc->desc[0] = desc; + return vertex_desc; } void vertex_scan_end(TableScanDesc sscan) { + VertexScanDescData *vertex_desc = sscan; + + //Relation rel = table_open(RelnameGetRelid(make_vertex_adjlist_alias(RelationGetRelationName(relation))), AccessShareLock); + TableAmRoutine *tableam = GetHeapamTableAmRoutine(); + //Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(vertex_desc->rs_base.rs_rd->rd_id)), get_rel_namespace(vertex_desc->rs_base.rs_rd->rd_id)); + //table_close((*vertex_desc->desc[0]->rs_rd, AccessShareLock); + tableam->scan_end(vertex_desc->desc[0]); - return; - HeapScanDesc scan = (HeapScanDesc) sscan; - - hash_seq_term(&scanStatus); - - /* Note: no locking manipulations needed */ - /* - * unpin scan buffers - */ - if (BufferIsValid(scan->rs_cbuf)) - ReleaseBuffer(scan->rs_cbuf); - - /* - * decrement relation reference count and free scan descriptor storage - */ - RelationDecrementReferenceCount(scan->rs_base.rs_rd); - - if (scan->rs_base.rs_key) - pfree(scan->rs_base.rs_key); - - if (scan->rs_strategy != NULL) - FreeAccessStrategy(scan->rs_strategy); - - if (scan->rs_parallelworkerdata != NULL) - pfree(scan->rs_parallelworkerdata); - - if (scan->rs_base.rs_flags & SO_TEMP_SNAPSHOT) - UnregisterSnapshot(scan->rs_base.rs_snapshot); - - pfree(scan); + return ; } /* @@ -1309,6 +1294,11 @@ void vertex_scan_rescan(TableScanDesc scan, struct ScanKeyData *key, */ bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot) { + VertexScanDescData *vertex_desc = sscan; + TableAmRoutine *tableam = GetHeapamTableAmRoutine(); + + ereport(WARNING,errmsg_internal("here")); + return tableam->scan_getnextslot(vertex_desc->desc[0], direction, slot); VertexHeapScanDesc so = (VertexHeapScanDesc) sscan; bool res; @@ -1515,16 +1505,6 @@ vertex_RelationPutHeapTuple(Relation relation, HeapTuple tuple, bool token); - - - -/* - * RelationPutHeapTuple - place tuple at specified page - * - * !!! EREPORT(ERROR) IS DISALLOWED HERE !!! Must PANIC on failure!!! - * - * Note - caller must hold BUFFER_LOCK_EXCLUSIVE on the buffer. - */ Page vertex_RelationPutHeapTuple(Relation relation, Buffer buffer, @@ -1587,30 +1567,11 @@ vertex_RelationPutHeapTuple(Relation relation, void vertex_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid, int options, struct BulkInsertStateData *bistate) { - if (relation->rd_indexcxt == NULL) { - relation->rd_indexcxt= AllocSetContextCreate(CacheMemoryContext, - "index info", - ALLOCSET_SMALL_SIZES); - MemoryContextCopyAndSetIdentifier(relation->rd_indexcxt, - RelationGetRelationName(relation)); - } - - Datum index_values[1]; - bool index_isnull[1]; - IndexTuple itup; - bool shouldFree; - HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); - tuple->t_tableOid = slot->tts_tableOid; - - /* form an index tuple and point it at the heap tuple */ - //itup = index_form_tuple(RelationGetDescr(relation), index_values, index_isnull); - //itup->t_tid = *ht_ctid; - - vertex_hash_doinsert(relation, tuple, relation); - - if(shouldFree) - pfree(tuple); - + TableAmRoutine *tableam = GetHeapamTableAmRoutine(); + Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(RelationGetRelid(relation))), get_rel_namespace(RelationGetRelid(relation))); + Relation rel = table_open(oid, RowExclusiveLock); + tableam->tuple_insert(rel, slot, cid, options, bistate); + table_close(rel, RowExclusiveLock); } /* see table_tuple_insert_speculative() for reference about parameters */ @@ -1640,413 +1601,8 @@ void vertex_multi_insert(Relation rel, TupleTableSlot **slots, int nslots, TM_Result vertex_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, bool changingPart) { - TM_Result result; - TransactionId xid = GetCurrentTransactionId(); - ItemId lp; - HeapTupleData tp; - Page page; - BlockNumber block; - Buffer buffer; - Buffer vmbuffer = InvalidBuffer; - TransactionId new_xmax; - uint16 new_infomask, - new_infomask2; - bool have_tuple_lock = false; - bool iscombo; - bool all_visible_cleared = false; - HeapTuple old_key_tuple = NULL; /* replica identity of the tuple */ - bool old_key_copied = false; - - Assert(ItemPointerIsValid(tid)); - - /* - * * Forbid this during a parallel operation, lest it allocate a combo CID. - * * Other workers might need that combo CID for visibility checks, and we - * * have no provision for broadcasting it to them. - * */ - if (IsInParallelMode()) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TRANSACTION_STATE), - errmsg("cannot delete tuples during a parallel operation"))); - - block = ItemPointerGetBlockNumber(tid); - buffer = ReadBuffer(relation, block); - page = BufferGetPage(buffer); - - /* - * * Before locking the buffer, pin the visibility map page if it appears to - * * be necessary. Since we haven't got the lock yet, someone else might be - * * in the middle of changing this, so we'll need to recheck after we have - * * the lock. - * */ - if (PageIsAllVisible(page)) - visibilitymap_pin(relation, block, &vmbuffer); - - LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); - - lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); - Assert(ItemIdIsNormal(lp)); - - tp.t_tableOid = RelationGetRelid(relation); - tp.t_data = (HeapTupleHeader) PageGetItem(page, lp); - tp.t_len = ItemIdGetLength(lp); - tp.t_self = *tid; - -l1: - /* - * * If we didn't pin the visibility map page and the page has become all - * * visible while we were busy locking the buffer, we'll have to unlock and - * * re-lock, to avoid holding the buffer lock across an I/O. That's a bit - * * unfortunate, but hopefully shouldn't happen often. - * */ - if (vmbuffer == InvalidBuffer && PageIsAllVisible(page)) - { - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - visibilitymap_pin(relation, block, &vmbuffer); - LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); - } - - result = HeapTupleSatisfiesUpdate(&tp, cid, buffer); - - if (result == TM_Invisible) - { - UnlockReleaseBuffer(buffer); - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("attempted to delete invisible tuple"))); - } - else if (result == TM_BeingModified && wait) - { - TransactionId xwait; - uint16 infomask; - - /* must copy state data before unlocking buffer */ - xwait = HeapTupleHeaderGetRawXmax(tp.t_data); - infomask = tp.t_data->t_infomask; - - /* - * * Sleep until concurrent transaction ends -- except when there's a - * * single locker and it's our own transaction. Note we don't care - * * which lock mode the locker has, because we need the strongest one. - * * - * * Before sleeping, we need to acquire tuple lock to establish our - * * priority for the tuple (see heap_lock_tuple). LockTuple will - * * release us when we are next-in-line for the tuple. - * * - * * If we are forced to "start over" below, we keep the tuple lock; - * * this arranges that we stay at the head of the line while rechecking - * * tuple state. - * */ - if (infomask & HEAP_XMAX_IS_MULTI) - { - bool current_is_member = false; - - if (DoesMultiXactIdConflict((MultiXactId) xwait, infomask, - LockTupleExclusive, ¤t_is_member)) - { - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - - /* - * * Acquire the lock, if necessary (but skip it when we're - * * requesting a lock and already have one; avoids deadlock). - * */ - if (!current_is_member) - heap_acquire_tuplock(relation, &(tp.t_self), LockTupleExclusive, - LockWaitBlock, &have_tuple_lock); - - /* wait for multixact */ - MultiXactIdWait((MultiXactId) xwait, MultiXactStatusUpdate, infomask, - relation, &(tp.t_self), XLTW_Delete, - NULL); - LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); - - /* - * * If xwait had just locked the tuple then some other xact - * * could update this tuple before we get to this point. Check - * * for xmax change, and start over if so. - * * - * * We also must start over if we didn't pin the VM page, and - * * the page has become all visible. - * */ - if ((vmbuffer == InvalidBuffer && PageIsAllVisible(page)) || - xmax_infomask_changed(tp.t_data->t_infomask, infomask) || - !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tp.t_data), - xwait)) - goto l1; - } - - /* - * * You might think the multixact is necessarily done here, but not - * * so: it could have surviving members, namely our own xact or - * * other subxacts of this backend. It is legal for us to delete - * * the tuple in either case, however (the latter case is - * * essentially a situation of upgrading our former shared lock to - * * exclusive). We don't bother changing the on-disk hint bits - * * since we are about to overwrite the xmax altogether. - * */ - } - else if (!TransactionIdIsCurrentTransactionId(xwait)) - { - /* - * * Wait for regular transaction to end; but first, acquire tuple - * * lock. - * */ - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - heap_acquire_tuplock(relation, &(tp.t_self), LockTupleExclusive, - LockWaitBlock, &have_tuple_lock); - XactLockTableWait(xwait, relation, &(tp.t_self), XLTW_Delete); - LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); - - /* - * * xwait is done, but if xwait had just locked the tuple then some - * * other xact could update this tuple before we get to this point. - * * Check for xmax change, and start over if so. - * * - * * We also must start over if we didn't pin the VM page, and the - * * page has become all visible. - * */ - if ((vmbuffer == InvalidBuffer && PageIsAllVisible(page)) || - xmax_infomask_changed(tp.t_data->t_infomask, infomask) || - !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tp.t_data), - xwait)) - goto l1; - - /* Otherwise check if it committed or aborted */ - UpdateXmaxHintBits(tp.t_data, buffer, xwait); - } - - /* - * * We may overwrite if previous xmax aborted, or if it committed but - * * only locked the tuple without updating it. - * */ - if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) || - HEAP_XMAX_IS_LOCKED_ONLY(tp.t_data->t_infomask) || - HeapTupleHeaderIsOnlyLocked(tp.t_data)) - result = TM_Ok; - else if (!ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid)) - result = TM_Updated; - else - result = TM_Deleted; - } - - /* sanity check the result HeapTupleSatisfiesUpdate() and the logic above */ - if (result != TM_Ok) - { - Assert(result == TM_SelfModified || - result == TM_Updated || - result == TM_Deleted || - result == TM_BeingModified); - Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID)); - Assert(result != TM_Updated || - !ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid)); - } - - if (crosscheck != InvalidSnapshot && result == TM_Ok) - { - /* Perform additional check for transaction-snapshot mode RI updates */ - if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer)) - result = TM_Updated; - } - - if (result != TM_Ok) - { - tmfd->ctid = tp.t_data->t_ctid; - tmfd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data); - if (result == TM_SelfModified) - tmfd->cmax = HeapTupleHeaderGetCmax(tp.t_data); - else - tmfd->cmax = InvalidCommandId; - UnlockReleaseBuffer(buffer); - if (have_tuple_lock) - UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive); - if (vmbuffer != InvalidBuffer) - ReleaseBuffer(vmbuffer); - return result; - } - - /* - * * We're about to do the actual delete -- check for conflict first, to - * * avoid possibly having to roll back work we've just done. - * * - * * This is safe without a recheck as long as there is no possibility of - * * another process scanning the page between this check and the delete - * * being visible to the scan (i.e., an exclusive buffer content lock is - * * continuously held from this point until the tuple delete is visible). - * */ - CheckForSerializableConflictIn(relation, tid, BufferGetBlockNumber(buffer)); - - /* replace cid with a combo CID if necessary */ - HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo); - - /* - * * Compute replica identity tuple before entering the critical section so - * * we don't PANIC upon a memory allocation failure. - * */ - old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); - - /* - * * If this is the first possibly-multixact-able operation in the current - * * transaction, set my per-backend OldestMemberMXactId setting. We can be - * * certain that the transaction will never become a member of any older - * * MultiXactIds than that. (We have to do this even if we end up just - * * using our own TransactionId below, since some other backend could - * * incorporate our XID into a MultiXact immediately afterwards.) - * */ - MultiXactIdSetOldestMember(); - - compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(tp.t_data), - tp.t_data->t_infomask, tp.t_data->t_infomask2, - xid, LockTupleExclusive, true, - &new_xmax, &new_infomask, &new_infomask2); - - START_CRIT_SECTION(); - - /* - * * If this transaction commits, the tuple will become DEAD sooner or - * * later. Set flag that this page is a candidate for pruning once our xid - * * falls below the OldestXmin horizon. If the transaction finally aborts, - * * the subsequent page pruning will be a no-op and the hint will be - * * cleared. - * */ - PageSetPrunable(page, xid); - - if (PageIsAllVisible(page)) - { - all_visible_cleared = true; - PageClearAllVisible(page); - visibilitymap_clear(relation, BufferGetBlockNumber(buffer), - vmbuffer, VISIBILITYMAP_VALID_BITS); - } - - /* store transaction information of xact deleting the tuple */ - tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED); - tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED; - tp.t_data->t_infomask |= new_infomask; - tp.t_data->t_infomask2 |= new_infomask2; - HeapTupleHeaderClearHotUpdated(tp.t_data); - HeapTupleHeaderSetXmax(tp.t_data, new_xmax); - HeapTupleHeaderSetCmax(tp.t_data, cid, iscombo); - /* Make sure there is no forward chain link in t_ctid */ - tp.t_data->t_ctid = tp.t_self; - - /* Signal that this is actually a move into another partition */ - if (changingPart) - HeapTupleHeaderSetMovedPartitions(tp.t_data); - - MarkBufferDirty(buffer); - - /* - * * XLOG stuff - * * - * * NB: heap_abort_speculative() uses the same xlog record and replay - * * routines. - * */ - if (RelationNeedsWAL(relation)) - { - xl_heap_delete xlrec; - xl_heap_header xlhdr; - XLogRecPtr recptr; - - /* - * * For logical decode we need combo CIDs to properly decode the - * * catalog - * */ - if (RelationIsAccessibleInLogicalDecoding(relation)) - log_heap_new_cid(relation, &tp); - - xlrec.flags = 0; - if (all_visible_cleared) - xlrec.flags |= XLH_DELETE_ALL_VISIBLE_CLEARED; - if (changingPart) - xlrec.flags |= XLH_DELETE_IS_PARTITION_MOVE; - xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask, - tp.t_data->t_infomask2); - xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self); - xlrec.xmax = new_xmax; - - if (old_key_tuple != NULL) - { - if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL) - xlrec.flags |= XLH_DELETE_CONTAINS_OLD_TUPLE; - else - xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY; - } - - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, SizeOfHeapDelete); - - XLogRegisterBuffer(0, buffer, REGBUF_STANDARD); - - /* - * * Log replica identity of the deleted tuple if there is one - * */ - if (old_key_tuple != NULL) - { - xlhdr.t_infomask2 = old_key_tuple->t_data->t_infomask2; - xlhdr.t_infomask = old_key_tuple->t_data->t_infomask; - xlhdr.t_hoff = old_key_tuple->t_data->t_hoff; - - XLogRegisterData((char *) &xlhdr, SizeOfHeapHeader); - XLogRegisterData((char *) old_key_tuple->t_data - + SizeofHeapTupleHeader, - old_key_tuple->t_len - - SizeofHeapTupleHeader); - } - - /* filtering by origin on a row level is much more efficient */ - XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN); - - recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - - if (vmbuffer != InvalidBuffer) - ReleaseBuffer(vmbuffer); - - /* - * * If the tuple has toasted out-of-line attributes, we need to delete - * * those items too. We have to do this before releasing the buffer - * * because we need to look at the contents of the tuple, but it's OK to - * * release the content lock on the buffer first. - * */ - if (relation->rd_rel->relkind != RELKIND_RELATION && - relation->rd_rel->relkind != RELKIND_MATVIEW) - { - /* toast table entries should never be recursively toasted */ - Assert(!HeapTupleHasExternal(&tp)); - } - else if (HeapTupleHasExternal(&tp)) - heap_toast_delete(relation, &tp, false); - - /* - * * Mark tuple for invalidation from system caches at next command - * * boundary. We have to do this before releasing the buffer because we - * * need to look at the contents of the tuple. - * */ - CacheInvalidateHeapTuple(relation, &tp, NULL); - - /* Now we can release the buffer */ - ReleaseBuffer(buffer); - - /* - * * Release the lmgr tuple lock, if we had it. - * */ - if (have_tuple_lock) - UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive); - - pgstat_count_heap_delete(relation); - - if (old_key_tuple != NULL && old_key_copied) - heap_freetuple(old_key_tuple); - - return TM_Ok; - - + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg_internal("vertex_tuple_delete not implemented"))); } /* see table_tuple_update() for reference about parameters */ @@ -2157,9 +1713,6 @@ void vertex_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, * See also table_relation_nontransactional_truncate(). */ void vertex_relation_nontransactional_truncate(Relation rel) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - } @@ -2579,6 +2132,7 @@ bool vertex_scan_sample_next_tuple(TableScanDesc scan, struct SampleScanState *s return false; } + /* ------------------------------------------------------------------------ * Definition of the heap table access method. * ------------------------------------------------------------------------ @@ -2621,13 +2175,13 @@ static const TableAmRoutine vertex_heapam_methods = { .index_delete_tuples = vertex_index_delete_tuples, .relation_set_new_filenode = vertex_relation_set_new_filenode, - .relation_nontransactional_truncate = NULL, + .relation_nontransactional_truncate = vertex_relation_nontransactional_truncate, .relation_copy_data = vertex_relation_copy_data, - .relation_copy_for_cluster = NULL, + .relation_copy_for_cluster = vertex_relation_copy_for_cluster, .relation_vacuum = vertex_relation_vacuum, .scan_analyze_next_block = vertex_scan_analyze_next_block, .scan_analyze_next_tuple = vertex_scan_analyze_next_tuple, - .index_build_range_scan = NULL, + .index_build_range_scan = vertex_index_build_range_scan, .index_validate_scan = vertex_index_validate_scan, .relation_size = vertex_relation_size, @@ -3228,7 +2782,9 @@ vertex_hash_doinsert(Relation rel, HeapTuple itup, Relation heapRel) static exec_seq_scan_scan_key_hook_type prev_exec_seq_scan_scan_key_hook = NULL; void postgraph_seq_scan_key_hook (SeqScanState *node, - int *numScanKeys, ScanKey scanKeys) { + int *numScanKeys, ScanKey scanKeys) { + + ereport(WARNING, errmsg("In hook")); return; } @@ -3239,4 +2795,5 @@ void register_seq_scan_hook(void){ void unregister_seq_scan_hook(void){ exec_seq_scan_scan_key_hook = prev_exec_seq_scan_scan_key_hook; -} \ No newline at end of file +} + \ No newline at end of file diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index c1f0fa29..44be1a35 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -94,7 +94,7 @@ static void range_var_callback_for_remove_relation(const RangeVar *rel, Oid odl_rel_oid, void *arg); static char *make_vertex_adjlist_alias(char *var_name); - +static List *create_edge_table_elements_topmost(void); PG_FUNCTION_INFO_V1(create_vlabel); Datum create_vlabel(PG_FUNCTION_ARGS) @@ -523,7 +523,7 @@ void create_vertex_adjlist(char *graph_name, char *label_name) { create_stmt->relation = makeRangeVar(graph_name, rel_name, -1); - create_stmt->tableElts = create_vertex_adjlist_table_elements(); + create_stmt->tableElts = create_edge_table_elements_topmost(); create_stmt->inhRelations = NIL; @@ -625,6 +625,12 @@ void create_label(char *graph_name, char *label_name, char label_type, if (label_type == 'v') { create_vertex_adjlist(graph_name, label_name); Oid adj_oid = get_relname_relid(make_vertex_adjlist_alias(label_name), nsp_id); + + // 08/2/25: TODO - this is incorrect, but it will allow me to get the first two edge table I need created. + // So do it for now, change in the very near future + create_label(graph_name, make_vertex_adjlist_alias(make_vertex_adjlist_alias(label_name)), 'e', NIL , NULL); + + insert_label(label_name, graph_oid, label_id, label_type, relation_id, NULL, adj_oid); }else { @@ -693,6 +699,43 @@ static void create_table_for_label(char *graph_name, char *label_name, // CommandCounterIncrement() is called in ProcessUtility() } + +// CREATE TABLE `schema_name`.`rel_name` ( +// "id" graphid PRIMARY KEY DEFAULT CATALOG_SCHEMA."_graphid"(...), +// "start_id" graphid NOT NULL +// "end_id" graphid NOT NULL +// "properties" gtype NOT NULL DEFAULT CATALOG_SCHEMA."gtype_build_map"() +// ) +static List *create_edge_table_elements_topmost(void) +{ + ColumnDef *id; + ColumnDef *start_id; + ColumnDef *end_id; + ColumnDef *props; + + // "id" graphid PRIMARY KEY DEFAULT CATALOG_SCHEMA."_graphid"(...) + id = makeColumnDef(AG_EDGE_COLNAME_ID, GRAPHIDOID, -1, InvalidOid); + id->constraints = NIL; + + // "start_id" graphid NOT NULL + start_id = makeColumnDef(AG_EDGE_COLNAME_START_ID, GRAPHIDOID, -1, + InvalidOid); + start_id->constraints = list_make1(build_not_null_constraint()); + + // "end_id" graphid NOT NULL + end_id = makeColumnDef(AG_EDGE_COLNAME_END_ID, GRAPHIDOID, -1, InvalidOid); + end_id->constraints = list_make1(build_not_null_constraint()); + + // "properties" gtype NOT NULL DEFAULT CATALOG_SCHEMA."gtype_build_map"() + props = makeColumnDef(AG_EDGE_COLNAME_PROPERTIES, GTYPEOID, -1, + InvalidOid); + props->constraints = list_make2(build_null_constraint(), + build_properties_default()); + + return list_make4(id, start_id, end_id, props); +} + + // CREATE TABLE `schema_name`.`rel_name` ( // "id" graphid PRIMARY KEY DEFAULT CATALOG_SCHEMA."_graphid"(...), // "start_id" graphid NOT NULL diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index e553b43e..c8713264 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -194,7 +194,6 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag // Open all indexes for the relation ExecOpenIndices(cypher_node->resultRelInfo, false); - // Setup the relation's tuple slot cypher_node->elemTupleSlot = table_slot_create(rel, &estate->es_tupleTable); @@ -290,9 +289,6 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) } - - //TODO: the edges aren't setup to use this. I gotta get the correct relid in cypher_target_node. - // Then I gotta get any existing tuple, and update it if it exists, and insert if it doesn't for (int i = 0; i < list_length(path->target_nodes)/2 + 1; i++) { cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); @@ -330,9 +326,15 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; elemTupleSlot->tts_isnull[0] = false; + elemTupleSlot->tts_values[1] = css->vertex_ids[0][i]; + elemTupleSlot->tts_isnull[1] = false; + + elemTupleSlot->tts_values[2] = css->vertex_ids[0][i+1]; + elemTupleSlot->tts_isnull[2] = false; // get the properties for this vertex - elemTupleSlot->tts_values[1] = NULL; - elemTupleSlot->tts_isnull[1] = true; + elemTupleSlot->tts_values[3] = NULL; + elemTupleSlot->tts_isnull[3] = true; + // Insert the new vertex insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); } @@ -364,8 +366,12 @@ static void end_cypher_create(CustomScanState *node) continue; ExecCloseIndices(cypher_node->resultRelInfo); + table_close(cypher_node->resultRelInfo->ri_RelationDesc, RowExclusiveLock); ExecCloseIndices(cypher_node->adj_resultRelInfo); table_close(cypher_node->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); + + + } } } @@ -454,8 +460,6 @@ static void insert_vertex(cypher_create_custom_scan_state *css, /* * Insert the edge/vertex tuple into the table and indices. Check that the * table's constraints have not been violated. - * - * This function uses the passed cid for updates. */ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, @@ -464,14 +468,14 @@ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, HeapTuple tuple = NULL; ExecStoreVirtualTuple(elemTupleSlot); - tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); + // tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); /* Check the constraints of the tuple */ - tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); - if (resultRelInfo->ri_RelationDesc->rd_att->constr != NULL) + //tuple->t_tableOid = resultRelInfo->ri_RelationDesc->rd_id; + /* if (resultRelInfo->ri_RelationDesc->rd_att->constr != NULL) { ExecConstraints(resultRelInfo, elemTupleSlot, estate); - } + }*/ // Insert the tuple normally table_tuple_insert(resultRelInfo->ri_RelationDesc, elemTupleSlot, estate->es_output_cid, 0, NULL); @@ -482,7 +486,7 @@ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, ExecInsertIndexTuples(resultRelInfo, elemTupleSlot, estate, false, false, NULL, NIL); } - return tuple; + return NULL; } diff --git a/src/backend/postgraph.c b/src/backend/postgraph.c index 8ffb77b7..f53a5898 100644 --- a/src/backend/postgraph.c +++ b/src/backend/postgraph.c @@ -27,6 +27,7 @@ #include "optimizer/cypher_paths.h" #include "parser/cypher_analyze.h" #include "access/vertex.h" +#include "executor/nodeSeqscan.h" PG_MODULE_MAGIC; diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index 820d5fc5..da43a0bf 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -88,6 +88,7 @@ typedef struct label_relation_cache_entry label_cache_data data; } label_relation_cache_entry; + // ag_graph.name static HTAB *graph_name_cache_hash = NULL; static ScanKeyData graph_name_scan_keys[1]; diff --git a/src/include/access/vertex.h b/src/include/access/vertex.h index 2505cb61..2a265a6e 100644 --- a/src/include/access/vertex.h +++ b/src/include/access/vertex.h @@ -21,6 +21,10 @@ typedef struct VertexScanDescData TableScanDescData rs_base; /* AM independent part of the descriptor */ + int ndesc; + TableScanDesc **desc; + + /* Hash value of the scan key, ie, the hash key we seek */ uint32 hashso_sk_hash; diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h index 4163e30b..d77e5500 100644 --- a/src/include/catalog/ag_label.h +++ b/src/include/catalog/ag_label.h @@ -63,6 +63,11 @@ #define ag_label_label_index_id() \ ag_relation_id("ag_label_ltree_index", "index") +#define edge_metatable_id() \ + ag_relation_id("edge_metatable_id", "index") +#define edge_metatable_id_level() \ + ag_relation_id("edge_metatable_id_level", "index") + #define LABEL_ID_SEQ_NAME "_label_id_seq" #define LABEL_KIND_VERTEX 'v' diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index 1c7a3989..58fbe857 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -53,4 +53,5 @@ label_cache_data *search_label_name_graph_cache(const char *name, Oid graph); label_cache_data *search_label_graph_oid_cache(Oid graph, int32 id); label_cache_data *search_label_relation_cache(Oid relation); + #endif From 5a305fe93f4b14a06b4dd31e293b23028b95aa9a Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sun, 3 Aug 2025 20:49:33 -0500 Subject: [PATCH 22/56] update --- postgraph--0.1.0.sql | 5 + .../vertex_heap/vertex_heapam_handler.c | 214 +++++++++++++++++- src/backend/utils/cache/ag_cache.c | 127 +++++++++++ src/include/utils/ag_cache.h | 2 +- 4 files changed, 344 insertions(+), 4 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 249120b8..c3b7a21f 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -75,6 +75,11 @@ CREATE UNIQUE INDEX ag_label_relation_index ON ag_label USING btree (relation); +CREATE UNIQUE INDEX ag_label_vertex_adjlist_index +ON ag_label +USING btree (vertex_adjlist); + + CREATE UNIQUE INDEX ag_label_path_graph_index ON ag_label USING btree (label_path, graph); diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index e9554589..24d08a69 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -50,6 +50,9 @@ #include "utils/builtins.h" #include "utils/rel.h" +#include "access/nbtree.h" +#include "nodes/nodeFuncs.h" + #include "optimizer/plancat.h" #include "utils/inval.h" #include "access/visibilitymap.h" @@ -79,7 +82,10 @@ uint32 vertex_hash_init(Relation rel, double num_tuples, ForkNumber forkNum); void vertex_hash_doinsert(Relation rel, HeapTuple itup, Relation heapRel); - +void +vertex_exec_index_build_ScanKeys(PlanState *planstate, Relation index, + List *quals, + ScanKey scanKeys, int *numScanKeys); typedef struct vertex_hash_struct { graphid id; // hash key @@ -2784,7 +2790,12 @@ static exec_seq_scan_scan_key_hook_type prev_exec_seq_scan_scan_key_hook = NULL; void postgraph_seq_scan_key_hook (SeqScanState *node, int *numScanKeys, ScanKey scanKeys) { - ereport(WARNING, errmsg("In hook")); + ereport(WARNING, errmsg("In hook")); + +vertex_exec_index_build_ScanKeys(node, node->ss.ss_currentRelation, + node->ss.ps.plan->qual, + scanKeys, numScanKeys); + return; } @@ -2796,4 +2807,201 @@ void register_seq_scan_hook(void){ void unregister_seq_scan_hook(void){ exec_seq_scan_scan_key_hook = prev_exec_seq_scan_scan_key_hook; } - \ No newline at end of file + + + + +/* + * ExecIndexBuildScanKeys + * Build the index scan keys from the index qualification expressions + * + * The index quals are passed to the index AM in the form of a ScanKey array. + * This routine sets up the ScanKeys, fills in all constant fields of the + * ScanKeys, and prepares information about the keys that have non-constant + * comparison values. We divide index qual expressions into five types: + * + * 1. Simple operator with constant comparison value ("indexkey op constant"). + * For these, we just fill in a ScanKey containing the constant value. + * + * 2. Simple operator with non-constant value ("indexkey op expression"). + * For these, we create a ScanKey with everything filled in except the + * expression value, and set up an IndexRuntimeKeyInfo struct to drive + * evaluation of the expression at the right times. + * + * 3. RowCompareExpr ("(indexkey, indexkey, ...) op (expr, expr, ...)"). + * For these, we create a header ScanKey plus a subsidiary ScanKey array, + * as specified in access/skey.h. The elements of the row comparison + * can have either constant or non-constant comparison values. + * + * 4. ScalarArrayOpExpr ("indexkey op ANY (array-expression)"). If the index + * supports amsearcharray, we handle these the same as simple operators, + * setting the SK_SEARCHARRAY flag to tell the AM to handle them. Otherwise, + * we create a ScanKey with everything filled in except the comparison value, + * and set up an IndexArrayKeyInfo struct to drive processing of the qual. + * (Note that if we use an IndexArrayKeyInfo struct, the array expression is + * always treated as requiring runtime evaluation, even if it's a constant.) + * + * 5. NullTest ("indexkey IS NULL/IS NOT NULL"). We just fill in the + * ScanKey properly. + * + * This code is also used to prepare ORDER BY expressions for amcanorderbyop + * indexes. The behavior is exactly the same, except that we have to look up + * the operator differently. Note that only cases 1 and 2 are currently + * possible for ORDER BY. + * + * Input params are: + * + * planstate: executor state node we are working for + * index: the index we are building scan keys for + * quals: indexquals (or indexorderbys) expressions + * isorderby: true if processing ORDER BY exprs, false if processing quals + * *runtimeKeys: ptr to pre-existing IndexRuntimeKeyInfos, or NULL if none + * *numRuntimeKeys: number of pre-existing runtime keys + * + * Output params are: + * + * *scanKeys: receives ptr to array of ScanKeys + * *numScanKeys: receives number of scankeys + * *runtimeKeys: receives ptr to array of IndexRuntimeKeyInfos, or NULL if none + * *numRuntimeKeys: receives number of runtime keys + * *arrayKeys: receives ptr to array of IndexArrayKeyInfos, or NULL if none + * *numArrayKeys: receives number of array keys + * + * Caller may pass NULL for arrayKeys and numArrayKeys to indicate that + * IndexArrayKeyInfos are not supported. + */ +void +vertex_exec_index_build_ScanKeys(PlanState *planstate, Relation index, + List *quals, + ScanKey scanKeys, int *numScanKeys) +{ + ListCell *qual_cell; + + IndexRuntimeKeyInfo *runtime_keys; + int n_runtime_keys; + int max_runtime_keys; + int j; + + /* Allocate array for ScanKey */ + scanKeys = palloc(sizeof(IndexRuntimeKeyInfo)); + + /* + * runtime_keys array is dynamically resized as needed. We handle it this + * way so that the same runtime keys array can be shared between + * indexquals and indexorderbys, which will be processed in separate calls + * of this function. Caller must be sure to pass in NULL/0 for first + * call. + */ + numScanKeys = 0; + + + + /* + * for each opclause in the given qual, convert the opclause into a single + * scan key + */ + + j = 0; + foreach(qual_cell, quals) + { + Expr *clause = (Expr *) lfirst(qual_cell); + + Oid opno; /* operator's OID */ + RegProcedure opfuncid; /* operator proc id used in scan */ + Oid opfamily; /* opfamily of index column */ + int op_strategy; /* operator's strategy number */ + Oid op_lefttype; /* operator's declared input types */ + Oid op_righttype; + Expr *leftop; /* expr on lhs of operator */ + Expr *rightop; /* expr on rhs ... */ + AttrNumber varattno; /* att number used in scan */ + int indnkeyatts; + + indnkeyatts = 4;// id, startid, endid, properties + if (IsA(clause, OpExpr)) + { + ScanKey this_scan_key = palloc(sizeof(ScanKey)); + + + IndexRuntimeKeyInfo * runtimeKey = (IndexRuntimeKeyInfo *)scanKeys; + /* indexkey op const or indexkey op expression */ + int flags = 0; + Datum scanvalue; + + opno = ((OpExpr *) clause)->opno; + opfuncid = ((OpExpr *) clause)->opfuncid; + + /* + * leftop should be the index key Var, possibly relabeled + */ + leftop = (Expr *) get_leftop(clause); + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!(IsA(leftop, Var) && + ((Var *) leftop)->varno == INDEX_VAR)) + elog(ERROR, "indexqual doesn't have key on left side"); + + varattno = ((Var *) leftop)->varattno; + if (varattno < 1 || varattno > indnkeyatts) + elog(ERROR, "bogus index qualification"); + + if (varattno != 1) + continue; + /* + * We have to look up the operator's strategy number. This + * provides a cross-check that the operator does match the index. + */ + opfamily = index->rd_opfamily[varattno - 1]; + + bool *isorderby; + get_op_opfamily_properties(opno, opfamily, isorderby, + &op_strategy, + &op_lefttype, + &op_righttype); + + if (isorderby) + flags |= SK_ORDER_BY; + + /* + * rightop is the constant or variable comparison value + */ + rightop = (Expr *) get_rightop(clause); + + if (rightop && IsA(rightop, RelabelType)) + rightop = ((RelabelType *) rightop)->arg; + + Assert(rightop != NULL); + + + + + + /* + * initialize the scan key's fields appropriately + */ + ScanKeyEntryInitialize(this_scan_key, + flags, + varattno, /* attribute number to scan */ + op_strategy, /* op's strategy */ + op_righttype, /* strategy subtype */ + ((OpExpr *) clause)->inputcollid, /* collation */ + opfuncid, /* reg proc to use */ + scanvalue); /* constant */ + + + runtimeKey->scan_key = this_scan_key; + runtimeKey->key_expr = ExecInitExpr(rightop, planstate); + runtimeKey->key_toastable = TypeIsToastable(op_righttype); + + scanvalue = (Datum) 0; + numScanKeys++; + + break; + } + } + +} \ No newline at end of file diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index da43a0bf..c9b9ce8a 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -88,6 +88,12 @@ typedef struct label_relation_cache_entry label_cache_data data; } label_relation_cache_entry; +typedef struct label_vertex_adjlist_cache_entry +{ + Oid relation; // hash key + label_cache_data data; +} label_vertex_adjlist_cache_entry; + // ag_graph.name static HTAB *graph_name_cache_hash = NULL; @@ -109,6 +115,10 @@ static ScanKeyData label_graph_oid_scan_keys[2]; static HTAB *label_relation_cache_hash = NULL; static ScanKeyData label_relation_scan_keys[1]; +// ag_label.vertex_adjlist +static HTAB *label_vertex_adjlist_cache_hash = NULL; +static ScanKeyData label_vertex_adjlist_scan_keys[1]; + // initialize all caches static void initialize_caches(void); @@ -137,6 +147,7 @@ static void create_label_caches(void); static void create_label_name_graph_cache(void); static void create_label_graph_oid_cache(void); static void create_label_relation_cache(void); +static void create_label_vertex_adjlist_cache(void); static void invalidate_label_caches(Datum arg, Oid relid); static void invalidate_label_name_graph_cache(Oid relid); static void flush_label_name_graph_cache(void); @@ -144,6 +155,9 @@ static void invalidate_label_graph_oid_cache(Oid relid); static void flush_label_graph_oid_cache(void); static void invalidate_label_relation_cache(Oid relid); static void flush_label_relation_cache(void); +static void invalidate_vertex_adjlist_cache(Oid relid); +static void flush_label_vertex_adjlist_cache(void); + static label_cache_data *search_label_name_graph_cache_miss(Name name, Oid graph); static void *label_name_graph_cache_hash_search(Name name, Oid graph, @@ -153,6 +167,9 @@ static label_cache_data *search_label_graph_oid_cache_miss(Oid graph, static void *label_graph_oid_cache_hash_search(uint32 graph, int32 id, HASHACTION action, bool *found); static label_cache_data *search_label_relation_cache_miss(Oid relation); + +static label_cache_data *search_label_vertex_adjlist_cache_miss(Oid relation); + static void fill_label_cache_data(label_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc); @@ -503,6 +520,8 @@ static void create_label_caches(void) create_label_name_graph_cache(); create_label_graph_oid_cache(); create_label_relation_cache(); + create_label_vertex_adjlist_cache(); + } static void create_label_name_graph_cache(void) @@ -555,6 +574,24 @@ static void create_label_relation_cache(void) &hash_ctl, HASH_ELEM | HASH_BLOBS); } + +static void create_label_vertex_adjlist_cache(void) +{ + HASHCTL hash_ctl; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(label_vertex_adjlist_cache_entry); + + /* + * Please see the comment of hash_create() for the nelem value 16 here. + * HASH_BLOBS flag is set because the size of the key is sizeof(uint32). + */ + label_vertex_adjlist_cache_hash = ShmemInitHash("ag_label (vertex_adjlist) cache", 16, 1000, + &hash_ctl, HASH_ELEM | HASH_BLOBS); +} + + static void invalidate_label_caches(Datum arg, Oid relid) { Assert(label_name_graph_cache_hash); @@ -564,12 +601,14 @@ static void invalidate_label_caches(Datum arg, Oid relid) invalidate_label_name_graph_cache(relid); invalidate_label_graph_oid_cache(relid); invalidate_label_relation_cache(relid); + invalidate_label_vertex_adjlist_cache(relid); } else { flush_label_name_graph_cache(); flush_label_graph_oid_cache(); flush_label_relation_cache(); + flush_label_vertex_adjlist_cache(); } } @@ -722,6 +761,32 @@ static void flush_label_relation_cache(void) } } + +static void flush_label_vertex_adjlist_cache(void) +{ + HASH_SEQ_STATUS hash_seq; + + hash_seq_init(&hash_seq, label_vertex_adjlist_cache_hash); + for (;;) + { + label_vertex_adjlist_cache_entry *entry; + void *removed; + + entry = hash_seq_search(&hash_seq); + if (!entry) + break; + + removed = hash_search(label_vertex_adjlist_cache_hash, &entry->relation, + HASH_REMOVE, NULL); + if (!removed) + { + ereport(ERROR, + (errmsg_internal("label (vertex_adjlist) cache corrupted"))); + } + } +} + + label_cache_data *search_label_name_graph_cache(const char *name, Oid graph) { NameData name_key; @@ -939,6 +1004,68 @@ static label_cache_data *search_label_relation_cache_miss(Oid relation) return entry; } + + +label_cache_data *search_label_vertex_adjlist_cache(Oid relation) +{ + label_vertex_adjlist_cache_entry *entry; + + initialize_caches(); + + entry = hash_search(label_vertex_adjlist_cache_hash, &relation, HASH_FIND, NULL); + if (entry) + return &entry->data; + + return search_label_vertex_adjlist_cache_miss(relation); +} + +static label_cache_data *search_label_vertex_adjlist_cache_miss(Oid relation) +{ + ScanKeyData scan_keys[1]; + Relation ag_label; + SysScanDesc scan_desc; + HeapTuple tuple; + bool found; + label_cache_data *entry; + + memcpy(scan_keys, label_relation_scan_keys, + sizeof(label_relation_scan_keys)); + scan_keys[0].sk_argument = ObjectIdGetDatum(relation); + + /* + * Calling table_open() might call AcceptInvalidationMessage() and that + * might invalidate the label caches. This is OK because this function is + * called when the desired entry is not in the cache. + */ + ag_label = table_open(ag_label_vertex_adjlist_id(), AccessShareLock); + scan_desc = systable_beginscan(ag_label, ag_label_vertex_adjlist_index_id(), true, + NULL, 1, scan_keys); + + // don't need to loop over scan_desc because ag_label_relation_index is + // UNIQUE + tuple = systable_getnext(scan_desc); + if (!HeapTupleIsValid(tuple)) + { + systable_endscan(scan_desc); + table_close(ag_label, AccessShareLock); + + return NULL; + } + + // get a new entry + entry = hash_search(label_relation_cache_hash, &relation, HASH_ENTER, + &found); + Assert(!found); // no concurrent update on label_relation_cache_hash + + // fill the new entry with the retrieved tuple + fill_label_cache_data(entry, tuple, RelationGetDescr(ag_label)); + + systable_endscan(scan_desc); + table_close(ag_label, AccessShareLock); + + return entry; +} + static void fill_label_cache_data(label_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc) { diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index 58fbe857..67c4d02a 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -52,6 +52,6 @@ label_cache_data *search_label_oid_cache(Oid oid); label_cache_data *search_label_name_graph_cache(const char *name, Oid graph); label_cache_data *search_label_graph_oid_cache(Oid graph, int32 id); label_cache_data *search_label_relation_cache(Oid relation); - +label_cache_data *search_label_vertex_adjlist_cache(Oid relation); #endif From 9c04dc2608fcf5f9fc6aef899454b10ecfa665ce Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Mon, 4 Aug 2025 08:58:56 -0700 Subject: [PATCH 23/56] Update --- Makefile | 1 - regress/sql/cypher_create.sql | 5 + regress/sql/vertex_am.sql | 8 +- .../vertex_heap/vertex_heapam_handler.c | 21 +- src/backend/parser/cypher_clause.c | 315 +++++++++++++----- src/backend/parser/cypher_gram.y | 4 +- src/backend/utils/cache/ag_cache.c | 28 +- src/include/catalog/ag_label.h | 4 + 8 files changed, 292 insertions(+), 94 deletions(-) diff --git a/Makefile b/Makefile index 2e7c26b3..d4fea7b0 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,6 @@ EXTENSION = postgraph DATA = postgraph--0.1.0.sql REGRESS = new_cypher \ - vertex_am \ cypher_create srcdir=`pwd` diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 024e8d99..428258d0 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -46,6 +46,11 @@ MATCH () RETURN 1; CREATE ()-[]->(); +EXPLAIN MATCH ()-[]->() RETURN 1; + + +MATCH ()-[]->() RETURN 1; + SELECT * FROM cypher_create._ag_label_vertex; SELECT * FROM cypher_create._adj__adj__ag_label_vertex; diff --git a/regress/sql/vertex_am.sql b/regress/sql/vertex_am.sql index 4a90193d..9286c590 100644 --- a/regress/sql/vertex_am.sql +++ b/regress/sql/vertex_am.sql @@ -17,16 +17,16 @@ -- Regression tests don't preload extensions, gotta load it first LOAD 'postgraph'; -set search_path 'postgraph'; -/* +set search_path = 'postgraph'; + CREATE TABLE vertex_am_tst (id postgraph.graphid not null, props postgraph.gtype not null) USING vertex_adjlist; INSERT INTO vertex_am_tst (id, props) VALUES ('1'::postgraph.graphid, postgraph.gtype_build_map('id', 1)); - -SELECT '1'::postgraph.graphid OPERATOR(postgraph.=) '1'::postgraph.graphid; +/* +--SELECT '1'::postgraph.graphid OPERATOR(postgraph.=) '1'::postgraph.graphid; EXPLAIN SELECT * FROM vertex_am_tst WHERE id OPERATOR(postgraph.=) '1'::postgraph.graphid; SELECT * FROM vertex_am_tst WHERE id OPERATOR(postgraph.=) '1'::postgraph.graphid; diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 24d08a69..75021a4f 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -1301,9 +1301,16 @@ void vertex_scan_rescan(TableScanDesc scan, struct ScanKeyData *key, bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot) { VertexScanDescData *vertex_desc = sscan; - TableAmRoutine *tableam = GetHeapamTableAmRoutine(); + + if(vertex_desc->rs_base.rs_nkeys != 1) ereport(WARNING,errmsg_internal("here")); + else +ereport(WARNING,errmsg_internal("there")); + + + TableAmRoutine *tableam = GetHeapamTableAmRoutine(); + return tableam->scan_getnextslot(vertex_desc->desc[0], direction, slot); VertexHeapScanDesc so = (VertexHeapScanDesc) sscan; @@ -2784,18 +2791,18 @@ vertex_hash_doinsert(Relation rel, HeapTuple itup, Relation heapRel) } #include "executor/nodeSeqscan.h" - +#include "utils/ag_cache.h" static exec_seq_scan_scan_key_hook_type prev_exec_seq_scan_scan_key_hook = NULL; void postgraph_seq_scan_key_hook (SeqScanState *node, int *numScanKeys, ScanKey scanKeys) { - ereport(WARNING, errmsg("In hook")); - -vertex_exec_index_build_ScanKeys(node, node->ss.ss_currentRelation, - node->ss.ps.plan->qual, - scanKeys, numScanKeys); + label_cache_data *lcd = search_label_vertex_adjlist_cache(RelationGetRelid(node->ss.ss_currentRelation)); + if (lcd) { + ereport(WARNING, errmsg("Found an edge scan")); + vertex_exec_index_build_ScanKeys(node, node->ss.ss_currentRelation, node->ss.ps.plan->qual, scanKeys, numScanKeys); + } return; } diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 6fa120c5..30da0d96 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -85,6 +85,7 @@ static char *make_id_alias(char *var_name); static char *make_property_alias(char *var_name); static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); +static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); List * transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); @@ -197,6 +198,29 @@ static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pn } +static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { + ParseState *pstate = (ParseState *)cpstate; + + Oid func_oid = get_ag_func_oid("build_edge", 5, GRAPHIDOID, GRAPHIDOID, GRAPHIDOID, OIDOID, GTYPEOID); + + Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); + + Node *start_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); + + Node *end_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); + + Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), + ObjectIdGetDatum(cpstate->graph_oid), false, true); + + Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1); + + List *args = list_make5(id, start_id, end_id, graph_oid_const, props); + + FuncExpr *func_expr = makeFuncExpr(func_oid, EDGEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + func_expr->location = -1; + + return (Node *)func_expr; +} static Node * make_int_placeholder(cypher_parsestate *cpstate) { @@ -803,107 +827,244 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li // TODO: implement the new match logic //qual = transform_match_path(cpstate, query, path); - if (list_length(path->path) != 1) + if (list_length(path->path) != 1 && list_length(path->path) != 3) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH paths only support 1 vertex"))); - cypher_node *node = linitial(path->path); + ListCell *path_cell; + int i = 1; + foreach(path_cell, path->path) { + if (i % 2 == 1) { + cypher_node *node = lfirst(path_cell); - if (node->name) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH variable names are not supported"))); - else - node->name = get_next_default_alias(cpstate); + if (node->name) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH variable names are not supported"))); + else + node->name = get_next_default_alias(cpstate); - bool is_default_label = true; - if (node->label) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH labels are not supported"))); - else - node->label = AG_DEFAULT_LABEL_VERTEX; + bool is_default_label = true; + if (node->label) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH labels are not supported"))); + else + node->label = AG_DEFAULT_LABEL_VERTEX; - char *schema_name; - char *rel_name; - int resno; - TargetEntry *te; - Expr *expr; - ParseNamespaceItem *pnsi; - RangeVar *label_range_var; - Alias *alias; + char *schema_name; + char *rel_name; + int resno; + TargetEntry *te; + Expr *expr; + ParseNamespaceItem *pnsi; + RangeVar *label_range_var; + Alias *alias; + + schema_name = get_graph_namespace_name(cpstate->graph_name); + + // XXX: LTree Labeling Project, first code is here + Datum label_lquery; + if (is_default_label) + label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(node->label)); + else + label_lquery = DirectFunctionCall2(ltree_addltree, + DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), + DirectFunctionCall1(ltree_in, CStringGetDatum(node->label))); + + int ltq_query_args[2]; + ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + + Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); + + ScanKeyData scan_keys[1]; + ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); + + Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); + SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); - schema_name = get_graph_namespace_name(cpstate->graph_name); + HeapTuple tuple = systable_getnext(scan_desc); - // XXX: LTree Labeling Project, first code is here - Datum label_lquery; - if (is_default_label) - label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(node->label)); - else - label_lquery = DirectFunctionCall2(ltree_addltree, - DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), - DirectFunctionCall1(ltree_in, CStringGetDatum(node->label))); - - int ltq_query_args[2]; - ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - - Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); - - ScanKeyData scan_keys[1]; - ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); - - Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); - SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("not found %s", node->label))); + + bool is_null; + rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); - HeapTuple tuple = systable_getnext(scan_desc); + systable_endscan(scan_desc); + table_close(label_catalog, AccessShareLock); - if (!HeapTupleIsValid(tuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("not found %s", node->label))); - - bool is_null; - rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); + //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); + label_range_var = makeRangeVar(schema_name, rel_name, -1); + alias = makeAlias(node->name, NIL); - systable_endscan(scan_desc); - table_close(label_catalog, AccessShareLock); + pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); + Assert(pnsi != NULL); - //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); - label_range_var = makeRangeVar(schema_name, rel_name, -1); - alias = makeAlias(node->name, NIL); + addNSItemToQuery(pstate, pnsi, true, true, true); - pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); - Assert(pnsi != NULL); + resno = pstate->p_next_resno++; + // XXX: End LTree Code here + + expr = (Expr *)make_vertex_expr(cpstate, pnsi); + + // make target entry and add it + te = makeTargetEntry(expr, resno, node->name, false); + query->targetList = lappend(query->targetList, te); + + // id field + Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + resno = pstate->p_next_resno++; + + te = makeTargetEntry(id, resno, make_id_alias(node->name), false); + query->targetList = lappend(query->targetList, te); + + // properties field + Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); + resno = pstate->p_next_resno++; + + te = makeTargetEntry(props, resno, make_property_alias(node->name), false); + query->targetList = lappend(query->targetList, te); - addNSItemToQuery(pstate, pnsi, true, true, true); - resno = pstate->p_next_resno++; - // XXX: End LTree Code here + quals = list_concat(quals, NULL); + } else { + + + + + + + + + + + + + + + + + cypher_relationship *edge = lfirst(path_cell); + + if (edge->name) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH variable names are not supported"))); + else + edge->name = get_next_default_alias(cpstate); + + bool is_default_label = true; + if (edge->label) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH labels are not supported"))); + else + edge->label = AG_DEFAULT_LABEL_EDGE; + + char *schema_name; + char *rel_name; + int resno; + TargetEntry *te; + Expr *expr; + ParseNamespaceItem *pnsi; + RangeVar *label_range_var; + Alias *alias; + + schema_name = get_graph_namespace_name(cpstate->graph_name); + + // XXX: LTree Labeling Project, first code is here + Datum label_lquery; + if (is_default_label) + label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(edge->label)); + else + label_lquery = DirectFunctionCall2(ltree_addltree, + DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), + DirectFunctionCall1(ltree_in, CStringGetDatum(edge->label))); + + int ltq_query_args[2]; + ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + + Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); + + ScanKeyData scan_keys[1]; + ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); + + Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); + SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); + + HeapTuple tuple = systable_getnext(scan_desc); + + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("not found %s", edge->label))); + + bool is_null; + rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); + + systable_endscan(scan_desc); + table_close(label_catalog, AccessShareLock); + + //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); + label_range_var = makeRangeVar(schema_name, rel_name, -1); + alias = makeAlias(edge->name, NIL); + + pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); + Assert(pnsi != NULL); + + addNSItemToQuery(pstate, pnsi, true, true, true); + + resno = pstate->p_next_resno++; + // XXX: End LTree Code here + + expr = (Expr *)make_edge_expr(cpstate, pnsi); + + // make target entry and add it + te = makeTargetEntry(expr, resno, edge->name, false); + query->targetList = lappend(query->targetList, te); - expr = (Expr *)make_vertex_expr(cpstate, pnsi); + // id field + Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); + resno = pstate->p_next_resno++; - // make target entry and add it - te = makeTargetEntry(expr, resno, node->name, false); - query->targetList = lappend(query->targetList, te); + te = makeTargetEntry(id, resno, make_id_alias(edge->name), false); + query->targetList = lappend(query->targetList, te); - // id field - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - resno = pstate->p_next_resno++; - te = makeTargetEntry(id, resno, make_id_alias(node->name), false); - query->targetList = lappend(query->targetList, te); + // start id field + Node *startid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); + resno = pstate->p_next_resno++; - // properties field - Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); - resno = pstate->p_next_resno++; + te = makeTargetEntry(startid, resno, make_id_alias(edge->name), false); + query->targetList = lappend(query->targetList, te); - te = makeTargetEntry(props, resno, make_property_alias(node->name), false); - query->targetList = lappend(query->targetList, te); + // id field + Node *endid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); + resno = pstate->p_next_resno++; - quals = list_concat(quals, NULL); + te = makeTargetEntry(endid, resno, make_id_alias(edge->name), false); + query->targetList = lappend(query->targetList, te); + + // properties field + Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1); + resno = pstate->p_next_resno++; + + te = makeTargetEntry(props, resno, make_property_alias(edge->name), false); + query->targetList = lappend(query->targetList, te); + + + quals = list_concat(quals, NULL); + + } + i++; + } } // AND the quals for each path together diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index 8329d501..ad79d9eb 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -12813,7 +12813,9 @@ ExplainableStmt: | CreateAsStmt | CreateMatViewStmt | RefreshMatViewStmt - | ExecuteStmt + | ExecuteStmt + | cypher_stmt + ; diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index c9b9ce8a..db72504f 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -156,6 +156,7 @@ static void flush_label_graph_oid_cache(void); static void invalidate_label_relation_cache(Oid relid); static void flush_label_relation_cache(void); static void invalidate_vertex_adjlist_cache(Oid relid); +static void invalidate_label_vertex_adjlist_cache(Oid relid); static void flush_label_vertex_adjlist_cache(void); static label_cache_data *search_label_name_graph_cache_miss(Name name, @@ -502,6 +503,10 @@ static void initialize_label_caches(void) ag_cache_scan_key_init(&label_relation_scan_keys[0], Anum_ag_label_relation, F_OIDEQ); + + // ag_label.vertex_adjlist + ag_cache_scan_key_init(&label_vertex_adjlist_scan_keys[0], + 6, F_OIDEQ); create_label_caches(); /* @@ -762,6 +767,21 @@ static void flush_label_relation_cache(void) } +static void invalidate_label_vertex_adjlist_cache(Oid relid) +{ + label_vertex_adjlist_cache_entry *entry; + void *removed; + + entry = hash_search(label_vertex_adjlist_cache_hash, &relid, HASH_FIND, NULL); + if (!entry) + return; + + removed = hash_search(label_vertex_adjlist_cache_hash, &relid, HASH_REMOVE, + NULL); + if (!removed) + ereport(ERROR, (errmsg_internal("label (vertex_adjlist) cache corrupted"))); +} + static void flush_label_vertex_adjlist_cache(void) { HASH_SEQ_STATUS hash_seq; @@ -1028,8 +1048,8 @@ static label_cache_data *search_label_vertex_adjlist_cache_miss(Oid relation) bool found; label_cache_data *entry; - memcpy(scan_keys, label_relation_scan_keys, - sizeof(label_relation_scan_keys)); + memcpy(scan_keys, label_vertex_adjlist_scan_keys, + sizeof(label_vertex_adjlist_scan_keys)); scan_keys[0].sk_argument = ObjectIdGetDatum(relation); /* @@ -1037,7 +1057,7 @@ static label_cache_data *search_label_vertex_adjlist_cache_miss(Oid relation) * might invalidate the label caches. This is OK because this function is * called when the desired entry is not in the cache. */ - ag_label = table_open(ag_label_vertex_adjlist_id(), AccessShareLock); + ag_label = table_open(ag_label_relation_id(), AccessShareLock); scan_desc = systable_beginscan(ag_label, ag_label_vertex_adjlist_index_id(), true, NULL, 1, scan_keys); @@ -1053,7 +1073,7 @@ static label_cache_data *search_label_vertex_adjlist_cache_miss(Oid relation) } // get a new entry - entry = hash_search(label_relation_cache_hash, &relation, HASH_ENTER, + entry = hash_search(label_vertex_adjlist_cache_hash, &relation, HASH_ENTER, &found); Assert(!found); // no concurrent update on label_relation_cache_hash diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h index d77e5500..8430af39 100644 --- a/src/include/catalog/ag_label.h +++ b/src/include/catalog/ag_label.h @@ -60,6 +60,10 @@ ag_relation_id("ag_label_graph_oid_index", "index") #define ag_label_relation_index_id() \ ag_relation_id("ag_label_relation_index", "index") + +#define ag_label_vertex_adjlist_index_id() \ + ag_relation_id("ag_label_vertex_adjlist_index", "index") + #define ag_label_label_index_id() \ ag_relation_id("ag_label_ltree_index", "index") From ecf41297cce4ab377380f75d1e39669442ba1a4c Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Mon, 4 Aug 2025 09:55:32 -0700 Subject: [PATCH 24/56] Make cypher statements explainable --- src/backend/parser/cypher_analyze.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index c3eba450..25eb44d9 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -1205,7 +1205,32 @@ cypher_parse_analyze(RawStmt *parseTree, const char *sourceText, Node *n = parseTree->stmt; // TODO: Cypher should be an ExtensibleNode, not a List - if (IsA(n, List)) { + + if (IsA(n, ExplainStmt)) { + ExplainStmt *explainStmt = n; + if (IsA(explainStmt->query, List)) { + cypher_parsestate *cpstate = pstate; + Oid graph_oid = get_session_graph_oid(); + graph_cache_data *gcd = search_graph_namespace_cache(graph_oid); + + cpstate->graph_name = gcd->name.data; + cpstate->graph_oid = graph_oid; + + explainStmt->query = analyze_cypher(explainStmt->query, pstate, sourceText, 0, gcd->name.data, graph_oid, NULL); + Query *result; + + /* transform contained query, allowing SELECT INTO */ + //stmt->query = (Node *) transformOptionalSelectInto(pstate, stmt->query); + + /* represent the command as a utility Query */ + result = makeNode(Query); + result->commandType = CMD_UTILITY; + result->utilityStmt = (Node *) explainStmt; + result->querySource = QSRC_ORIGINAL; + result->canSetTag = true; + return result; + } + } else if (IsA(n, List)) { cypher_parsestate *cpstate = pstate; Oid graph_oid = get_session_graph_oid(); graph_cache_data *gcd = search_graph_namespace_cache(graph_oid); From 597508d74ebe52ef1e4e59fc7d87a72aa5134013 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Wed, 13 Aug 2025 18:14:17 -0700 Subject: [PATCH 25/56] Get Basic MATCH pattern ready ()-[]->() works, kinda. This is the base that everything after needs to do MATCH patterns efficiently, or... at least more efficiently than it was before --- Makefile | 1 + postgraph--0.1.0.sql | 20 + regress/sql/cypher_create.sql | 3 + .../access/vertex_heap/vertex_hash_search.c | 94 -- .../vertex_heap/vertex_heapam_handler.c | 1310 +---------------- src/backend/executor/cypher_create.c | 8 +- src/backend/parser/cypher_clause.c | 569 +++++-- src/backend/parser/cypher_expr.c | 4 + src/backend/postgraph.c | 4 +- src/backend/utils/adt/graphid.c | 18 + src/backend/utils/edge_searching.c | 192 +++ src/include/access/vertex.h | 3 - 12 files changed, 734 insertions(+), 1492 deletions(-) create mode 100644 src/backend/utils/edge_searching.c diff --git a/Makefile b/Makefile index d4fea7b0..a9c8b136 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,7 @@ OBJS = src/backend/postgraph.o \ src/backend/utils/adt/vector.o \ src/backend/utils/adt/vertex.o \ src/backend/utils/ag_func.o \ + src/backend/utils/edge_searching.o \ src/backend/utils/cache/ag_cache.o \ EXTENSION = postgraph diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index c3b7a21f..62b58279 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -429,6 +429,26 @@ PARALLEL SAFE RETURNS NULL ON NULL INPUT as 'MODULE_PATHNAME'; +CREATE TYPE edge_composite AS (id graphid, startid graphid, endid graphid, properties gtype); + +CREATE FUNCTION edge_search(graph_oid gtype, id graphid, label gtype, include_props gtype) +RETURNS TABLE (id graphid, startid graphid, endid graphid, properties gtype) +CALLED ON NULL INPUT +STABLE +PARALLEL SAFE +LANGUAGE C +COST 5000 +AS 'MODULE_PATHNAME'; + + +CREATE FUNCTION retrieve_vertex(graph_oid gtype, id graphid) +RETURNS gtype +RETURNS NULL ON NULL INPUT +STABLE +PARALLEL SAFE +LANGUAGE C +COST 5000 +AS 'MODULE_PATHNAME'; -- -- There are times when the optimizer might eliminate diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 428258d0..c77ed0b9 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -48,6 +48,7 @@ CREATE ()-[]->(); EXPLAIN MATCH ()-[]->() RETURN 1; +EXPLAIN ANALYZE MATCH ()-[]->() RETURN 1; MATCH ()-[]->() RETURN 1; @@ -57,11 +58,13 @@ SELECT * FROM cypher_create._adj__adj__ag_label_vertex; SELECT * FROM cypher_create._adj__ag_label_vertex; MATCH () RETURN 1; + CYPHER WITH 1 as a CREATE (); CREATE () RETURN 1 as a; + -- -- Clean up -- diff --git a/src/backend/access/vertex_heap/vertex_hash_search.c b/src/backend/access/vertex_heap/vertex_hash_search.c index af4437f8..04f4bedd 100644 --- a/src/backend/access/vertex_heap/vertex_hash_search.c +++ b/src/backend/access/vertex_heap/vertex_hash_search.c @@ -34,100 +34,6 @@ static void vertex_hash_readnext(TableScanDesc scan, Buffer *bufp, Page *pagep, HashPageOpaque *opaquep); static void vertex_hash_kill_items(TableScanDesc scan); -/* - * vertex_hash_next() -- Get the next item in a scan. - * - * On entry, so->currPos describes the current page, which may - * be pinned but not locked, and so->currPos.itemIndex identifies - * which item was previously returned. - * - * On successful exit, scan->xs_ctup.t_self is set to the TID - * of the next heap tuple. so->currPos is updated as needed. - * - * On failure exit (no more tuples), we return false with pin - * held on bucket page but no pins or locks held on overflow - * page. - */ -bool -vertex_hash_next(TableScanDesc scan, ScanDirection dir) -{ - Relation rel = scan->rs_rd; - VertexHeapScanDesc so = (VertexHeapScanDesc) scan; - HashScanPosItem *currItem; - BlockNumber blkno; - Buffer buf; - bool end_of_scan = false; - - /* - * Advance to the next tuple on the current page; or if done, try to read - * data from the next or previous page based on the scan direction. Before - * moving to the next or previous page make sure that we deal with all the - * killed items. - */ - if (ScanDirectionIsForward(dir)) - { - if (++so->currPos.itemIndex > so->currPos.lastItem) - { - if (so->numKilled > 0) - vertex_hash_kill_items(scan); - - blkno = so->currPos.nextPage; - if (BlockNumberIsValid(blkno)) - { - buf = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE); - TestForOldSnapshot(scan->rs_snapshot, rel, BufferGetPage(buf)); - if (!vertex_hash_readpage(scan, &buf, dir)) - end_of_scan = true; - } - else - end_of_scan = true; - } - } - else - { - if (--so->currPos.itemIndex < so->currPos.firstItem) - { - if (so->numKilled > 0) - vertex_hash_kill_items(scan); - - blkno = so->currPos.prevPage; - if (BlockNumberIsValid(blkno)) - { - buf = _hash_getbuf(rel, blkno, HASH_READ, - LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); - TestForOldSnapshot(scan->rs_snapshot, rel, BufferGetPage(buf)); - - /* - * We always maintain the pin on bucket page for whole scan - * operation, so releasing the additional pin we have acquired - * here. - */ - if (buf == so->hashso_bucket_buf || - buf == so->hashso_split_bucket_buf) - _hash_dropbuf(rel, buf); - - if (!vertex_hash_readpage(scan, &buf, dir)) - end_of_scan = true; - } - else - end_of_scan = true; - } - } - - if (end_of_scan) - { - //_hash_dropscanbuf(rel,( TableScanDesc)so); TODO - HashScanPosInvalidate(so->currPos); - return false; - } - - /* OK, itemIndex says what to return */ - currItem = &so->currPos.items[so->currPos.itemIndex]; - //scan->xs_heaptid = currItem->heapTid; - - return true; -} - /* * Advance to next page in a bucket, if any. If we are scanning the bucket * being populated during split operation then this function advances to the diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index 75021a4f..e649a042 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -72,20 +72,11 @@ static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, uint16 *new_infomask2); -static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); -static MultiXactStatus -get_mxact_status_for_lock(LockTupleMode mode, bool is_update); -static bool heap_acquire_tuplock(Relation relation, ItemPointer tid, - LockTupleMode mode, LockWaitPolicy wait_policy, - bool *have_tuple_lock); -uint32 -vertex_hash_init(Relation rel, double num_tuples, ForkNumber forkNum); -void -vertex_hash_doinsert(Relation rel, HeapTuple itup, Relation heapRel); + void vertex_exec_index_build_ScanKeys(PlanState *planstate, Relation index, List *quals, - ScanKey scanKeys, int *numScanKeys); + ScanKey *scanKeys, int *numScanKeys); typedef struct vertex_hash_struct { graphid id; // hash key @@ -156,15 +147,6 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid, - - - - - - - - -//BlackPink /* * * Given infomask/infomask2, compute the bits that must be saved in the * * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock, @@ -286,371 +268,6 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] = */ -/* - * * Given an original set of Xmax and infomask, and a transaction (identified by - * * add_to_xmax) acquiring a new lock of some mode, compute the new Xmax and - * * corresponding infomasks to use on the tuple. - * * - * * Note that this might have side effects such as creating a new MultiXactId. - * * - * * Most callers will have called HeapTupleSatisfiesUpdate before this function; - * * that will have set the HEAP_XMAX_INVALID bit if the xmax was a MultiXactId - * * but it was not running anymore. There is a race condition, which is that the - * * MultiXactId may have finished since then, but that uncommon case is handled - * * either here, or within MultiXactIdExpand. - * * - * * There is a similar race condition possible when the old xmax was a regular - * * TransactionId. We test TransactionIdIsInProgress again just to narrow the - * * window, but it's still possible to end up creating an unnecessary - * * MultiXactId. Fortunately this is harmless. - * */ -static void -compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask, - uint16 old_infomask2, TransactionId add_to_xmax, - LockTupleMode mode, bool is_update, - TransactionId *result_xmax, uint16 *result_infomask, - uint16 *result_infomask2) -{ - TransactionId new_xmax; - uint16 new_infomask, - new_infomask2; - - Assert(TransactionIdIsCurrentTransactionId(add_to_xmax)); - -l5: - new_infomask = 0; - new_infomask2 = 0; - if (old_infomask & HEAP_XMAX_INVALID) - { - /* - * * No previous locker; we just insert our own TransactionId. - * * - * * Note that it's critical that this case be the first one checked, - * * because there are several blocks below that come back to this one - * * to implement certain optimizations; old_infomask might contain - * * other dirty bits in those cases, but we don't really care. - * */ - if (is_update) - { - new_xmax = add_to_xmax; - if (mode == LockTupleExclusive) - new_infomask2 |= HEAP_KEYS_UPDATED; - } - else - { - new_infomask |= HEAP_XMAX_LOCK_ONLY; - switch (mode) - { - case LockTupleKeyShare: - new_xmax = add_to_xmax; - new_infomask |= HEAP_XMAX_KEYSHR_LOCK; - break; - case LockTupleShare: - new_xmax = add_to_xmax; - new_infomask |= HEAP_XMAX_SHR_LOCK; - break; - case LockTupleNoKeyExclusive: - new_xmax = add_to_xmax; - new_infomask |= HEAP_XMAX_EXCL_LOCK; - break; - case LockTupleExclusive: - new_xmax = add_to_xmax; - new_infomask |= HEAP_XMAX_EXCL_LOCK; - new_infomask2 |= HEAP_KEYS_UPDATED; - break; - default: - new_xmax = InvalidTransactionId; /* silence compiler */ - elog(ERROR, "invalid lock mode"); - } - } - } - else if (old_infomask & HEAP_XMAX_IS_MULTI) - { - MultiXactStatus new_status; - - /* - * * Currently we don't allow XMAX_COMMITTED to be set for multis, so - * * cross-check. - * */ - Assert(!(old_infomask & HEAP_XMAX_COMMITTED)); - - /* - * * A multixact together with LOCK_ONLY set but neither lock bit set - * * (i.e. a pg_upgraded share locked tuple) cannot possibly be running - * * anymore. This check is critical for databases upgraded by - * * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume - * * that such multis are never passed. - * */ - if (HEAP_LOCKED_UPGRADED(old_infomask)) - { - old_infomask &= ~HEAP_XMAX_IS_MULTI; - old_infomask |= HEAP_XMAX_INVALID; - goto l5; - } - - /* - * * If the XMAX is already a MultiXactId, then we need to expand it to - * * include add_to_xmax; but if all the members were lockers and are - * * all gone, we can do away with the IS_MULTI bit and just set - * * add_to_xmax as the only locker/updater. If all lockers are gone - * * and we have an updater that aborted, we can also do without a - * * multi. - * * - * * The cost of doing GetMultiXactIdMembers would be paid by - * * MultiXactIdExpand if we weren't to do this, so this check is not - * * incurring extra work anyhow. - * */ - if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))) - { - if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) || - !TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax, - old_infomask))) - { - /* - * * Reset these bits and restart; otherwise fall through to - * * create a new multi below. - * */ - old_infomask &= ~HEAP_XMAX_IS_MULTI; - old_infomask |= HEAP_XMAX_INVALID; - goto l5; - } - } - - new_status = get_mxact_status_for_lock(mode, is_update); - - new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax, - new_status); - GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); - } - else if (old_infomask & HEAP_XMAX_COMMITTED) - { - /* - * * It's a committed update, so we need to preserve him as updater of - * * the tuple. - * */ - MultiXactStatus status; - MultiXactStatus new_status; - - if (old_infomask2 & HEAP_KEYS_UPDATED) - status = MultiXactStatusUpdate; - else - status = MultiXactStatusNoKeyUpdate; - - new_status = get_mxact_status_for_lock(mode, is_update); - - /* - * * since it's not running, it's obviously impossible for the old - * * updater to be identical to the current one, so we need not check - * * for that case as we do in the block above. - * */ - new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status); - GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); - } - else if (TransactionIdIsInProgress(xmax)) - { - /* - * * If the XMAX is a valid, in-progress TransactionId, then we need to - * * create a new MultiXactId that includes both the old locker or - * * updater and our own TransactionId. - * */ - MultiXactStatus new_status; - MultiXactStatus old_status; - LockTupleMode old_mode; - - if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)) - { - if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask)) - old_status = MultiXactStatusForKeyShare; - else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask)) - old_status = MultiXactStatusForShare; - else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask)) - { - if (old_infomask2 & HEAP_KEYS_UPDATED) - old_status = MultiXactStatusForUpdate; - else - old_status = MultiXactStatusForNoKeyUpdate; - } - else - { - /* - * * LOCK_ONLY can be present alone only when a page has been - * * upgraded by pg_upgrade. But in that case, - * * TransactionIdIsInProgress() should have returned false. We - * * assume it's no longer locked in this case. - * */ - elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax); - old_infomask |= HEAP_XMAX_INVALID; - old_infomask &= ~HEAP_XMAX_LOCK_ONLY; - goto l5; - } - } - else - { - /* it's an update, but which kind? */ - if (old_infomask2 & HEAP_KEYS_UPDATED) - old_status = MultiXactStatusUpdate; - else - old_status = MultiXactStatusNoKeyUpdate; - } - - old_mode = TUPLOCK_from_mxstatus(old_status); - - /* - * * If the lock to be acquired is for the same TransactionId as the - * * existing lock, there's an optimization possible: consider only the - * * strongest of both locks as the only one present, and restart. - * */ - if (xmax == add_to_xmax) - { - /* - * * Note that it's not possible for the original tuple to be - * * updated: we wouldn't be here because the tuple would have been - * * invisible and we wouldn't try to update it. As a subtlety, - * * this code can also run when traversing an update chain to lock - * * future versions of a tuple. But we wouldn't be here either, - * * because the add_to_xmax would be different from the original - * * updater. - * */ - Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)); - - /* acquire the strongest of both */ - if (mode < old_mode) - mode = old_mode; - /* mustn't touch is_update */ - - old_infomask |= HEAP_XMAX_INVALID; - goto l5; - } - - /* otherwise, just fall back to creating a new multixact */ - new_status = get_mxact_status_for_lock(mode, is_update); - new_xmax = MultiXactIdCreate(xmax, old_status, - add_to_xmax, new_status); - GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); - } - else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) && - TransactionIdDidCommit(xmax)) - { - /* - * * It's a committed update, so we gotta preserve him as updater of the - * * tuple. - * */ - MultiXactStatus status; - MultiXactStatus new_status; - - if (old_infomask2 & HEAP_KEYS_UPDATED) - status = MultiXactStatusUpdate; - else - status = MultiXactStatusNoKeyUpdate; - - new_status = get_mxact_status_for_lock(mode, is_update); - - /* - * * since it's not running, it's obviously impossible for the old - * * updater to be identical to the current one, so we need not check - * * for that case as we do in the block above. - * */ - new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status); - GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); - } - else - { - /* - * * Can get here iff the locking/updating transaction was running when - * * the infomask was extracted from the tuple, but finished before - * * TransactionIdIsInProgress got to run. Deal with it as if there was - * * no locker at all in the first place. - * */ - old_infomask |= HEAP_XMAX_INVALID; - goto l5; - } - - *result_infomask = new_infomask; - *result_infomask2 = new_infomask2; - *result_xmax = new_xmax; -} - - - -/* - * * For a given MultiXactId, return the hint bits that should be set in the - * * tuple's infomask. - * * - * * Normally this should be called for a multixact that was just created, and - * * so is on our local cache, so the GetMembers call is fast. - * */ -static void -GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, - uint16 *new_infomask2) -{ - int nmembers; - MultiXactMember *members; - int i; - uint16 bits = HEAP_XMAX_IS_MULTI; - uint16 bits2 = 0; - bool has_update = false; - LockTupleMode strongest = LockTupleKeyShare; - - /* - * * We only use this in multis we just created, so they cannot be values - * * pre-pg_upgrade. - * */ - nmembers = GetMultiXactIdMembers(multi, &members, false, false); - - for (i = 0; i < nmembers; i++) - { - LockTupleMode mode; - - /* - * * Remember the strongest lock mode held by any member of the - * * multixact. - * */ - mode = TUPLOCK_from_mxstatus(members[i].status); - if (mode > strongest) - strongest = mode; - - /* See what other bits we need */ - switch (members[i].status) - { - case MultiXactStatusForKeyShare: - case MultiXactStatusForShare: - case MultiXactStatusForNoKeyUpdate: - break; - - case MultiXactStatusForUpdate: - bits2 |= HEAP_KEYS_UPDATED; - break; - - case MultiXactStatusNoKeyUpdate: - has_update = true; - break; - - case MultiXactStatusUpdate: - bits2 |= HEAP_KEYS_UPDATED; - has_update = true; - break; - } - } - - if (strongest == LockTupleExclusive || - strongest == LockTupleNoKeyExclusive) - bits |= HEAP_XMAX_EXCL_LOCK; - else if (strongest == LockTupleShare) - bits |= HEAP_XMAX_SHR_LOCK; - else if (strongest == LockTupleKeyShare) - bits |= HEAP_XMAX_KEYSHR_LOCK; - - if (!has_update) - bits |= HEAP_XMAX_LOCK_ONLY; - - if (nmembers > 0) - pfree(members); - - *new_infomask = bits; - *new_infomask2 = bits2; -} - - /* * * Build a heap tuple representing the configured REPLICA IDENTITY to represent * * the old tuple in a UPDATE or DELETE. @@ -1256,15 +873,13 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, //vertex_desc->rs_base.rs_maxtid = NULL; vertex_desc->rs_base.rs_flags = flags; vertex_desc->rs_base.rs_parallel = NULL; - vertex_desc->ndesc = 1; - //Relation rel = table_open(RelnameGetRelid(make_vertex_adjlist_alias(RelationGetRelationName(relation))), AccessShareLock); TableAmRoutine *tableam = GetHeapamTableAmRoutine(); Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(RelationGetRelid(relation))), get_rel_namespace(RelationGetRelid(relation))); + ereport(WARNING, errmsg("begin_scan runtimeKey %p",&key)); Relation rel = RelationIdGetRelation(oid); - TableScanDesc *desc = tableam->scan_begin(rel, - snapshot, 0, NULL, parallel_scan, flags); + TableScanDesc *desc = tableam->scan_begin(rel, snapshot, nkeys, key, parallel_scan, flags); vertex_desc->desc = palloc(sizeof (TableScanDesc *) * vertex_desc->ndesc); vertex_desc->desc[0] = desc; return vertex_desc; @@ -1291,7 +906,7 @@ void vertex_scan_rescan(TableScanDesc scan, struct ScanKeyData *key, bool set_params, bool allow_strat, bool allow_sync, bool allow_pagemode) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_estimate not implemented"))); + errmsg_internal("vertex_scan_rescan not implemented"))); } @@ -1302,29 +917,43 @@ bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot) { VertexScanDescData *vertex_desc = sscan; - - if(vertex_desc->rs_base.rs_nkeys != 1) - ereport(WARNING,errmsg_internal("here")); - else -ereport(WARNING,errmsg_internal("there")); + /*if(vertex_desc->rs_base.rs_nkeys != 1) + ereport(ERROR,errmsg_internal("edges are a key value store, provide the key")); - + ereport(WARNING, errmsg("scankey in get slot %p %p", vertex_desc->rs_base.rs_key, &vertex_desc->rs_base.rs_key)); + if(vertex_desc->rs_base.rs_key == 0) + ereport(ERROR,errmsg_internal("edges are a key value store, provide the key")); + ereport(WARNING, errmsg("runtimeKey %p", vertex_desc->rs_base.rs_key)); + TableAmRoutine *tableam = GetHeapamTableAmRoutine(); + IndexRuntimeKeyInfo *indexRuntimeKeyInfo = (IndexRuntimeKeyInfo *)vertex_desc->rs_base.rs_key; + */ TableAmRoutine *tableam = GetHeapamTableAmRoutine(); - + //ereport(WARNING, errmsg("Datum %d graphid %ld", ((Datum)((IndexRuntimeKeyInfo *)vertex_desc->rs_base.rs_key)->scan_key->sk_argument)), DATUM_GET_GRAPHID((Datum)((IndexRuntimeKeyInfo *)vertex_desc->rs_base.rs_key)->scan_key->sk_argument)); + + bool result =true; return tableam->scan_getnextslot(vertex_desc->desc[0], direction, slot); +/* while (result) { + result = tableam->scan_getnextslot(vertex_desc->desc[0], direction, slot); - VertexHeapScanDesc so = (VertexHeapScanDesc) sscan; - bool res; + if (!result) { + ereport(WARNING, errmsg("result")); + return result; + } + SeqScanState *node= ((IndexRuntimeKeyInfo *)vertex_desc->rs_base.rs_key)->key_expr->parent; + ExprState *qual = node->ss.ps.qual; + ExprContext *econtext = node->ss.ps.ps_ExprContext; - /* - * If we've already initialized this scan, we can just advance it in the - * appropriate direction. If we haven't done so yet, we call a routine to - * get the first item in the scan. - */ - if (!HashScanPosIsValid(so->currPos)) - return vertex_hash_first(sscan, direction); + ResetExprContext(econtext); + + econtext->ecxt_scantuple = slot; - return vertex_hash_next(sscan, direction); + if (qual == NULL || ExecQual(qual, econtext)) { + ereport(WARNING, errmsg("qual passed")); + return true; + } else + ereport(WARNING, errmsg("qual failed")); + } + return result;*/ } /* ------------------------------------------------------------------------ @@ -1512,63 +1141,6 @@ TransactionId vertex_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstat return InvalidTransactionId; } -Page -vertex_RelationPutHeapTuple(Relation relation, - Buffer buffer, - HeapTuple tuple, - bool token); - -Page -vertex_RelationPutHeapTuple(Relation relation, - Buffer buffer, - HeapTuple tuple, - bool token) -{ - Page pageHeader; - OffsetNumber offnum; - - /* - * A tuple that's being inserted speculatively should already have its - * token set. - */ - Assert(!token || HeapTupleHeaderIsSpeculative(tuple->t_data)); - - /* - * Do not allow tuples with invalid combinations of hint bits to be placed - * on a page. This combination is detected as corruption by the - * contrib/amcheck logic, so if you disable this assertion, make - * corresponding changes there. - */ - Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) && - (tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI))); - - /* Add the tuple to the page */ - pageHeader = BufferGetPage(buffer); - - offnum = PageAddItem(pageHeader, (Item) tuple->t_data, - tuple->t_len, InvalidOffsetNumber, false, true); - - if (offnum == InvalidOffsetNumber) - elog(PANIC, "failed to add tuple to page"); - - /* Update tuple->t_self to the actual position where it was stored */ - ItemPointerSet(&(tuple->t_self), BufferGetBlockNumber(buffer), offnum); - - /* - * Insert the correct position into CTID of the stored tuple, too (unless - * this is a speculative insertion, in which case the token is held in - * CTID field instead) - */ - if (!token) - { - ItemId itemId = PageGetItemId(pageHeader, offnum); - HeapTupleHeader item = (HeapTupleHeader) PageGetItem(pageHeader, itemId); - - item->t_ctid = tuple->t_self; - } - return pageHeader; -} - /* ------------------------------------------------------------------------ @@ -1694,27 +1266,6 @@ void vertex_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, if(persistence != RELPERSISTENCE_PERMANENT || rel->rd_rel->relkind != RELKIND_RELATION) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex am can only work on permenant tables"))); - - - - SMgrRelation srel; - *minmulti = GetOldestMultiXactId(); - - srel = RelationCreateStorage(*newrnode, persistence); - smgrcreate(srel, INIT_FORKNUM, false); - log_smgrcreate(newrnode, INIT_FORKNUM); - smgrimmedsync(srel, INIT_FORKNUM); - - smgrclose(srel); - HASHCTL hash_ctl; - - MemSet(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = sizeof(graphid); - hash_ctl.entrysize = sizeof(vertex_hash_struct); - - HTAB *rel_hash_table = ShmemInitHash(RelationGetRelationName(rel),16, 1000, &hash_ctl, - HASH_ELEM | HASH_BLOBS | HASH_COMPARE); - vertex_hash_init(rel, 0, INIT_FORKNUM); } /* @@ -2222,793 +1773,4 @@ Datum vertex_adjlist_tableam_handler(PG_FUNCTION_ARGS) { PG_RETURN_POINTER(&vertex_heapam_methods); -} - - - -/* - * * Perform XLogInsert of an XLOG_HEAP2_NEW_CID record - * * - * * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog - * * tuples. - * */ -static XLogRecPtr -log_heap_new_cid(Relation relation, HeapTuple tup) -{ - xl_heap_new_cid xlrec; - - XLogRecPtr recptr; - HeapTupleHeader hdr = tup->t_data; - - Assert(ItemPointerIsValid(&tup->t_self)); - Assert(tup->t_tableOid != InvalidOid); - - xlrec.top_xid = GetTopTransactionId(); - xlrec.target_node = relation->rd_node; - xlrec.target_tid = tup->t_self; - - /* - * * If the tuple got inserted & deleted in the same TX we definitely have a - * * combo CID, set cmin and cmax. - * */ - if (hdr->t_infomask & HEAP_COMBOCID) - { - Assert(!(hdr->t_infomask & HEAP_XMAX_INVALID)); - Assert(!HeapTupleHeaderXminInvalid(hdr)); - xlrec.cmin = HeapTupleHeaderGetCmin(hdr); - xlrec.cmax = HeapTupleHeaderGetCmax(hdr); - xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr); - } - /* No combo CID, so only cmin or cmax can be set by this TX */ - else - { - /* - * * Tuple inserted. - * * - * * We need to check for LOCK ONLY because multixacts might be - * * transferred to the new tuple in case of FOR KEY SHARE updates in - * * which case there will be an xmax, although the tuple just got - * * inserted. - * */ - if (hdr->t_infomask & HEAP_XMAX_INVALID || - HEAP_XMAX_IS_LOCKED_ONLY(hdr->t_infomask)) - { - xlrec.cmin = HeapTupleHeaderGetRawCommandId(hdr); - xlrec.cmax = InvalidCommandId; - } - /* Tuple from a different tx updated or deleted. */ - else - { - xlrec.cmin = InvalidCommandId; - xlrec.cmax = HeapTupleHeaderGetRawCommandId(hdr); - - } - xlrec.combocid = InvalidCommandId; - } - - /* - * * Note that we don't need to register the buffer here, because this - * * operation does not modify the page. The insert/update/delete that - * * called us certainly did, but that's WAL-logged separately. - * */ - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, SizeOfHeapNewCid); - - /* will be looked at irrespective of origin */ - - recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_NEW_CID); - - return recptr; -} - - - -/* - * * Return the MultiXactStatus corresponding to the given tuple lock mode. - * */ -static MultiXactStatus -get_mxact_status_for_lock(LockTupleMode mode, bool is_update) -{ - int retval; - - if (is_update) - retval = tupleLockExtraInfo[mode].updstatus; - else - retval = tupleLockExtraInfo[mode].lockstatus; - - if (retval == -1) - elog(ERROR, "invalid lock tuple mode %d/%s", mode, - is_update ? "true" : "false"); - - return (MultiXactStatus) retval; -} - - -/* - * * Acquire heavyweight lock on the given tuple, in preparation for acquiring - * * its normal, Xmax-based tuple lock. - * * - * * have_tuple_lock is an input and output parameter: on input, it indicates - * * whether the lock has previously been acquired (and this function does - * * nothing in that case). If this function returns success, have_tuple_lock - * * has been flipped to true. - * * - * * Returns false if it was unable to obtain the lock; this can only happen if - * * wait_policy is Skip. - * */ -static bool -heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode, - LockWaitPolicy wait_policy, bool *have_tuple_lock) -{ - if (*have_tuple_lock) - return true; - - switch (wait_policy) - { - case LockWaitBlock: - LockTupleTuplock(relation, tid, mode); - break; - - case LockWaitSkip: - if (!ConditionalLockTupleTuplock(relation, tid, mode)) - return false; - break; - - case LockWaitError: - if (!ConditionalLockTupleTuplock(relation, tid, mode)) - ereport(ERROR, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("could not obtain lock on row in relation \"%s\"", - RelationGetRelationName(relation)))); - break; - } - *have_tuple_lock = true; - - return true; -} - - -#define VertexHashGetFillFactor(relation) \ - (relation)->rd_options ? \ - ((HashOptions *) (relation)->rd_options)->fillfactor : \ - HASH_DEFAULT_FILLFACTOR -#define VertexHashGetTargetPageUsage(relation) \ - (BLCKSZ * VertexHashGetFillFactor(relation) / 100) - - -#include "access/hash_xlog.h" - -/* - * _hash_init() -- Initialize the metadata page of a hash index, - * the initial buckets, and the initial bitmap page. - * - * The initial number of buckets is dependent on num_tuples, an estimate - * of the number of tuples to be loaded into the index initially. The - * chosen number of buckets is returned. - * - * We are fairly cavalier about locking here, since we know that no one else - * could be accessing this index. In particular the rule about not holding - * multiple buffer locks is ignored. - */ -uint32 -vertex_hash_init(Relation rel, double num_tuples, ForkNumber forkNum) -{ - Buffer metabuf; - Buffer buf; - Buffer bitmapbuf; - Page pg; - HashMetaPage metap; - RegProcedure procid; - int32 data_width; - int32 item_width; - int32 ffactor; - uint32 num_buckets; - uint32 i; - bool use_wal; - zero_damaged_pages = true; - /* safety check */ - if (RelationGetNumberOfBlocksInFork(rel, forkNum) != 0) - elog(ERROR, "cannot initialize non-empty hash index \"%s\"", - RelationGetRelationName(rel)); - - /* - * WAL log creation of pages if the relation is persistent, or this is the - * init fork. Init forks for unlogged relations always need to be WAL - * logged. - */ - use_wal = RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM; - - /* - * Determine the target fill factor (in tuples per bucket) for this index. - * The idea is to make the fill factor correspond to pages about as full - * as the user-settable fillfactor parameter says. We can compute it - * exactly since the index datatype (i.e. uint32 hash key) is fixed-width. - */ - data_width = sizeof(uint32); - item_width = MAXALIGN(sizeof(IndexTupleData)) + MAXALIGN(data_width) + - sizeof(ItemIdData); /* include the line pointer */ - //TODO FillFactor is a configuration setting, don't hard coded this forever - ffactor = 75 / item_width; - /* keep to a sane range */ - if (ffactor < 10) - ffactor = 10; - - - /* - * We initialize the metapage, the first N bucket pages, and the first - * bitmap page in sequence, using _hash_getnewbuf to cause smgrextend() - * calls to occur. This ensures that the smgr level has the right idea of - * the physical index length. - * - * Critical section not required, because on error the creation of the - * whole relation will be rolled back. - */ - metabuf = _hash_getnewbuf(rel, HASH_METAPAGE, MAIN_FORKNUM); - _hash_init_metabuffer(metabuf, num_tuples, 0, ffactor, false); - MarkBufferDirty(metabuf); - - pg = BufferGetPage(metabuf); - metap = HashPageGetMeta(pg); - - /* XLOG stuff */ - if (use_wal) - { - xl_hash_init_meta_page xlrec; - XLogRecPtr recptr; - - xlrec.num_tuples = num_tuples; - xlrec.procid = metap->hashm_procid; - xlrec.ffactor = metap->hashm_ffactor; - - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, SizeOfHashInitMetaPage); - XLogRegisterBuffer(0, metabuf, REGBUF_WILL_INIT | REGBUF_STANDARD); - - recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_INIT_META_PAGE); - - PageSetLSN(BufferGetPage(metabuf), recptr); - } - - num_buckets = metap->hashm_maxbucket + 1; - - /* - * Release buffer lock on the metapage while we initialize buckets. - * Otherwise, we'll be in interrupt holdoff and the CHECK_FOR_INTERRUPTS - * won't accomplish anything. It's a bad idea to hold buffer locks for - * long intervals in any case, since that can block the bgwriter. - */ - LockBuffer(metabuf, BUFFER_LOCK_UNLOCK); - - /* - * Initialize and WAL Log the first N buckets - */ - for (i = 0; i < num_buckets; i++) - { - BlockNumber blkno; - - /* Allow interrupts, in case N is huge */ - CHECK_FOR_INTERRUPTS(); - - blkno = BUCKET_TO_BLKNO(metap, i); - buf = _hash_getnewbuf(rel, blkno, MAIN_FORKNUM); - _hash_initbuf(buf, metap->hashm_maxbucket, i, LH_BUCKET_PAGE, false); - MarkBufferDirty(buf); - - if (use_wal) - log_newpage(&rel->rd_node, - forkNum, - blkno, - BufferGetPage(buf), - true); - _hash_relbuf(rel, buf); - } - - /* Now reacquire buffer lock on metapage */ - LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE); - - /* - * Initialize bitmap page - */ - bitmapbuf = _hash_getnewbuf(rel, num_buckets + 1, MAIN_FORKNUM); - _hash_initbitmapbuffer(bitmapbuf, metap->hashm_bmsize, false); - MarkBufferDirty(bitmapbuf); - - /* add the new bitmap page to the metapage's list of bitmaps */ - /* metapage already has a write lock */ - if (metap->hashm_nmaps >= HASH_MAX_BITMAPS) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("out of overflow pages in hash index \"%s\"", - RelationGetRelationName(rel)))); - - metap->hashm_mapp[metap->hashm_nmaps] = num_buckets + 1; - - metap->hashm_nmaps++; - MarkBufferDirty(metabuf); - - /* XLOG stuff */ - if (use_wal) - { - xl_hash_init_bitmap_page xlrec; - XLogRecPtr recptr; - - xlrec.bmsize = metap->hashm_bmsize; - - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, SizeOfHashInitBitmapPage); - XLogRegisterBuffer(0, bitmapbuf, REGBUF_WILL_INIT); - - /* - * This is safe only because nobody else can be modifying the index at - * this stage; it's only visible to the transaction that is creating - * it. - */ - XLogRegisterBuffer(1, metabuf, REGBUF_STANDARD); - - recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_INIT_BITMAP_PAGE); - - PageSetLSN(BufferGetPage(bitmapbuf), recptr); - PageSetLSN(BufferGetPage(metabuf), recptr); - } - - /* all done */ - _hash_relbuf(rel, bitmapbuf); - _hash_relbuf(rel, metabuf); - - return num_buckets; -} - - - -/* - * _hash_doinsert() -- Handle insertion of a single index tuple. - * - * This routine is called by the public interface routines, hashbuild - * and hashinsert. By here, itup is completely filled in. - */ -void -vertex_hash_doinsert(Relation rel, HeapTuple itup, Relation heapRel) -{ - HeapTuple tuple = itup; - Buffer buf = InvalidBuffer; - Buffer bucket_buf; - Buffer metabuf; - HashMetaPage metap; - HashMetaPage usedmetap = NULL; - Page metapage; - Page page; - HashPageOpaque pageopaque; - Size itemsz; - bool do_expand; - uint32 hashkey; - Bucket bucket; - OffsetNumber itup_off; - - /* - * Get the hash key for the item (it's stored in the index tuple itself). - */ - bool isnull; - hashkey = heap_getattr(itup, 1, RelationGetDescr(rel), &isnull); - //hashkey = vertex_hash_get_indextuple_hashkey(itup); //TODO - - /* compute item size too */ - itemsz = (itup)->t_len; - itemsz = MAXALIGN(itemsz); /* be safe, PageAddItem will do this but we - * need to be consistent */ - -restart_insert: - - /* - * Read the metapage. We don't lock it yet; HashMaxItemSize() will - * examine pd_pagesize_version, but that can't change so we can examine it - * without a lock. - */ - metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_NOLOCK, LH_META_PAGE); - metapage = BufferGetPage(metabuf); - - /* - * Check whether the item can fit on a hash page at all. (Eventually, we - * ought to try to apply TOAST methods if not.) Note that at this point, - * itemsz doesn't include the ItemId. - * - * XXX this is useless code if we are only storing hash keys. - */ - if (itemsz > HashMaxItemSize(metapage)) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("index row size %zu exceeds hash maximum %zu", - itemsz, HashMaxItemSize(metapage)), - errhint("Values larger than a buffer page cannot be indexed."))); - - /* Lock the primary bucket page for the target bucket. */ - buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_WRITE, - &usedmetap); - Assert(usedmetap != NULL); - - CheckForSerializableConflictIn(rel, NULL, BufferGetBlockNumber(buf)); - - /* remember the primary bucket buffer to release the pin on it at end. */ - bucket_buf = buf; - - page = BufferGetPage(buf); - pageopaque = (HashPageOpaque) PageGetSpecialPointer(page); - bucket = pageopaque->hasho_bucket; - - /* - * If this bucket is in the process of being split, try to finish the - * split before inserting, because that might create room for the - * insertion to proceed without allocating an additional overflow page. - * It's only interesting to finish the split if we're trying to insert - * into the bucket from which we're removing tuples (the "old" bucket), - * not if we're trying to insert into the bucket into which tuples are - * being moved (the "new" bucket). - */ - if (H_BUCKET_BEING_SPLIT(pageopaque) && IsBufferCleanupOK(buf)) - { - /* release the lock on bucket buffer, before completing the split. */ - LockBuffer(buf, BUFFER_LOCK_UNLOCK); - - _hash_finish_split(rel, metabuf, buf, bucket, - usedmetap->hashm_maxbucket, - usedmetap->hashm_highmask, - usedmetap->hashm_lowmask); - - /* release the pin on old and meta buffer. retry for insert. */ - _hash_dropbuf(rel, buf); - _hash_dropbuf(rel, metabuf); - goto restart_insert; - } - - /* Do the insertion */ - while (PageGetFreeSpace(page) < itemsz) - { - BlockNumber nextblkno; - - /* - * Check if current page has any DEAD tuples. If yes, delete these - * tuples and see if we can get a space for the new item to be - * inserted before moving to the next page in the bucket chain. - */ - if (H_HAS_DEAD_TUPLES(pageopaque)) - { - - /*if (IsBufferCleanupOK(buf)) - { - _hash_vacuum_one_page(rel, heapRel, metabuf, buf); - - if (PageGetFreeSpace(page) >= itemsz) - break; // OK, now we have enough space - }*/ - } - - /* - * no space on this page; check for an overflow page - */ - nextblkno = pageopaque->hasho_nextblkno; - - if (BlockNumberIsValid(nextblkno)) - { - /* - * ovfl page exists; go get it. if it doesn't have room, we'll - * find out next pass through the loop test above. we always - * release both the lock and pin if this is an overflow page, but - * only the lock if this is the primary bucket page, since the pin - * on the primary bucket must be retained throughout the scan. - */ - if (buf != bucket_buf) - _hash_relbuf(rel, buf); - else - LockBuffer(buf, BUFFER_LOCK_UNLOCK); - buf = _hash_getbuf(rel, nextblkno, HASH_WRITE, LH_OVERFLOW_PAGE); - page = BufferGetPage(buf); - } - else - { - /* - * we're at the end of the bucket chain and we haven't found a - * page with enough room. allocate a new overflow page. - */ - - /* release our write lock without modifying buffer */ - LockBuffer(buf, BUFFER_LOCK_UNLOCK); - - /* chain to a new overflow page */ - buf = _hash_addovflpage(rel, metabuf, buf, (buf == bucket_buf) ? true : false); - page = BufferGetPage(buf); - - /* should fit now, given test above */ - Assert(PageGetFreeSpace(page) >= itemsz); - } - pageopaque = (HashPageOpaque) PageGetSpecialPointer(page); - Assert((pageopaque->hasho_flag & LH_PAGE_TYPE) == LH_OVERFLOW_PAGE); - Assert(pageopaque->hasho_bucket == bucket); - } - - /* - * Write-lock the metapage so we can increment the tuple count. After - * incrementing it, check to see if it's time for a split. - */ - LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE); - - /* Do the update. No ereport(ERROR) until changes are logged */ - START_CRIT_SECTION(); - - /* found page with enough space, so add the item here */ - itup_off = _hash_pgaddtup(rel, buf, itemsz, (IndexTuple)itup); - MarkBufferDirty(buf); - - /* metapage operations */ - metap = HashPageGetMeta(metapage); - metap->hashm_ntuples += 1; - - /* Make sure this stays in sync with _hash_expandtable() */ - do_expand = metap->hashm_ntuples > - (double) metap->hashm_ffactor * (metap->hashm_maxbucket + 1); - - MarkBufferDirty(metabuf); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel)) - { - xl_hash_insert xlrec; - XLogRecPtr recptr; - - xlrec.offnum = itup_off; - - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, SizeOfHashInsert); - - XLogRegisterBuffer(1, metabuf, REGBUF_STANDARD); - - XLogRegisterBuffer(0, buf, REGBUF_STANDARD); - XLogRegisterBufData(0, (char *) itup, itemsz); - - recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_INSERT); - - PageSetLSN(BufferGetPage(buf), recptr); - PageSetLSN(BufferGetPage(metabuf), recptr); - } - - END_CRIT_SECTION(); - - /* drop lock on metapage, but keep pin */ - LockBuffer(metabuf, BUFFER_LOCK_UNLOCK); - - /* - * Release the modified page and ensure to release the pin on primary - * page. - */ - _hash_relbuf(rel, buf); - if (buf != bucket_buf) - _hash_dropbuf(rel, bucket_buf); - - /* Attempt to split if a split is needed */ - if (do_expand) - _hash_expandtable(rel, metabuf); - - /* Finally drop our pin on the metapage */ - _hash_dropbuf(rel, metabuf); -} - -#include "executor/nodeSeqscan.h" -#include "utils/ag_cache.h" -static exec_seq_scan_scan_key_hook_type prev_exec_seq_scan_scan_key_hook = NULL; - -void postgraph_seq_scan_key_hook (SeqScanState *node, - int *numScanKeys, ScanKey scanKeys) { - - - label_cache_data *lcd = search_label_vertex_adjlist_cache(RelationGetRelid(node->ss.ss_currentRelation)); - if (lcd) { - ereport(WARNING, errmsg("Found an edge scan")); - vertex_exec_index_build_ScanKeys(node, node->ss.ss_currentRelation, node->ss.ps.plan->qual, scanKeys, numScanKeys); - } - return; -} - -void register_seq_scan_hook(void){ - prev_exec_seq_scan_scan_key_hook = exec_seq_scan_scan_key_hook; - exec_seq_scan_scan_key_hook = postgraph_seq_scan_key_hook; -} - -void unregister_seq_scan_hook(void){ - exec_seq_scan_scan_key_hook = prev_exec_seq_scan_scan_key_hook; -} - - - - -/* - * ExecIndexBuildScanKeys - * Build the index scan keys from the index qualification expressions - * - * The index quals are passed to the index AM in the form of a ScanKey array. - * This routine sets up the ScanKeys, fills in all constant fields of the - * ScanKeys, and prepares information about the keys that have non-constant - * comparison values. We divide index qual expressions into five types: - * - * 1. Simple operator with constant comparison value ("indexkey op constant"). - * For these, we just fill in a ScanKey containing the constant value. - * - * 2. Simple operator with non-constant value ("indexkey op expression"). - * For these, we create a ScanKey with everything filled in except the - * expression value, and set up an IndexRuntimeKeyInfo struct to drive - * evaluation of the expression at the right times. - * - * 3. RowCompareExpr ("(indexkey, indexkey, ...) op (expr, expr, ...)"). - * For these, we create a header ScanKey plus a subsidiary ScanKey array, - * as specified in access/skey.h. The elements of the row comparison - * can have either constant or non-constant comparison values. - * - * 4. ScalarArrayOpExpr ("indexkey op ANY (array-expression)"). If the index - * supports amsearcharray, we handle these the same as simple operators, - * setting the SK_SEARCHARRAY flag to tell the AM to handle them. Otherwise, - * we create a ScanKey with everything filled in except the comparison value, - * and set up an IndexArrayKeyInfo struct to drive processing of the qual. - * (Note that if we use an IndexArrayKeyInfo struct, the array expression is - * always treated as requiring runtime evaluation, even if it's a constant.) - * - * 5. NullTest ("indexkey IS NULL/IS NOT NULL"). We just fill in the - * ScanKey properly. - * - * This code is also used to prepare ORDER BY expressions for amcanorderbyop - * indexes. The behavior is exactly the same, except that we have to look up - * the operator differently. Note that only cases 1 and 2 are currently - * possible for ORDER BY. - * - * Input params are: - * - * planstate: executor state node we are working for - * index: the index we are building scan keys for - * quals: indexquals (or indexorderbys) expressions - * isorderby: true if processing ORDER BY exprs, false if processing quals - * *runtimeKeys: ptr to pre-existing IndexRuntimeKeyInfos, or NULL if none - * *numRuntimeKeys: number of pre-existing runtime keys - * - * Output params are: - * - * *scanKeys: receives ptr to array of ScanKeys - * *numScanKeys: receives number of scankeys - * *runtimeKeys: receives ptr to array of IndexRuntimeKeyInfos, or NULL if none - * *numRuntimeKeys: receives number of runtime keys - * *arrayKeys: receives ptr to array of IndexArrayKeyInfos, or NULL if none - * *numArrayKeys: receives number of array keys - * - * Caller may pass NULL for arrayKeys and numArrayKeys to indicate that - * IndexArrayKeyInfos are not supported. - */ -void -vertex_exec_index_build_ScanKeys(PlanState *planstate, Relation index, - List *quals, - ScanKey scanKeys, int *numScanKeys) -{ - ListCell *qual_cell; - - IndexRuntimeKeyInfo *runtime_keys; - int n_runtime_keys; - int max_runtime_keys; - int j; - - /* Allocate array for ScanKey */ - scanKeys = palloc(sizeof(IndexRuntimeKeyInfo)); - - /* - * runtime_keys array is dynamically resized as needed. We handle it this - * way so that the same runtime keys array can be shared between - * indexquals and indexorderbys, which will be processed in separate calls - * of this function. Caller must be sure to pass in NULL/0 for first - * call. - */ - numScanKeys = 0; - - - - /* - * for each opclause in the given qual, convert the opclause into a single - * scan key - */ - - j = 0; - foreach(qual_cell, quals) - { - Expr *clause = (Expr *) lfirst(qual_cell); - - Oid opno; /* operator's OID */ - RegProcedure opfuncid; /* operator proc id used in scan */ - Oid opfamily; /* opfamily of index column */ - int op_strategy; /* operator's strategy number */ - Oid op_lefttype; /* operator's declared input types */ - Oid op_righttype; - Expr *leftop; /* expr on lhs of operator */ - Expr *rightop; /* expr on rhs ... */ - AttrNumber varattno; /* att number used in scan */ - int indnkeyatts; - - indnkeyatts = 4;// id, startid, endid, properties - if (IsA(clause, OpExpr)) - { - ScanKey this_scan_key = palloc(sizeof(ScanKey)); - - - IndexRuntimeKeyInfo * runtimeKey = (IndexRuntimeKeyInfo *)scanKeys; - /* indexkey op const or indexkey op expression */ - int flags = 0; - Datum scanvalue; - - opno = ((OpExpr *) clause)->opno; - opfuncid = ((OpExpr *) clause)->opfuncid; - - /* - * leftop should be the index key Var, possibly relabeled - */ - leftop = (Expr *) get_leftop(clause); - - if (leftop && IsA(leftop, RelabelType)) - leftop = ((RelabelType *) leftop)->arg; - - Assert(leftop != NULL); - - if (!(IsA(leftop, Var) && - ((Var *) leftop)->varno == INDEX_VAR)) - elog(ERROR, "indexqual doesn't have key on left side"); - - varattno = ((Var *) leftop)->varattno; - if (varattno < 1 || varattno > indnkeyatts) - elog(ERROR, "bogus index qualification"); - - if (varattno != 1) - continue; - /* - * We have to look up the operator's strategy number. This - * provides a cross-check that the operator does match the index. - */ - opfamily = index->rd_opfamily[varattno - 1]; - - bool *isorderby; - get_op_opfamily_properties(opno, opfamily, isorderby, - &op_strategy, - &op_lefttype, - &op_righttype); - - if (isorderby) - flags |= SK_ORDER_BY; - - /* - * rightop is the constant or variable comparison value - */ - rightop = (Expr *) get_rightop(clause); - - if (rightop && IsA(rightop, RelabelType)) - rightop = ((RelabelType *) rightop)->arg; - - Assert(rightop != NULL); - - - - - - /* - * initialize the scan key's fields appropriately - */ - ScanKeyEntryInitialize(this_scan_key, - flags, - varattno, /* attribute number to scan */ - op_strategy, /* op's strategy */ - op_righttype, /* strategy subtype */ - ((OpExpr *) clause)->inputcollid, /* collation */ - opfuncid, /* reg proc to use */ - scanvalue); /* constant */ - - - runtimeKey->scan_key = this_scan_key; - runtimeKey->key_expr = ExecInitExpr(rightop, planstate); - runtimeKey->key_toastable = TypeIsToastable(op_righttype); - - scanvalue = (Datum) 0; - numScanKeys++; - - break; - } - } - } \ No newline at end of file diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index c8713264..94254f6e 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -282,10 +282,10 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) if(i % 2 == 1) css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); else { - css->edge_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - i++; + css->edge_ids[0][(i-1)/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + } - + i++; } @@ -323,7 +323,7 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) ExecClearTuple(elemTupleSlot); // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; + elemTupleSlot->tts_values[0] = css->edge_ids[0][i-1]; elemTupleSlot->tts_isnull[0] = false; elemTupleSlot->tts_values[1] = css->vertex_ids[0][i]; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 30da0d96..e33e89c9 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -83,6 +83,8 @@ static void setNamespaceLateralState(List *namespace, bool lateral_only, bool la static char *make_id_alias(char *var_name); static char *make_property_alias(char *var_name); +static char *make_startid_alias(char *var_name); +static char *make_endid_alias(char *var_name); static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); @@ -90,6 +92,22 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi List * transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); +static char *make_vertex_adjlist_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'a'; + str[2] = 'd'; + str[3] = 'j'; + str[4] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 5] = var_name[i]; + str[i + 5] = '\0'; + + return str; +} /* * transform a cypher_clause */ @@ -155,6 +173,43 @@ static char *make_id_alias(char *var_name) { return str; } +static char *make_startid_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 's'; + str[2] = 't'; + str[3] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; + + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\0'; + + return str; +} + +static char *make_endid_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'e'; + str[2] = 'n'; + str[3] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; + + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\0'; + + return str; +} static char *make_property_alias(char *var_name) { @@ -809,6 +864,290 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_ } +static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace) ; +static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); + + +static Node *make_null_const(int location) { + A_Const *n; + + n = makeNode(A_Const); + n->val.type = T_Null; + n->location = location; + + return (Node *)n; +} + +static A_Expr * +makeSimpleCypherA_Expr(A_Expr_Kind kind, char *name, + Node *lexpr, Node *rexpr, int location) +{ + A_Expr *a = makeNode(A_Expr); + + a->kind = kind; + a->name = list_make2(makeString("postgraph"), makeString((char *) name)); + a->lexpr = lexpr; + a->rexpr = rexpr; + a->location = location; + return a; +} + + + +Alias *make_alias(char *name, List *colnames) { + Alias *alias = makeNode(Alias); + + alias->aliasname = name; + alias->colnames = colnames; + + return alias; +} + + + +RangeFunction *make_range_function(FuncCall *func, Alias *alias, bool is_lateral, bool ordinality, bool is_rows_from) { + RangeFunction *rf = makeNode(RangeFunction); + + rf->lateral = is_lateral; + rf->ordinality = ordinality; + rf->is_rowsfrom = is_rows_from; + rf->functions = list_make1(list_make2(func, NIL)); + rf->alias = alias; + + return rf; +} + +static ParseNamespaceItem *add_srf_to_query(cypher_parsestate *cpstate, Node *n, char *var_name) { + RangeTblEntry *rte = NULL; + RangeTblRef *rtr; + List *namespace = NULL; + int rtindex; + ParseState *pstate = (ParseState *)cpstate; + + Alias *alias = make_alias(var_name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); + //Alias *alias = make_alias(var_name, NIL); + RangeFunction *rf = make_range_function(n, alias, true, false, false); + + + rtr = transform_srf_function(cpstate, rf, &rte, &rtindex, &namespace); + Assert(rtr != NULL); + + checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + + setNamespaceLateralState(namespace, true, true); + //((ParseNamespaceItem *)lfirst(list_head(namespace)))->p_names = alias;//list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties")); + pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); + pstate->p_namespace = list_concat(pstate->p_namespace, namespace); + + setNamespaceLateralState(pstate->p_namespace, true, true); + + return lfirst(list_head(namespace)); +} + + + +static ParseNamespaceItem *add_srf_to_query1(cypher_parsestate *cpstate, Node *n, char *var_name) { + RangeTblEntry *rte = NULL; + RangeTblRef *rtr; + List *namespace = NULL; + int rtindex; + ParseState *pstate = (ParseState *)cpstate; + + Alias *alias = make_alias(var_name, list_make1(makeString("properties"))); + //Alias *alias = make_alias(var_name, NIL); + RangeFunction *rf = make_range_function(n, alias, true, false, false); + + + rtr = transform_srf_function(cpstate, rf, &rte, &rtindex, &namespace); + Assert(rtr != NULL); + + checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + + setNamespaceLateralState(namespace, true, true); + //((ParseNamespaceItem *)lfirst(list_head(namespace)))->p_names = alias;//list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties")); + pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); + pstate->p_namespace = list_concat(pstate->p_namespace, namespace); + + setNamespaceLateralState(pstate->p_namespace, true, true); + + return lfirst(list_head(namespace)); +} + + +// transform a function call appearing in FROM +static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, RangeFunction *r) { + ParseState *pstate = NULL; + List *funcexprs = NIL; + List *funcnames = NIL; + List *coldeflists = NIL; + bool is_lateral = false; + ListCell *lc = NULL; + ParseNamespaceItem *pnsi; + + pstate = &cpstate->pstate; + + Assert(!pstate->p_lateral_active); + pstate->p_lateral_active = true; + + // transform the raw expressions + foreach(lc, r->functions) + { + List *pair = (List*)lfirst(lc); + Node *fexpr; + List *coldeflist; + Node *newfexpr; + Node *last_srf; + + // Disassemble the function-call/column-def-list pairs + Assert(list_length(pair) == 2); + fexpr = (Node*) linitial(pair); + coldeflist = (List*) lsecond(pair); + + // normal case ... + last_srf = pstate->p_last_srf; + + // transform the function expression + newfexpr = transform_cypher_expr(cpstate, fexpr, EXPR_KIND_FROM_FUNCTION); + + // nodeFunctionscan.c requires SRFs to be at top level + if (pstate->p_last_srf != last_srf && pstate->p_last_srf != newfexpr) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-returning functions must appear at top level of FROM"), + parser_errposition(pstate, exprLocation(pstate->p_last_srf)))); + + funcexprs = lappend(funcexprs, newfexpr); + funcnames = lappend(funcnames, FigureColname(fexpr)); + + if (coldeflist && r->coldeflist) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple column definition lists are not allowed for the same function"), + parser_errposition(pstate, exprLocation((Node *) r->coldeflist)))); + + coldeflists = lappend(coldeflists, coldeflist); + } + + pstate->p_lateral_active = false; + + /* + * We must assign collations now so that the RTE exposes correct collation + * info for Vars created from it. + */ + assign_list_collations(pstate, funcexprs); + + // currently this is not used by the VLE + Assert(r->coldeflist == NULL); + + // mark the RTE as LATERAL + is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0); + + // build an RTE for the function + pnsi = addRangeTableEntryForFunction(pstate, funcnames, funcexprs, coldeflists, r, is_lateral, true); + + return pnsi; +} + +static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace) { + ParseState *pstate = (ParseState *)cpstate; + + Assert(IsA(n, RangeFunction)); + + if (IsA(n, RangeFunction)) { + RangeTblRef *rtr; + RangeTblEntry *rte; + ParseNamespaceItem *nsitem; + int rtindex; + + nsitem = transform_RangeFunction(cpstate, (RangeFunction *) n); + rte = nsitem->p_rte; + rtindex = list_length(pstate->p_rtable); + Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); + *top_rte = rte; + *top_rti = rtindex; + *namespace = list_make1(nsitem); + rtr = makeNode(RangeTblRef); + rtr->rtindex = rtindex; + return (Node *) rtr; + } + + return NULL; +} + + +static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace) { + ParseState *pstate = &cpstate->pstate; + + Assert(IsA(n, RangeFunction)); + + if (IsA(n, RangeFunction)) { + RangeTblRef *rtr; + RangeTblEntry *rte; + ParseNamespaceItem *nsitem; + int rtindex; + + nsitem = transform_RangeFunction(cpstate, (RangeFunction *) n); + rte = nsitem->p_rte; + rtindex = list_length(pstate->p_rtable); + Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); + *top_rte = rte; + *top_rti = rtindex; + *namespace = list_make1(nsitem); + rtr = makeNode(RangeTblRef); + rtr->rtindex = rtindex; + return (Node *) rtr; + } + + return NULL; +} + + +static ParseNamespaceItem *add_vle_to_query(cypher_parsestate *cpstate, Node *n) { + ParseState *pstate = &cpstate->pstate; + RangeTblEntry *rte = NULL; + List *namespace = NULL; + int rtindex; + + n = transform_VLE_Function(cpstate, n, &rte, &rtindex, &namespace); + Assert(n != NULL); + + checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + + setNamespaceLateralState(namespace, true, true); + + pstate->p_joinlist = lappend(pstate->p_joinlist, n); + pstate->p_namespace = list_concat(pstate->p_namespace, namespace); + + setNamespaceLateralState(pstate->p_namespace, false, true); + + return lfirst(list_head(namespace)); +} + + +// setNamespaceLateralState - subroutine to update LATERAL flags in a namespace list. +static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok) { + ListCell *lc; + + foreach(lc, namespace) { + ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc); + + nsitem->p_lateral_only = lateral_only; + nsitem->p_lateral_ok = lateral_ok; + } +} + +static Node *make_int_const(int i, int location) { + A_Const *n; + + n = makeNode(A_Const); + n->val.type = T_Integer; + n->val.val.ival = i; + n->location = location; + + return (Node *)n; +} + + static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc; @@ -832,12 +1171,17 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH paths only support 1 vertex"))); + char *vertex_label = NULL; + ParseNamespaceItem *vertex_pnsi; + Node *vertex_id = NULL; ListCell *path_cell; + cypher_node *prev_node; + ParseNamespaceItem *edge_pnsi; int i = 1; foreach(path_cell, path->path) { if (i % 2 == 1) { cypher_node *node = lfirst(path_cell); - + prev_node = node; if (node->name) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -853,71 +1197,92 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li else node->label = AG_DEFAULT_LABEL_VERTEX; - char *schema_name; - char *rel_name; - int resno; - TargetEntry *te; - Expr *expr; - ParseNamespaceItem *pnsi; - RangeVar *label_range_var; - Alias *alias; + char *schema_name; + char *rel_name; + int resno; + TargetEntry *te; + Expr *expr; + ParseNamespaceItem *pnsi; + RangeVar *label_range_var; + Alias *alias; - schema_name = get_graph_namespace_name(cpstate->graph_name); + if (i < 2) { - // XXX: LTree Labeling Project, first code is here - Datum label_lquery; - if (is_default_label) - label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(node->label)); - else - label_lquery = DirectFunctionCall2(ltree_addltree, - DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), - DirectFunctionCall1(ltree_in, CStringGetDatum(node->label))); - - int ltq_query_args[2]; - ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - - Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); - - ScanKeyData scan_keys[1]; - ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); - - Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); - SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); - HeapTuple tuple = systable_getnext(scan_desc); + schema_name = get_graph_namespace_name(cpstate->graph_name); - if (!HeapTupleIsValid(tuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("not found %s", node->label))); - - bool is_null; - rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); + // XXX: LTree Labeling Project, first code is here + Datum label_lquery; + if (is_default_label) + label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(node->label)); + else + label_lquery = DirectFunctionCall2(ltree_addltree, + DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), + DirectFunctionCall1(ltree_in, CStringGetDatum(node->label))); + - systable_endscan(scan_desc); - table_close(label_catalog, AccessShareLock); - //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); - label_range_var = makeRangeVar(schema_name, rel_name, -1); - alias = makeAlias(node->name, NIL); + int ltq_query_args[2]; + ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + + Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); + + ScanKeyData scan_keys[1]; + ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); + + Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); + SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); - pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); - Assert(pnsi != NULL); + HeapTuple tuple = systable_getnext(scan_desc); - addNSItemToQuery(pstate, pnsi, true, true, true); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("not found %s", node->label))); + + bool is_null; + rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); - resno = pstate->p_next_resno++; - // XXX: End LTree Code here + systable_endscan(scan_desc); + table_close(label_catalog, AccessShareLock); + + //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); + label_range_var = makeRangeVar(schema_name, rel_name, -1); + vertex_label = node->label; + alias = makeAlias(node->name, NIL); + + pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); + vertex_pnsi = pnsi; + Assert(pnsi != NULL); + + addNSItemToQuery(pstate, pnsi, true, true, true); - expr = (Expr *)make_vertex_expr(cpstate, pnsi); + //resno = pstate->p_next_resno++; + // XXX: End LTree Code here + // + } else { + + int sublevels_up =1; + Node * id_field = scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1); + + FuncCall *fc = makeFuncCall(list_make2(makeString("postgraph"), makeString("retrieve_vertex")), + list_make2(make_int_const(cpstate->graph_oid, -1),id_field), + COERCE_EXPLICIT_CALL, -1); + pnsi = add_srf_to_query1(cpstate, fc, node->name); + //edge_pnsi = pnsi; + } + //expr = (Expr *)make_vertex_expr(cpstate, pnsi); // make target entry and add it - te = makeTargetEntry(expr, resno, node->name, false); - query->targetList = lappend(query->targetList, te); + // pstate->p_next_resno++; + //te = makeTargetEntry(expr, resno, node->name, false); + + //query->targetList = lappend(query->targetList, te); // id field Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + vertex_id = id; resno = pstate->p_next_resno++; te = makeTargetEntry(id, resno, make_id_alias(node->name), false); @@ -933,22 +1298,6 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li quals = list_concat(quals, NULL); } else { - - - - - - - - - - - - - - - - cypher_relationship *edge = lfirst(path_cell); if (edge->name) @@ -977,60 +1326,39 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li schema_name = get_graph_namespace_name(cpstate->graph_name); - // XXX: LTree Labeling Project, first code is here - Datum label_lquery; - if (is_default_label) - label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(edge->label)); - else - label_lquery = DirectFunctionCall2(ltree_addltree, - DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), - DirectFunctionCall1(ltree_in, CStringGetDatum(edge->label))); - - int ltq_query_args[2]; - ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - - Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); - - ScanKeyData scan_keys[1]; - ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); - - Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); - SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); - - HeapTuple tuple = systable_getnext(scan_desc); - if (!HeapTupleIsValid(tuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("not found %s", edge->label))); - - bool is_null; - rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); + //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); - systable_endscan(scan_desc); - table_close(label_catalog, AccessShareLock); - //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); - label_range_var = makeRangeVar(schema_name, rel_name, -1); - alias = makeAlias(edge->name, NIL); - pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); - Assert(pnsi != NULL); + int sublevels_up; + Node * id_field = scanNSItemForColumn(cpstate, vertex_pnsi, 0, "id", -1); - addNSItemToQuery(pstate, pnsi, true, true, true); + //Node * id_field = makeColumnRef(makeString(prev_node->name), list_make1(makeString("id")), -1, NULL); + //alias = makeAlias(edge->name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); + //make_alias(var_name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); + FuncCall *fc = makeFuncCall(list_make2(makeString("postgraph"), makeString("edge_search")), + list_make4(make_int_const(cpstate->graph_oid, -1),id_field, make_null_const(-1), make_null_const(-1)), + COERCE_EXPLICIT_CALL, -1); + pnsi = add_srf_to_query(cpstate, fc, edge->name); + edge_pnsi = pnsi; + //RangeFunction *rf = make_range_function(fc, alias, false, false, false); + //pnsi = add_vle_to_query(cpstate, (Node*)rf); + //addNSItemToQuery(pstate, pnsi, true, true, true); - resno = pstate->p_next_resno++; + // resno = pstate->p_next_resno++; // XXX: End LTree Code here - expr = (Expr *)make_edge_expr(cpstate, pnsi); + // expr = (Expr *)make_edge_expr(cpstate, pnsi); // make target entry and add it - te = makeTargetEntry(expr, resno, edge->name, false); - query->targetList = lappend(query->targetList, te); + // te = makeTargetEntry(expr, resno, edge->name, false); + // query->targetList = lappend(query->targetList, te); + // id field Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); + //vertex_id = id; resno = pstate->p_next_resno++; te = makeTargetEntry(id, resno, make_id_alias(edge->name), false); @@ -1041,7 +1369,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li Node *startid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); resno = pstate->p_next_resno++; - te = makeTargetEntry(startid, resno, make_id_alias(edge->name), false); + te = makeTargetEntry(startid, resno, make_startid_alias(edge->name), false); query->targetList = lappend(query->targetList, te); @@ -1049,7 +1377,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li Node *endid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); resno = pstate->p_next_resno++; - te = makeTargetEntry(endid, resno, make_id_alias(edge->name), false); + te = makeTargetEntry(endid, resno, make_endid_alias(edge->name), false); query->targetList = lappend(query->targetList, te); // properties field @@ -1060,8 +1388,19 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li query->targetList = lappend(query->targetList, te); - quals = list_concat(quals, NULL); + //quals = list_concat(quals, NULL); + + + int sublevels; + char *entity_name = vertex_label; + + + //ParseNamespaceItem *pnsi1 = refnameNamespaceItem(cpstate, NULL, entity_name, -1, &sublevels); +//scanNSItemForColumn(pstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + //quals = list_concat(quals, list_make1(makeSimpleCypherA_Expr(AEXPR_OP, "=", startid, vertex_id, -1))); + + } i++; } @@ -1069,11 +1408,11 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li // AND the quals for each path together Expr *q = NULL; - /* + if (quals != NIL) { q = makeBoolExpr(AND_EXPR, quals, -1); expr = (Expr *)sql_transform_expr(&cpstate->pstate, (Node *)q, EXPR_KIND_WHERE); - }*/ + } /* / property constraints on MATCH Pattern @@ -1112,8 +1451,8 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li // expr = (Expr *)coerce_to_boolean(pstate, (Node *)expr, "WHERE"); query->rtable = cpstate->pstate.p_rtable; - //query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr); - query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)NULL); + query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr); + //query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)NULL); } diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index bb8a6d71..9b53c50d 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -236,6 +236,10 @@ transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr) { return transformSQLValueFunction(cpstate, (SQLValueFunction *)expr); case T_String: return makeConst(GTYPEOID, -1, InvalidOid, -1, string_to_gtype(strVal(expr)), false, false); + case T_Var: + { + return (Node *) expr; + } default: ereport(ERROR, (errmsg_internal("unrecognized node type: %d", nodeTag(expr)))); } diff --git a/src/backend/postgraph.c b/src/backend/postgraph.c index f53a5898..f653acc8 100644 --- a/src/backend/postgraph.c +++ b/src/backend/postgraph.c @@ -39,7 +39,7 @@ void _PG_init(void) set_rel_pathlist_init(); parse_analyze_init(); parse_init(); - register_seq_scan_hook(); + //register_seq_scan_hook(); } void _PG_fini(void); @@ -49,5 +49,5 @@ void _PG_fini(void) set_rel_pathlist_fini(); parse_analyze_fini(); parse_fini(); - unregister_seq_scan_hook(); + //unregister_seq_scan_hook(); } diff --git a/src/backend/utils/adt/graphid.c b/src/backend/utils/adt/graphid.c index 1e98c66e..44ca8bc1 100644 --- a/src/backend/utils/adt/graphid.c +++ b/src/backend/utils/adt/graphid.c @@ -52,6 +52,7 @@ Datum graphid_in(PG_FUNCTION_ARGS) PG_FUNCTION_INFO_V1(graphid_out); // graphid type output function Datum graphid_out(PG_FUNCTION_ARGS) { +/* graphid gid = AG_GETARG_GRAPHID(0); char buf[32]; // greater than MAXINT8LEN+1 char *out; @@ -60,6 +61,23 @@ Datum graphid_out(PG_FUNCTION_ARGS) { out = pstrdup(buf); PG_RETURN_CSTRING(out); +*/ + graphid gid = AG_GETARG_GRAPHID(0); + int32 first_num; + int64_t second_num; + char *out; + + // The first number is the last 8 bits of the graphid + first_num = gid >> ENTRY_ID_BITS; + + // The second number is the remaining bits, shifted right by 8 + second_num = gid & ENTRY_ID_MASK ; + + // Use psprintf to format the output string + out = psprintf("(%d, %ld)", first_num, second_num); + + PG_RETURN_CSTRING(out); + } // graphid_recv - converts external binary format to a graphid. diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c new file mode 100644 index 00000000..c2ef343f --- /dev/null +++ b/src/backend/utils/edge_searching.c @@ -0,0 +1,192 @@ + +#include "postgres.h" + +#include + +#include "access/table.h" +#include "access/tableam.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "catalog/namespace.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "catalog/pg_aggregate_d.h" +#include "catalog/pg_collation_d.h" +#include "catalog/pg_operator_d.h" +#include "executor/nodeAgg.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "parser/parse_coerce.h" +#include "portability/instr_time.h" +#include "nodes/pg_list.h" +#include "utils/builtins.h" +#include "utils/float.h" +#include "utils/fmgroids.h" +#include "utils/int8.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/timestamp.h" +#include "utils/typcache.h" + +#include "utils/graphid.h" +#include "utils/gtype.h" +#include "utils/edge.h" +#include "utils/variable_edge.h" +#include "utils/vector.h" +#include "utils/vertex.h" +#include "utils/gtype.h" +#include "utils/gtype_parser.h" +#include "utils/gtype_typecasting.h" +#include "catalog/ag_graph.h" +#include "catalog/ag_label.h" +#include "utils/graphid.h" +#include "utils/numeric.h" + +#include "access/vertex.h" +#include "utils/ag_cache.h" + +#include "ltree.h" + +static Datum get_vertex(Oid graph_oid, int64 graphid) +{ + ScanKeyData scan_keys[1]; + Relation graph_vertex_label; + TableScanDesc scan_desc; + HeapTuple tuple; + TupleDesc tupdesc; + Datum properties, result; + int32 labelid = (graphid >> ENTRY_ID_BITS); + + label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, labelid); + + Snapshot snapshot = GetActiveSnapshot(); + + ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_OIDEQ, Int64GetDatum(graphid)); + + graph_vertex_label = table_open(lcd->relation, ShareLock); + scan_desc = table_beginscan(graph_vertex_label, snapshot, 1, scan_keys); + tuple = heap_getnext(scan_desc, ForwardScanDirection); + + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); + + tupdesc = RelationGetDescr(graph_vertex_label); + + properties = column_get_datum(tupdesc, tuple, 1, "properties", GTYPEOID, true); + + table_endscan(scan_desc); + table_close(graph_vertex_label, ShareLock); + + ereport(WARNING, (errmsg("Vertex %lu found", graphid))); + return properties; +} + +PG_FUNCTION_INFO_V1(retrieve_vertex); +/* + * Function to convert the Array type of Agtype into each row. It is used for + * Cypher `UNWIND` clause, but considering the situation in which the user can + * directly use this function in vanilla PGSQL, put a second parameter related + * to this. + */ +static int this_is_a_test1 = 0; +Datum retrieve_vertex(PG_FUNCTION_ARGS) { + gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); + graphid id = AG_GETARG_GRAPHID(1); + + PG_RETURN_POINTER(get_vertex(graph_oid, id)); +} + + +struct edge_search_cxt +{ + ScanKey scanKey; + TableScanDesc scan_desc; + Relation rel; + TupleTableSlot *slot; +} edge_search_cxt; + +PG_FUNCTION_INFO_V1(edge_search); +/* + * Function to convert the Array type of Agtype into each row. It is used for + * Cypher `UNWIND` clause, but considering the situation in which the user can + * directly use this function in vanilla PGSQL, put a second parameter related + * to this. + */ +static int this_is_a_test = 0; +Datum edge_search(PG_FUNCTION_ARGS) +{ + gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); + graphid id = AG_GETARG_GRAPHID(1); + + //lquery *label = PG_GETARG_LQUERY_P(1); + //bool include_props = PG_GETARG_BOOL(2); + FuncCallContext *funcctx; + FmgrInfo *arrayinp; + ereport(WARNING, (errmsg("Vertex %lu", id))); + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(4); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "id", + GRAPHIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "startid", + GRAPHIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "endid", + GRAPHIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "properties", + GTYPEOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + struct edge_search_cxt *cxt= palloc(sizeof(struct edge_search_cxt)); + cxt->scanKey = palloc(sizeof(ScanKeyData)); + + int32 labelid = (id >> ENTRY_ID_BITS); + label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, labelid); + + Snapshot snapshot = GetActiveSnapshot(); + + ScanKeyInit(cxt->scanKey, Anum_ag_label_edge_table_start_id, BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); + + cxt->rel = table_open(lcd->vertex_adjlist, ShareLock); + cxt->scan_desc = table_beginscan(cxt->rel, snapshot, 1, cxt->scanKey); + List *empty = NIL; + cxt->slot = table_slot_create(cxt->rel, &empty); + funcctx->user_fctx = cxt; + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + struct edge_search_cxt *cxt = (struct edge_search_cxt *) funcctx->user_fctx; + TableAmRoutine *vertex_am = GetVertexHeapamTableAmRoutine(); + + if (!table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { + + table_endscan(cxt->scan_desc); + table_close(cxt->rel, ShareLock); + SRF_RETURN_DONE(funcctx); + } + + Datum values[4]; + bool nulls[4]; + HeapTuple tuple1; + + memset(nulls, true, sizeof(nulls)); + + values[0] = cxt->slot->tts_values[0]; + values[1] = cxt->slot->tts_values[1]; + values[2] = cxt->slot->tts_values[2]; + values[3] = cxt->slot->tts_values[3]; + tuple1 = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple1)); +} diff --git a/src/include/access/vertex.h b/src/include/access/vertex.h index 2a265a6e..153833cd 100644 --- a/src/include/access/vertex.h +++ b/src/include/access/vertex.h @@ -65,7 +65,4 @@ vertex_hash_next(TableScanDesc scan, ScanDirection dir); bool vertex_hash_first(TableScanDesc scan, ScanDirection dir); -void unregister_seq_scan_hook(void); -void register_seq_scan_hook(void); - #endif /* ACCESS_VERTEX_H */ From 84a6bb28421e6402ba2af4808e950306938cb10d Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Thu, 14 Aug 2025 00:06:11 -0700 Subject: [PATCH 26/56] Cleanup refrences leaks --- Makefile | 1 - .../access/vertex_heap/vertex_hash_search.c | 767 -------------- .../vertex_heap/vertex_heapam_handler.c | 941 +----------------- src/backend/parser/cypher_clause.c | 4 +- src/backend/utils/edge_searching.c | 14 +- src/include/access/vertex.h | 38 - 6 files changed, 15 insertions(+), 1750 deletions(-) delete mode 100644 src/backend/access/vertex_heap/vertex_hash_search.c diff --git a/Makefile b/Makefile index a9c8b136..e9d2320f 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,6 @@ MODULE_big = postgraph OBJS = src/backend/postgraph.o \ src/backend/access/vertex_heap/vertex_heapam_handler.o \ - src/backend/access/vertex_heap/vertex_hash_search.o \ src/backend/catalog/ag_catalog.o \ src/backend/catalog/ag_graph.o \ src/backend/catalog/ag_label.o \ diff --git a/src/backend/access/vertex_heap/vertex_hash_search.c b/src/backend/access/vertex_heap/vertex_hash_search.c deleted file mode 100644 index 04f4bedd..00000000 --- a/src/backend/access/vertex_heap/vertex_hash_search.c +++ /dev/null @@ -1,767 +0,0 @@ -/*------------------------------------------------------------------------- - * - * hashsearch.c - * search code for postgres hash tables - * - * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/backend/access/hash/hashsearch.c - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include "access/hash.h" -#include "access/relscan.h" -#include "miscadmin.h" -#include "pgstat.h" -#include "storage/predicate.h" -#include "utils/rel.h" -#include "storage/procarray.h" - -#include "access/vertex.h" - -static bool vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, - ScanDirection dir); -static int vertex_hash_load_qualified_items(TableScanDesc scan, Page page, - OffsetNumber offnum, ScanDirection dir); -static inline void vertex_hash_saveitem(VertexHeapScanDesc so, int itemIndex, - OffsetNumber offnum, IndexTuple itup); -static void vertex_hash_readnext(TableScanDesc scan, Buffer *bufp, - Page *pagep, HashPageOpaque *opaquep); -static void vertex_hash_kill_items(TableScanDesc scan); - -/* - * Advance to next page in a bucket, if any. If we are scanning the bucket - * being populated during split operation then this function advances to the - * bucket being split after the last bucket page of bucket being populated. - */ -static void -vertex_hash_readnext(TableScanDesc scan, - Buffer *bufp, Page *pagep, HashPageOpaque *opaquep) -{ - BlockNumber blkno; - Relation rel = scan->rs_rd; - VertexHeapScanDesc so = (VertexHeapScanDesc) scan; - bool block_found = false; - - blkno = (*opaquep)->hasho_nextblkno; - - /* - * Retain the pin on primary bucket page till the end of scan. Refer the - * comments in vertex_hash_first to know the reason of retaining pin. - */ - if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf) - LockBuffer(*bufp, BUFFER_LOCK_UNLOCK); - else - _hash_relbuf(rel, *bufp); - - *bufp = InvalidBuffer; - /* check for interrupts while we're not holding any buffer lock */ - CHECK_FOR_INTERRUPTS(); - if (BlockNumberIsValid(blkno)) - { - *bufp = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE); - block_found = true; - } - else if (so->hashso_buc_populated && !so->hashso_buc_split) - { - /* - * end of bucket, scan bucket being split if there was a split in - * progress at the start of scan. - */ - *bufp = so->hashso_split_bucket_buf; - - /* - * buffer for bucket being split must be valid as we acquire the pin - * on it before the start of scan and retain it till end of scan. - */ - Assert(BufferIsValid(*bufp)); - - LockBuffer(*bufp, BUFFER_LOCK_SHARE); - PredicateLockPage(rel, BufferGetBlockNumber(*bufp), scan->rs_snapshot); - - /* - * setting hashso_buc_split to true indicates that we are scanning - * bucket being split. - */ - so->hashso_buc_split = true; - - block_found = true; - } - - if (block_found) - { - *pagep = BufferGetPage(*bufp); - TestForOldSnapshot(scan->rs_snapshot, rel, *pagep); - *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); - } -} - -/* - * Advance to previous page in a bucket, if any. If the current scan has - * started during split operation then this function advances to bucket - * being populated after the first bucket page of bucket being split. - */ -static void -vertex__hash_readprev(TableScanDesc scan, - Buffer *bufp, Page *pagep, HashPageOpaque *opaquep) -{ - BlockNumber blkno; - Relation rel = scan->rs_rd; - VertexHeapScanDesc so = (VertexHeapScanDesc) scan; - bool haveprevblk; - - blkno = (*opaquep)->hasho_prevblkno; - - /* - * Retain the pin on primary bucket page till the end of scan. Refer the - * comments in vertex_hash_first to know the reason of retaining pin. - */ - if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf) - { - LockBuffer(*bufp, BUFFER_LOCK_UNLOCK); - haveprevblk = false; - } - else - { - _hash_relbuf(rel, *bufp); - haveprevblk = true; - } - - *bufp = InvalidBuffer; - /* check for interrupts while we're not holding any buffer lock */ - CHECK_FOR_INTERRUPTS(); - - if (haveprevblk) - { - Assert(BlockNumberIsValid(blkno)); - *bufp = _hash_getbuf(rel, blkno, HASH_READ, - LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); - *pagep = BufferGetPage(*bufp); - TestForOldSnapshot(scan->rs_snapshot, rel, *pagep); - *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); - - /* - * We always maintain the pin on bucket page for whole scan operation, - * so releasing the additional pin we have acquired here. - */ - if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf) - _hash_dropbuf(rel, *bufp); - } - else if (so->hashso_buc_populated && so->hashso_buc_split) - { - /* - * end of bucket, scan bucket being populated if there was a split in - * progress at the start of scan. - */ - *bufp = so->hashso_bucket_buf; - - /* - * buffer for bucket being populated must be valid as we acquire the - * pin on it before the start of scan and retain it till end of scan. - */ - Assert(BufferIsValid(*bufp)); - - LockBuffer(*bufp, BUFFER_LOCK_SHARE); - *pagep = BufferGetPage(*bufp); - *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); - - /* move to the end of bucket chain */ - while (BlockNumberIsValid((*opaquep)->hasho_nextblkno)) - vertex_hash_readnext(scan, bufp, pagep, opaquep); - - /* - * setting hashso_buc_split to false indicates that we are scanning - * bucket being populated. - */ - so->hashso_buc_split = false; - } -} - -/* - * vertex_hash_first() -- Find the first item in a scan. - * - * We find the first item (or, if backward scan, the last item) in the - * index that satisfies the qualification associated with the scan - * descriptor. - * - * On successful exit, if the page containing current index tuple is an - * overflow page, both pin and lock are released whereas if it is a bucket - * page then it is pinned but not locked and data about the matching - * tuple(s) on the page has been loaded into so->currPos, - * scan->xs_ctup.t_self is set to the heap TID of the current tuple. - * - * On failure exit (no more tuples), we return false, with pin held on - * bucket page but no pins or locks held on overflow page. - */ -bool -vertex_hash_first(TableScanDesc scan, ScanDirection dir) -{ - Relation rel = scan->rs_rd; - VertexHeapScanDesc so = (VertexHeapScanDesc) scan; - ScanKey cur; - uint32 hashkey; - Bucket bucket; - Buffer buf; - Page page; - HashPageOpaque opaque; - HashScanPosItem *currItem; - - pgstat_count_index_scan(rel); - - /* - * We do not support hash scans with no index qualification, because we - * would have to read the whole index rather than just one bucket. That - * creates a whole raft of problems, since we haven't got a practical way - * to lock all the buckets against splits or compactions. - */ - // XXX: This is a major problem and must be solved - if (scan->rs_nkeys < 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("hash indexes do not support whole-index scans"))); - - /* There may be more than one index qual, but we hash only the first */ - cur = &scan->rs_key[0]; - - /* We support only single-column hash indexes */ - //Assert(cur->sk_attno == 1); - /* And there's only one operator strategy, too */ - //Assert(cur->sk_strategy == HTEqualStrategyNumber); - - /* - * If the constant in the index qual is NULL, assume it cannot match any - * items in the index. - */ - if (cur->sk_flags & SK_ISNULL) - return false; - - /* - * Okay to compute the hash key. We want to do this before acquiring any - * locks, in case a user-defined hash function happens to be slow. - * - * If scankey operator is not a cross-type comparison, we can use the - * cached hash function; otherwise gotta look it up in the catalogs. - * - * We support the convention that sk_subtype == InvalidOid means the - * opclass input type; this is a hack to simplify life for ScanKeyInit(). - */ - if (cur->sk_subtype == rel->rd_opcintype[0] || - cur->sk_subtype == InvalidOid) - hashkey = _hash_datum2hashkey(rel, cur->sk_argument); - else - hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument, cur->sk_subtype); - - so->hashso_sk_hash = hashkey; - - buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_READ, NULL); - PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->rs_snapshot); - page = BufferGetPage(buf); - //TestForOldSnapshot(scan->rs_snapshot, rel, page); - opaque = (HashPageOpaque) PageGetSpecialPointer(page); - bucket = opaque->hasho_bucket; - - so->hashso_bucket_buf = buf; - - /* - * If a bucket split is in progress, then while scanning the bucket being - * populated, we need to skip tuples that were copied from bucket being - * split. We also need to maintain a pin on the bucket being split to - * ensure that split-cleanup work done by vacuum doesn't remove tuples - * from it till this scan is done. We need to maintain a pin on the - * bucket being populated to ensure that vacuum doesn't squeeze that - * bucket till this scan is complete; otherwise, the ordering of tuples - * can't be maintained during forward and backward scans. Here, we have - * to be cautious about locking order: first, acquire the lock on bucket - * being split; then, release the lock on it but not the pin; then, - * acquire a lock on bucket being populated and again re-verify whether - * the bucket split is still in progress. Acquiring the lock on bucket - * being split first ensures that the vacuum waits for this scan to - * finish. - */ - if (H_BUCKET_BEING_POPULATED(opaque)) - { - BlockNumber old_blkno; - Buffer old_buf; - - old_blkno = _hash_get_oldblock_from_newbucket(rel, bucket); - - /* - * release the lock on new bucket and re-acquire it after acquiring - * the lock on old bucket. - */ - LockBuffer(buf, BUFFER_LOCK_UNLOCK); - - old_buf = _hash_getbuf(rel, old_blkno, HASH_READ, LH_BUCKET_PAGE); - TestForOldSnapshot(scan->rs_snapshot, rel, BufferGetPage(old_buf)); - - /* - * remember the split bucket buffer so as to use it later for - * scanning. - */ - so->hashso_split_bucket_buf = old_buf; - LockBuffer(old_buf, BUFFER_LOCK_UNLOCK); - - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - opaque = (HashPageOpaque) PageGetSpecialPointer(page); - Assert(opaque->hasho_bucket == bucket); - - if (H_BUCKET_BEING_POPULATED(opaque)) - so->hashso_buc_populated = true; - else - { - _hash_dropbuf(rel, so->hashso_split_bucket_buf); - so->hashso_split_bucket_buf = InvalidBuffer; - } - } - - /* If a backwards scan is requested, move to the end of the chain */ - if (ScanDirectionIsBackward(dir)) - { - /* - * Backward scans that start during split needs to start from end of - * bucket being split. - */ - while (BlockNumberIsValid(opaque->hasho_nextblkno) || - (so->hashso_buc_populated && !so->hashso_buc_split)) - vertex_hash_readnext(scan, &buf, &page, &opaque); - } - - /* remember which buffer we have pinned, if any */ - Assert(BufferIsInvalid(so->currPos.buf)); - so->currPos.buf = buf; - - /* Now find all the tuples satisfying the qualification from a page */ - if (!vertex_hash_readpage(scan, &buf, dir)) - return false; - - /* OK, itemIndex says what to return */ - currItem = &so->currPos.items[so->currPos.itemIndex]; - //scan->xs_heaptid = currItem->heapTid; - - /* if we're here, vertex_hash_readpage found a valid tuples */ - return true; -} - -void -vertex_hash_kill_items(TableScanDesc scan) -{ - HashScanOpaque so = (HashScanOpaque) scan; - Relation rel = scan->rs_rd; - BlockNumber blkno; - Buffer buf; - Page page; - HashPageOpaque opaque; - OffsetNumber offnum, - maxoff; - int numKilled = so->numKilled; - int i; - bool killedsomething = false; - bool havePin = false; - - Assert(so->numKilled > 0); - Assert(so->killedItems != NULL); - Assert(HashScanPosIsValid(so->currPos)); - - /* - * Always reset the scan state, so we don't look for same items on other - * pages. - */ - so->numKilled = 0; - - blkno = so->currPos.currPage; - if (HashScanPosIsPinned(so->currPos)) - { - /* - * We already have pin on this buffer, so, all we need to do is - * acquire lock on it. - */ - havePin = true; - buf = so->currPos.buf; - LockBuffer(buf, BUFFER_LOCK_SHARE); - } - else - buf = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE); - - page = BufferGetPage(buf); - opaque = (HashPageOpaque) PageGetSpecialPointer(page); - maxoff = PageGetMaxOffsetNumber(page); - - for (i = 0; i < numKilled; i++) - { - int itemIndex = so->killedItems[i]; - HashScanPosItem *currItem = &so->currPos.items[itemIndex]; - - offnum = currItem->indexOffset; - - Assert(itemIndex >= so->currPos.firstItem && - itemIndex <= so->currPos.lastItem); - - while (offnum <= maxoff) - { - ItemId iid = PageGetItemId(page, offnum); - IndexTuple ituple = (IndexTuple) PageGetItem(page, iid); - - if (ItemPointerEquals(&ituple->t_tid, &currItem->heapTid)) - { - /* found the item */ - ItemIdMarkDead(iid); - killedsomething = true; - break; /* out of inner search loop */ - } - offnum = OffsetNumberNext(offnum); - } - } - - /* - * Since this can be redone later if needed, mark as dirty hint. Whenever - * we mark anything LP_DEAD, we also set the page's - * LH_PAGE_HAS_DEAD_TUPLES flag, which is likewise just a hint. - */ - if (killedsomething) - { - opaque->hasho_flag |= LH_PAGE_HAS_DEAD_TUPLES; - MarkBufferDirtyHint(buf, true); - } - - if (so->hashso_bucket_buf == so->currPos.buf || - havePin) - LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK); - else - _hash_relbuf(rel, buf); -} - - -/* - * vertex_hash_readpage() -- Load data from current index page into so->currPos - * - * We scan all the items in the current index page and save them into - * so->currPos if it satisfies the qualification. If no matching items - * are found in the current page, we move to the next or previous page - * in a bucket chain as indicated by the direction. - * - * Return true if any matching items are found else return false. - */ -static bool -vertex_hash_readpage(TableScanDesc scan, Buffer *bufP, ScanDirection dir) -{ - Relation rel = scan->rs_rd; - VertexHeapScanDesc so = (VertexHeapScanDesc) scan; - Buffer buf; - Page page; - HashPageOpaque opaque; - OffsetNumber offnum; - uint16 itemIndex; - - buf = *bufP; - Assert(BufferIsValid(buf)); - _hash_checkpage(rel, buf, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); - page = BufferGetPage(buf); - opaque = (HashPageOpaque) PageGetSpecialPointer(page); - - so->currPos.buf = buf; - so->currPos.currPage = BufferGetBlockNumber(buf); - - if (ScanDirectionIsForward(dir)) - { - BlockNumber prev_blkno = InvalidBlockNumber; - - for (;;) - { - /* new page, locate starting position by binary search */ - offnum = _hash_binsearch(page, so->hashso_sk_hash); - - itemIndex = vertex_hash_load_qualified_items(scan, page, offnum, dir); - - if (itemIndex != 0) - break; - - /* - * Could not find any matching tuples in the current page, move to - * the next page. Before leaving the current page, deal with any - * killed items. - */ - if (so->numKilled > 0) - vertex_hash_kill_items(scan); - - /* - * If this is a primary bucket page, hasho_prevblkno is not a real - * block number. - */ - if (so->currPos.buf == so->hashso_bucket_buf || - so->currPos.buf == so->hashso_split_bucket_buf) - prev_blkno = InvalidBlockNumber; - else - prev_blkno = opaque->hasho_prevblkno; - - vertex_hash_readnext(scan, &buf, &page, &opaque); - if (BufferIsValid(buf)) - { - so->currPos.buf = buf; - so->currPos.currPage = BufferGetBlockNumber(buf); - } - else - { - /* - * Remember next and previous block numbers for scrollable - * cursors to know the start position and return false - * indicating that no more matching tuples were found. Also, - * don't reset currPage or lsn, because we expect - * vertex_hash_kill_items to be called for the old page after this - * function returns. - */ - so->currPos.prevPage = prev_blkno; - so->currPos.nextPage = InvalidBlockNumber; - so->currPos.buf = buf; - return false; - } - } - - so->currPos.firstItem = 0; - so->currPos.lastItem = itemIndex - 1; - so->currPos.itemIndex = 0; - } - else - { - BlockNumber next_blkno = InvalidBlockNumber; - - for (;;) - { - /* new page, locate starting position by binary search */ - offnum = _hash_binsearch_last(page, so->hashso_sk_hash); - - itemIndex = vertex_hash_load_qualified_items(scan, page, offnum, dir); - - if (itemIndex != MaxIndexTuplesPerPage) - break; - - /* - * Could not find any matching tuples in the current page, move to - * the previous page. Before leaving the current page, deal with - * any killed items. - */ - if (so->numKilled > 0) - vertex_hash_kill_items(scan); - - if (so->currPos.buf == so->hashso_bucket_buf || - so->currPos.buf == so->hashso_split_bucket_buf) - next_blkno = opaque->hasho_nextblkno; - - vertex__hash_readprev(scan, &buf, &page, &opaque); - if (BufferIsValid(buf)) - { - so->currPos.buf = buf; - so->currPos.currPage = BufferGetBlockNumber(buf); - } - else - { - /* - * Remember next and previous block numbers for scrollable - * cursors to know the start position and return false - * indicating that no more matching tuples were found. Also, - * don't reset currPage or lsn, because we expect - * vertex_hash_kill_items to be called for the old page after this - * function returns. - */ - so->currPos.prevPage = InvalidBlockNumber; - so->currPos.nextPage = next_blkno; - so->currPos.buf = buf; - return false; - } - } - - so->currPos.firstItem = itemIndex; - so->currPos.lastItem = MaxIndexTuplesPerPage - 1; - so->currPos.itemIndex = MaxIndexTuplesPerPage - 1; - } - - if (so->currPos.buf == so->hashso_bucket_buf || - so->currPos.buf == so->hashso_split_bucket_buf) - { - so->currPos.prevPage = InvalidBlockNumber; - so->currPos.nextPage = opaque->hasho_nextblkno; - LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK); - } - else - { - so->currPos.prevPage = opaque->hasho_prevblkno; - so->currPos.nextPage = opaque->hasho_nextblkno; - _hash_relbuf(rel, so->currPos.buf); - so->currPos.buf = InvalidBuffer; - } - - Assert(so->currPos.firstItem <= so->currPos.lastItem); - return true; -} - -/* - * vertex_hash_checkqual -- does the index tuple satisfy the scan conditions? - */ -bool -vertex_hash_checkqual(TableScanDesc scan, IndexTuple itup) -{ - /* - * Currently, we can't check any of the scan conditions since we do not - * have the original index entry value to supply to the sk_func. Always - * return true; we expect that hashgettuple already set the recheck flag - * to make the main indexscan code do it. - */ -#ifdef NOT_USED - TupleDesc tupdesc = RelationGetDescr(scan->indexRelation); - ScanKey key = scan->keyData; - int scanKeySize = scan->numberOfKeys; - - while (scanKeySize > 0) - { - Datum datum; - bool isNull; - Datum test; - - datum = index_getattr(itup, - key->sk_attno, - tupdesc, - &isNull); - - /* assume sk_func is strict */ - if (isNull) - return false; - if (key->sk_flags & SK_ISNULL) - return false; - - test = FunctionCall2Coll(&key->sk_func, key->sk_collation, - datum, key->sk_argument); - - if (!DatumGetBool(test)) - return false; - - key++; - scanKeySize--; - } -#endif - - return true; -} - -/* - * Load all the qualified items from a current index page - * into so->currPos. Helper function for vertex_hash_readpage. - */ -static int -vertex_hash_load_qualified_items(TableScanDesc scan, Page page, - OffsetNumber offnum, ScanDirection dir) -{ - VertexHeapScanDesc so = (VertexHeapScanDesc)scan; - IndexTuple itup; - int itemIndex; - OffsetNumber maxoff; - - maxoff = PageGetMaxOffsetNumber(page); - - if (ScanDirectionIsForward(dir)) - { - /* load items[] in ascending order */ - itemIndex = 0; - - while (offnum <= maxoff) - { - Assert(offnum >= FirstOffsetNumber); - itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); - - /* - * skip the tuples that are moved by split operation for the scan - * that has started when split was in progress. Also, skip the - * tuples that are marked as dead. - */ - if ((so->hashso_buc_populated && !so->hashso_buc_split && - (itup->t_info & INDEX_MOVED_BY_SPLIT_MASK)) || - ((ItemIdIsDead(PageGetItemId(page, offnum))))) - { - offnum = OffsetNumberNext(offnum); /* move forward */ - continue; - } - - if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) && - vertex_hash_checkqual(scan, itup)) - { - /* tuple is qualified, so remember it */ - vertex_hash_saveitem(so, itemIndex, offnum, itup); - itemIndex++; - } - else - { - /* - * No more matching tuples exist in this page. so, exit while - * loop. - */ - break; - } - - offnum = OffsetNumberNext(offnum); - } - - Assert(itemIndex <= MaxIndexTuplesPerPage); - return itemIndex; - } - else - { - /* load items[] in descending order */ - itemIndex = MaxIndexTuplesPerPage; - - while (offnum >= FirstOffsetNumber) - { - Assert(offnum <= maxoff); - itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); - - /* - * skip the tuples that are moved by split operation for the scan - * that has started when split was in progress. Also, skip the - * tuples that are marked as dead. - */ - if ((so->hashso_buc_populated && !so->hashso_buc_split && - (itup->t_info & INDEX_MOVED_BY_SPLIT_MASK)) || - (true && - (ItemIdIsDead(PageGetItemId(page, offnum))))) - { - offnum = OffsetNumberPrev(offnum); /* move back */ - continue; - } - - if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) && - vertex_hash_checkqual(scan, itup)) - { - itemIndex--; - /* tuple is qualified, so remember it */ - vertex_hash_saveitem(so, itemIndex, offnum, itup); - } - else - { - /* - * No more matching tuples exist in this page. so, exit while - * loop. - */ - break; - } - - offnum = OffsetNumberPrev(offnum); - } - - Assert(itemIndex >= 0); - return itemIndex; - } -} - -/* Save an index item into so->currPos.items[itemIndex] */ -static inline void -vertex_hash_saveitem(VertexHeapScanDesc so, int itemIndex, - OffsetNumber offnum, IndexTuple itup) -{ - HashScanPosItem *currItem = &so->currPos.items[itemIndex]; - - currItem->heapTid = itup->t_tid; - currItem->indexOffset = offnum; -} diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index e649a042..aca2a45a 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -73,761 +73,6 @@ static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, uint16 *new_infomask2); -void -vertex_exec_index_build_ScanKeys(PlanState *planstate, Relation index, - List *quals, - ScanKey *scanKeys, int *numScanKeys); -typedef struct vertex_hash_struct -{ - graphid id; // hash key - ItemPointerData itemPointer; - gtype *properties; - Page page; - ItemId itemId; - Buffer buffer; -} vertex_hash_struct; - - -static vertex_hash_struct *iterator = NULL; -static HTAB *vertex_hash = NULL; -static HASH_SEQ_STATUS scanStatus; - -#include "access/parallel.h" -/* - * Subroutine for heap_insert(). Prepares a tuple for insertion. This sets the - * tuple header fields and toasts the tuple if necessary. Returns a toasted - * version of the tuple if it was toasted, or the original tuple if not. Note - * that in any case, the header fields are also set in the original tuple. - */ -static HeapTuple -heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid, - CommandId cid, int options) -{ - /* - * To allow parallel inserts, we need to ensure that they are safe to be - * performed in workers. We have the infrastructure to allow parallel - * inserts in general except for the cases where inserts generate a new - * CommandId (eg. inserts into a table having a foreign key column). - */ - if (IsParallelWorker()) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TRANSACTION_STATE), - errmsg("cannot insert tuples in a parallel worker"))); - - tup->t_data->t_infomask &= ~(HEAP_XACT_MASK); - tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK); - tup->t_data->t_infomask |= HEAP_XMAX_INVALID; - HeapTupleHeaderSetXmin(tup->t_data, xid); - if (options & HEAP_INSERT_FROZEN) - HeapTupleHeaderSetXminFrozen(tup->t_data); - - HeapTupleHeaderSetCmin(tup->t_data, cid); - HeapTupleHeaderSetXmax(tup->t_data, 0); /* for cleanliness */ - tup->t_tableOid = RelationGetRelid(relation); - - /* - * If the new tuple is too big for storage or contains already toasted - * out-of-line attributes from some other relation, invoke the toaster. - */ - if (relation->rd_rel->relkind != RELKIND_RELATION && - relation->rd_rel->relkind != RELKIND_MATVIEW) - { - /* toast table entries should never be recursively toasted */ - Assert(!HeapTupleHasExternal(tup)); - return tup; - } - else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD) - return heap_toast_insert_or_update(relation, tup, NULL, options); - else - return tup; -} - - - - - - -/* - * * Given infomask/infomask2, compute the bits that must be saved in the - * * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock, - * * xl_heap_lock_updated WAL records. - * * - * * See fix_infomask_from_infobits. - * */ -static uint8 -compute_infobits(uint16 infomask, uint16 infomask2) -{ - return - ((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) | - ((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) | - ((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) | - /* note we ignore HEAP_XMAX_SHR_LOCK here */ - ((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) | - ((infomask2 & HEAP_KEYS_UPDATED) != 0 ? - XLHL_KEYS_UPDATED : 0); -} - -/* - * MultiXactIdGetUpdateXid - * - * Given a multixact Xmax and corresponding infomask, which does not have the - * HEAP_XMAX_LOCK_ONLY bit set, obtain and return the Xid of the updating - * transaction. - * - * Caller is expected to check the status of the updating transaction, if - * necessary. - */ -static TransactionId -MultiXactIdGetUpdateXid(TransactionId xmax, uint16 t_infomask) -{ - TransactionId update_xact = InvalidTransactionId; - MultiXactMember *members; - int nmembers; - - Assert(!(t_infomask & HEAP_XMAX_LOCK_ONLY)); - Assert(t_infomask & HEAP_XMAX_IS_MULTI); - - /* - * Since we know the LOCK_ONLY bit is not set, this cannot be a multi from - * pre-pg_upgrade. - */ - nmembers = GetMultiXactIdMembers(xmax, &members, false, false); - - if (nmembers > 0) - { - int i; - - for (i = 0; i < nmembers; i++) - { - /* Ignore lockers */ - if (!ISUPDATE_from_mxstatus(members[i].status)) - continue; - - /* there can be at most one updater */ - Assert(update_xact == InvalidTransactionId); - update_xact = members[i].xid; -#ifndef USE_ASSERT_CHECKING - /* - * in an assert-enabled build, walk the whole array to ensure - * there's no other updater. - */ - break; -#endif - } - - pfree(members); - } - - return update_xact; -} - - - -/* - * * Given two versions of the same t_infomask for a tuple, compare them and - * * return whether the relevant status for a tuple Xmax has changed. This is - * * used after a buffer lock has been released and reacquired: we want to ensure - * * that the tuple state continues to be the same it was when we previously - * * examined it. - * * - * * Note the Xmax field itself must be compared separately. - * */ -static inline bool -xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask) -{ - const uint16 interesting = - HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK; - - if ((new_infomask & interesting) != (old_infomask & interesting)) - return true; - - return false; -} - -/* - * * This table maps tuple lock strength values for each particular - * * MultiXactStatus value. - * */ -static const int MultiXactStatusLock[MaxMultiXactStatus + 1] = -{ - LockTupleKeyShare, /* ForKeyShare */ - LockTupleShare, /* ForShare */ - LockTupleNoKeyExclusive, /* ForNoKeyUpdate */ - LockTupleExclusive, /* ForUpdate */ - LockTupleNoKeyExclusive, /* NoKeyUpdate */ - LockTupleExclusive /* Update */ -}; - - -/* Get the LockTupleMode for a given MultiXactStatus */ -#define TUPLOCK_from_mxstatus(status) \ - (MultiXactStatusLock[(status)]) -/* ---------------------------------------------------------------- - * heap support routines - * ---------------------------------------------------------------- - */ - - -/* - * * Build a heap tuple representing the configured REPLICA IDENTITY to represent - * * the old tuple in a UPDATE or DELETE. - * * - * * Returns NULL if there's no need to log an identity or if there's no suitable - * * key defined. - * * - * * Pass key_required true if any replica identity columns changed value, or if - * * any of them have any external data. Delete must always pass true. - * * - * * *copy is set to true if the returned tuple is a modified copy rather than - * * the same tuple that was passed in. - * */ -static HeapTuple -ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, - bool *copy) -{ - TupleDesc desc = RelationGetDescr(relation); - char replident = relation->rd_rel->relreplident; - Bitmapset *idattrs; - HeapTuple key_tuple; - bool nulls[MaxHeapAttributeNumber]; - Datum values[MaxHeapAttributeNumber]; - - *copy = false; - - if (!RelationIsLogicallyLogged(relation)) - return NULL; - - if (replident == REPLICA_IDENTITY_NOTHING) - return NULL; - - if (replident == REPLICA_IDENTITY_FULL) - { - /* - * * When logging the entire old tuple, it very well could contain - * * toasted columns. If so, force them to be inlined. - * */ - if (HeapTupleHasExternal(tp)) - { - *copy = true; - tp = toast_flatten_tuple(tp, desc); - } - return tp; - } - - /* if the key isn't required and we're only logging the key, we're done */ - if (!key_required) - return NULL; - - /* find out the replica identity columns */ - idattrs = RelationGetIndexAttrBitmap(relation, - INDEX_ATTR_BITMAP_IDENTITY_KEY); - - /* - * * If there's no defined replica identity columns, treat as !key_required. - * * (This case should not be reachable from heap_update, since that should - * * calculate key_required accurately. But heap_delete just passes - * * constant true for key_required, so we can hit this case in deletes.) - * */ - if (bms_is_empty(idattrs)) - return NULL; - - /* - * * Construct a new tuple containing only the replica identity columns, - * * with nulls elsewhere. While we're at it, assert that the replica - * * identity columns aren't null. - * */ - heap_deform_tuple(tp, desc, values, nulls); - - for (int i = 0; i < desc->natts; i++) - { - if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber, - idattrs)) - Assert(!nulls[i]); - else - nulls[i] = true; - } - - key_tuple = heap_form_tuple(desc, values, nulls); - *copy = true; - - bms_free(idattrs); - - /* - * * If the tuple, which by here only contains indexed columns, still has - * * toasted columns, force them to be inlined. This is somewhat unlikely - * * since there's limits on the size of indexed columns, so we don't - * * duplicate toast_flatten_tuple()s functionality in the above loop over - * * the indexed columns, even if it would be more efficient. - * */ - if (HeapTupleHasExternal(key_tuple)) - { - HeapTuple oldtup = key_tuple; - - key_tuple = toast_flatten_tuple(oldtup, desc); - heap_freetuple(oldtup); - } - - return key_tuple; -} - -/* - * * Each tuple lock mode has a corresponding heavyweight lock, and one or two - * * corresponding MultiXactStatuses (one to merely lock tuples, another one to - * * update them). This table (and the macros below) helps us determine the - * * heavyweight lock mode and MultiXactStatus values to use for any particular - * * tuple lock strength. - * * - * * Don't look at lockstatus/updstatus directly! Use get_mxact_status_for_lock - * * instead. - * */ -static const struct -{ - LOCKMODE hwlock; - int lockstatus; - int updstatus; -} - - tupleLockExtraInfo[MaxLockTupleMode + 1] = -{ - { /* LockTupleKeyShare */ - AccessShareLock, - MultiXactStatusForKeyShare, - -1 /* KeyShare does not allow updating tuples */ - }, - { /* LockTupleShare */ - RowShareLock, - MultiXactStatusForShare, - -1 /* Share does not allow updating tuples */ - }, - { /* LockTupleNoKeyExclusive */ - ExclusiveLock, - MultiXactStatusForNoKeyUpdate, - MultiXactStatusNoKeyUpdate - }, - { /* LockTupleExclusive */ - AccessExclusiveLock, - MultiXactStatusForUpdate, - MultiXactStatusUpdate - } -}; - - - -/* Get the LOCKMODE for a given MultiXactStatus */ -#define LOCKMODE_from_mxstatus(status) \ - (tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock) - -/* - * * Acquire heavyweight locks on tuples, using a LockTupleMode strength value. - * * This is more readable than having every caller translate it to lock.h's - * * LOCKMODE. - * */ -#define LockTupleTuplock(rel, tup, mode) \ - LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock) -#define UnlockTupleTuplock(rel, tup, mode) \ - UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock) -#define ConditionalLockTupleTuplock(rel, tup, mode) \ - ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock) - -#ifdef USE_PREFETCH -/* - * * heap_index_delete_tuples and index_delete_prefetch_buffer use this - * * structure to coordinate prefetching activity - * */ -typedef struct -{ - BlockNumber cur_hblkno; - int next_item; - int ndeltids; - TM_IndexDelete *deltids; -} IndexDeletePrefetchState; -#endif - -/* heap_index_delete_tuples bottom-up index deletion costing constants */ -#define BOTTOMUP_MAX_NBLOCKS 6 -#define BOTTOMUP_TOLERANCE_NBLOCKS 3 - -/* - * * heap_index_delete_tuples uses this when determining which heap blocks it - * * must visit to help its bottom-up index deletion caller - * */ -typedef struct IndexDeleteCounts -{ - int16 npromisingtids; /* Number of "promising" TIDs in group */ - int16 ntids; /* Number of TIDs in group */ - int16 ifirsttid; /* Offset to group's first deltid */ -} IndexDeleteCounts; - -/* - * * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends - * * - * * This is called after we have waited for the XMAX transaction to terminate. - * * If the transaction aborted, we guarantee the XMAX_INVALID hint bit will - * * be set on exit. If the transaction committed, we set the XMAX_COMMITTED - * * hint bit if possible --- but beware that that may not yet be possible, - * * if the transaction committed asynchronously. - * * - * * Note that if the transaction was a locker only, we set HEAP_XMAX_INVALID - * * even if it commits. - * * - * * Hence callers should look only at XMAX_INVALID. - * * - * * Note this is not allowed for tuples whose xmax is a multixact. - * */ -static void -UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid) -{ - Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid)); - Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI)); - - if (!(tuple->t_infomask & (HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID))) - { - if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) && - TransactionIdDidCommit(xid)) - HeapTupleSetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, - xid); - else - HeapTupleSetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - } -} - -/* - * * Does the given multixact conflict with the current transaction grabbing a - * * tuple lock of the given strength? - * * - * * The passed infomask pairs up with the given multixact in the tuple header. - * * - * * If current_is_member is not NULL, it is set to 'true' if the current - * * transaction is a member of the given multixact. - * */ -static bool -DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask, - LockTupleMode lockmode, bool *current_is_member) -{ - int nmembers; - MultiXactMember *members; - bool result = false; - LOCKMODE wanted = tupleLockExtraInfo[lockmode].hwlock; - - if (HEAP_LOCKED_UPGRADED(infomask)) - return false; - - nmembers = GetMultiXactIdMembers(multi, &members, false, - HEAP_XMAX_IS_LOCKED_ONLY(infomask)); - if (nmembers >= 0) - { - int i; - - for (i = 0; i < nmembers; i++) - { - TransactionId memxid; - LOCKMODE memlockmode; - - if (result && (current_is_member == NULL || *current_is_member)) - break; - - memlockmode = LOCKMODE_from_mxstatus(members[i].status); - - /* ignore members from current xact (but track their presence) */ - memxid = members[i].xid; - if (TransactionIdIsCurrentTransactionId(memxid)) - { - if (current_is_member != NULL) - *current_is_member = true; - continue; - } - else if (result) - continue; - - /* ignore members that don't conflict with the lock we want */ - if (!DoLockModesConflict(memlockmode, wanted)) - continue; - - if (ISUPDATE_from_mxstatus(members[i].status)) - { - /* ignore aborted updaters */ - if (TransactionIdDidAbort(memxid)) - continue; - } - else - { - /* ignore lockers-only that are no longer in progress */ - if (!TransactionIdIsInProgress(memxid)) - continue; - } - - /* - * * Whatever remains are either live lockers that conflict with our - * * wanted lock, and updaters that are not aborted. Those conflict - * * with what we want. Set up to return true, but keep going to - * * look for the current transaction among the multixact members, - * * if needed. - * */ - result = true; - } - pfree(members); - } - - return result; -} - -/* - * * Do_MultiXactIdWait - * * Actual implementation for the two functions below. - * * - * * 'multi', 'status' and 'infomask' indicate what to sleep on (the status is - * * needed to ensure we only sleep on conflicting members, and the infomask is - * * used to optimize multixact access in case it's a lock-only multi); 'nowait' - * * indicates whether to use conditional lock acquisition, to allow callers to - * * fail if lock is unavailable. 'rel', 'ctid' and 'oper' are used to set up - * * context information for error messages. 'remaining', if not NULL, receives - * * the number of members that are still running, including any (non-aborted) - * * subtransactions of our own transaction. - * * - * * We do this by sleeping on each member using XactLockTableWait. Any - * * members that belong to the current backend are *not* waited for, however; - * * this would not merely be useless but would lead to Assert failure inside - * * XactLockTableWait. By the time this returns, it is certain that all - * * transactions *of other backends* that were members of the MultiXactId - * * that conflict with the requested status are dead (and no new ones can have - * * been added, since it is not legal to add members to an existing - * * MultiXactId). - * * - * * But by the time we finish sleeping, someone else may have changed the Xmax - * * of the containing tuple, so the caller needs to iterate on us somehow. - * * - * * Note that in case we return false, the number of remaining members is - * * not to be trusted. - * */ -static bool -Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, - uint16 infomask, bool nowait, - Relation rel, ItemPointer ctid, XLTW_Oper oper, - int *remaining) -{ - bool result = true; - MultiXactMember *members; - int nmembers; - int remain = 0; - - /* for pre-pg_upgrade tuples, no need to sleep at all */ - nmembers = HEAP_LOCKED_UPGRADED(infomask) ? -1 : - GetMultiXactIdMembers(multi, &members, false, - HEAP_XMAX_IS_LOCKED_ONLY(infomask)); - - if (nmembers >= 0) - { - int i; - - for (i = 0; i < nmembers; i++) - { - TransactionId memxid = members[i].xid; - MultiXactStatus memstatus = members[i].status; - - if (TransactionIdIsCurrentTransactionId(memxid)) - { - remain++; - continue; - } - - if (!DoLockModesConflict(LOCKMODE_from_mxstatus(memstatus), - LOCKMODE_from_mxstatus(status))) - { - if (remaining && TransactionIdIsInProgress(memxid)) - remain++; - continue; - } - - /* - * * This member conflicts with our multi, so we have to sleep (or - * * return failure, if asked to avoid waiting.) - * * - * * Note that we don't set up an error context callback ourselves, - * * but instead we pass the info down to XactLockTableWait. This - * * might seem a bit wasteful because the context is set up and - * * tore down for each member of the multixact, but in reality it - * * should be barely noticeable, and it avoids duplicate code. - * */ - if (nowait) - { - result = ConditionalXactLockTableWait(memxid); - if (!result) - break; - } - else - XactLockTableWait(memxid, rel, ctid, oper); - } - - pfree(members); - } - - if (remaining) - *remaining = remain; - - return result; -} - -/* - * * MultiXactIdWait - * * Sleep on a MultiXactId. - * * - * * By the time we finish sleeping, someone else may have changed the Xmax - * * of the containing tuple, so the caller needs to iterate on us somehow. - * * - * * We return (in *remaining, if not NULL) the number of members that are still - * * running, including any (non-aborted) subtransactions of our own transaction. - * */ -static void -MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask, - Relation rel, ItemPointer ctid, XLTW_Oper oper, - int *remaining) -{ - (void) Do_MultiXactIdWait(multi, status, infomask, false, - rel, ctid, oper, remaining); -} - -/* - * * ConditionalMultiXactIdWait - * * As above, but only lock if we can get the lock without blocking. - * * - * * By the time we finish sleeping, someone else may have changed the Xmax - * * of the containing tuple, so the caller needs to iterate on us somehow. - * * - * * If the multixact is now all gone, return true. Returns false if some - * * transactions might still be running. - * * - * * We return (in *remaining, if not NULL) the number of members that are still - * * running, including any (non-aborted) subtransactions of our own transaction. - * */ -static bool -ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status, - uint16 infomask, Relation rel, int *remaining) -{ - return Do_MultiXactIdWait(multi, status, infomask, true, - rel, NULL, XLTW_None, remaining); -} - - - -/* ---------------- - * initscan - scan code common to heap_beginscan and heap_rescan - * ---------------- - */ -static void -initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock) -{ - ParallelBlockTableScanDesc bpscan = NULL; - bool allow_strat; - bool allow_sync; - - /* - * Determine the number of blocks we have to scan. - * - * It is sufficient to do this once at scan start, since any tuples added - * while the scan is in progress will be invisible to my snapshot anyway. - * (That is not true when using a non-MVCC snapshot. However, we couldn't - * guarantee to return tuples added after scan start anyway, since they - * might go into pages we already scanned. To guarantee consistent - * results for a non-MVCC snapshot, the caller must hold some higher-level - * lock that ensures the interesting tuple(s) won't change.) - */ - if (scan->rs_base.rs_parallel != NULL) - { - bpscan = (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; - scan->rs_nblocks = bpscan->phs_nblocks; - } - else - scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_base.rs_rd); - - /* - * If the table is large relative to NBuffers, use a bulk-read access - * strategy and enable synchronized scanning (see syncscan.c). Although - * the thresholds for these features could be different, we make them the - * same so that there are only two behaviors to tune rather than four. - * (However, some callers need to be able to disable one or both of these - * behaviors, independently of the size of the table; also there is a GUC - * variable that can disable synchronized scanning.) - * - * Note that table_block_parallelscan_initialize has a very similar test; - * if you change this, consider changing that one, too. - */ - if (!RelationUsesLocalBuffers(scan->rs_base.rs_rd) && scan->rs_nblocks > NBuffers / 4) { - allow_strat = (scan->rs_base.rs_flags & SO_ALLOW_STRAT) != 0; - allow_sync = (scan->rs_base.rs_flags & SO_ALLOW_SYNC) != 0; - } - else - allow_strat = allow_sync = false; - - if (allow_strat) - { - /* During a rescan, keep the previous strategy object. */ - if (scan->rs_strategy == NULL) - scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD); - } - else - { - if (scan->rs_strategy != NULL) - FreeAccessStrategy(scan->rs_strategy); - scan->rs_strategy = NULL; - } - - if (scan->rs_base.rs_parallel != NULL) - { - /* For parallel scan, believe whatever ParallelTableScanDesc says. */ - if (scan->rs_base.rs_parallel->phs_syncscan) - scan->rs_base.rs_flags |= SO_ALLOW_SYNC; - else - scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; - } - else if (keep_startblock) - { - /* - * When rescanning, we want to keep the previous startblock setting, - * so that rewinding a cursor doesn't generate surprising results. - * Reset the active syncscan setting, though. - */ - if (allow_sync && synchronize_seqscans) - scan->rs_base.rs_flags |= SO_ALLOW_SYNC; - else - scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; - } - else if (allow_sync && synchronize_seqscans) - { - scan->rs_base.rs_flags |= SO_ALLOW_SYNC; - scan->rs_startblock = ss_get_location(scan->rs_base.rs_rd, scan->rs_nblocks); - } - else - { - scan->rs_base.rs_flags &= ~SO_ALLOW_SYNC; - scan->rs_startblock = 0; - } - - scan->rs_numblocks = InvalidBlockNumber; - scan->rs_inited = false; - scan->rs_ctup.t_data = NULL; - ItemPointerSetInvalid(&scan->rs_ctup.t_self); - scan->rs_cbuf = InvalidBuffer; - scan->rs_cblock = InvalidBlockNumber; - - /* page-at-a-time fields are always invalid when not rs_inited */ - - /* - * copy the scan key, if appropriate - */ - if (key != NULL && scan->rs_base.rs_nkeys > 0) - memcpy(scan->rs_base.rs_key, key, scan->rs_base.rs_nkeys * sizeof(ScanKeyData)); - - /* - * Currently, we only have a stats counter for sequential heap scans (but - * e.g for bitmap scans the underlying bitmap index scans will be counted, - * and for sample scans we update stats for tuple fetches). - */ - if (scan->rs_base.rs_flags & SO_TYPE_SEQSCAN) - pgstat_count_heap_scan(scan->rs_base.rs_rd); -} - - /* ------------------------------------------------------------------------ * Slot related callbacks for vertex heap AM * ------------------------------------------------------------------------ @@ -876,7 +121,6 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, vertex_desc->ndesc = 1; TableAmRoutine *tableam = GetHeapamTableAmRoutine(); Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(RelationGetRelid(relation))), get_rel_namespace(RelationGetRelid(relation))); - ereport(WARNING, errmsg("begin_scan runtimeKey %p",&key)); Relation rel = RelationIdGetRelation(oid); TableScanDesc *desc = tableam->scan_begin(rel, snapshot, nkeys, key, parallel_scan, flags); @@ -887,15 +131,9 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, void vertex_scan_end(TableScanDesc sscan) { VertexScanDescData *vertex_desc = sscan; - - //Relation rel = table_open(RelnameGetRelid(make_vertex_adjlist_alias(RelationGetRelationName(relation))), AccessShareLock); - TableAmRoutine *tableam = GetHeapamTableAmRoutine(); - //Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(vertex_desc->rs_base.rs_rd->rd_id)), get_rel_namespace(vertex_desc->rs_base.rs_rd->rd_id)); - //table_close((*vertex_desc->desc[0]->rs_rd, AccessShareLock); - - tableam->scan_end(vertex_desc->desc[0]); + RelationDecrementReferenceCount( ((HeapScanDesc)vertex_desc->desc[0])->rs_base.rs_rd); - return ; + GetHeapamTableAmRoutine()->scan_end(vertex_desc->desc[0]); } /* @@ -917,43 +155,9 @@ bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot) { VertexScanDescData *vertex_desc = sscan; - /*if(vertex_desc->rs_base.rs_nkeys != 1) - ereport(ERROR,errmsg_internal("edges are a key value store, provide the key")); - - ereport(WARNING, errmsg("scankey in get slot %p %p", vertex_desc->rs_base.rs_key, &vertex_desc->rs_base.rs_key)); - if(vertex_desc->rs_base.rs_key == 0) - ereport(ERROR,errmsg_internal("edges are a key value store, provide the key")); - ereport(WARNING, errmsg("runtimeKey %p", vertex_desc->rs_base.rs_key)); - TableAmRoutine *tableam = GetHeapamTableAmRoutine(); - IndexRuntimeKeyInfo *indexRuntimeKeyInfo = (IndexRuntimeKeyInfo *)vertex_desc->rs_base.rs_key; - */ TableAmRoutine *tableam = GetHeapamTableAmRoutine(); - //ereport(WARNING, errmsg("Datum %d graphid %ld", ((Datum)((IndexRuntimeKeyInfo *)vertex_desc->rs_base.rs_key)->scan_key->sk_argument)), DATUM_GET_GRAPHID((Datum)((IndexRuntimeKeyInfo *)vertex_desc->rs_base.rs_key)->scan_key->sk_argument)); - bool result =true; return tableam->scan_getnextslot(vertex_desc->desc[0], direction, slot); -/* while (result) { - result = tableam->scan_getnextslot(vertex_desc->desc[0], direction, slot); - - if (!result) { - ereport(WARNING, errmsg("result")); - return result; - } - SeqScanState *node= ((IndexRuntimeKeyInfo *)vertex_desc->rs_base.rs_key)->key_expr->parent; - ExprState *qual = node->ss.ps.qual; - ExprContext *econtext = node->ss.ps.ps_ExprContext; - - ResetExprContext(econtext); - - econtext->ecxt_scantuple = slot; - - if (qual == NULL || ExecQual(qual, econtext)) { - ereport(WARNING, errmsg("qual passed")); - return true; - } else - ereport(WARNING, errmsg("qual failed")); - } - return result;*/ } /* ------------------------------------------------------------------------ @@ -1022,8 +226,6 @@ struct IndexFetchTableData *vertex_index_fetch_begin(Relation rel) { void vertex_index_fetch_reset(struct IndexFetchTableData *data) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - } /* @@ -1032,8 +234,6 @@ void vertex_index_fetch_reset(struct IndexFetchTableData *data) { void vertex_index_fetch_end(struct IndexFetchTableData *data) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - } /* @@ -1062,8 +262,6 @@ bool vertex_index_fetch_tuple(struct IndexFetchTableData *scan, bool *call_again, bool *all_dead) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - return false; } @@ -1195,33 +393,7 @@ TM_Result vertex_tuple_update(Relation relation, ItemPointer otid, TupleTableSlo CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, bool *update_indexes) { - - bool shouldFree = true; - HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); - TM_Result result; - - /* Update the tuple with table oid */ - slot->tts_tableOid = RelationGetRelid(relation); - tuple->t_tableOid = slot->tts_tableOid; - - result = heap_update(relation, otid, tuple, cid, crosscheck, wait, - tmfd, lockmode); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); - - /* - * Decide whether new index entries are needed for the tuple - * - * Note: heap_update returns the tid (location) of the new tuple in the - * t_self field. - * - * If it's a HOT update, we mustn't insert new index entries. - */ - *update_indexes = result == TM_Ok && !HeapTupleIsHeapOnly(tuple); - - if (shouldFree) - pfree(tuple); - - return result; + return TM_Invisible; } /* see table_tuple_lock() for reference about parameters */ @@ -1230,14 +402,9 @@ TM_Result vertex_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, LockWaitPolicy wait_policy, uint8 flags, TM_FailureData *tmfd) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - return 0; } - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ @@ -1303,9 +470,6 @@ void vertex_relation_copy_for_cluster(Relation OldTable, Relation NewTable, double *tups_vacuumed, double *tups_recently_dead) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - } /* @@ -1328,9 +492,6 @@ void vertex_relation_vacuum(Relation rel, struct VacuumParams *params, ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - } /* @@ -1382,12 +543,8 @@ double vertex_index_build_range_scan(Relation table_rel, Relation index_rel, bool anyvisible, bool progress, BlockNumber start_blockno, BlockNumber numblocks, IndexBuildCallback callback, void *callback_state, TableScanDesc scan) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - return 0; } @@ -1397,9 +554,6 @@ void vertex_index_validate_scan(Relation table_rel, Relation index_rel, struct ValidateIndexState *state) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - - - } @@ -1486,96 +640,7 @@ void vertex_relation_fetch_toast_slice(Relation toastrel, Oid valueid, void vertex_relation_estimate_size(Relation rel, int32 *attr_widths, BlockNumber *pages, double *tuples, double *allvisfrac) { - BlockNumber curpages; - BlockNumber relpages; - double reltuples; - BlockNumber relallvisible; - double density; - double overhead_bytes_per_tuple = HEAP_OVERHEAD_BYTES_PER_TUPLE; - double usable_bytes_per_page = HEAP_USABLE_BYTES_PER_PAGE; - /* it should have storage, so we can call the smgr */ - curpages = RelationGetNumberOfBlocks(rel); - - /* coerce values in pg_class to more desirable types */ - relpages = (BlockNumber) rel->rd_rel->relpages; - reltuples = (double) rel->rd_rel->reltuples; - relallvisible = (BlockNumber) rel->rd_rel->relallvisible; - - /* - * HACK: if the relation has never yet been vacuumed, use a minimum size - * estimate of 10 pages. The idea here is to avoid assuming a - * newly-created table is really small, even if it currently is, because - * that may not be true once some data gets loaded into it. Once a vacuum - * or analyze cycle has been done on it, it's more reasonable to believe - * the size is somewhat stable. - * - * (Note that this is only an issue if the plan gets cached and used again - * after the table has been filled. What we're trying to avoid is using a - * nestloop-type plan on a table that has grown substantially since the - * plan was made. Normally, autovacuum/autoanalyze will occur once enough - * inserts have happened and cause cached-plan invalidation; but that - * doesn't happen instantaneously, and it won't happen at all for cases - * such as temporary tables.) - * - * We test "never vacuumed" by seeing whether reltuples < 0. - * - * If the table has inheritance children, we don't apply this heuristic. - * Totally empty parent tables are quite common, so we should be willing - * to believe that they are empty. - */ - if (curpages < 10 && reltuples < 0 && !rel->rd_rel->relhassubclass) - curpages = 10; - - /* report estimated # pages */ - *pages = curpages; - /* quick exit if rel is clearly empty */ - if (curpages == 0) - { - *tuples = 0; - *allvisfrac = 0; - return; - } - - /* estimate number of tuples from previous tuple density */ - if (reltuples >= 0 && relpages > 0) - density = reltuples / (double) relpages; - else { - /* - * When we have no data because the relation was never yet vacuumed, - * estimate tuple width from attribute datatypes. We assume here that - * the pages are completely full, which is OK for tables but is - * probably an overestimate for indexes. Fortunately - * get_relation_info() can clamp the overestimate to the parent - * table's size. - * - * Note: this code intentionally disregards alignment considerations, - * because (a) that would be gilding the lily considering how crude - * the estimate is, (b) it creates platform dependencies in the - * default plans which are kind of a headache for regression testing, - * and (c) different table AMs might use different padding schemes. - */ - int32 tuple_width; - - tuple_width = get_rel_data_width(rel, attr_widths); - tuple_width += overhead_bytes_per_tuple; - /* note: integer division is intentional here */ - density = usable_bytes_per_page / tuple_width; - } - *tuples = rint(density * (double) curpages); - - /* - * We use relallvisible as-is, rather than scaling it up like we do for - * the pages and tuples counts, on the theory that any pages added since - * the last VACUUM are most likely not marked all-visible. But costsize.c - * wants it converted to a fraction. - */ - if (relallvisible == 0 || curpages <= 0) - *allvisfrac = 0; - else if ((double) relallvisible >= curpages) - *allvisfrac = 1; - else - *allvisfrac = (double) relallvisible / curpages; } diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index e33e89c9..0f90876e 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1231,7 +1231,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li ScanKeyData scan_keys[1]; ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); - Relation label_catalog = table_open(ag_label_relation_id(), AccessShareLock); + Relation label_catalog = table_open(ag_label_relation_id(), ShareLock); SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); HeapTuple tuple = systable_getnext(scan_desc); @@ -1245,7 +1245,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); systable_endscan(scan_desc); - table_close(label_catalog, AccessShareLock); + table_close(label_catalog, ShareLock); //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); label_range_var = makeRangeVar(schema_name, rel_name, -1); diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index c2ef343f..a532ee20 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -81,7 +81,7 @@ static Datum get_vertex(Oid graph_oid, int64 graphid) table_endscan(scan_desc); table_close(graph_vertex_label, ShareLock); - ereport(WARNING, (errmsg("Vertex %lu found", graphid))); + return properties; } @@ -125,8 +125,6 @@ Datum edge_search(PG_FUNCTION_ARGS) //lquery *label = PG_GETARG_LQUERY_P(1); //bool include_props = PG_GETARG_BOOL(2); FuncCallContext *funcctx; - FmgrInfo *arrayinp; - ereport(WARNING, (errmsg("Vertex %lu", id))); if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -170,9 +168,17 @@ Datum edge_search(PG_FUNCTION_ARGS) TableAmRoutine *vertex_am = GetVertexHeapamTableAmRoutine(); if (!table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { - + //DecrTupleDescRefCount(funcctx->tuple_desc); + //if (funcctx->tuple_desc->tdrefcount > 1) { + // ereport(WARNING, errmsg("tdrefcount is %d", funcctx->tuple_desc->tdrefcount)); + //} + ReleaseTupleDesc(RelationGetDescr(cxt->rel)); table_endscan(cxt->scan_desc); table_close(cxt->rel, ShareLock); + + //DecrTupleDescRefCount(funcctx->tuple_desc); + pfree(cxt->scan_desc); + pfree(cxt); SRF_RETURN_DONE(funcctx); } diff --git a/src/include/access/vertex.h b/src/include/access/vertex.h index 153833cd..8c26f188 100644 --- a/src/include/access/vertex.h +++ b/src/include/access/vertex.h @@ -23,46 +23,8 @@ typedef struct VertexScanDescData int ndesc; TableScanDesc **desc; - - - /* Hash value of the scan key, ie, the hash key we seek */ - uint32 hashso_sk_hash; - - /* remember the buffer associated with primary bucket */ - Buffer hashso_bucket_buf; - - /* - * remember the buffer associated with primary bucket page of bucket being - * split. it is required during the scan of the bucket which is being - * populated during split operation. - */ - Buffer hashso_split_bucket_buf; - - /* Whether scan starts on bucket being populated due to split */ - bool hashso_buc_populated; - - /* - * Whether scanning bucket being split? The value of this parameter is - * referred only when hashso_buc_populated is true. - */ - bool hashso_buc_split; - /* info about killed items if any (killedItems is NULL if never used) */ - int *killedItems; /* currPos.items indexes of killed items */ - int numKilled; /* number of currently stored items */ - - /* - * Identify all the matching items on a page and save them in - * HashScanPosData - */ - HashScanPosData currPos; /* current position data */ - - } VertexScanDescData; typedef struct VertexScanDescData *VertexHeapScanDesc; -bool -vertex_hash_next(TableScanDesc scan, ScanDirection dir); -bool -vertex_hash_first(TableScanDesc scan, ScanDirection dir); #endif /* ACCESS_VERTEX_H */ From 73df6c36f154fc6999e784dcb0319b08f79bde48 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Thu, 14 Aug 2025 00:11:28 -0700 Subject: [PATCH 27/56] Add switch for retrieve vertex to remove when no prop constraints are used --- regress/expected/cypher_create.out | 155 ++++++++++++++++++++--------- regress/expected/new_cypher.out | 22 ++-- src/backend/parser/cypher_clause.c | 4 + src/backend/utils/edge_searching.c | 9 -- 4 files changed, 126 insertions(+), 64 deletions(-) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 361bfb67..f84989fb 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -33,14 +33,13 @@ USE GRAPH cypher_create; CREATE (); WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -- (0 rows) SELECT * FROM cypher_create._ag_label_vertex; - id | properties ------------------+------------ - 281474976710657 | + id | properties +--------+------------ + (1, 1) | (1 row) MATCH () RETURN 1; @@ -57,64 +56,75 @@ NOTICE: VLabel "test" has been created (1 row) SELECT * FROM postgraph.ag_label; - name | graph | id | kind | relation | vertex_adjlist | label_path -------------------+-------+----+------+--------------------------------+-------------------------------------+----------------------- - _ag_label_vertex | 18780 | 1 | v | 18783 | 18792 | _ag_label_vertex - _ag_label_edge | 18780 | 2 | e | 18796 | | _ag_label_edge - _ag_label_vertex | 18805 | 1 | v | 18808 | 18817 | _ag_label_vertex - _ag_label_edge | 18805 | 2 | e | 18821 | | _ag_label_edge - _ag_label_vertex | 18833 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex - _ag_label_edge | 18833 | 2 | e | cypher_create._ag_label_edge | | _ag_label_edge - test | 18833 | 3 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test -(7 rows) + name | graph | id | kind | relation | vertex_adjlist | label_path +----------------------------+-------+----+------+------------------------------------------+-------------------------------------+------------------------------------------- + _adj__adj__ag_label_vertex | 18790 | 2 | e | 18807 | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18790 | 1 | v | 18793 | 18802 | _ag_label_vertex + _ag_label_edge | 18790 | 3 | e | 18817 | | _ag_label_edge + _adj__adj__ag_label_vertex | 18826 | 2 | e | 18843 | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18826 | 1 | v | 18829 | 18838 | _ag_label_vertex + _ag_label_edge | 18826 | 3 | e | 18853 | | _ag_label_edge + _adj__adj__ag_label_vertex | 18862 | 2 | e | cypher_create._adj__adj__ag_label_vertex | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18862 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex + _ag_label_edge | 18862 | 3 | e | cypher_create._ag_label_edge | | _ag_label_edge + _adj__adj_test | 18862 | 5 | e | cypher_create._adj__adj_test | | _ag_label_edge._adj__adj_test + test | 18862 | 4 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test +(11 rows) CREATE (:test); WARNING: relcache reference leak: relation "test" not closed -WARNING: relcache reference leak: relation "test" not closed -- (0 rows) SELECT * FROM postgraph.ag_label; - name | graph | id | kind | relation | vertex_adjlist | label_path -------------------+-------+----+------+--------------------------------+-------------------------------------+----------------------- - _ag_label_vertex | 18780 | 1 | v | 18783 | 18792 | _ag_label_vertex - _ag_label_edge | 18780 | 2 | e | 18796 | | _ag_label_edge - _ag_label_vertex | 18805 | 1 | v | 18808 | 18817 | _ag_label_vertex - _ag_label_edge | 18805 | 2 | e | 18821 | | _ag_label_edge - _ag_label_vertex | 18833 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex - _ag_label_edge | 18833 | 2 | e | cypher_create._ag_label_edge | | _ag_label_edge - test | 18833 | 3 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test -(7 rows) + name | graph | id | kind | relation | vertex_adjlist | label_path +----------------------------+-------+----+------+------------------------------------------+-------------------------------------+------------------------------------------- + _adj__adj__ag_label_vertex | 18790 | 2 | e | 18807 | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18790 | 1 | v | 18793 | 18802 | _ag_label_vertex + _ag_label_edge | 18790 | 3 | e | 18817 | | _ag_label_edge + _adj__adj__ag_label_vertex | 18826 | 2 | e | 18843 | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18826 | 1 | v | 18829 | 18838 | _ag_label_vertex + _ag_label_edge | 18826 | 3 | e | 18853 | | _ag_label_edge + _adj__adj__ag_label_vertex | 18862 | 2 | e | cypher_create._adj__adj__ag_label_vertex | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18862 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex + _ag_label_edge | 18862 | 3 | e | cypher_create._ag_label_edge | | _ag_label_edge + _adj__adj_test | 18862 | 5 | e | cypher_create._adj__adj_test | | _ag_label_edge._adj__adj_test + test | 18862 | 4 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test +(11 rows) SELECT * FROM cypher_create.test; - id | properties ------------------+------------ - 844424930131969 | + id | properties +--------+------------ + (4, 1) | (1 row) CREATE (:test2); WARNING: relcache reference leak: relation "test2" not closed -WARNING: relcache reference leak: relation "test2" not closed -- (0 rows) SELECT * FROM postgraph.ag_label; - name | graph | id | kind | relation | vertex_adjlist | label_path -------------------+-------+----+------+--------------------------------+-------------------------------------+------------------------ - _ag_label_vertex | 18780 | 1 | v | 18783 | 18792 | _ag_label_vertex - _ag_label_edge | 18780 | 2 | e | 18796 | | _ag_label_edge - _ag_label_vertex | 18805 | 1 | v | 18808 | 18817 | _ag_label_vertex - _ag_label_edge | 18805 | 2 | e | 18821 | | _ag_label_edge - _ag_label_vertex | 18833 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex - _ag_label_edge | 18833 | 2 | e | cypher_create._ag_label_edge | | _ag_label_edge - test | 18833 | 3 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test - test2 | 18833 | 4 | v | cypher_create.test2 | cypher_create._adj_test2 | _ag_label_vertex.test2 -(8 rows) + name | graph | id | kind | relation | vertex_adjlist | label_path +----------------------------+-------+----+------+------------------------------------------+-------------------------------------+------------------------------------------- + _adj__adj__ag_label_vertex | 18790 | 2 | e | 18807 | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18790 | 1 | v | 18793 | 18802 | _ag_label_vertex + _ag_label_edge | 18790 | 3 | e | 18817 | | _ag_label_edge + _adj__adj__ag_label_vertex | 18826 | 2 | e | 18843 | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18826 | 1 | v | 18829 | 18838 | _ag_label_vertex + _ag_label_edge | 18826 | 3 | e | 18853 | | _ag_label_edge + _adj__adj__ag_label_vertex | 18862 | 2 | e | cypher_create._adj__adj__ag_label_vertex | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18862 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex + _ag_label_edge | 18862 | 3 | e | cypher_create._ag_label_edge | | _ag_label_edge + _adj__adj_test | 18862 | 5 | e | cypher_create._adj__adj_test | | _ag_label_edge._adj__adj_test + test | 18862 | 4 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test + _adj__adj_test2 | 18862 | 7 | e | cypher_create._adj__adj_test2 | | _ag_label_edge._adj__adj_test2 + test2 | 18862 | 6 | v | cypher_create.test2 | cypher_create._adj_test2 | _ag_label_vertex.test2 +(13 rows) SELECT * FROM cypher_create.test2; - id | properties -------------------+------------ - 1125899906842625 | + id | properties +--------+------------ + (6, 1) | (1 row) MATCH () RETURN 1; @@ -128,12 +138,62 @@ MATCH () RETURN 1; CREATE ()-[]->(); WARNING: relcache reference leak: relation "_ag_label_vertex" not closed WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) +EXPLAIN MATCH ()-[]->() RETURN 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Nested Loop (cost=12.50..50471.30 rows=2520000 width=32) + -> Append (cost=0.00..58.80 rows=2520 width=8) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) + -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=0) +(6 rows) + +EXPLAIN ANALYZE MATCH ()-[]->() RETURN 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop (cost=12.50..50471.30 rows=2520000 width=32) (actual time=0.060..0.237 rows=1 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.005..0.013 rows=5 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.005..0.006 rows=3 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.002 rows=1 loops=1) + -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.002 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.043..0.043 rows=0 loops=5) + Planning Time: 0.095 ms + Execution Time: 0.262 ms +(8 rows) + +MATCH ()-[]->() RETURN 1; + ?column? +---------- + 1 +(1 row) + +SELECT * FROM cypher_create._ag_label_vertex; + id | properties +--------+------------ + (1, 1) | + (1, 2) | + (1, 3) | + (4, 1) | + (6, 1) | +(5 rows) + +SELECT * FROM cypher_create._adj__adj__ag_label_vertex; + id | start_id | end_id | properties +---------------+----------+--------+------------ + (0, 41914048) | (1, 2) | (1, 3) | +(1 row) + +SELECT * FROM cypher_create._adj__ag_label_vertex; + id | start_id | end_id | properties +---------------+----------+--------+------------ + (0, 41914048) | (1, 2) | (1, 3) | +(1 row) + MATCH () RETURN 1; ?column? ---------- @@ -153,14 +213,17 @@ ERROR: CREATE doesn't work with next clauses -- Clean up -- DROP GRAPH cypher_create CASCADE; -NOTICE: drop cascades to 7 other objects +NOTICE: drop cascades to 10 other objects DETAIL: drop cascades to table cypher_create._ag_label_vertex drop cascades to table cypher_create._adj__ag_label_vertex +drop cascades to table cypher_create._adj__adj__ag_label_vertex drop cascades to table cypher_create._ag_label_edge drop cascades to table cypher_create.test drop cascades to table cypher_create._adj_test +drop cascades to table cypher_create._adj__adj_test drop cascades to table cypher_create.test2 drop cascades to table cypher_create._adj_test2 +drop cascades to table cypher_create._adj__adj_test2 NOTICE: graph "cypher_create" has been dropped drop_graph ------------ diff --git a/regress/expected/new_cypher.out b/regress/expected/new_cypher.out index 1dd89823..05ddf924 100644 --- a/regress/expected/new_cypher.out +++ b/regress/expected/new_cypher.out @@ -72,16 +72,18 @@ USE GRAPH new_cypher; SELECT * FROM postgraph.ag_label WHERE name = '_ag_label_vertex'; name | graph | id | kind | relation | vertex_adjlist | label_path ------------------+-------+----+------+-------------------------------+------------------------------------+------------------ - _ag_label_vertex | 18780 | 1 | v | new_cypher._ag_label_vertex | new_cypher._adj__ag_label_vertex | _ag_label_vertex - _ag_label_vertex | 18805 | 1 | v | new_cypher_2._ag_label_vertex | new_cypher_2._adj__ag_label_vertex | _ag_label_vertex + _ag_label_vertex | 18790 | 1 | v | new_cypher._ag_label_vertex | new_cypher._adj__ag_label_vertex | _ag_label_vertex + _ag_label_vertex | 18826 | 1 | v | new_cypher_2._ag_label_vertex | new_cypher_2._adj__ag_label_vertex | _ag_label_vertex (2 rows) \d+ new_cypher._adj__ag_label_vertex - Table "new_cypher._adj__ag_label_vertex" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ----------+---------+-----------+----------+---------+----------+--------------+------------- - id | graphid | | | | plain | | - adjlist | gtype | | | | extended | | + Table "new_cypher._adj__ag_label_vertex" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +------------+---------+-----------+----------+-------------------+----------+--------------+------------- + id | graphid | | | | plain | | + start_id | graphid | | not null | | plain | | + end_id | graphid | | not null | | plain | | + properties | gtype | | | gtype_build_map() | extended | | SELECT c.relname AS table_name,am.amname AS access_method FROM pg_class c @@ -94,9 +96,10 @@ WHERE c.relkind = 'r' AND c.relname = '_adj__ag_label_vertex'; (2 rows) DROP GRAPH new_cypher CASCADE; -NOTICE: drop cascades to 3 other objects +NOTICE: drop cascades to 4 other objects DETAIL: drop cascades to table new_cypher._ag_label_vertex drop cascades to table new_cypher._adj__ag_label_vertex +drop cascades to table new_cypher._adj__adj__ag_label_vertex drop cascades to table new_cypher._ag_label_edge NOTICE: graph "new_cypher" has been dropped drop_graph @@ -105,9 +108,10 @@ NOTICE: graph "new_cypher" has been dropped (1 row) DROP GRAPH new_cypher_2 CASCADE; -NOTICE: drop cascades to 3 other objects +NOTICE: drop cascades to 4 other objects DETAIL: drop cascades to table new_cypher_2._ag_label_vertex drop cascades to table new_cypher_2._adj__ag_label_vertex +drop cascades to table new_cypher_2._adj__adj__ag_label_vertex drop cascades to table new_cypher_2._ag_label_edge NOTICE: graph "new_cypher_2" has been dropped drop_graph diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 0f90876e..b719659c 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1264,6 +1264,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li // } else { + if (node->props) { int sublevels_up =1; Node * id_field = scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1); @@ -1271,6 +1272,9 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li list_make2(make_int_const(cpstate->graph_oid, -1),id_field), COERCE_EXPLICIT_CALL, -1); pnsi = add_srf_to_query1(cpstate, fc, node->name); + } else { + continue; + } //edge_pnsi = pnsi; } //expr = (Expr *)make_vertex_expr(cpstate, pnsi); diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index a532ee20..c11e356c 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -81,7 +81,6 @@ static Datum get_vertex(Oid graph_oid, int64 graphid) table_endscan(scan_desc); table_close(graph_vertex_label, ShareLock); - return properties; } @@ -168,17 +167,9 @@ Datum edge_search(PG_FUNCTION_ARGS) TableAmRoutine *vertex_am = GetVertexHeapamTableAmRoutine(); if (!table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { - //DecrTupleDescRefCount(funcctx->tuple_desc); - //if (funcctx->tuple_desc->tdrefcount > 1) { - // ereport(WARNING, errmsg("tdrefcount is %d", funcctx->tuple_desc->tdrefcount)); - //} ReleaseTupleDesc(RelationGetDescr(cxt->rel)); table_endscan(cxt->scan_desc); table_close(cxt->rel, ShareLock); - - //DecrTupleDescRefCount(funcctx->tuple_desc); - pfree(cxt->scan_desc); - pfree(cxt); SRF_RETURN_DONE(funcctx); } From 1f231780970feae2d8f9d7afe029a5f53807c849 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Thu, 14 Aug 2025 00:23:26 -0700 Subject: [PATCH 28/56] Start Vertex can be a variable --- postgraph--0.1.0.sql | 2 +- regress/expected/cypher_create.out | 20 ++++++++++++++------ regress/sql/cypher_create.sql | 2 ++ src/backend/parser/cypher_clause.c | 20 ++++++++++---------- src/backend/utils/adt/vertex.c | 20 +++++++++++++++++--- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 62b58279..670ae60a 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -314,7 +314,7 @@ CREATE FUNCTION build_vertex(graphid, oid, gtype) RETURNS vertex LANGUAGE c IMMUTABLE -RETURNS NULL ON NULL INPUT +CALLED ON NULL INPUT PARALLEL SAFE COST 50 AS 'MODULE_PATHNAME'; diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index f84989fb..c0ee7371 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -135,6 +135,14 @@ MATCH () RETURN 1; 1 (3 rows) +MATCH (a) RETURN a; + a +-------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} +(3 rows) + CREATE ()-[]->(); WARNING: relcache reference leak: relation "_ag_label_vertex" not closed WARNING: relcache reference leak: relation "_ag_label_vertex" not closed @@ -156,14 +164,14 @@ EXPLAIN MATCH ()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..50471.30 rows=2520000 width=32) (actual time=0.060..0.237 rows=1 loops=1) + Nested Loop (cost=12.50..50471.30 rows=2520000 width=32) (actual time=0.046..0.207 rows=1 loops=1) -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.005..0.013 rows=5 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.005..0.006 rows=3 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.002 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.002 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.043..0.043 rows=0 loops=5) - Planning Time: 0.095 ms - Execution Time: 0.262 ms + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.037..0.037 rows=0 loops=5) + Planning Time: 0.093 ms + Execution Time: 0.231 ms (8 rows) MATCH ()-[]->() RETURN 1; @@ -185,13 +193,13 @@ SELECT * FROM cypher_create._ag_label_vertex; SELECT * FROM cypher_create._adj__adj__ag_label_vertex; id | start_id | end_id | properties ---------------+----------+--------+------------ - (0, 41914048) | (1, 2) | (1, 3) | + (0, 25304768) | (1, 2) | (1, 3) | (1 row) SELECT * FROM cypher_create._adj__ag_label_vertex; id | start_id | end_id | properties ---------------+----------+--------+------------ - (0, 41914048) | (1, 2) | (1, 3) | + (0, 25304768) | (1, 2) | (1, 3) | (1 row) MATCH () RETURN 1; diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index c77ed0b9..ddcc62e7 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -44,6 +44,8 @@ SELECT * FROM cypher_create.test2; MATCH () RETURN 1; +MATCH (a) RETURN a; + CREATE ()-[]->(); EXPLAIN MATCH ()-[]->() RETURN 1; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index b719659c..d2c1b1c4 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1182,10 +1182,10 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li if (i % 2 == 1) { cypher_node *node = lfirst(path_cell); prev_node = node; + bool has_variable = false; + if (node->name) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH variable names are not supported"))); + has_variable = true; else node->name = get_next_default_alias(cpstate); @@ -1260,11 +1260,17 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li //resno = pstate->p_next_resno++; // XXX: End LTree Code here + expr = (Expr *)make_vertex_expr(cpstate, pnsi); + // make target entry and add it + resno = pstate->p_next_resno++; + te = makeTargetEntry(expr, resno, node->name, false); + + query->targetList = lappend(query->targetList, te); // } else { - if (node->props) { + if (node->props || has_variable) { int sublevels_up =1; Node * id_field = scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1); @@ -1277,12 +1283,6 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li } //edge_pnsi = pnsi; } - //expr = (Expr *)make_vertex_expr(cpstate, pnsi); - // make target entry and add it - // pstate->p_next_resno++; - //te = makeTargetEntry(expr, resno, node->name, false); - - //query->targetList = lappend(query->targetList, te); // id field Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); diff --git a/src/backend/utils/adt/vertex.c b/src/backend/utils/adt/vertex.c index c8192fde..0c24ebea 100644 --- a/src/backend/utils/adt/vertex.c +++ b/src/backend/utils/adt/vertex.c @@ -101,10 +101,13 @@ Datum build_vertex(PG_FUNCTION_ARGS) { graphid id = AG_GETARG_GRAPHID(0); Oid graph_oid = PG_GETARG_OID(1); - gtype *properties = AG_GET_ARG_GTYPE_P(2); + gtype *properties = NULL; + if (!PG_ARGISNULL(2)) { + properties = AG_GET_ARG_GTYPE_P(2); - if (!AGT_ROOT_IS_OBJECT(properties)) - PG_RETURN_NULL(); + if (!AGT_ROOT_IS_OBJECT(properties)) + PG_RETURN_NULL(); + } AG_RETURN_VERTEX(create_vertex(id, graph_oid, properties)); } @@ -124,6 +127,17 @@ create_vertex(graphid id, Oid graph_oid, gtype *properties) { append_to_buffer(&buffer, (char *)&graph_oid, sizeof(Oid)); // properties + if (properties == NULL) { + gtype_in_state result; + + memset(&result, 0, sizeof(gtype_in_state)); + + push_gtype_value(&result.parse_state, WGT_BEGIN_OBJECT, NULL); + result.res = push_gtype_value(&result.parse_state, WGT_END_OBJECT, NULL); + + properties = gtype_value_to_gtype(result.res); + } + append_to_buffer(&buffer, properties, VARSIZE(properties)); vertex *v = (vertex *)buffer.data; From a72fce3132e248738a48ef0b9d593a3806adccc5 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Thu, 14 Aug 2025 00:35:06 -0700 Subject: [PATCH 29/56] Support Edge Labels in CREATE Clause --- regress/expected/cypher_create.out | 53 +++++++++++++++++++++--------- regress/sql/cypher_create.sql | 5 +++ src/backend/parser/cypher_clause.c | 28 ++++++++++++++-- 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index c0ee7371..8c97ade8 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -143,6 +143,12 @@ MATCH (a) RETURN a; {"id": 1688849860263937, "label": "test2", "properties": {}} (3 rows) +MATCH (a:test) RETURN a; + a +------------------------------------------------------------- + {"id": 1125899906842625, "label": "test", "properties": {}} +(1 row) + CREATE ()-[]->(); WARNING: relcache reference leak: relation "_ag_label_vertex" not closed WARNING: relcache reference leak: relation "_ag_label_vertex" not closed @@ -150,6 +156,13 @@ WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) +CREATE ()-[:elabel]->(); +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "elabel" not closed +-- +(0 rows) + EXPLAIN MATCH ()-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------- @@ -164,21 +177,22 @@ EXPLAIN MATCH ()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..50471.30 rows=2520000 width=32) (actual time=0.046..0.207 rows=1 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.005..0.013 rows=5 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.005..0.006 rows=3 loops=1) - -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.002 rows=1 loops=1) - -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.002 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.037..0.037 rows=0 loops=5) - Planning Time: 0.093 ms - Execution Time: 0.231 ms + Nested Loop (cost=12.50..50471.30 rows=2520000 width=32) (actual time=0.057..0.302 rows=2 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.009..0.018 rows=7 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.008..0.010 rows=5 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.003 rows=1 loops=1) + -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.003 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.039..0.039 rows=0 loops=7) + Planning Time: 0.126 ms + Execution Time: 0.332 ms (8 rows) MATCH ()-[]->() RETURN 1; ?column? ---------- 1 -(1 row) + 1 +(2 rows) SELECT * FROM cypher_create._ag_label_vertex; id | properties @@ -186,21 +200,25 @@ SELECT * FROM cypher_create._ag_label_vertex; (1, 1) | (1, 2) | (1, 3) | + (1, 4) | + (1, 5) | (4, 1) | (6, 1) | -(5 rows) +(7 rows) SELECT * FROM cypher_create._adj__adj__ag_label_vertex; id | start_id | end_id | properties ---------------+----------+--------+------------ - (0, 25304768) | (1, 2) | (1, 3) | -(1 row) + (0, 36327104) | (1, 2) | (1, 3) | + (0, 35832480) | (1, 4) | (1, 5) | +(2 rows) SELECT * FROM cypher_create._adj__ag_label_vertex; id | start_id | end_id | properties ---------------+----------+--------+------------ - (0, 25304768) | (1, 2) | (1, 3) | -(1 row) + (0, 36327104) | (1, 2) | (1, 3) | + (0, 35832480) | (1, 4) | (1, 5) | +(2 rows) MATCH () RETURN 1; ?column? @@ -210,7 +228,9 @@ MATCH () RETURN 1; 1 1 1 -(5 rows) + 1 + 1 +(7 rows) CYPHER WITH 1 as a CREATE (); @@ -221,7 +241,7 @@ ERROR: CREATE doesn't work with next clauses -- Clean up -- DROP GRAPH cypher_create CASCADE; -NOTICE: drop cascades to 10 other objects +NOTICE: drop cascades to 11 other objects DETAIL: drop cascades to table cypher_create._ag_label_vertex drop cascades to table cypher_create._adj__ag_label_vertex drop cascades to table cypher_create._adj__adj__ag_label_vertex @@ -232,6 +252,7 @@ drop cascades to table cypher_create._adj__adj_test drop cascades to table cypher_create.test2 drop cascades to table cypher_create._adj_test2 drop cascades to table cypher_create._adj__adj_test2 +drop cascades to table cypher_create.elabel NOTICE: graph "cypher_create" has been dropped drop_graph ------------ diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index ddcc62e7..a1150356 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -46,8 +46,13 @@ MATCH () RETURN 1; MATCH (a) RETURN a; +MATCH (a:test) RETURN a; + CREATE ()-[]->(); +CREATE ()-[:elabel]->(); + + EXPLAIN MATCH ()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->() RETURN 1; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index d2c1b1c4..5172c3b8 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -336,6 +336,26 @@ static Expr *add_volatile_wrapper(Expr *node) { return (Expr *)makeFuncExpr(oid, GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } +static void validate_or_create_elabel(cypher_parsestate *cpstate, cypher_relationship *edge) { + ParseState *pstate = (ParseState *)cpstate; + label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); + + if (lcd && lcd->kind != LABEL_KIND_EDGE) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("label %s is for vertices, not edges", edge->label), + parser_errposition(pstate, edge->location))); + } else if (!lcd) { + List *parent; + RangeVar *rv; + + rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_EDGE); + + parent = list_make1(rv); + + create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, parent, NULL); + } +} + static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *node) { ParseState *pstate = (ParseState *)cpstate; label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); @@ -423,7 +443,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause cypher_relationship *edge = lfirst(lc2); if (edge->label) - ereport(ERROR, (errmsg_internal("edges in CREATE cannot have labels"))); + validate_or_create_elabel(cpstate, edge); else edge->label = AG_DEFAULT_LABEL_EDGE; @@ -1190,10 +1210,12 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li node->name = get_next_default_alias(cpstate); bool is_default_label = true; - if (node->label) + if (node->label && i != 1) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH labels are not supported"))); + else if (node->label) + is_default_label = false; else node->label = AG_DEFAULT_LABEL_VERTEX; @@ -1281,7 +1303,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li } else { continue; } - //edge_pnsi = pnsi; + } // id field From 66aeee2fc346209e31ff370fbdd0bc0a442db207 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Fri, 15 Aug 2025 00:03:38 -0700 Subject: [PATCH 30/56] Add Hash Indices to label tables --- Makefile | 6 +- postgraph--0.1.0.sql | 9 ++ .../vertex_heap/vertex_heapam_handler.c | 92 +++++++++++++------ src/backend/commands/label_commands.c | 92 ++++++++++++++++++- src/backend/executor/cypher_create.c | 6 +- src/backend/utils/edge_searching.c | 1 + src/include/access/vertex.h | 1 + 7 files changed, 172 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index e9d2320f..97f87abd 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ PG_LIBS += -ltree all: postgraph--0.1.0.sql ag_regress_dir = $(srcdir)/regress -REGRESS_OPTS = --load-extension=postgis --load-extension=ltree --load-extension=postgraph --inputdir=$(ag_regress_dir) --outputdir=$(ag_regress_dir) --temp-instance=$(ag_regress_dir)/instance --port=61958 --encoding=UTF-8 +REGRESS_OPTS = --load-extension=bloom --load-extension=postgis --load-extension=ltree --load-extension=postgraph --inputdir=$(ag_regress_dir) --outputdir=$(ag_regress_dir) --temp-instance=$(ag_regress_dir)/instance --port=61958 --encoding=UTF-8 ag_regress_out = instance/ log/ results/ regression.* EXTRA_CLEAN = $(addprefix $(ag_regress_dir)/, $(ag_regress_out)) src/backend/parser/cypher_gram.c src/backend/parser/ag_scanner.c src/include/parser/cypher_gram_def.h src/include/parser/cypher_kwlist_d.h @@ -90,9 +90,9 @@ GEN_KEYWORDLIST = $(PERL) -I ./tools/ ./tools/gen_keywordlist.pl GEN_KEYWORDLIST_DEPS = ./tools/gen_keywordlist.pl tools/PerfectHash.pm ag_include_dir = $(srcdir)/src/include -PG_CPPFLAGS = -w -I$(ag_include_dir) -I$(ag_include_dir)/parser -I$(POSTGIS_DIR) -I$(POSTGIS_DIR)/liblwgeom -I/home/josh/postgres/contrib/ltree +PG_CPPFLAGS = -w -I$(ag_include_dir) -I$(ag_include_dir)/parser -I$(POSTGIS_DIR) -I$(POSTGIS_DIR)/liblwgeom -I/home/josh/postgres/contrib/ltree -I/home/josh/postgres/contrib/bloom PG_LDFLAGS = -lgeos_c -SHLIB_LINK= $(POSTGIS_DIR)/liblwgeom/.libs/liblwgeom.a -Wl,--no-as-needed -Wl,-l:postgis-3.so -Wl,-l:ltree.so +SHLIB_LINK= $(POSTGIS_DIR)/liblwgeom/.libs/liblwgeom.a -Wl,--no-as-needed -Wl,-l:postgis-3.so -Wl,-l:ltree.so -Wl,-l:bloom.so PG_CONFIG ?= pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 670ae60a..61d0750b 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -751,6 +751,15 @@ USING hash AS OPERATOR 1 =, FUNCTION 1 graphid_hash_cmp(graphid); + + +CREATE OPERATOR CLASS graphid_ops_bloom +DEFAULT FOR TYPE graphid +USING bloom +AS + OPERATOR 1 =, + FUNCTION 1 graphid_hash_cmp(graphid); + -- -- btree operator classes for graphid -- diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index aca2a45a..ca513d0b 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -105,6 +105,7 @@ static char *make_vertex_adjlist_alias(char *var_name) { return str; } +#include "bloom.h" TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, struct ScanKeyData *key, @@ -123,9 +124,29 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(RelationGetRelid(relation))), get_rel_namespace(RelationGetRelid(relation))); Relation rel = RelationIdGetRelation(oid); - TableScanDesc *desc = tableam->scan_begin(rel, snapshot, nkeys, key, parallel_scan, flags); - vertex_desc->desc = palloc(sizeof (TableScanDesc *) * vertex_desc->ndesc); - vertex_desc->desc[0] = desc; + vertex_desc->desc = palloc(sizeof (TableScanDesc *) * vertex_desc->ndesc); + vertex_desc->isIndex = palloc(sizeof (bool) * vertex_desc->ndesc); + + List *indexoidlist = RelationGetIndexList(relation); + if(list_length(indexoidlist) == 1) { + Oid idx = linitial_oid(indexoidlist); + + IndexScanDesc *desc = index_beginscan(rel, + RelationIdGetRelation(idx), + snapshot, + nkeys, 0); + + index_rescan(desc, key, nkeys, NULL, 0); + + vertex_desc->desc[0] = desc; + vertex_desc->isIndex[0] = true; + } else { + + TableScanDesc *desc = tableam->scan_begin(rel, snapshot, nkeys, key, parallel_scan, flags); + + vertex_desc->desc[0] = desc; + vertex_desc->isIndex[0] = false; + } return vertex_desc; } @@ -133,7 +154,10 @@ void vertex_scan_end(TableScanDesc sscan) { VertexScanDescData *vertex_desc = sscan; RelationDecrementReferenceCount( ((HeapScanDesc)vertex_desc->desc[0])->rs_base.rs_rd); - GetHeapamTableAmRoutine()->scan_end(vertex_desc->desc[0]); + if (vertex_desc->isIndex[0]) + index_endscan(vertex_desc->desc[0]); + else + GetHeapamTableAmRoutine()->scan_end(vertex_desc->desc[0]); } /* @@ -156,8 +180,12 @@ bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, VertexScanDescData *vertex_desc = sscan; TableAmRoutine *tableam = GetHeapamTableAmRoutine(); - - return tableam->scan_getnextslot(vertex_desc->desc[0], direction, slot); + if (vertex_desc->desc[0]) + return false; + if (vertex_desc->isIndex[0]) + return index_getnext_slot(vertex_desc->desc[0], direction, slot); + else + return tableam->scan_getnextslot(vertex_desc->desc[0], direction, slot); } /* ------------------------------------------------------------------------ @@ -214,7 +242,7 @@ void vertex_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan) */ struct IndexFetchTableData *vertex_index_fetch_begin(Relation rel) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_index_fetch_begin not implemented"))); return NULL; } @@ -225,7 +253,7 @@ struct IndexFetchTableData *vertex_index_fetch_begin(Relation rel) { */ void vertex_index_fetch_reset(struct IndexFetchTableData *data) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_index_fetch_reset not implemented"))); } /* @@ -233,7 +261,7 @@ void vertex_index_fetch_reset(struct IndexFetchTableData *data) { */ void vertex_index_fetch_end(struct IndexFetchTableData *data) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_index_fetch_end not implemented"))); } /* @@ -261,7 +289,7 @@ bool vertex_index_fetch_tuple(struct IndexFetchTableData *scan, TupleTableSlot *slot, bool *call_again, bool *all_dead) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_index_fetch_tuple not implemented"))); return false; } @@ -301,7 +329,7 @@ bool vertex_tuple_fetch_row_version(Relation relation, ItemPointer tid, */ bool vertex_tuple_tid_valid(TableScanDesc scan, ItemPointer tid) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_tuple_tid_valid not implemented"))); return false; @@ -313,7 +341,7 @@ bool vertex_tuple_tid_valid(TableScanDesc scan, ItemPointer tid) { */ void vertex_tuple_get_latest_tid(TableScanDesc scan, ItemPointer tid) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_tuple_get_latest_tid not implemented"))); } @@ -325,7 +353,7 @@ void vertex_tuple_get_latest_tid(TableScanDesc scan, ItemPointer tid) { bool vertex_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, Snapshot snapshot) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_tuple_satisfies_snapshot not implemented"))); return false; } @@ -333,7 +361,7 @@ bool vertex_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, /* see table_index_delete_tuples() */ TransactionId vertex_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_index_delete_tuples not implemented"))); return InvalidTransactionId; @@ -370,14 +398,14 @@ void vertex_tuple_insert_speculative(Relation rel, TupleTableSlot *slot, void vertex_tuple_complete_speculative(Relation rel, TupleTableSlot *slot, uint32 specToken, bool succeeded) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_tuple_complete_speculative not implemented"))); } /* see table_multi_insert() for reference about parameters */ void vertex_multi_insert(Relation rel, TupleTableSlot **slots, int nslots, CommandId cid, int options, struct BulkInsertStateData *bistate) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_multi_insert not implemented"))); } /* see table_tuple_delete() for reference about parameters */ @@ -401,7 +429,7 @@ TM_Result vertex_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, uint8 flags, TM_FailureData *tmfd) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_tuple_lock not implemented"))); return 0; } @@ -456,7 +484,7 @@ void vertex_relation_nontransactional_truncate(Relation rel) { */ void vertex_relation_copy_data(Relation rel, const RelFileNode *newrnode) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_relation_copy_data not implemented"))); @@ -469,7 +497,7 @@ void vertex_relation_copy_for_cluster(Relation OldTable, Relation NewTable, MultiXactId *multi_cutoff, double *num_tuples, double *tups_vacuumed, double *tups_recently_dead) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_relation_copy_for_cluster not implemented"))); } /* @@ -491,7 +519,7 @@ void vertex_relation_vacuum(Relation rel, struct VacuumParams *params, BufferAccessStrategy bstrategy) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_relation_vacuum not implemented"))); } /* @@ -514,7 +542,7 @@ void vertex_relation_vacuum(Relation rel, struct VacuumParams *params, bool vertex_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, BufferAccessStrategy bstrategy) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_scan_analyze_next_block not implemented"))); return false; } @@ -530,7 +558,7 @@ bool vertex_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, bool vertex_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, double *liverows, double *deadrows, TupleTableSlot *slot) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_scan_analyze_next_tuple not implemented"))); @@ -543,9 +571,13 @@ double vertex_index_build_range_scan(Relation table_rel, Relation index_rel, bool anyvisible, bool progress, BlockNumber start_blockno, BlockNumber numblocks, IndexBuildCallback callback, void *callback_state, TableScanDesc scan) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); - return 0; + Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(RelationGetRelid(table_rel))), get_rel_namespace(RelationGetRelid(table_rel))); + + return GetHeapamTableAmRoutine()->index_build_range_scan(RelationIdGetRelation(oid), + index_rel, index_info, allow_sync, + anyvisible, progress, start_blockno, + numblocks, callback, callback_state, + scan); } /* see table_index_validate_scan for reference about parameters */ @@ -553,7 +585,7 @@ void vertex_index_validate_scan(Relation table_rel, Relation index_rel, struct IndexInfo *index_info, Snapshot snapshot, struct ValidateIndexState *state) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_index_validate_scan not implemented"))); } @@ -683,7 +715,7 @@ void vertex_relation_estimate_size(Relation rel, int32 *attr_widths, */ bool vertex_scan_bitmap_next_block(TableScanDesc scan, struct TBMIterateResult *tbmres) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_scan_bitmap_next_block not implemented"))); return false; } @@ -702,7 +734,7 @@ bool vertex_scan_bitmap_next_block(TableScanDesc scan, struct TBMIterateResult * bool vertex_scan_bitmap_next_tuple(TableScanDesc scan, struct TBMIterateResult *tbmres, TupleTableSlot *slot) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_scan_bitmap_next_tuple not implemented"))); return false; } @@ -734,7 +766,7 @@ bool vertex_scan_bitmap_next_tuple(TableScanDesc scan, struct TBMIterateResult * */ bool vertex_scan_sample_next_block(TableScanDesc scan, struct SampleScanState *scanstate) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_scan_sample_next_block not implemented"))); return false; } @@ -756,7 +788,7 @@ bool vertex_scan_sample_next_tuple(TableScanDesc scan, struct SampleScanState *s TupleTableSlot *slot) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("vertex_parallelscan_reinitialize not implemented"))); + errmsg_internal("vertex_scan_sample_next_tuple not implemented"))); return false; } diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index 44be1a35..a91f05b8 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -63,7 +63,10 @@ static void create_table_for_label(char *graph_name, char *label_name, char *schema_name, char *rel_name, char *seq_name, char label_type, List *parents); - +static void create_hash_idx(char *graph_name, char *label_name, + char *schema_name, char *rel_name, + char *seq_name, char label_type, + List *parents, char *col_name); // common static List *create_edge_table_elements(char *graph_name, char *label_name, char *schema_name, char *rel_name, @@ -495,6 +498,26 @@ Datum create_property_index(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +static char *make_hash_idx_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 10); + + str[0] = 'i'; + str[1] = 'd'; + str[2] = 'x'; + str[3] = '_'; + str[4] = 'h'; + str[5] = 'a'; + str[6] = 's'; + str[7] = 'h'; + str[8] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 9] = var_name[i]; + str[i + 9] = '\0'; + + return str; +} static char *make_vertex_adjlist_alias(char *var_name) { char *str = palloc0(strlen(var_name) + 8); @@ -629,18 +652,85 @@ void create_label(char *graph_name, char *label_name, char label_type, // 08/2/25: TODO - this is incorrect, but it will allow me to get the first two edge table I need created. // So do it for now, change in the very near future create_label(graph_name, make_vertex_adjlist_alias(make_vertex_adjlist_alias(label_name)), 'e', NIL , NULL); + create_hash_idx(graph_name, label_name, + schema_name, label_name, seq_name, label_type, + parents, AG_VERTEX_COLNAME_ID); + + + create_hash_idx(graph_name, label_name, + schema_name, make_vertex_adjlist_alias(label_name), seq_name, label_type, + parents, AG_EDGE_COLNAME_START_ID); insert_label(label_name, graph_oid, label_id, label_type, relation_id, NULL, adj_oid); }else { insert_label(label_name, graph_oid, label_id, label_type, relation_id, NULL, InvalidOid); + } CommandCounterIncrement(); } +static void create_hash_idx(char *graph_name, char *label_name, + char *schema_name, char *rel_name, + char *seq_name, char label_type, + List *parents, char *col_name) { + IndexStmt *index_stmt; + PlannedStmt *wrapper; + + index_stmt = makeNode(IndexStmt); + + + IndexElem *start_id_elem = makeNode(IndexElem); + start_id_elem->name = col_name; + start_id_elem->expr = NULL; + start_id_elem->indexcolname = NULL; + start_id_elem->collation = NIL; + start_id_elem->opclass = NIL; + start_id_elem->opclassopts = NIL; + start_id_elem->ordering = SORTBY_DEFAULT; + start_id_elem->nulls_ordering = SORTBY_NULLS_DEFAULT; + + index_stmt->idxname = make_hash_idx_alias(rel_name);; + index_stmt->relation = makeRangeVar(schema_name, rel_name, -1); + index_stmt->accessMethod = "hash"; + index_stmt->tableSpace = NULL; + index_stmt->indexParams = list_make1(start_id_elem); + index_stmt->indexIncludingParams = NIL; + index_stmt->options = NIL; + index_stmt->whereClause = NULL; + index_stmt->excludeOpNames = NIL; + index_stmt->idxcomment = NULL; + index_stmt->indexOid = InvalidOid; + index_stmt->oldNode = InvalidOid; + index_stmt->oldCreateSubid = InvalidSubTransactionId; + index_stmt->oldFirstRelfilenodeSubid = InvalidSubTransactionId; + index_stmt->unique = false; + index_stmt->primary = false; + index_stmt->isconstraint = false; + 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; + + + wrapper = makeNode(PlannedStmt); + wrapper->commandType = CMD_UTILITY; + wrapper->canSetTag = false; + wrapper->utilityStmt = (Node *)index_stmt; + wrapper->stmt_location = -1; + wrapper->stmt_len = 0; + + ProcessUtility(wrapper, "(generated CREATE INDEX command)", false, + PROCESS_UTILITY_SUBCOMMAND, NULL, NULL, None_Receiver, + NULL); + +} + // CREATE TABLE `schema_name`.`rel_name` ( // "id" graphid PRIMARY KEY DEFAULT CATALOG_SCHEMA."_graphid"(...), // "start_id" graphid NOT NULL note: only for edge labels diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 94254f6e..72445f2e 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -468,7 +468,7 @@ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, HeapTuple tuple = NULL; ExecStoreVirtualTuple(elemTupleSlot); - // tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); + //tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); /* Check the constraints of the tuple */ //tuple->t_tableOid = resultRelInfo->ri_RelationDesc->rd_id; @@ -483,7 +483,11 @@ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, // Insert index entries for the tuple if (resultRelInfo->ri_NumIndices > 0) { + Oid oid = elemTupleSlot->tts_tableOid; + elemTupleSlot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);// RelationGetRelid(heapRelation) + ExecInsertIndexTuples(resultRelInfo, elemTupleSlot, estate, false, false, NULL, NIL); + elemTupleSlot->tts_tableOid = oid; } return NULL; diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index c11e356c..052d294d 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -67,6 +67,7 @@ static Datum get_vertex(Oid graph_oid, int64 graphid) ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_OIDEQ, Int64GetDatum(graphid)); + graph_vertex_label = table_open(lcd->relation, ShareLock); scan_desc = table_beginscan(graph_vertex_label, snapshot, 1, scan_keys); tuple = heap_getnext(scan_desc, ForwardScanDirection); diff --git a/src/include/access/vertex.h b/src/include/access/vertex.h index 8c26f188..12a9ee93 100644 --- a/src/include/access/vertex.h +++ b/src/include/access/vertex.h @@ -23,6 +23,7 @@ typedef struct VertexScanDescData int ndesc; TableScanDesc **desc; + bool *isIndex; } VertexScanDescData; typedef struct VertexScanDescData *VertexHeapScanDesc; From fb2d95f5444a6c4b69f98a6bcd8b2cbe4572bc35 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Fri, 15 Aug 2025 17:48:43 -0700 Subject: [PATCH 31/56] Failed --- regress/sql/cypher_create.sql | 2 + .../vertex_heap/vertex_heapam_handler.c | 8 +- src/backend/parser/cypher_clause.c | 540 +++++++++--------- src/include/nodes/cypher_nodes.h | 5 + 4 files changed, 279 insertions(+), 276 deletions(-) diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index a1150356..43c6d733 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -59,6 +59,8 @@ EXPLAIN ANALYZE MATCH ()-[]->() RETURN 1; MATCH ()-[]->() RETURN 1; +MATCH ()<-[]-() RETURN 1; + SELECT * FROM cypher_create._ag_label_vertex; SELECT * FROM cypher_create._adj__adj__ag_label_vertex; diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index ca513d0b..ce07bdbd 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -127,7 +127,7 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, vertex_desc->desc = palloc(sizeof (TableScanDesc *) * vertex_desc->ndesc); vertex_desc->isIndex = palloc(sizeof (bool) * vertex_desc->ndesc); - List *indexoidlist = RelationGetIndexList(relation); + /*List *indexoidlist = RelationGetIndexList(relation); if(list_length(indexoidlist) == 1) { Oid idx = linitial_oid(indexoidlist); @@ -140,13 +140,13 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, vertex_desc->desc[0] = desc; vertex_desc->isIndex[0] = true; - } else { + } else {*/ TableScanDesc *desc = tableam->scan_begin(rel, snapshot, nkeys, key, parallel_scan, flags); vertex_desc->desc[0] = desc; vertex_desc->isIndex[0] = false; - } + //} return vertex_desc; } @@ -180,7 +180,7 @@ bool vertex_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, VertexScanDescData *vertex_desc = sscan; TableAmRoutine *tableam = GetHeapamTableAmRoutine(); - if (vertex_desc->desc[0]) + if (!vertex_desc->desc[0]) return false; if (vertex_desc->isIndex[0]) return index_getnext_slot(vertex_desc->desc[0], direction, slot); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 5172c3b8..c104a3a9 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -166,7 +166,7 @@ static char *make_id_alias(char *var_name) { int i = 0; for (; i < strlen(var_name); i++) str[i + 4] = var_name[i]; - str[i + 4] = '_'; + str[i + 5] = '_'; str[i + 6] = '_'; str[i + 7] = '\0'; @@ -966,36 +966,8 @@ static ParseNamespaceItem *add_srf_to_query(cypher_parsestate *cpstate, Node *n, -static ParseNamespaceItem *add_srf_to_query1(cypher_parsestate *cpstate, Node *n, char *var_name) { - RangeTblEntry *rte = NULL; - RangeTblRef *rtr; - List *namespace = NULL; - int rtindex; - ParseState *pstate = (ParseState *)cpstate; - - Alias *alias = make_alias(var_name, list_make1(makeString("properties"))); - //Alias *alias = make_alias(var_name, NIL); - RangeFunction *rf = make_range_function(n, alias, true, false, false); - - - rtr = transform_srf_function(cpstate, rf, &rte, &rtindex, &namespace); - Assert(rtr != NULL); - - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); - - setNamespaceLateralState(namespace, true, true); - //((ParseNamespaceItem *)lfirst(list_head(namespace)))->p_names = alias;//list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties")); - pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); - pstate->p_namespace = list_concat(pstate->p_namespace, namespace); - - setNamespaceLateralState(pstate->p_namespace, true, true); - - return lfirst(list_head(namespace)); -} - - // transform a function call appearing in FROM -static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, RangeFunction *r) { +static ParseNamespaceItem *transformRangeFunction(cypher_parsestate *cpstate, RangeFunction *r) { ParseState *pstate = NULL; List *funcexprs = NIL; List *funcnames = NIL; @@ -1079,7 +1051,7 @@ static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, RangeTb ParseNamespaceItem *nsitem; int rtindex; - nsitem = transform_RangeFunction(cpstate, (RangeFunction *) n); + nsitem = transformRangeFunction(cpstate, (RangeFunction *) n); rte = nsitem->p_rte; rtindex = list_length(pstate->p_rtable); Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); @@ -1106,7 +1078,7 @@ static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, RangeTb ParseNamespaceItem *nsitem; int rtindex; - nsitem = transform_RangeFunction(cpstate, (RangeFunction *) n); + nsitem = transformRangeFunction(cpstate, (RangeFunction *) n); rte = nsitem->p_rte; rtindex = list_length(pstate->p_rtable); Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); @@ -1122,26 +1094,6 @@ static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, RangeTb } -static ParseNamespaceItem *add_vle_to_query(cypher_parsestate *cpstate, Node *n) { - ParseState *pstate = &cpstate->pstate; - RangeTblEntry *rte = NULL; - List *namespace = NULL; - int rtindex; - - n = transform_VLE_Function(cpstate, n, &rte, &rtindex, &namespace); - Assert(n != NULL); - - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); - - setNamespaceLateralState(namespace, true, true); - - pstate->p_joinlist = lappend(pstate->p_joinlist, n); - pstate->p_namespace = list_concat(pstate->p_namespace, namespace); - - setNamespaceLateralState(pstate->p_namespace, false, true); - - return lfirst(list_head(namespace)); -} // setNamespaceLateralState - subroutine to update LATERAL flags in a namespace list. @@ -1167,269 +1119,313 @@ static Node *make_int_const(int i, int location) { return (Node *)n; } - -static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { - ParseState *pstate = (ParseState *)cpstate; - ListCell *lc; - List *quals = NIL; - - Expr *expr = NULL; - - - if (list_length(pattern) !=1 ) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH found more than one pattern"))); - - foreach (lc, pattern) { - List *qual = NULL; - cypher_path *path = (cypher_path *) lfirst(lc); - // TODO: implement the new match logic - //qual = transform_match_path(cpstate, query, path); - - if (list_length(path->path) != 1 && list_length(path->path) != 3) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH paths only support 1 vertex"))); - - char *vertex_label = NULL; - ParseNamespaceItem *vertex_pnsi; - Node *vertex_id = NULL; - ListCell *path_cell; - cypher_node *prev_node; - ParseNamespaceItem *edge_pnsi; - int i = 1; - foreach(path_cell, path->path) { - if (i % 2 == 1) { - cypher_node *node = lfirst(path_cell); - prev_node = node; - bool has_variable = false; - - if (node->name) - has_variable = true; - else - node->name = get_next_default_alias(cpstate); - - bool is_default_label = true; - if (node->label && i != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH labels are not supported"))); - else if (node->label) - is_default_label = false; - else - node->label = AG_DEFAULT_LABEL_VERTEX; - - char *schema_name; - char *rel_name; - int resno; - TargetEntry *te; - Expr *expr; - ParseNamespaceItem *pnsi; - RangeVar *label_range_var; - Alias *alias; - - if (i < 2) { - - - schema_name = get_graph_namespace_name(cpstate->graph_name); - +char *get_vertex_relation_name(cypher_parsestate *cpstate, char *label, bool is_default_label) { // XXX: LTree Labeling Project, first code is here - Datum label_lquery; - if (is_default_label) - label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(node->label)); - else - label_lquery = DirectFunctionCall2(ltree_addltree, - DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), - DirectFunctionCall1(ltree_in, CStringGetDatum(node->label))); - + Datum label_lquery; + if (is_default_label) + label_lquery = DirectFunctionCall1(ltree_in, CStringGetDatum(label)); + else + label_lquery = DirectFunctionCall2(ltree_addltree, + DirectFunctionCall1(ltree_in, CStringGetDatum(AG_DEFAULT_LABEL_VERTEX)), + DirectFunctionCall1(ltree_in, CStringGetDatum(label))); + - int ltq_query_args[2]; - ltq_query_args[0] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - ltq_query_args[1] = LookupTypeNameOid(pstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + int ltq_query_args[2]; + ltq_query_args[0] = LookupTypeNameOid(cpstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); + ltq_query_args[1] = LookupTypeNameOid(cpstate, makeTypeNameFromNameList(list_make2(makeString("public"), makeString("ltree"))), false); - Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); + Oid ltree_contains_oid = LookupFuncName(list_make2(makeString("public"), makeString("ltree_risparent")), 2, <q_query_args, false); - ScanKeyData scan_keys[1]; - ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); + ScanKeyData scan_keys[1]; + ScanKeyInit(&scan_keys[0], Anum_ag_label_label_path, BTEqualStrategyNumber, ltree_contains_oid, label_lquery); - Relation label_catalog = table_open(ag_label_relation_id(), ShareLock); - SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); + Relation label_catalog = table_open(ag_label_relation_id(), ShareLock); + SysScanDesc scan_desc = systable_beginscan(label_catalog, ag_label_label_index_id(), true, NULL, 1, scan_keys); - HeapTuple tuple = systable_getnext(scan_desc); + HeapTuple tuple = systable_getnext(scan_desc); - if (!HeapTupleIsValid(tuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("not found %s", node->label))); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR,(errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("not found %s", label))); - bool is_null; - rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); - - systable_endscan(scan_desc); - table_close(label_catalog, ShareLock); - - //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); - label_range_var = makeRangeVar(schema_name, rel_name, -1); - vertex_label = node->label; - alias = makeAlias(node->name, NIL); - - pnsi = addRangeTableEntry(pstate, label_range_var, alias, label_range_var->inh, true); - vertex_pnsi = pnsi; - Assert(pnsi != NULL); - - addNSItemToQuery(pstate, pnsi, true, true, true); - - //resno = pstate->p_next_resno++; - // XXX: End LTree Code here - expr = (Expr *)make_vertex_expr(cpstate, pnsi); - // make target entry and add it - resno = pstate->p_next_resno++; - te = makeTargetEntry(expr, resno, node->name, false); - - query->targetList = lappend(query->targetList, te); - - // - } else { - - if (node->props || has_variable) { - int sublevels_up =1; - Node * id_field = scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1); - - FuncCall *fc = makeFuncCall(list_make2(makeString("postgraph"), makeString("retrieve_vertex")), - list_make2(make_int_const(cpstate->graph_oid, -1),id_field), - COERCE_EXPLICIT_CALL, -1); - pnsi = add_srf_to_query1(cpstate, fc, node->name); - } else { - continue; - } - - } - - // id field - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - vertex_id = id; - resno = pstate->p_next_resno++; - - te = makeTargetEntry(id, resno, make_id_alias(node->name), false); - query->targetList = lappend(query->targetList, te); + bool is_null; + char *rel_name = heap_getattr(tuple, Anum_ag_label_name, RelationGetDescr(label_catalog), &is_null); - // properties field - Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); - resno = pstate->p_next_resno++; + systable_endscan(scan_desc); + table_close(label_catalog, ShareLock); - te = makeTargetEntry(props, resno, make_property_alias(node->name), false); - query->targetList = lappend(query->targetList, te); + return rel_name; +} +static ParseNamespaceItem * +add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node) +{ + ParseState *pstate = (ParseState *)cpstate; - quals = list_concat(quals, NULL); - } else { - cypher_relationship *edge = lfirst(path_cell); + bool has_variable = false; + if (node->name) + has_variable = true; + else + node->name = get_next_default_alias(cpstate); - if (edge->name) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH variable names are not supported"))); - else - edge->name = get_next_default_alias(cpstate); + node->is_default_label = true; + if (node->label) + node->is_default_label = false; + else + node->label = AG_DEFAULT_LABEL_VERTEX; - bool is_default_label = true; - if (edge->label) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH labels are not supported"))); - else - edge->label = AG_DEFAULT_LABEL_EDGE; - - char *schema_name; - char *rel_name; - int resno; - TargetEntry *te; - Expr *expr; - ParseNamespaceItem *pnsi; - RangeVar *label_range_var; - Alias *alias; + node->in_join_tree = true; - schema_name = get_graph_namespace_name(cpstate->graph_name); + ParseNamespaceItem *pnsi = addRangeTableEntry(pstate, + makeRangeVar(get_graph_namespace_name(cpstate->graph_name), + get_vertex_relation_name(cpstate, node->label, node->is_default_label), + -1), + makeAlias(node->name, list_make2(makeString("id"), makeString("properties"))), + true, + true); - //rel_name = get_label_relation_name(node->label, cpstate->graph_oid); + addNSItemToQuery(pstate, pnsi, true, true, true); + return pnsi; +} +static ParseNamespaceItem * +add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, ParseNamespaceItem *edge_pnsi) +{ + ParseState *pstate = (ParseState *)cpstate; - int sublevels_up; - Node * id_field = scanNSItemForColumn(cpstate, vertex_pnsi, 0, "id", -1); + bool has_variable = false; + if (node->name) + has_variable = true; + else { + node->in_join_tree = false; + return NULL; + } + node->in_join_tree = true; + + bool is_default_label = true; + if (node->label) + is_default_label = false; + else + node->label = AG_DEFAULT_LABEL_VERTEX; + + + ParseNamespaceItem *pnsi = transformRangeFunction(cpstate, + make_range_function( + makeFuncCall( + list_make2(makeString("postgraph"), makeString("retrieve_vertex")), + list_make2( + make_int_const(cpstate->graph_oid, -1), + scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1)), + COERCE_EXPLICIT_CALL, -1), + make_alias(node->name, + list_make1(makeString("properties"))), + true, + false, + false)); + + addNSItemToQuery(pstate, pnsi, true, true, true); - //Node * id_field = makeColumnRef(makeString(prev_node->name), list_make1(makeString("id")), -1, NULL); - //alias = makeAlias(edge->name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); - //make_alias(var_name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); - FuncCall *fc = makeFuncCall(list_make2(makeString("postgraph"), makeString("edge_search")), - list_make4(make_int_const(cpstate->graph_oid, -1),id_field, make_null_const(-1), make_null_const(-1)), - COERCE_EXPLICIT_CALL, -1); - pnsi = add_srf_to_query(cpstate, fc, edge->name); - edge_pnsi = pnsi; - //RangeFunction *rf = make_range_function(fc, alias, false, false, false); - //pnsi = add_vle_to_query(cpstate, (Node*)rf); - //addNSItemToQuery(pstate, pnsi, true, true, true); + return pnsi; +} - // resno = pstate->p_next_resno++; - // XXX: End LTree Code here +static ParseNamespaceItem * +add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi) +{ + ParseState *pstate = (ParseState *)cpstate; - // expr = (Expr *)make_edge_expr(cpstate, pnsi); + if (edge->name) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH variable names are not supported"))); + else + edge->name = get_next_default_alias(cpstate); + + bool is_default_label = true; + if (edge->label) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH labels are not supported"))); + else + edge->label = AG_DEFAULT_LABEL_EDGE; + + Node *id_field = scanNSItemForColumn(cpstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + + FuncCall *fc = makeFuncCall( + list_make2(makeString("postgraph"), makeString("edge_search")), + list_make4(make_int_const(cpstate->graph_oid, -1), id_field, make_null_const(-1), make_null_const(-1)), + COERCE_EXPLICIT_CALL, -1); + + return add_srf_to_query(cpstate, fc, edge->name); +} - // make target entry and add it - // te = makeTargetEntry(expr, resno, edge->name, false); - // query->targetList = lappend(query->targetList, te); +static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *query, + cypher_node *left_vertex, cypher_relationship *edge, cypher_node *right_vertex) { + ParseState *pstate = (ParseState *)cpstate; - - // id field - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); - //vertex_id = id; - resno = pstate->p_next_resno++; + // left vertex id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + edge->dir == CYPHER_REL_DIR_RIGHT ? + scanNSItemForColumn(pstate, left_vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1) : + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), + pstate->p_next_resno++, + make_id_alias(left_vertex->name), + false)); + + // Vertex expression + if (left_vertex->has_variable) { + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, left_vertex->pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1), + pstate->p_next_resno++, + make_property_alias(left_vertex->name), + false)); +/* + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)make_vertex_expr(cpstate, left_vertex->pnsi), + pstate->p_next_resno++, + left_vertex->name, + false)); +*/ + } - te = makeTargetEntry(id, resno, make_id_alias(edge->name), false); - query->targetList = lappend(query->targetList, te); + // id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_ID, -1), + pstate->p_next_resno++, + make_id_alias(edge->name), + false)); + + // start id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_START_ID, -1), + pstate->p_next_resno++, + make_startid_alias(edge->name), + false)); + + // end id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), + pstate->p_next_resno++, + make_endid_alias(edge->name), + false)); + + // properties field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1), + pstate->p_next_resno++, + make_property_alias(edge->name), + false)); + + // id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + edge->dir == CYPHER_REL_DIR_RIGHT ? + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1) : + scanNSItemForColumn(pstate, right_vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1), + pstate->p_next_resno++, + make_id_alias(left_vertex->name), + false)); + + // properties field + if (right_vertex->in_join_tree) + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, right_vertex->pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1), + pstate->p_next_resno++, + make_property_alias(right_vertex->name), + false)); +} - // start id field - Node *startid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); - resno = pstate->p_next_resno++; +static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { + ParseState *pstate = (ParseState *)cpstate; + ListCell *lc; + List *quals = NIL; - te = makeTargetEntry(startid, resno, make_startid_alias(edge->name), false); - query->targetList = lappend(query->targetList, te); + Expr *expr = NULL; - // id field - Node *endid = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); - resno = pstate->p_next_resno++; + if (list_length(pattern) !=1 ) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH found more than one pattern"))); - te = makeTargetEntry(endid, resno, make_endid_alias(edge->name), false); - query->targetList = lappend(query->targetList, te); + foreach (lc, pattern) { + cypher_path *path = (cypher_path *) lfirst(lc); - // properties field - Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1); - resno = pstate->p_next_resno++; + if (list_length(path->path) == 1) { + cypher_node *node = linitial(path->path); + + node->pnsi = add_vertex_to_query(cpstate, query, node); + // left vertex id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, node->pnsi, 0, AG_VERTEX_COLNAME_ID, -1), + pstate->p_next_resno++, + make_id_alias(node->name), + false)); + + // Vertex expression + if (node->has_variable) { + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, node->pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1), + pstate->p_next_resno++, + make_property_alias(node->name), + false)); + + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)make_vertex_expr(cpstate, node->pnsi), + pstate->p_next_resno++, + node->name, + false)); - te = makeTargetEntry(props, resno, make_property_alias(edge->name), false); - query->targetList = lappend(query->targetList, te); + } + continue; + } + if (list_length(path->path) != 3) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH paths only support 3 vertex"))); - //quals = list_concat(quals, NULL); + if( ((cypher_relationship *)lsecond(path->path))->dir == CYPHER_REL_DIR_RIGHT) { + ListCell *path_cell; + // left vertex FROM item + cypher_node *node = linitial(path->path); + node->pnsi = add_vertex_to_query(cpstate, query, node); - int sublevels; - char *entity_name = vertex_label; - + + // edge FROM item + cypher_relationship *edge = lsecond(path->path); + edge->pnsi = add_edge_to_query(cpstate, query, edge, node->pnsi); - //ParseNamespaceItem *pnsi1 = refnameNamespaceItem(cpstate, NULL, entity_name, -1, &sublevels); -//scanNSItemForColumn(pstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + // right vertex FROM item + node = lthird(path->path); - //quals = list_concat(quals, list_make1(makeSimpleCypherA_Expr(AEXPR_OP, "=", startid, vertex_id, -1))); + node->pnsi = add_vertex_retrieval_to_query(cpstate, query, node, edge->pnsi); - - } - i++; + // SELECT fields + add_all_fields_to_target_list(cpstate, + query, + (cypher_node *)linitial(path->path), + (cypher_relationship *)lsecond(path->path), + (cypher_node *)lthird(path->path)); + } else { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH edge can't be bi directional"))); } + + } // AND the quals for each path together diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index ca6dcef5..3216e8bc 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -192,6 +192,10 @@ typedef struct cypher_node char *label; Node *props; // map or parameter int location; + ParseNamespaceItem *pnsi; + bool has_variable; + bool is_default_label; + bool in_join_tree; } cypher_node; typedef enum @@ -210,6 +214,7 @@ typedef struct cypher_relationship Node *props; // map or parameter Node *varlen; // variable length relationships (A_Indices) cypher_rel_dir dir; + ParseNamespaceItem *pnsi; int location; } cypher_relationship; From 3e8affde923e3a72051672aefa99b5a7f921f828 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 16 Aug 2025 17:55:14 -0700 Subject: [PATCH 32/56] Update --- postgraph--0.1.0.sql | 2 +- regress/sql/cypher_create.sql | 17 ++++ src/backend/executor/cypher_create.c | 4 +- src/backend/parser/cypher_clause.c | 145 +++++++++++++++++---------- src/backend/utils/adt/edge.c | 22 +++- src/backend/utils/edge_searching.c | 65 +++++++----- src/include/access/vertex.h | 2 +- src/include/nodes/cypher_nodes.h | 1 + 8 files changed, 171 insertions(+), 87 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 61d0750b..6fea0ef9 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -347,7 +347,7 @@ CREATE FUNCTION build_edge(graphid, graphid, graphid, oid, gtype) RETURNS edge LANGUAGE c STABLE -RETURNS NULL ON NULL INPUT +CALLED ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 43c6d733..e48c9ff0 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -61,6 +61,23 @@ MATCH ()-[]->() RETURN 1; MATCH ()<-[]-() RETURN 1; +MATCH ()<-[q]-() RETURN 1; + + +MATCH ()<-[q]-() RETURN q; + +MATCH (e)<-[q]-() RETURN q, e; + +/* +MATCH (a)-[]->() RETURN a; + +MATCH (a)<-[]-() RETURN a; + + +MATCH ()-[]->(a) RETURN a; + +MATCH ()<-[]-(a) RETURN a; +*/ SELECT * FROM cypher_create._ag_label_vertex; SELECT * FROM cypher_create._adj__adj__ag_label_vertex; diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 72445f2e..e033c277 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -323,9 +323,9 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) ExecClearTuple(elemTupleSlot); // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->edge_ids[0][i-1]; + elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; elemTupleSlot->tts_isnull[0] = false; - + elemTupleSlot->tts_values[1] = css->vertex_ids[0][i]; elemTupleSlot->tts_isnull[1] = false; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index c104a3a9..313510e2 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -88,6 +88,7 @@ static char *make_endid_alias(char *var_name); static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); +static Node *make_vertex_expr_with_edge(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi, ParseNamespaceItem *edge_pnsi); List * transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); @@ -253,6 +254,29 @@ static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pn } +static Node *make_vertex_expr_with_edge(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi, ParseNamespaceItem *edge_pnsi) { + ParseState *pstate = (ParseState *)cpstate; + + Oid func_oid = get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID); + + Node *id = scanNSItemForColumn(pstate, edge_pnsi, 0, "endid", -1); + + Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), + ObjectIdGetDatum(cpstate->graph_oid), false, true); + + Node * props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); + + List *args = list_make3(id, graph_oid_const, props); + + FuncExpr *func_expr = makeFuncExpr(func_oid, VERTEXOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + func_expr->location = -1; + + + return (Node *)func_expr; +} + + + static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { ParseState *pstate = (ParseState *)cpstate; @@ -260,9 +284,9 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); - Node *start_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); + Node *start_id = scanNSItemForColumn(pstate, pnsi, 0, "startid", -1); - Node *end_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); + Node *end_id = scanNSItemForColumn(pstate, pnsi, 0, "endid", -1); Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true); @@ -1067,34 +1091,6 @@ static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, RangeTb } -static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace) { - ParseState *pstate = &cpstate->pstate; - - Assert(IsA(n, RangeFunction)); - - if (IsA(n, RangeFunction)) { - RangeTblRef *rtr; - RangeTblEntry *rte; - ParseNamespaceItem *nsitem; - int rtindex; - - nsitem = transformRangeFunction(cpstate, (RangeFunction *) n); - rte = nsitem->p_rte; - rtindex = list_length(pstate->p_rtable); - Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); - *top_rte = rte; - *top_rti = rtindex; - *namespace = list_make1(nsitem); - rtr = makeNode(RangeTblRef); - rtr->rtindex = rtindex; - return (Node *) rtr; - } - - return NULL; -} - - - // setNamespaceLateralState - subroutine to update LATERAL flags in a namespace list. static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok) { @@ -1163,9 +1159,9 @@ add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node) { ParseState *pstate = (ParseState *)cpstate; - bool has_variable = false; + node->has_variable = false; if (node->name) - has_variable = true; + node->has_variable = true; else node->name = get_next_default_alias(cpstate); @@ -1196,9 +1192,9 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n { ParseState *pstate = (ParseState *)cpstate; - bool has_variable = false; + node->has_variable = false; if (node->name) - has_variable = true; + node->has_variable = true; else { node->in_join_tree = false; return NULL; @@ -1236,10 +1232,9 @@ add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship { ParseState *pstate = (ParseState *)cpstate; + edge->has_variable = false; if (edge->name) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH variable names are not supported"))); + edge->has_variable = true; else edge->name = get_next_default_alias(cpstate); @@ -1266,7 +1261,8 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que ParseState *pstate = (ParseState *)cpstate; // left vertex id field - query->targetList = lappend(query->targetList, + if (left_vertex->in_join_tree) { + query->targetList = lappend(query->targetList, makeTargetEntry( edge->dir == CYPHER_REL_DIR_RIGHT ? scanNSItemForColumn(pstate, left_vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1) : @@ -1275,22 +1271,24 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que make_id_alias(left_vertex->name), false)); - // Vertex expression - if (left_vertex->has_variable) { query->targetList = lappend(query->targetList, makeTargetEntry( scanNSItemForColumn(pstate, left_vertex->pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1), pstate->p_next_resno++, make_property_alias(left_vertex->name), false)); -/* - query->targetList = lappend(query->targetList, - makeTargetEntry( - (Expr *)make_vertex_expr(cpstate, left_vertex->pnsi), - pstate->p_next_resno++, - left_vertex->name, - false)); -*/ + + // Vertex expression + if (left_vertex->has_variable) + query->targetList = lappend(query->targetList, + makeTargetEntry( + edge->dir == CYPHER_REL_DIR_LEFT ? + (Expr *)make_vertex_expr_with_edge(cpstate, left_vertex->pnsi, edge->pnsi) : + (Expr *)make_vertex_expr(cpstate, left_vertex->pnsi), + pstate->p_next_resno++, + left_vertex->name, + false)); + } // id field @@ -1326,17 +1324,25 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que false)); // id field - query->targetList = lappend(query->targetList, + if (edge->has_variable) + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)make_edge_expr(cpstate, edge->pnsi), + pstate->p_next_resno++, + edge->name, + false)); + + // properties field + if (right_vertex->in_join_tree){ + query->targetList = lappend(query->targetList, makeTargetEntry( edge->dir == CYPHER_REL_DIR_RIGHT ? scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1) : scanNSItemForColumn(pstate, right_vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1), pstate->p_next_resno++, - make_id_alias(left_vertex->name), + make_id_alias(right_vertex->name), false)); - // properties field - if (right_vertex->in_join_tree) query->targetList = lappend(query->targetList, makeTargetEntry( scanNSItemForColumn(pstate, right_vertex->pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1), @@ -1344,6 +1350,18 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que make_property_alias(right_vertex->name), false)); + // Vertex expression + if (right_vertex->has_variable) + query->targetList = lappend(query->targetList, + makeTargetEntry( + edge->dir == CYPHER_REL_DIR_RIGHT ? + (Expr *)make_vertex_expr_with_edge(cpstate, right_vertex->pnsi, edge->pnsi) : + (Expr *)make_vertex_expr(cpstate, right_vertex->pnsi), + pstate->p_next_resno++, + right_vertex->name, + false)); + } + } static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { @@ -1414,6 +1432,29 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li node->pnsi = add_vertex_retrieval_to_query(cpstate, query, node, edge->pnsi); + // SELECT fields + add_all_fields_to_target_list(cpstate, + query, + (cypher_node *)linitial(path->path), + (cypher_relationship *)lsecond(path->path), + (cypher_node *)lthird(path->path)); + } else if( ((cypher_relationship *)lsecond(path->path))->dir == CYPHER_REL_DIR_LEFT) { + ListCell *path_cell; + + // left vertex FROM item + cypher_node *node = lthird(path->path); + node->pnsi = add_vertex_to_query(cpstate, query, node); + + + // edge FROM item + cypher_relationship *edge = lsecond(path->path); + edge->pnsi = add_edge_to_query(cpstate, query, edge, node->pnsi); + + // right vertex FROM item + node = linitial(path->path); + + node->pnsi = add_vertex_retrieval_to_query(cpstate, query, node, edge->pnsi); + // SELECT fields add_all_fields_to_target_list(cpstate, query, diff --git a/src/backend/utils/adt/edge.c b/src/backend/utils/adt/edge.c index 37c0597d..f9dc18ad 100644 --- a/src/backend/utils/adt/edge.c +++ b/src/backend/utils/adt/edge.c @@ -133,11 +133,14 @@ build_edge(PG_FUNCTION_ARGS) { graphid end_id = AG_GETARG_GRAPHID(2); Oid graph_oid = PG_GETARG_OID(3); //char *label = PG_GETARG_CSTRING(3); - gtype *properties = AG_GET_ARG_GTYPE_P(4); - if (!AGT_ROOT_IS_OBJECT(properties)) - PG_RETURN_NULL(); - + gtype *properties = NULL; + if (!PG_ARGISNULL(4)) { + properties = AG_GET_ARG_GTYPE_P(4); + + if (!AGT_ROOT_IS_OBJECT(properties)) + PG_RETURN_NULL(); + } AG_RETURN_EDGE(create_edge(id, start_id, end_id, graph_oid, properties)); } @@ -162,6 +165,17 @@ create_edge(graphid id,graphid start_id,graphid end_id, Oid graph_oid, gtype *pr append_to_buffer(&buffer, (char *)&graph_oid, sizeof(Oid)); // properties + // properties + if (properties == NULL) { + gtype_in_state result; + + memset(&result, 0, sizeof(gtype_in_state)); + + push_gtype_value(&result.parse_state, WGT_BEGIN_OBJECT, NULL); + result.res = push_gtype_value(&result.parse_state, WGT_END_OBJECT, NULL); + + properties = gtype_value_to_gtype(result.res); + } append_to_buffer(&buffer, properties, VARSIZE(properties)); edge *v = (edge *)buffer.data; diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 052d294d..51b228a4 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -86,13 +86,6 @@ static Datum get_vertex(Oid graph_oid, int64 graphid) } PG_FUNCTION_INFO_V1(retrieve_vertex); -/* - * Function to convert the Array type of Agtype into each row. It is used for - * Cypher `UNWIND` clause, but considering the situation in which the user can - * directly use this function in vanilla PGSQL, put a second parameter related - * to this. - */ -static int this_is_a_test1 = 0; Datum retrieve_vertex(PG_FUNCTION_ARGS) { gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); graphid id = AG_GETARG_GRAPHID(1); @@ -110,13 +103,6 @@ struct edge_search_cxt } edge_search_cxt; PG_FUNCTION_INFO_V1(edge_search); -/* - * Function to convert the Array type of Agtype into each row. It is used for - * Cypher `UNWIND` clause, but considering the situation in which the user can - * directly use this function in vanilla PGSQL, put a second parameter related - * to this. - */ -static int this_is_a_test = 0; Datum edge_search(PG_FUNCTION_ARGS) { gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); @@ -134,6 +120,9 @@ Datum edge_search(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); tupdesc = CreateTemplateTupleDesc(4); + + tupdesc->tdtypeid = RECORDOID; /* not right, but we don't care */ + tupdesc->tdtypmod = -1; TupleDescInitEntry(tupdesc, (AttrNumber) 1, "id", GRAPHIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "startid", @@ -157,8 +146,10 @@ Datum edge_search(PG_FUNCTION_ARGS) cxt->rel = table_open(lcd->vertex_adjlist, ShareLock); cxt->scan_desc = table_beginscan(cxt->rel, snapshot, 1, cxt->scanKey); + //VertexHeapScanDesc vertex_desc = cxt->scan_desc; List *empty = NIL; cxt->slot = table_slot_create(cxt->rel, &empty); + //funcctx->tuple_desc = RelationBuildTupleDesc(cxt->rel); funcctx->user_fctx = cxt; MemoryContextSwitchTo(oldcontext); } @@ -173,18 +164,38 @@ Datum edge_search(PG_FUNCTION_ARGS) table_close(cxt->rel, ShareLock); SRF_RETURN_DONE(funcctx); } - - Datum values[4]; - bool nulls[4]; - HeapTuple tuple1; - - memset(nulls, true, sizeof(nulls)); - - values[0] = cxt->slot->tts_values[0]; - values[1] = cxt->slot->tts_values[1]; - values[2] = cxt->slot->tts_values[2]; - values[3] = cxt->slot->tts_values[3]; - tuple1 = heap_form_tuple(funcctx->tuple_desc, values, nulls); - + + + Datum values[4]; + bool nulls[4]; + HeapTuple tuple1; + cxt->slot->tts_ops->materialize(cxt->slot); + //cxt->slot->tts_ops->getsomeattrs(cxt->slot, 10000); + //memset(nulls, false, sizeof(nulls)); + //datum = heap_getattr(cxt->slot.tuple, 1, + // RelationGetDescr(cxt->rel), &isnull); + + VertexHeapScanDesc vertex_desc = cxt->scan_desc; + //vertex_desc->desc[0] + bool isnull; + //slot_getattr(cxt->slot, i, &isnull); + values[0] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 1, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); + nulls[0] = isnull; + //ereport(WARNING, errmsg("value %lu", DatumGetInt64(values[0]))); + values[1] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 2, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); + nulls[1] = isnull; + //ereport(WARNING, errmsg("value %lu", DatumGetInt64(values[1]))); + values[2] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 3, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); + nulls[2] = isnull; + //ereport(WARNING, errmsg("value %lu", DatumGetInt64(values[2]))); + values[3] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 4, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); + nulls[3] = isnull; + tuple1 = heap_form_tuple(funcctx->tuple_desc, &values, &nulls); + //cxt->slot->tts_ops->get_heap_tuple(cxt->slot); + + //tuple1 = cxt->slot->tts_ops->copy_heap_tuple(cxt->slot); + //tuple1->t_tableOid = RelationGetRelid(cxt->rel); + //SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(cxt->slot->tts_ops->get_heap_tuple(cxt->slot))); + //SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(cxt->slot->tts_ops->get_heap_tuple(cxt->slot))); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple1)); } diff --git a/src/include/access/vertex.h b/src/include/access/vertex.h index 12a9ee93..fba764c7 100644 --- a/src/include/access/vertex.h +++ b/src/include/access/vertex.h @@ -22,7 +22,7 @@ typedef struct VertexScanDescData TableScanDescData rs_base; /* AM independent part of the descriptor */ int ndesc; - TableScanDesc **desc; + TableScanDesc *desc; bool *isIndex; } VertexScanDescData; diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index 3216e8bc..a821968d 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -214,6 +214,7 @@ typedef struct cypher_relationship Node *props; // map or parameter Node *varlen; // variable length relationships (A_Indices) cypher_rel_dir dir; + bool has_variable; ParseNamespaceItem *pnsi; int location; } cypher_relationship; From 603f4e20fb17bf32f2ce842b595a45edac331c62 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 16 Aug 2025 23:25:51 -0700 Subject: [PATCH 33/56] Support MATCH Variables in endpoint Inefficient, to be redone, but it works --- postgraph--0.1.0.sql | 10 +++--- regress/sql/cypher_create.sql | 7 +--- src/backend/utils/adt/vertex.c | 4 +-- src/backend/utils/edge_searching.c | 55 +++++++++++++++++++++++++++--- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 6fea0ef9..ef0d51d0 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -313,7 +313,7 @@ AS 'MODULE_PATHNAME'; CREATE FUNCTION build_vertex(graphid, oid, gtype) RETURNS vertex LANGUAGE c -IMMUTABLE +STABLE CALLED ON NULL INPUT PARALLEL SAFE COST 50 @@ -442,7 +442,7 @@ AS 'MODULE_PATHNAME'; CREATE FUNCTION retrieve_vertex(graph_oid gtype, id graphid) -RETURNS gtype +RETURNS TABLE (properties gtype) RETURNS NULL ON NULL INPUT STABLE PARALLEL SAFE @@ -590,9 +590,9 @@ CREATE OPERATOR = ( COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, - JOIN = eqjoinsel--, - --HASHES, - --MERGES + JOIN = eqjoinsel, + HASHES, + MERGES ); CREATE FUNCTION graphid_ne(graphid, graphid) diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index e48c9ff0..16316549 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -64,11 +64,6 @@ MATCH ()<-[]-() RETURN 1; MATCH ()<-[q]-() RETURN 1; -MATCH ()<-[q]-() RETURN q; - -MATCH (e)<-[q]-() RETURN q, e; - -/* MATCH (a)-[]->() RETURN a; MATCH (a)<-[]-() RETURN a; @@ -77,7 +72,7 @@ MATCH (a)<-[]-() RETURN a; MATCH ()-[]->(a) RETURN a; MATCH ()<-[]-(a) RETURN a; -*/ + SELECT * FROM cypher_create._ag_label_vertex; SELECT * FROM cypher_create._adj__adj__ag_label_vertex; diff --git a/src/backend/utils/adt/vertex.c b/src/backend/utils/adt/vertex.c index 0c24ebea..777f0e01 100644 --- a/src/backend/utils/adt/vertex.c +++ b/src/backend/utils/adt/vertex.c @@ -102,13 +102,13 @@ build_vertex(PG_FUNCTION_ARGS) { graphid id = AG_GETARG_GRAPHID(0); Oid graph_oid = PG_GETARG_OID(1); gtype *properties = NULL; - if (!PG_ARGISNULL(2)) { + /* if (!PG_ARGISNULL(2)) { properties = AG_GET_ARG_GTYPE_P(2); if (!AGT_ROOT_IS_OBJECT(properties)) PG_RETURN_NULL(); } - +*/ AG_RETURN_VERTEX(create_vertex(id, graph_oid, properties)); } diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 51b228a4..11f8e8ba 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -87,10 +87,57 @@ static Datum get_vertex(Oid graph_oid, int64 graphid) PG_FUNCTION_INFO_V1(retrieve_vertex); Datum retrieve_vertex(PG_FUNCTION_ARGS) { - gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); - graphid id = AG_GETARG_GRAPHID(1); + + //PG_RETURN_POINTER(get_vertex(graph_oid, id)); + + //gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); + //graphid id = AG_GETARG_GRAPHID(1); - PG_RETURN_POINTER(get_vertex(graph_oid, id)); + //lquery *label = PG_GETARG_LQUERY_P(1); + //bool include_props = PG_GETARG_BOOL(2); + FuncCallContext *funcctx; + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(1); + + tupdesc->tdtypeid = RECORDOID; /* not right, but we don't care */ + tupdesc->tdtypmod = -1; + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "properties", + GTYPEOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + MemoryContextSwitchTo(oldcontext); + + funcctx = SRF_PERCALL_SETUP(); + + + Datum values[1]; + bool nulls[1]; + HeapTuple tuple1; + gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); + graphid id = AG_GETARG_GRAPHID(1); + + bool isnull; + values[0] = get_vertex(graph_oid, id); + nulls[0] = true; + tuple1 = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple1)); + } + + SRF_RETURN_DONE(funcctx); + + + + + + } @@ -190,7 +237,7 @@ Datum edge_search(PG_FUNCTION_ARGS) //ereport(WARNING, errmsg("value %lu", DatumGetInt64(values[2]))); values[3] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 4, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); nulls[3] = isnull; - tuple1 = heap_form_tuple(funcctx->tuple_desc, &values, &nulls); + tuple1 = heap_form_tuple(funcctx->tuple_desc, values, nulls); //cxt->slot->tts_ops->get_heap_tuple(cxt->slot); //tuple1 = cxt->slot->tts_ops->copy_heap_tuple(cxt->slot); From f5f76557412d68f98a8659f42293a8d6ef7e2cd3 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sun, 17 Aug 2025 03:43:06 -0700 Subject: [PATCH 34/56] Support vertex properties in CREATE Clause --- regress/sql/cypher_create.sql | 27 +++------ src/backend/executor/cypher_create.c | 33 +++++++--- src/backend/nodes/cypher_copyfuncs.c | 1 + src/backend/nodes/cypher_outfuncs.c | 1 + src/backend/nodes/cypher_readfuncs.c | 1 + src/backend/parser/cypher_clause.c | 90 ++++++++++++++++++++++------ src/backend/utils/adt/vertex.c | 4 +- src/include/nodes/cypher_nodes.h | 31 +--------- 8 files changed, 115 insertions(+), 73 deletions(-) diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 16316549..0e1cd436 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -29,18 +29,16 @@ SELECT * FROM cypher_create._ag_label_vertex; MATCH () RETURN 1; -CREATE VLABEL test; -SELECT * FROM postgraph.ag_label; +SELECT '{}'::postgraph.gtype; +CREATE ({msg: 'Hello World'}); +MATCH (a) RETURN a; +SELECT * FROM cypher_create._ag_label_vertex; -CREATE (:test); -SELECT * FROM postgraph.ag_label; -SELECT * FROM cypher_create.test; +CREATE VLABEL test; +CREATE (:test); CREATE (:test2); -SELECT * FROM postgraph.ag_label; -SELECT * FROM cypher_create.test2; - MATCH () RETURN 1; @@ -52,11 +50,6 @@ CREATE ()-[]->(); CREATE ()-[:elabel]->(); - -EXPLAIN MATCH ()-[]->() RETURN 1; - -EXPLAIN ANALYZE MATCH ()-[]->() RETURN 1; - MATCH ()-[]->() RETURN 1; MATCH ()<-[]-() RETURN 1; @@ -73,12 +66,10 @@ MATCH ()-[]->(a) RETURN a; MATCH ()<-[]-(a) RETURN a; -SELECT * FROM cypher_create._ag_label_vertex; - -SELECT * FROM cypher_create._adj__adj__ag_label_vertex; -SELECT * FROM cypher_create._adj__ag_label_vertex; -MATCH () RETURN 1; +CREATE ()<-[{msg: 'Hello edge'}]-(); +CREATE ()-[{msg: 'Hello edge'}]->(); +MATCH ()<-[a]-() RETURN a; CYPHER WITH 1 as a CREATE (); diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index e033c277..04c6a696 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -267,8 +267,12 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) EState *estate = css->css.ss.ps.state; ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - + //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; + //econtext->ecxt_scantuple = + // csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; + TupleTableSlot *slot = ExecProcNode(csnode->ss.ps.lefttree); + slot = csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; + slot->tts_ops->materialize(slot); if (list_length(css->pattern) != 1) ereport(ERROR, (errmsg_internal("executor create found a multi pattern"))); @@ -309,8 +313,16 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) elemTupleSlot->tts_isnull[0] = false; // get the properties for this vertex - elemTupleSlot->tts_values[1] = NULL; - elemTupleSlot->tts_isnull[1] = true; + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + } else { + //TupleTableSlot *scanTupleSlot = csnode->ss.ss_ScanTupleSlot; + //ereport(WARNING, errmsg("adsf %i", scanTupleSlot->tts_values[node->prop_attr_num])); + elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->prop_attr_num - 1]; + } + // Insert the new vertex insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); @@ -331,9 +343,16 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) elemTupleSlot->tts_values[2] = css->vertex_ids[0][i+1]; elemTupleSlot->tts_isnull[2] = false; - // get the properties for this vertex - elemTupleSlot->tts_values[3] = NULL; - elemTupleSlot->tts_isnull[3] = true; + + + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[3] = NULL; + elemTupleSlot->tts_isnull[3] = true; + } else { + //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; + elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num]; + elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num]; + } // Insert the new vertex insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); diff --git a/src/backend/nodes/cypher_copyfuncs.c b/src/backend/nodes/cypher_copyfuncs.c index b8498383..e2367f7e 100644 --- a/src/backend/nodes/cypher_copyfuncs.c +++ b/src/backend/nodes/cypher_copyfuncs.c @@ -100,6 +100,7 @@ void copy_cypher_target_node(ExtensibleNode *newnode, const ExtensibleNode *from COPY_SCALAR_FIELD(type); COPY_SCALAR_FIELD(flags); COPY_SCALAR_FIELD(dir); + COPY_SCALAR_FIELD(id_attr_num); COPY_SCALAR_FIELD(prop_attr_num); COPY_SCALAR_FIELD(relid); COPY_SCALAR_FIELD(adj_relid); diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c index 72a23ec3..9b717422 100644 --- a/src/backend/nodes/cypher_outfuncs.c +++ b/src/backend/nodes/cypher_outfuncs.c @@ -367,6 +367,7 @@ void out_cypher_target_node(StringInfo str, const ExtensibleNode *node) WRITE_ENUM_FIELD(dir, cypher_rel_dir); WRITE_NODE_FIELD(id_expr); WRITE_NODE_FIELD(id_expr_state); + WRITE_INT32_FIELD(id_attr_num); WRITE_NODE_FIELD(prop_expr); WRITE_NODE_FIELD(prop_expr_state); WRITE_INT32_FIELD(prop_attr_num); diff --git a/src/backend/nodes/cypher_readfuncs.c b/src/backend/nodes/cypher_readfuncs.c index 2eec5b3f..1ec8f75e 100644 --- a/src/backend/nodes/cypher_readfuncs.c +++ b/src/backend/nodes/cypher_readfuncs.c @@ -216,6 +216,7 @@ void read_cypher_target_node(struct ExtensibleNode *node) READ_ENUM_FIELD(dir, cypher_rel_dir); READ_NODE_FIELD(id_expr); READ_NODE_FIELD(id_expr_state); + READ_INT_FIELD(id_attr_num); READ_NODE_FIELD(prop_expr); READ_NODE_FIELD(prop_expr_state); READ_INT_FIELD(prop_attr_num); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 313510e2..a625b136 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -301,6 +301,17 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi return (Node *)func_expr; } + +static Node * +make_graphid_placeholder(cypher_parsestate *cpstate) { + + // typtypmod, typcollation, typlen, and typbyval of gtype are hard-coded. + Const *c = makeConst(GRAPHIDOID, -1, InvalidOid, -1, 0, false, false); + c->location = -1; + + return (Node *)c; +} + static Node * make_int_placeholder(cypher_parsestate *cpstate) { @@ -400,6 +411,7 @@ static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *n } } + static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause) { ParseState *pstate = (ParseState *)cpstate; cypher_create *self = (cypher_create *)clause->self; @@ -439,18 +451,54 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause if (i % 2 == 1) { cypher_node *node = (cypher_node *)lfirst(lc2); + cypher_target_node *target = make_ag_node(cypher_target_node); + if (node->label) validate_or_create_vlabel(cpstate, node); else node->label = AG_DEFAULT_LABEL_VERTEX; - if (node->name) - ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have variable names"))); - - if (node->props) - ereport(ERROR, (errmsg_internal("nodes in CREATE cannot have properties"))); - - cypher_target_node *target = make_ag_node(cypher_target_node); + if (node->name) { + ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); + /* TODO: After props are supported + target->variable_name = node->name; + + query->targetList = lappend(query->targetList, + makeTargetEntry( + make_graphid_placeholder(cpstate), + pstate->p_next_resno++, + make_id_alias(get_next_default_alias(cpstate)), + false)); + } + target->id_attr_num = list_length(query->targetList); + + query->targetList = lappend(query->targetList, + makeTargetEntry( + make_int_placeholder(cpstate), + pstate->p_next_resno++, + make_id_alias(get_next_default_alias(cpstate)), + false)); + } + + target->props_attr_num = list_length(query->targetList); +*/ + } else + node->name = get_next_default_alias(cpstate); + + if (node->props) { + target->prop_attr_num = pstate->p_next_resno; + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)add_volatile_wrapper( + transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), + pstate->p_next_resno++, + make_property_alias(node->name), + false)); + + } else { + target->prop_attr_num = InvalidAttrNumber; + } + label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); @@ -458,14 +506,11 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause target->relid = lcd->relation; target->adj_relid = lcd->vertex_adjlist; - TargetEntry *te = makeTargetEntry(make_int_placeholder(cpstate), pstate->p_next_resno++, make_id_alias(get_next_default_alias(cpstate)), false); - ccp->target_nodes = lappend(ccp->target_nodes, target); - query->targetList = lappend(query->targetList, te); } else { cypher_relationship *edge = lfirst(lc2); - + cypher_target_node *target = make_ag_node(cypher_target_node); if (edge->label) validate_or_create_elabel(cpstate, edge); else @@ -473,19 +518,30 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause if (edge->name) ereport(ERROR, (errmsg_internal("edges in CREATE cannot have variable names"))); - - if (edge->props) - ereport(ERROR, (errmsg_internal("edges in CREATE cannot have properties"))); - + else + edge->name = get_next_default_alias(cpstate); + + if (edge->props) { + target->prop_attr_num = pstate->p_next_resno; + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)add_volatile_wrapper( + transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), + pstate->p_next_resno++, + make_property_alias(edge->name), + false)); + } else { + target->prop_attr_num = InvalidAttrNumber; + } if (edge->dir != CYPHER_REL_DIR_RIGHT) ereport(ERROR, (errmsg_internal("edges CREATE are right only right now"))); - cypher_target_node *target = make_ag_node(cypher_target_node); + label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); target->relid = lcd->relation; target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); - + //target->prop_attr_num = InvalidAttrNumber; ccp->target_nodes = lappend(ccp->target_nodes, target); } diff --git a/src/backend/utils/adt/vertex.c b/src/backend/utils/adt/vertex.c index 777f0e01..0c24ebea 100644 --- a/src/backend/utils/adt/vertex.c +++ b/src/backend/utils/adt/vertex.c @@ -102,13 +102,13 @@ build_vertex(PG_FUNCTION_ARGS) { graphid id = AG_GETARG_GRAPHID(0); Oid graph_oid = PG_GETARG_OID(1); gtype *properties = NULL; - /* if (!PG_ARGISNULL(2)) { + if (!PG_ARGISNULL(2)) { properties = AG_GET_ARG_GTYPE_P(2); if (!AGT_ROOT_IS_OBJECT(properties)) PG_RETURN_NULL(); } -*/ + AG_RETURN_VERTEX(create_vertex(id, graph_oid, properties)); } diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index a821968d..21fee0f6 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -348,52 +348,25 @@ typedef struct cypher_create_path typedef struct cypher_target_node { ExtensibleNode extensible; - // 'v' for vertex or 'e' for edge char type; - // flags defined below, prefaced with CYPHER_TARGET_NODE_FLAG_* uint32 flags; - // if an edge, denotes direction cypher_rel_dir dir; - /* - * Used to create the id for the vertex/edge, - * if the CYPHER_TARGET_NODE_FLAG_INSERT flag - * is set. Doing it this way will protect us when - * rescan gets implemented. By calling the function - * that creates the id ourselves, we won't have an - * issue where the id could be created then not used. - * Since there is a limited number of ids available, we - * don't want to waste them. - */ Expr *id_expr; ExprState *id_expr_state; - + AttrNumber id_attr_num; Expr *prop_expr; ExprState *prop_expr_state; - /* - * Attribute Number that this entity's properties - * are stored in the CustomScanState's child TupleTableSlot - */ AttrNumber prop_attr_num; - // RelInfo for the table this entity will be stored in ResultRelInfo *resultRelInfo; - // elemTupleSlot used to insert the entity into its table TupleTableSlot *elemTupleSlot; - // relid that the label stores its entity Oid relid; Oid adj_relid; ResultRelInfo *adj_resultRelInfo; - // elemTupleSlot used to insert the entity into its table TupleTableSlot *adj_elemTupleSlot; - // label this entity belongs to. char *label_name; - // variable name for this entity char *variable_name; - /* - * Attribute number this entity needs to be stored in - * for parent execution nodes to reference it. If the - * entity is a varaible (CYPHER_TARGET_NODE_IS_VAR). - */ + AttrNumber tuple_position; } cypher_target_node; From edaa308de027cf60bf66b04b166102e86e86c38d Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sun, 17 Aug 2025 04:10:50 -0700 Subject: [PATCH 35/56] Support RIGHT directed edges in CREATE --- src/backend/executor/cypher_create.c | 4 ++-- src/backend/parser/cypher_clause.c | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 04c6a696..f016cc30 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -338,10 +338,10 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; elemTupleSlot->tts_isnull[0] = false; - elemTupleSlot->tts_values[1] = css->vertex_ids[0][i]; + elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; elemTupleSlot->tts_isnull[1] = false; - elemTupleSlot->tts_values[2] = css->vertex_ids[0][i+1]; + elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i+1 : i]; elemTupleSlot->tts_isnull[2] = false; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index a625b136..87d868e1 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -460,18 +460,18 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause if (node->name) { ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); - /* TODO: After props are supported + target->variable_name = node->name; - + target->id_attr_num = pstate->p_next_resno; query->targetList = lappend(query->targetList, makeTargetEntry( make_graphid_placeholder(cpstate), pstate->p_next_resno++, make_id_alias(get_next_default_alias(cpstate)), false)); - } - target->id_attr_num = list_length(query->targetList); - + + +/* query->targetList = lappend(query->targetList, makeTargetEntry( make_int_placeholder(cpstate), @@ -533,8 +533,10 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause } else { target->prop_attr_num = InvalidAttrNumber; } - if (edge->dir != CYPHER_REL_DIR_RIGHT) - ereport(ERROR, (errmsg_internal("edges CREATE are right only right now"))); + + target->dir = edge->dir; + if (edge->dir == CYPHER_REL_DIR_NONE) + ereport(ERROR, (errmsg_internal("edges in CREATE must have a direction"))); label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); From c487ccff74129cedabbb8b5a28d3c6a0487e49c3 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sun, 17 Aug 2025 04:24:34 -0700 Subject: [PATCH 36/56] Native Graph Storage POC And now the real work can begin! What has held this system back since it was called AgensGraph is now over. Graph Native Storage is no longer a dream. --- .../access/vertex_heap/vertex_heapam_handler.c | 6 +++--- src/backend/utils/edge_searching.c | 15 +-------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index ce07bdbd..f2bff22a 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -127,7 +127,7 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, vertex_desc->desc = palloc(sizeof (TableScanDesc *) * vertex_desc->ndesc); vertex_desc->isIndex = palloc(sizeof (bool) * vertex_desc->ndesc); - /*List *indexoidlist = RelationGetIndexList(relation); + List *indexoidlist = RelationGetIndexList(relation); if(list_length(indexoidlist) == 1) { Oid idx = linitial_oid(indexoidlist); @@ -140,13 +140,13 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, vertex_desc->desc[0] = desc; vertex_desc->isIndex[0] = true; - } else {*/ + } else { TableScanDesc *desc = tableam->scan_begin(rel, snapshot, nkeys, key, parallel_scan, flags); vertex_desc->desc[0] = desc; vertex_desc->isIndex[0] = false; - //} + } return vertex_desc; } diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 11f8e8ba..7c7950a0 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -88,13 +88,6 @@ static Datum get_vertex(Oid graph_oid, int64 graphid) PG_FUNCTION_INFO_V1(retrieve_vertex); Datum retrieve_vertex(PG_FUNCTION_ARGS) { - //PG_RETURN_POINTER(get_vertex(graph_oid, id)); - - //gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); - //graphid id = AG_GETARG_GRAPHID(1); - - //lquery *label = PG_GETARG_LQUERY_P(1); - //bool include_props = PG_GETARG_BOOL(2); FuncCallContext *funcctx; if (SRF_IS_FIRSTCALL()) { @@ -132,12 +125,6 @@ Datum retrieve_vertex(PG_FUNCTION_ARGS) { } SRF_RETURN_DONE(funcctx); - - - - - - } @@ -189,7 +176,7 @@ Datum edge_search(PG_FUNCTION_ARGS) Snapshot snapshot = GetActiveSnapshot(); - ScanKeyInit(cxt->scanKey, Anum_ag_label_edge_table_start_id, BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); + ScanKeyInit(cxt->scanKey, 1, HTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); cxt->rel = table_open(lcd->vertex_adjlist, ShareLock); cxt->scan_desc = table_beginscan(cxt->rel, snapshot, 1, cxt->scanKey); From 2af285ffd07727b1210e96737ebe9c2891f43cd6 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Tue, 19 Aug 2025 22:32:56 -0700 Subject: [PATCH 37/56] Update --- postgraph--0.1.0.sql | 11 + regress/sql/cypher_create.sql | 8 + .../vertex_heap/vertex_heapam_handler.c | 34 +- src/backend/commands/label_commands.c | 5 +- src/backend/parser/cypher_clause.c | 138 +++-- src/backend/utils/adt/edge.c | 3 +- src/backend/utils/adt/gtype.c | 111 +--- src/backend/utils/edge_searching.c | 483 ++++++++++++++---- src/include/nodes/cypher_nodes.h | 4 +- src/include/utils/gtype.h | 3 - 10 files changed, 521 insertions(+), 279 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index ef0d51d0..31db535a 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -6818,3 +6818,14 @@ CREATE AGGREGATE regr_r2(gtype, gtype) ( parallel = SAFE ); + + +CREATE FUNCTION variable_edge_search(graph_oid gtype, id graphid, min gtype, max gtype) +RETURNS TABLE (edges variable_edge, endid graphid) +CALLED ON NULL INPUT +STABLE +PARALLEL SAFE +LANGUAGE C +COST 50000 +AS 'MODULE_PATHNAME'; + diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 0e1cd436..2ec1bfbb 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -76,7 +76,15 @@ CREATE (); CREATE () RETURN 1 as a; +CREATE ()-[]->()-[]->(); +SELECT * FROM cypher_create._adj__ag_label_vertex; +MATCH (a) RETURN a; +--SELECT * FROM cypher_create.idx_hash__adj__ag_label_vertex; + +MATCH ()-[*1]->() RETURN 1; +MATCH ()-[]->() RETURN 1; +MATCH ()-[*2]->() RETURN 1; -- -- Clean up -- diff --git a/src/backend/access/vertex_heap/vertex_heapam_handler.c b/src/backend/access/vertex_heap/vertex_heapam_handler.c index f2bff22a..556cde37 100644 --- a/src/backend/access/vertex_heap/vertex_heapam_handler.c +++ b/src/backend/access/vertex_heap/vertex_heapam_handler.c @@ -59,19 +59,10 @@ #include "utils/lsyscache.h" #include "access/table.h" - #include "access/vertex.h" #include "utils/graphid.h" #include "utils/gtype.h" -#define HEAP_OVERHEAD_BYTES_PER_TUPLE \ - (MAXALIGN(SizeofHeapTupleHeader) + sizeof(ItemIdData)) -#define HEAP_USABLE_BYTES_PER_PAGE \ - (BLCKSZ - SizeOfPageHeaderData) - -static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, - uint16 *new_infomask2); - /* ------------------------------------------------------------------------ * Slot related callbacks for vertex heap AM @@ -120,8 +111,9 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, vertex_desc->rs_base.rs_flags = flags; vertex_desc->rs_base.rs_parallel = NULL; vertex_desc->ndesc = 1; - TableAmRoutine *tableam = GetHeapamTableAmRoutine(); - Oid oid = get_relname_relid(make_vertex_adjlist_alias(get_rel_name(RelationGetRelid(relation))), get_rel_namespace(RelationGetRelid(relation))); + Oid oid = get_relname_relid( + make_vertex_adjlist_alias(get_rel_name(RelationGetRelid(relation))), + get_rel_namespace(RelationGetRelid(relation))); Relation rel = RelationIdGetRelation(oid); vertex_desc->desc = palloc(sizeof (TableScanDesc *) * vertex_desc->ndesc); @@ -130,11 +122,8 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, List *indexoidlist = RelationGetIndexList(relation); if(list_length(indexoidlist) == 1) { Oid idx = linitial_oid(indexoidlist); - - IndexScanDesc *desc = index_beginscan(rel, - RelationIdGetRelation(idx), - snapshot, - nkeys, 0); + Relation idxrel = index_open(idx, RowExclusiveLock); + IndexScanDesc *desc = index_beginscan(rel, idxrel, snapshot, nkeys, 0); index_rescan(desc, key, nkeys, NULL, 0); @@ -142,7 +131,7 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, vertex_desc->isIndex[0] = true; } else { - TableScanDesc *desc = tableam->scan_begin(rel, snapshot, nkeys, key, parallel_scan, flags); + TableScanDesc *desc = GetHeapamTableAmRoutine()->scan_begin(rel, snapshot, nkeys, key, parallel_scan, flags); vertex_desc->desc[0] = desc; vertex_desc->isIndex[0] = false; @@ -152,12 +141,16 @@ TableScanDesc vertex_scan_begin(Relation relation, Snapshot snapshot, int nkeys, void vertex_scan_end(TableScanDesc sscan) { VertexScanDescData *vertex_desc = sscan; - RelationDecrementReferenceCount( ((HeapScanDesc)vertex_desc->desc[0])->rs_base.rs_rd); + RelationDecrementReferenceCount( ((HeapScanDesc)vertex_desc->desc[0])->rs_base.rs_rd); - if (vertex_desc->isIndex[0]) + if (vertex_desc->isIndex[0]) { + index_close(((IndexScanDesc)vertex_desc->desc[0])->indexRelation, NoLock); index_endscan(vertex_desc->desc[0]); - else + + } else { + //RelationDecrementReferenceCount( ((HeapScanDesc)vertex_desc->desc[0])->rs_base.rs_rd); GetHeapamTableAmRoutine()->scan_end(vertex_desc->desc[0]); + } } /* @@ -169,7 +162,6 @@ void vertex_scan_rescan(TableScanDesc scan, struct ScanKeyData *key, bool allow_sync, bool allow_pagemode) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("vertex_scan_rescan not implemented"))); - } /* diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index a91f05b8..23600362 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -693,9 +693,9 @@ static void create_hash_idx(char *graph_name, char *label_name, start_id_elem->ordering = SORTBY_DEFAULT; start_id_elem->nulls_ordering = SORTBY_NULLS_DEFAULT; - index_stmt->idxname = make_hash_idx_alias(rel_name);; + index_stmt->idxname = make_hash_idx_alias(rel_name); index_stmt->relation = makeRangeVar(schema_name, rel_name, -1); - index_stmt->accessMethod = "hash"; + index_stmt->accessMethod = "btree"; index_stmt->tableSpace = NULL; index_stmt->indexParams = list_make1(start_id_elem); index_stmt->indexIncludingParams = NIL; @@ -717,7 +717,6 @@ static void create_hash_idx(char *graph_name, char *label_name, index_stmt->if_not_exists = false; index_stmt->reset_default_tblspc = false; - wrapper = makeNode(PlannedStmt); wrapper->commandType = CMD_UTILITY; wrapper->canSetTag = false; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 87d868e1..78a48ced 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1048,6 +1048,35 @@ static ParseNamespaceItem *add_srf_to_query(cypher_parsestate *cpstate, Node *n, +static ParseNamespaceItem *add_srf_to_query2(cypher_parsestate *cpstate, Node *n, char *var_name) { + RangeTblEntry *rte = NULL; + RangeTblRef *rtr; + List *namespace = NULL; + int rtindex; + ParseState *pstate = (ParseState *)cpstate; + + Alias *alias = make_alias(var_name, list_make2(makeString("edges"), makeString("endid"))); + //Alias *alias = make_alias(var_name, NIL); + RangeFunction *rf = make_range_function(n, alias, true, false, false); + + + rtr = transform_srf_function(cpstate, rf, &rte, &rtindex, &namespace); + Assert(rtr != NULL); + + checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + + setNamespaceLateralState(namespace, true, true); + //((ParseNamespaceItem *)lfirst(list_head(namespace)))->p_names = alias;//list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties")); + pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); + pstate->p_namespace = list_concat(pstate->p_namespace, namespace); + + setNamespaceLateralState(pstate->p_namespace, true, true); + + return lfirst(list_head(namespace)); +} + + + // transform a function call appearing in FROM static ParseNamespaceItem *transformRangeFunction(cypher_parsestate *cpstate, RangeFunction *r) { ParseState *pstate = NULL; @@ -1314,6 +1343,41 @@ add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship return add_srf_to_query(cpstate, fc, edge->name); } + +static ParseNamespaceItem * +add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi) +{ + ParseState *pstate = (ParseState *)cpstate; + + edge->has_variable = false; + if (edge->name) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH variable edge variables are not supported"))); + else + edge->name = get_next_default_alias(cpstate); + + bool is_default_label = true; + if (edge->label) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH labels are not supported"))); + else + edge->label = AG_DEFAULT_LABEL_EDGE; + + Node *id_field = scanNSItemForColumn(cpstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + + A_Indices *idx= edge->varlen; + + FuncCall *fc = makeFuncCall( + list_make2(makeString("postgraph"), makeString("variable_edge_search")), + list_make4(make_int_const(cpstate->graph_oid, -1), id_field, idx->lidx, make_null_const(-1)), + COERCE_EXPLICIT_CALL, -1); + + return add_srf_to_query2(cpstate, fc, edge->name); +} + + static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *query, cypher_node *left_vertex, cypher_relationship *edge, cypher_node *right_vertex) { ParseState *pstate = (ParseState *)cpstate; @@ -1349,37 +1413,47 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que } - // id field - query->targetList = lappend(query->targetList, - makeTargetEntry( - scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_ID, -1), - pstate->p_next_resno++, - make_id_alias(edge->name), - false)); + if (edge->varlen) { + // end id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), + pstate->p_next_resno++, + make_endid_alias(edge->name), + false)); + } else { + // id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_ID, -1), + pstate->p_next_resno++, + make_id_alias(edge->name), + false)); - // start id field - query->targetList = lappend(query->targetList, - makeTargetEntry( - scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_START_ID, -1), - pstate->p_next_resno++, - make_startid_alias(edge->name), - false)); + // start id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_START_ID, -1), + pstate->p_next_resno++, + make_startid_alias(edge->name), + false)); - // end id field - query->targetList = lappend(query->targetList, - makeTargetEntry( - scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), - pstate->p_next_resno++, - make_endid_alias(edge->name), - false)); + // end id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), + pstate->p_next_resno++, + make_endid_alias(edge->name), + false)); - // properties field - query->targetList = lappend(query->targetList, - makeTargetEntry( - scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1), - pstate->p_next_resno++, - make_property_alias(edge->name), - false)); + // properties field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1), + pstate->p_next_resno++, + make_property_alias(edge->name), + false)); + } // id field if (edge->has_variable) @@ -1483,8 +1557,11 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li // edge FROM item cypher_relationship *edge = lsecond(path->path); - edge->pnsi = add_edge_to_query(cpstate, query, edge, node->pnsi); - + if (edge->varlen) { + edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, node->pnsi); + } else { + edge->pnsi = add_edge_to_query(cpstate, query, edge, node->pnsi); + } // right vertex FROM item node = lthird(path->path); @@ -1496,6 +1573,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li (cypher_node *)linitial(path->path), (cypher_relationship *)lsecond(path->path), (cypher_node *)lthird(path->path)); + } else if( ((cypher_relationship *)lsecond(path->path))->dir == CYPHER_REL_DIR_LEFT) { ListCell *path_cell; diff --git a/src/backend/utils/adt/edge.c b/src/backend/utils/adt/edge.c index f9dc18ad..86342dd9 100644 --- a/src/backend/utils/adt/edge.c +++ b/src/backend/utils/adt/edge.c @@ -414,7 +414,8 @@ static Datum get_vertex(Oid graph_oid, int64 graphid) tupdesc = RelationGetDescr(graph_vertex_label); - properties = column_get_datum(tupdesc, tuple, 1, "properties", GTYPEOID, true); + bool isnull; + properties = heap_getattr(tuple, 2, tupdesc, &isnull); table_endscan(scan_desc); table_close(graph_vertex_label, ShareLock); diff --git a/src/backend/utils/adt/gtype.c b/src/backend/utils/adt/gtype.c index e2191d65..d3040a8f 100644 --- a/src/backend/utils/adt/gtype.c +++ b/src/backend/utils/adt/gtype.c @@ -2020,53 +2020,6 @@ Datum gtype_btree_cmp(PG_FUNCTION_ARGS) PG_RETURN_INT16(compare_gtype_containers_orderability(>ype_lhs->root, >ype_rhs->root)); } -/* - * Helper function to return the Datum value of a column (attribute) in a heap - * tuple (row) given the column number (starting from 0), attribute name, typid, - * and whether it can be null. The function is designed to extract and validate - * that the data (attribute) is what is expected. The function will error on any - * issues. - */ -Datum column_get_datum(TupleDesc tupdesc, HeapTuple tuple, int column, - const char *attname, Oid typid, bool isnull) -{ - Form_pg_attribute att; - HeapTupleHeader hth; - HeapTupleData tmptup, *htd; - Datum result; - bool _isnull = true; - - // build the heap tuple data - hth = tuple->t_data; - tmptup.t_len = HeapTupleHeaderGetDatumLength(hth); - tmptup.t_data = hth; - htd = &tmptup; - - // get the description for the column from the tuple descriptor - att = TupleDescAttr(tupdesc, column); - // get the datum (attribute) for that column - result = heap_getattr(htd, column + 1, tupdesc, &_isnull); - // verify that the attribute typid is as expected - if (att->atttypid != typid) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid attribute typid. Expected %d, found %d", typid, - att->atttypid))); - // verify that the attribute name is as expected - if (strcmp(att->attname.data, attname) != 0) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid attribute name. Expected %s, found %s", - attname, att->attname.data))); - // verify that if it is null, it is allowed to be null - if (isnull == false && _isnull == true) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Attribute was found to be null when null is not allowed."))); - - return result; -} - PG_FUNCTION_INFO_V1(gtype_head); Datum gtype_head(PG_FUNCTION_ARGS) @@ -2682,88 +2635,30 @@ Datum gtype_collect_aggfinalfn(PG_FUNCTION_ARGS) { PG_RETURN_POINTER(gtype_value_to_gtype(castate->res)); } -/* - * Extract an gtype_value from an gtype and optionally verify that it is of - * the correct type. It will always complain if the passed argument is not a - * scalar. - * - * Optionally, the function will throw an error, stating the calling function - * name, for invalid values - including AGTV_NULL - * - * Note: This only works for scalars wrapped in an array container, not - * in objects. - */ -gtype_value *get_gtype_value(char *funcname, gtype *agt_arg, - enum gtype_value_type type, bool error) -{ - gtype_value *agtv_value = NULL; - - // we need these - Assert(funcname != NULL); - Assert(agt_arg != NULL); - - // error if the argument is not a scalar - if (!GTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("%s: gtype argument must be a scalar", funcname))); - - // is it AGTV_NULL? - if (error && is_gtype_null(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("%s: gtype argument must not be AGTV_NULL", funcname))); - - // get the gtype value - agtv_value = get_ith_gtype_value_from_container(&agt_arg->root, 0); - - // is it the correct type? - if (error && agtv_value->type != type) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("%s: gtype argument of wrong type", funcname))); - - return agtv_value; -} - static gtype_iterator *get_next_object_key(gtype_iterator *it, gtype_container *agtc, gtype_value *key) { gtype_iterator_token itok; gtype_value tmp; - // verify input params - Assert(agtc != NULL); - Assert(key != NULL); - - // check to see if the container is empty if (GTYPE_CONTAINER_SIZE(agtc) == 0) return NULL; - // if the passed iterator is NULL, this is the first time, create it if (it == NULL) { - // initial the iterator it = gtype_iterator_init(agtc); - // get the first token itok = gtype_iterator_next(&it, &tmp, false); - // it should be WGT_BEGIN_OBJECT Assert(itok == WGT_BEGIN_OBJECT); } - // the next token should be a key or the end of the object itok = gtype_iterator_next(&it, &tmp, false); Assert(itok == WGT_KEY || WGT_END_OBJECT); - // if this is the end of the object return NULL if (itok == WGT_END_OBJECT) return NULL; - // this should be the key, copy it if (itok == WGT_KEY) memcpy(key, &tmp, sizeof(gtype_value)); - /* - * The next token should be a value but, it could be a begin tokens for - * arrays or objects. For those we just return NULL to ignore them. - */ itok = gtype_iterator_next(&it, &tmp, true); Assert(itok == WGT_VALUE); - // return the iterator return it; } @@ -2781,12 +2676,8 @@ Datum vertex_keys(PG_FUNCTION_ARGS) { while ((it = get_next_object_key(it, &agt_arg->root, &obj_key))) agtv_result = push_gtype_value(&parse_state, WGT_ELEM, &obj_key); - // push the end of the array agtv_result = push_gtype_value(&parse_state, WGT_END_ARRAY, NULL); - Assert(agtv_result != NULL); - Assert(agtv_result->type == AGTV_ARRAY); - PG_RETURN_POINTER(gtype_value_to_gtype(agtv_result)); } @@ -2807,7 +2698,7 @@ Datum edge_keys(PG_FUNCTION_ARGS) while ((it = get_next_object_key(it, &agt_arg->root, &obj_key))) agtv_result = push_gtype_value(&parse_state, WGT_ELEM, &obj_key); - // push the end of the array + agtv_result = push_gtype_value(&parse_state, WGT_END_ARRAY, NULL); Assert(agtv_result != NULL); diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 7c7950a0..1fb7c93d 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -30,6 +30,7 @@ #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/timestamp.h" +#include "utils/tuplestore.h" #include "utils/typcache.h" #include "utils/graphid.h" @@ -51,84 +52,55 @@ #include "ltree.h" -static Datum get_vertex(Oid graph_oid, int64 graphid) +static Datum get_vertex(Oid graph_oid, int64 graphid, bool *isnull) { - ScanKeyData scan_keys[1]; - Relation graph_vertex_label; - TableScanDesc scan_desc; - HeapTuple tuple; - TupleDesc tupdesc; - Datum properties, result; - int32 labelid = (graphid >> ENTRY_ID_BITS); - - label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, labelid); - - Snapshot snapshot = GetActiveSnapshot(); + label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, (graphid >> ENTRY_ID_BITS)); + ScanKeyData scan_keys[1]; ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_OIDEQ, Int64GetDatum(graphid)); - - graph_vertex_label = table_open(lcd->relation, ShareLock); - scan_desc = table_beginscan(graph_vertex_label, snapshot, 1, scan_keys); - tuple = heap_getnext(scan_desc, ForwardScanDirection); + Relation rel = table_open(lcd->relation, ShareLock); - if (!HeapTupleIsValid(tuple)) - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); + TableScanDesc scan_desc = table_beginscan(rel, GetActiveSnapshot(), 1, scan_keys); - tupdesc = RelationGetDescr(graph_vertex_label); + HeapTuple tuple; + if (!HeapTupleIsValid(tuple = heap_getnext(scan_desc, ForwardScanDirection))) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); - properties = column_get_datum(tupdesc, tuple, 1, "properties", GTYPEOID, true); + Datum properties = heap_getattr(tuple, 2, RelationGetDescr(rel), isnull); table_endscan(scan_desc); - table_close(graph_vertex_label, ShareLock); + table_close(rel, ShareLock); return properties; } + + PG_FUNCTION_INFO_V1(retrieve_vertex); Datum retrieve_vertex(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; - if (SRF_IS_FIRSTCALL()) - { - MemoryContext oldcontext; - TupleDesc tupdesc; - + if (SRF_IS_FIRSTCALL()) { funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - tupdesc = CreateTemplateTupleDesc(1); - - tupdesc->tdtypeid = RECORDOID; /* not right, but we don't care */ - tupdesc->tdtypmod = -1; - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "properties", - GTYPEOID, -1, 0); + TupleDesc tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, 1, "properties", GTYPEOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); - MemoryContextSwitchTo(oldcontext); - funcctx = SRF_PERCALL_SETUP(); - Datum values[1]; bool nulls[1]; - HeapTuple tuple1; - gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); - graphid id = AG_GETARG_GRAPHID(1); - - bool isnull; - values[0] = get_vertex(graph_oid, id); - nulls[0] = true; - tuple1 = heap_form_tuple(funcctx->tuple_desc, values, nulls); - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple1)); + values[0] = get_vertex(GT_ARG_TO_INT4_DATUM(0), AG_GETARG_GRAPHID(1), &nulls[0]); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); } SRF_RETURN_DONE(funcctx); } -struct edge_search_cxt +typedef struct edge_search_cxt { ScanKey scanKey; TableScanDesc scan_desc; @@ -138,98 +110,391 @@ struct edge_search_cxt PG_FUNCTION_INFO_V1(edge_search); Datum edge_search(PG_FUNCTION_ARGS) -{ - gtype *graph_oid = GT_ARG_TO_INT4_DATUM(0); - graphid id = AG_GETARG_GRAPHID(1); - - //lquery *label = PG_GETARG_LQUERY_P(1); - //bool include_props = PG_GETARG_BOOL(2); +{ FuncCallContext *funcctx; - if (SRF_IS_FIRSTCALL()) - { + if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; - TupleDesc tupdesc; + TupleDesc tupdesc; + graphid id = AG_GETARG_GRAPHID(1); funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); tupdesc = CreateTemplateTupleDesc(4); - tupdesc->tdtypeid = RECORDOID; /* not right, but we don't care */ - tupdesc->tdtypmod = -1; - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "id", - GRAPHIDOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "startid", - GRAPHIDOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "endid", - GRAPHIDOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "properties", - GTYPEOID, -1, 0); + TupleDescInitEntry(tupdesc, 1, "id", GRAPHIDOID, -1, 0); + TupleDescInitEntry(tupdesc, 2, "startid", GRAPHIDOID, -1, 0); + TupleDescInitEntry(tupdesc, 3, "endid", GRAPHIDOID, -1, 0); + TupleDescInitEntry(tupdesc, 4, "properties", GTYPEOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); - struct edge_search_cxt *cxt= palloc(sizeof(struct edge_search_cxt)); - cxt->scanKey = palloc(sizeof(ScanKeyData)); - - int32 labelid = (id >> ENTRY_ID_BITS); - label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, labelid); + edge_search_cxt *cxt= palloc(sizeof(edge_search_cxt)); - Snapshot snapshot = GetActiveSnapshot(); - - ScanKeyInit(cxt->scanKey, 1, HTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); + label_cache_data *lcd = search_label_graph_oid_cache(GT_ARG_TO_INT4_DATUM(0), (id >> ENTRY_ID_BITS)); + + cxt->scanKey = palloc(sizeof(ScanKeyData)); + ScanKeyInit(cxt->scanKey, 1, BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); cxt->rel = table_open(lcd->vertex_adjlist, ShareLock); - cxt->scan_desc = table_beginscan(cxt->rel, snapshot, 1, cxt->scanKey); - //VertexHeapScanDesc vertex_desc = cxt->scan_desc; + cxt->scan_desc = table_beginscan(cxt->rel, GetActiveSnapshot(), 1, cxt->scanKey); + List *empty = NIL; cxt->slot = table_slot_create(cxt->rel, &empty); - //funcctx->tuple_desc = RelationBuildTupleDesc(cxt->rel); + funcctx->user_fctx = cxt; + MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); - struct edge_search_cxt *cxt = (struct edge_search_cxt *) funcctx->user_fctx; - TableAmRoutine *vertex_am = GetVertexHeapamTableAmRoutine(); + edge_search_cxt *cxt = (edge_search_cxt *) funcctx->user_fctx; if (!table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { ReleaseTupleDesc(RelationGetDescr(cxt->rel)); table_endscan(cxt->scan_desc); - table_close(cxt->rel, ShareLock); + /* + List *indexoidlist = RelationGetIndexList(cxt->rel); + if(list_length(indexoidlist) == 1) { + Oid idx = linitial_oid(indexoidlist); + index_close(idx, RowExclusiveLock); + }*/ + + table_close(cxt->rel, ShareLock); + SRF_RETURN_DONE(funcctx); } - Datum values[4]; bool nulls[4]; - HeapTuple tuple1; + VertexHeapScanDesc vertex_desc = cxt->scan_desc; cxt->slot->tts_ops->materialize(cxt->slot); - //cxt->slot->tts_ops->getsomeattrs(cxt->slot, 10000); - //memset(nulls, false, sizeof(nulls)); - //datum = heap_getattr(cxt->slot.tuple, 1, - // RelationGetDescr(cxt->rel), &isnull); + HeapTuple heap_tuple = cxt->slot->tts_ops->get_heap_tuple(cxt->slot); + Relation rel = vertex_desc->desc[0]->rs_rd; + values[0] = heap_getattr(heap_tuple, 1, RelationGetDescr(rel), &nulls[0]); + values[1] = heap_getattr(heap_tuple, 2, RelationGetDescr(rel), &nulls[1]); + values[2] = heap_getattr(heap_tuple, 3, RelationGetDescr(rel), &nulls[2]); + values[3] = heap_getattr(heap_tuple, 4, RelationGetDescr(rel), &nulls[3]); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); +} +// Node for the linked list to handle collisions (chaining) +typedef struct HashSetNode { + graphid key; + struct HashSetNode* next; +} HashSetNode; + +// The main Hash Set structure +typedef struct HashSet { + HashSetNode** buckets; // Array of pointers to HashSetNodes (the buckets) + int capacity; // The total number of buckets + int size; // The current number of elements in the set +} HashSet; + + + +/** + * @brief Creates a new HashSetNode. + * @param key The graphid key for the new node. + * @return A pointer to the newly created HashSetNode. + */ +HashSetNode* createHashSetNode(graphid key) { + HashSetNode* newNode = (HashSetNode*)palloc(sizeof(HashSetNode)); + newNode->key = key; + newNode->next = NULL; + return newNode; +} + +/** + * @brief Creates and initializes a new HashSet. + * @param capacity The initial number of buckets for the hash set. + * @return A pointer to the newly created HashSet, or NULL if capacity is invalid. + */ +HashSet* createHashSet(int capacity) { + HashSet* set = (HashSet*)palloc(sizeof(HashSet)); + + set->capacity = capacity; + set->size = 0; + set->buckets = (HashSetNode**)palloc0(capacity * sizeof(HashSetNode*)); + + return set; +} + +/** + * @brief A simple hash function to map a key to a bucket index. + * @param key The key to hash. + * @param capacity The capacity of the hash set. + * @return The calculated index for the key. + */ +int hashFunction(graphid key, int capacity) { + return key % capacity; +} + +/** + * @brief Inserts a key into the hash set. + * @param set A pointer to the HashSet. + * @param key The key to insert. + */ +void insert(HashSet* set, graphid key) { + if (set == NULL) return; + + int index = hashFunction(key, set->capacity); + HashSetNode* currentNode = set->buckets[index]; + + while (currentNode != NULL) { + if (currentNode->key == key) { + return; + } + currentNode = currentNode->next; + } + + HashSetNode* newNode = createHashSetNode(key); + newNode->next = set->buckets[index]; + set->buckets[index] = newNode; + set->size++; +} + +/** + * @brief Checks if a key exists in the hash set. + * @param set A pointer to the HashSet. + * @param key The key to search for. + * @return true if the key is found, false otherwise. + */ +bool contains(HashSet* set, graphid key) { + if (set == NULL) return false; + + int index = hashFunction(key, set->capacity); + HashSetNode* currentNode = set->buckets[index]; + + while (currentNode != NULL) { + if (currentNode->key == key) { + return true; + } + currentNode = currentNode->next; + } + + return false; +} + +/** + * @brief Removes a key from the hash set. + * @param set A pointer to the HashSet. + * @param key The key to remove. + * @return true if the key was found and removed, false otherwise. + */ +bool removeElement(HashSet* set, graphid key) { + if (set == NULL) return false; + + int index = hashFunction(key, set->capacity); + HashSetNode* currentNode = set->buckets[index]; + HashSetNode* prevNode = NULL; + + while (currentNode != NULL) { + if (currentNode->key == key) { + if (prevNode == NULL) { + set->buckets[index] = currentNode->next; + } else { + prevNode->next = currentNode->next; + } + // Use the environment-specific free function + pfree(currentNode); + set->size--; + return true; + } + prevNode = currentNode; + currentNode = currentNode->next; + } + + return false; +} + +/** + * @brief Frees all memory associated with the hash set. + * @param set A pointer to the HashSet to destroy. + */ +void destroyHashSet(HashSet* set) { + if (set == NULL) return; + + for (int i = 0; i < set->capacity; i++) { + HashSetNode* currentNode = set->buckets[i]; + while (currentNode != NULL) { + HashSetNode* temp = currentNode; + currentNode = currentNode->next; + // Use the environment-specific free function + pfree(temp); + } + } + pfree(set->buckets); + pfree(set); +} + + +typedef struct variable_edge_stack +{ + graphid *array; + int top; + int capacity; +} variable_edge_stack; + +typedef struct graph_stack_count { + int count; + graphid id; +} graph_stack_count; + +typedef struct variable_edge_search_cxt +{ + ScanKey scanKey; + TableScanDesc scan_desc; + Relation rel; + TupleTableSlot *slot; + int min; + Oid graph_oid; + HashSet *hashSet; + variable_edge_stack *stack; + graph_stack_count *current_path; + int current_path_length; +} variable_edge_search_cxt; + +static void +variable_edge_stack_push(variable_edge_stack *stack, graphid value) +{ + + if (stack->top >= stack->capacity) + { + int new_capacity = stack->capacity * 2; + stack->array = repalloc(stack->array, sizeof(graphid) * (new_capacity + 1)); + stack->capacity = new_capacity; + } + stack->array[stack->top++] = value; + // ereport(WARNING, (errmsg("pushing to stack %i, %i, %lu, %lu", stack->top, stack->capacity, value, stack->array[stack->top-1]))); +} + +static bool +variable_edge_stack_pop(variable_edge_stack *stack, graphid *result) +{ + // ereport(WARNING, (errmsg("popping from stack %i, %i ", stack->top, stack->capacity))); + if (stack->top == 0) + return false; + *result = stack->array[--stack->top]; + return true; +} + +static void +scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) +{ + label_cache_data *lcd = search_label_graph_oid_cache(cxt->graph_oid, (id >> ENTRY_ID_BITS)); + //ereport(WARNING, (errmsg("here"))); + cxt->scanKey = palloc(sizeof(ScanKeyData)); + ScanKeyInit(cxt->scanKey, 1, BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); + + cxt->rel = table_open(lcd->vertex_adjlist, ShareLock); + cxt->scan_desc = table_beginscan(cxt->rel, GetActiveSnapshot(), 1, cxt->scanKey); + List *empty = NIL; + cxt->slot = table_slot_create(cxt->rel, &empty); + + while (table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { + cxt->slot->tts_ops->materialize(cxt->slot); + + bool isnull; + graphid new_id = DATUM_GET_GRAPHID( + heap_getattr( + cxt->slot->tts_ops->get_heap_tuple(cxt->slot), + 3, + RelationGetDescr(cxt->scan_desc->rs_rd), + &isnull)); + + if (!contains(cxt->hashSet, new_id)) { + cxt->current_path[cxt->current_path_length].count++; + variable_edge_stack_push(cxt->stack, new_id); + } + } + ReleaseTupleDesc(RelationGetDescr(cxt->rel)); + table_endscan(cxt->scan_desc); + table_close(cxt->rel, ShareLock); +} + +PG_FUNCTION_INFO_V1(variable_edge_search); +Datum variable_edge_search(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + TupleDesc tupdesc; + graphid id = AG_GETARG_GRAPHID(1); + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(2); + TupleDescInitEntry(tupdesc, 1, "edges", VARIABLEEDGEOID, -1, 0); + TupleDescInitEntry(tupdesc, 2, "endid", GRAPHIDOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + variable_edge_search_cxt *cxt= palloc(sizeof(variable_edge_search_cxt)); + cxt->graph_oid = GT_ARG_TO_INT4_DATUM(0); + cxt->min = GT_ARG_TO_INT4_DATUM(2); + + cxt->stack = palloc0(sizeof(variable_edge_stack)); + cxt->stack->array = palloc0(sizeof(graphid) * (1024)); + cxt->stack->top = 0; + cxt->stack->capacity = 1024; + + cxt->current_path = palloc(sizeof(graph_stack_count) * (cxt->min + 1)); + cxt->current_path_length = 0; + + cxt->hashSet = createHashSet(1024); + insert(cxt->hashSet, GRAPHID_GET_DATUM(id)); + scan_and_push_neighbors(cxt, id); + funcctx->user_fctx = cxt; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + + variable_edge_search_cxt *cxt = (variable_edge_search_cxt *) funcctx->user_fctx; + + while (cxt->hashSet->size > 0) { + graphid id; + + + if(!variable_edge_stack_pop(cxt->stack, &id)){ + //destroyHashSet(cxt->hashSet); + SRF_RETURN_DONE(funcctx); + } +/* + if (cxt->current_path[++cxt->current_path_length].count == 0) { + cxt->current_path_length--; + variable_edge_stack_pop(cxt->stack, &cxt->current_path[cxt->current_path_length--]); + } +*/ + cxt->current_path[++cxt->current_path_length].count = 0; + cxt->current_path[cxt->current_path_length].count = id; + + + insert(cxt->hashSet, GRAPHID_GET_DATUM(id)); + + if (cxt->hashSet->size == cxt->min + 1) { + Datum values[2]; + bool nulls[2]; + values[0] = NULL; + nulls[0] = true; + values[1] = GRAPHID_GET_DATUM(id); + nulls[1] = false; + removeElement(cxt->hashSet, id); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); + } else { + scan_and_push_neighbors(cxt, id); + } + + if (cxt->current_path[++cxt->current_path_length].count == 0) { + variable_edge_stack_pop(cxt->stack, &cxt->current_path[cxt->current_path_length--]); + removeElement(cxt->hashSet, id); + } + } + //destroyHashSet(cxt->hashSet); + SRF_RETURN_DONE(funcctx); + - VertexHeapScanDesc vertex_desc = cxt->scan_desc; - //vertex_desc->desc[0] - bool isnull; - //slot_getattr(cxt->slot, i, &isnull); - values[0] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 1, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); - nulls[0] = isnull; - //ereport(WARNING, errmsg("value %lu", DatumGetInt64(values[0]))); - values[1] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 2, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); - nulls[1] = isnull; - //ereport(WARNING, errmsg("value %lu", DatumGetInt64(values[1]))); - values[2] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 3, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); - nulls[2] = isnull; - //ereport(WARNING, errmsg("value %lu", DatumGetInt64(values[2]))); - values[3] = heap_getattr(cxt->slot->tts_ops->get_heap_tuple(cxt->slot), 4, RelationGetDescr(vertex_desc->desc[0]->rs_rd), &isnull); - nulls[3] = isnull; - tuple1 = heap_form_tuple(funcctx->tuple_desc, values, nulls); - //cxt->slot->tts_ops->get_heap_tuple(cxt->slot); - - //tuple1 = cxt->slot->tts_ops->copy_heap_tuple(cxt->slot); - //tuple1->t_tableOid = RelationGetRelid(cxt->rel); - //SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(cxt->slot->tts_ops->get_heap_tuple(cxt->slot))); - //SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(cxt->slot->tts_ops->get_heap_tuple(cxt->slot))); - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple1)); } diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index 21fee0f6..fcc87b39 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -211,8 +211,8 @@ typedef struct cypher_relationship ExtensibleNode extensible; char *name; char *label; - Node *props; // map or parameter - Node *varlen; // variable length relationships (A_Indices) + Node *props; + Node *varlen; cypher_rel_dir dir; bool has_variable; ParseNamespaceItem *pnsi; diff --git a/src/include/utils/gtype.h b/src/include/utils/gtype.h index 60925ebc..964e2c35 100644 --- a/src/include/utils/gtype.h +++ b/src/include/utils/gtype.h @@ -658,9 +658,6 @@ char *gtype_value_type_to_string(enum gtype_value_type type); bool is_decimal_needed(char *numstr); int compare_gtype_scalar_values(gtype_value *a, gtype_value *b); gtype_value *alter_property_value(gtype *properties, char *var_name, gtype *new_v, bool remove_property); -gtype *get_one_gtype_from_variadic_args(FunctionCallInfo fcinfo, int variadic_offset, int expected_nargs); -Datum column_get_datum(TupleDesc tupdesc, HeapTuple tuple, int column, const char *attname, Oid typid, bool isnull); -gtype_value *get_gtype_value(char *funcname, gtype *agt_arg, enum gtype_value_type type, bool error); bool is_gtype_null(gtype *agt_arg); gtype_value *string_to_gtype_value(char *s); void add_gtype(Datum val, bool is_null, gtype_in_state *result, Oid val_type, bool key_scalar); From cc5f378fa7bfbc626548c586db003dd1663d72e9 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Wed, 20 Aug 2025 19:51:57 -0700 Subject: [PATCH 38/56] Multilength paths and edge uniqueness (non-vle) support --- regress/sql/cypher_create.sql | 9 + src/backend/parser/cypher_clause.c | 409 +++++++++++++++++++++++++---- src/backend/utils/edge_searching.c | 7 +- 3 files changed, 366 insertions(+), 59 deletions(-) diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 2ec1bfbb..535ef4aa 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -77,6 +77,15 @@ CREATE (); CREATE () RETURN 1 as a; CREATE ()-[]->()-[]->(); +MATCH ()<-[]-()<-[]-() RETURN 1; +MATCH ()-[]->()-[]->() RETURN 1; +EXPLAIN MATCH ()-[]->()<-[]-() RETURN 1; +EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; +MATCH ()-[]->()<-[]-() RETURN 1; + +EXPLAIN MATCH (:test)-[]->()<-[]-(:test) RETURN 1; +EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; +MATCH (:test)-[]->()<-[]-(:test) RETURN 1; SELECT * FROM cypher_create._adj__ag_label_vertex; MATCH (a) RETURN a; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 78a48ced..6e9a9b1b 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -543,6 +543,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause target->relid = lcd->relation; target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); + //target->prop_attr_num = InvalidAttrNumber; ccp->target_nodes = lappend(ccp->target_nodes, target); @@ -1344,6 +1345,43 @@ add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship } +static ParseNamespaceItem * +add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *prev_pnsi, char *prev_name) +{ + ParseState *pstate = (ParseState *)cpstate; + + edge->has_variable = false; + if (edge->name) + edge->has_variable = true; + else + edge->name = get_next_default_alias(cpstate); + + bool is_default_label = true; + if (edge->label) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH labels are not supported"))); + else + edge->label = AG_DEFAULT_LABEL_EDGE; + + Node *id_field; + if(edge->dir != CYPHER_REL_DIR_NONE) + id_field = scanNSItemForColumn(cpstate, prev_pnsi, 0, "endid", -1); + else + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH edge can't be bi directional"))); + + + + FuncCall *fc = makeFuncCall( + list_make2(makeString("postgraph"), makeString("edge_search")), + list_make4(make_int_const(cpstate->graph_oid, -1), id_field, make_null_const(-1), make_null_const(-1)), + COERCE_EXPLICIT_CALL, -1); + + return add_srf_to_query(cpstate, fc, edge->name); +} + + static ParseNamespaceItem * add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi) { @@ -1496,6 +1534,218 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que } +static void add_new_fields_to_target_list(cypher_parsestate *cpstate, Query *query, + cypher_node *left_vertex, cypher_relationship *edge, cypher_node *right_vertex) { + ParseState *pstate = (ParseState *)cpstate; + + if (edge->varlen) { + // end id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), + pstate->p_next_resno++, + make_endid_alias(edge->name), + false)); + } else { + // id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_ID, -1), + pstate->p_next_resno++, + make_id_alias(edge->name), + false)); + + // start id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_START_ID, -1), + pstate->p_next_resno++, + make_startid_alias(edge->name), + false)); + + // end id field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), + pstate->p_next_resno++, + make_endid_alias(edge->name), + false)); + + // properties field + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1), + pstate->p_next_resno++, + make_property_alias(edge->name), + false)); + } + + // id field + if (edge->has_variable) + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)make_edge_expr(cpstate, edge->pnsi), + pstate->p_next_resno++, + edge->name, + false)); + + // properties field + if (right_vertex->in_join_tree){ + query->targetList = lappend(query->targetList, + makeTargetEntry( + edge->dir == CYPHER_REL_DIR_RIGHT ? + scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1) : + scanNSItemForColumn(pstate, right_vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1), + pstate->p_next_resno++, + make_id_alias(right_vertex->name), + false)); + + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, right_vertex->pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1), + pstate->p_next_resno++, + make_property_alias(right_vertex->name), + false)); + + // Vertex expression + if (right_vertex->has_variable) + query->targetList = lappend(query->targetList, + makeTargetEntry( + edge->dir == CYPHER_REL_DIR_RIGHT ? + (Expr *)make_vertex_expr_with_edge(cpstate, right_vertex->pnsi, edge->pnsi) : + (Expr *)make_vertex_expr(cpstate, right_vertex->pnsi), + pstate->p_next_resno++, + right_vertex->name, + false)); + } + +} +static void +process_three_element_path(cypher_parsestate *cpstate, Query *query, cypher_path *path) +{ + cypher_node *start_vertex; + cypher_node *end_vertex; + cypher_relationship *edge = lsecond(path->path); + + if (edge->dir == CYPHER_REL_DIR_RIGHT) { + start_vertex = linitial(path->path); + end_vertex = lthird(path->path); + } else if (edge->dir == CYPHER_REL_DIR_LEFT) { + start_vertex = lthird(path->path); + end_vertex = linitial(path->path); + } else { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH edge can't be bi directional"))); + } + + start_vertex->pnsi = add_vertex_to_query(cpstate, query, start_vertex); + if (edge->varlen) + edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, start_vertex->pnsi); + else + edge->pnsi = add_edge_to_query(cpstate, query, edge, start_vertex->pnsi); + + end_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, end_vertex, edge->pnsi); + + // SELECT fields + add_all_fields_to_target_list(cpstate, query, linitial(path->path), edge, lthird(path->path)); +} + +static Query * +transform_cypher_clause_as_subquery_match(cypher_parsestate *cpstate, cypher_path *path) { + ParseState *pstate = (ParseState *)cpstate; + ParseExprKind old_expr_kind = pstate->p_expr_kind; + bool lateral = pstate->p_lateral_active; + + + /* + * If this is a WHERE, pass it through and set lateral to true because it + * needs to see what comes before it. + */ + + + cypher_parsestate *sub_cpstate; + Query *query; + ParseState *parent_pstate = (ParseState*)cpstate; + ParseState *sub_pstate; + + sub_cpstate = make_cypher_parsestate(cpstate); + sub_pstate = (ParseState*)sub_cpstate; + + + sub_pstate->p_expr_kind = pstate->p_expr_kind; + + + query = makeNode(Query); + query->commandType = CMD_SELECT; + + process_three_element_path(sub_cpstate, query, path); + + + + markTargetListOrigins(sub_pstate, query->targetList); + + query->rtable = pstate->p_rtable; + query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + + assign_query_collations(sub_cpstate, query); + + + free_cypher_parsestate(sub_cpstate); + + pstate->p_expr_kind = old_expr_kind; + + // if (!alias) + // alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); + + // ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); + + // addNSItemToQuery(pstate, pnsi, true, false, true); + + return query; +} + +typedef struct path_parts { + int start; + int end; + cypher_rel_dir dir; +} path_parts; + +static List *find_path_parts(cypher_path *path) { + cypher_relationship *start = list_nth(path->path,1); + + if (start->dir == CYPHER_REL_DIR_NONE) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH edge can't be bi directional"))); + + path_parts *pp = palloc(sizeof(path_parts)); + pp->start = 1; + pp->dir = start->dir; + + List *parts = list_make1(pp); + int i; + for (i = 3; i < list_length(path->path); i += 2) { + cypher_relationship *rel = list_nth(path->path, i); + + if (rel->dir == CYPHER_REL_DIR_NONE) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH edge can't be bi directional"))); + + if (pp->dir != rel->dir) { + pp->end = i; + + pp = palloc(sizeof(path_parts)); + pp->start = i; + + pp->dir = rel->dir; + parts = lappend(parts, pp); + } else + pp->end = i; + } + pp->end = i; + + return parts; +} + static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc; @@ -1543,72 +1793,125 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li continue; } - if (list_length(path->path) != 3) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH paths only support 3 vertex"))); - - if( ((cypher_relationship *)lsecond(path->path))->dir == CYPHER_REL_DIR_RIGHT) { - ListCell *path_cell; - - // left vertex FROM item - cypher_node *node = linitial(path->path); - node->pnsi = add_vertex_to_query(cpstate, query, node); + + if (list_length(path->path) == 3) { + process_three_element_path(cpstate, query, path); + } else { + List *parts = find_path_parts(path); + ListCell *lc; + cypher_relationship *prev_edge = NULL; + foreach (lc, parts) { + path_parts *pp = (path_parts *) lfirst(lc); + + + if (pp->dir == CYPHER_REL_DIR_RIGHT) { + cypher_node *left_vertex = list_nth(path->path, pp->start - 1); + left_vertex->pnsi = add_vertex_to_query(cpstate, query, left_vertex); + + for (int i = pp->start; i < pp->end; i += 2) { + cypher_node *left_vertex = list_nth(path->path, i - 1); + cypher_relationship *edge = list_nth(path->path, i); + cypher_node *right_vertex = list_nth(path->path, i + 1); + + //left_vertex->pnsi = add_vertex_to_query(cpstate, query, left_vertex); + if (edge->varlen) + edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, left_vertex->pnsi); + else if ( i == pp->start) + edge->pnsi = add_edge_to_query(cpstate, query, edge, left_vertex->pnsi); + else { + cypher_relationship *prev_edge = list_nth(path->path, i - 2); + edge->pnsi = add_edge_to_query_with_prev_edge(cpstate, query, edge, prev_edge->pnsi, make_endid_alias(prev_edge->name)); + } + right_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, right_vertex, edge->pnsi); + + if (i == pp->start && prev_edge) { + ereport(WARNING, errmsg("start quals")); + quals = lappend(quals, + makeSimpleCypherA_Expr(AEXPR_OP, "=", + (Node *)scanNSItemForColumn(cpstate, prev_edge->pnsi, 0, "endid", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "endid", -1), -1)); + } - - // edge FROM item - cypher_relationship *edge = lsecond(path->path); - if (edge->varlen) { - edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, node->pnsi); - } else { - edge->pnsi = add_edge_to_query(cpstate, query, edge, node->pnsi); - } - // right vertex FROM item - node = lthird(path->path); + for (int j = 1; j < i; j += 2) { + cypher_relationship *earlier_edge = list_nth(path->path, j); + if (!earlier_edge->varlen) + quals = lappend(quals, + makeSimpleCypherA_Expr(AEXPR_OP, "<>", + (Node *)scanNSItemForColumn(cpstate, earlier_edge->pnsi, 0, "id", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); + } - node->pnsi = add_vertex_retrieval_to_query(cpstate, query, node, edge->pnsi); + // SELECT fields + if (pp->start == i) + add_all_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); + else + add_new_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); + + prev_edge = edge; + } + } else if (pp->dir == CYPHER_REL_DIR_LEFT) { + cypher_node *right_vertex = list_nth(path->path, pp->end - 1); + right_vertex->pnsi = add_vertex_to_query(cpstate, query, right_vertex); + + for (int i = pp->end; i > pp->start; i -= 2) { + cypher_node *left_vertex = list_nth(path->path, i - 3); + cypher_relationship *edge = list_nth(path->path, i - 2); + cypher_node *right_vertex = list_nth(path->path, i - 1); + + //left_vertex->pnsi = add_vertex_to_query(cpstate, query, left_vertex); + if (edge->varlen) + edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, right_vertex->pnsi); + else if ( i == pp->end) + edge->pnsi = add_edge_to_query(cpstate, query, edge, right_vertex->pnsi); + else { + cypher_relationship *prev_edge = list_nth(path->path, i); + edge->pnsi = add_edge_to_query_with_prev_edge(cpstate, query, edge, prev_edge->pnsi, make_endid_alias(prev_edge->name)); + } + right_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, left_vertex, edge->pnsi); - // SELECT fields - add_all_fields_to_target_list(cpstate, - query, - (cypher_node *)linitial(path->path), - (cypher_relationship *)lsecond(path->path), - (cypher_node *)lthird(path->path)); - - } else if( ((cypher_relationship *)lsecond(path->path))->dir == CYPHER_REL_DIR_LEFT) { - ListCell *path_cell; + if (i == pp->end && prev_edge != NULL) { + quals = lappend(quals, + makeSimpleCypherA_Expr(AEXPR_OP, "=", + (Node *)scanNSItemForColumn(cpstate, prev_edge->pnsi, 0, "endid", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "endid", -1), -1)); + } - // left vertex FROM item - cypher_node *node = lthird(path->path); - node->pnsi = add_vertex_to_query(cpstate, query, node); + for (int p = 1; p < pp->start; p += 2) { + cypher_relationship *prev_edge = list_nth(path->path, p); + quals = lappend(quals, + makeSimpleCypherA_Expr(AEXPR_OP, "<>", + (Node *)scanNSItemForColumn(cpstate, prev_edge->pnsi, 0, "id", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); + } - - // edge FROM item - cypher_relationship *edge = lsecond(path->path); - edge->pnsi = add_edge_to_query(cpstate, query, edge, node->pnsi); + // 2. All previous edges in this path_part (pp) + for (int k = pp->end; k > i; k -= 2) { + cypher_relationship *prev_edge = list_nth(path->path, k - 2); + quals = lappend(quals, + makeSimpleCypherA_Expr(AEXPR_OP, "<>", + (Node *)scanNSItemForColumn(cpstate, prev_edge->pnsi, 0, "id", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); + } - // right vertex FROM item - node = linitial(path->path); + // SELECT fields + if (pp->start == i) + add_all_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); + else + add_new_fields_to_target_list(cpstate, query, right_vertex, edge, left_vertex); - node->pnsi = add_vertex_retrieval_to_query(cpstate, query, node, edge->pnsi); + prev_edge = edge; + } + } - // SELECT fields - add_all_fields_to_target_list(cpstate, - query, - (cypher_node *)linitial(path->path), - (cypher_relationship *)lsecond(path->path), - (cypher_node *)lthird(path->path)); - } else { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH edge can't be bi directional"))); + } } - - - } - + } + // AND the quals for each path together Expr *q = NULL; if (quals != NIL) { + ereport(WARNING, errmsg("quals")); q = makeBoolExpr(AND_EXPR, quals, -1); expr = (Expr *)sql_transform_expr(&cpstate->pstate, (Node *)q, EXPR_KIND_WHERE); } diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 1fb7c93d..eec3efcf 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -463,12 +463,7 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) //destroyHashSet(cxt->hashSet); SRF_RETURN_DONE(funcctx); } -/* - if (cxt->current_path[++cxt->current_path_length].count == 0) { - cxt->current_path_length--; - variable_edge_stack_pop(cxt->stack, &cxt->current_path[cxt->current_path_length--]); - } -*/ + cxt->current_path[++cxt->current_path_length].count = 0; cxt->current_path[cxt->current_path_length].count = id; From 433746376803c419b5258412ab3fa1fb9446167e Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Fri, 22 Aug 2025 02:20:14 -0700 Subject: [PATCH 39/56] Update --- Makefile | 1 + postgraph--0.1.0.sql | 63 +- regress/sql/cypher_create.sql | 47 +- src/backend/parser/cypher_analyze.c | 1 - src/backend/parser/cypher_clause.c | 1152 ++++++++---------------- src/backend/parser/cypher_expr.c | 2 +- src/backend/parser/cypher_parse_node.c | 37 - src/backend/utils/adt/hashset.c | 303 +++++++ src/backend/utils/edge_searching.c | 209 ++--- src/include/nodes/cypher_nodes.h | 1 + src/include/parser/cypher_parse_node.h | 11 +- src/include/utils/gtype.h | 1 - src/include/utils/hashset.h | 73 ++ 13 files changed, 926 insertions(+), 975 deletions(-) create mode 100644 src/backend/utils/adt/hashset.c create mode 100644 src/include/utils/hashset.h diff --git a/Makefile b/Makefile index 97f87abd..5e2a7500 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,7 @@ OBJS = src/backend/postgraph.o \ src/backend/utils/adt/gtype_funcs.o \ src/backend/utils/adt/gtype_typecasting.o \ src/backend/utils/adt/gtype_util.o \ + src/backend/utils/adt/hashset.o \ src/backend/utils/adt/cypher_funcs.o \ src/backend/utils/adt/edge.o \ src/backend/utils/adt/graphid.o \ diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 31db535a..1e092745 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -6818,10 +6818,71 @@ CREATE AGGREGATE regr_r2(gtype, gtype) ( parallel = SAFE ); +CREATE TYPE hashset; +CREATE FUNCTION hashset_in(cstring) +RETURNS hashset +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION hashset_out(hashset) +RETURNS cstring +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- binary I/O functions +CREATE FUNCTION hashset_send(hashset) +RETURNS bytea +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION hashset_recv(internal) +RETURNS hashset +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE TYPE hashset ( + INPUT = hashset_in, + OUTPUT = hashset_out, + SEND = hashset_send, + RECEIVE = hashset_recv, + LIKE = jsonb, + STORAGE = extended +); + + +CREATE FUNCTION hashset_contains(hashset, graphid) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR @> ( + FUNCTION = hashset_contains, + LEFTARG = hashset, + RIGHTARG = graphid--, + --COMMUTATOR = <@, + --NEGATOR = <> , + --RESTRICT = edge_contains_restrict, + --JOIN = neqjoinsel +); CREATE FUNCTION variable_edge_search(graph_oid gtype, id graphid, min gtype, max gtype) -RETURNS TABLE (edges variable_edge, endid graphid) +RETURNS TABLE (edges variable_edge, endid graphid, hset hashset) CALLED ON NULL INPUT STABLE PARALLEL SAFE diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 535ef4aa..9228e776 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 PostGraphDB + * Copyright (C) 2023-2025 PostGraphDB * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -76,9 +76,50 @@ CREATE (); CREATE () RETURN 1 as a; +CREATE ()-[]->()-[]->(); CREATE ()-[]->()-[]->(); MATCH ()<-[]-()<-[]-() RETURN 1; MATCH ()-[]->()-[]->() RETURN 1; + + +EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; +MATCH (q)-[]->(q)-[]->() RETURN 1; + + + +EXPLAIN ANALYZE +MATCH (q) +MATCH (q)-[]->() +RETURN q; + +MATCH (q) +MATCH (q)-[]->() +RETURN q; + +EXPLAIN ANALYZE +MATCH (q) +MATCH ()-[]->(q) +RETURN q; + +MATCH (q) +MATCH ()-[]->(q) +RETURN q; + +EXPLAIN ANALYZE +MATCH (q) +MATCH (b)-[]->() +RETURN q; + +MATCH (q) +MATCH (b)-[]->() +RETURN q; + +EXPLAIN MATCH (q)-[]->()<-[]-(q) RETURN 1; +MATCH (q)-[]->()<-[]-(q) RETURN 1; + +EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; +MATCH (q)<-[]-(q) RETURN 1; + EXPLAIN MATCH ()-[]->()<-[]-() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; MATCH ()-[]->()<-[]-() RETURN 1; @@ -87,6 +128,10 @@ EXPLAIN MATCH (:test)-[]->()<-[]-(:test) RETURN 1; EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; MATCH (:test)-[]->()<-[]-(:test) RETURN 1; + +MATCH ()-[*1]->()-[]->() RETURN 1; +EXPLAIN ANALYZE MATCH ()-[*1]->()-[]->() RETURN 1; + SELECT * FROM cypher_create._adj__ag_label_vertex; MATCH (a) RETURN a; --SELECT * FROM cypher_create.idx_hash__adj__ag_label_vertex; diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index 25eb44d9..59a3458e 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -1340,7 +1340,6 @@ analyze_cypher(List *stmt, ParseState *parent_pstate, const char *query_str, int // we don't want functions that go up the pstate parent chain to access the * original SQL query pstate. pstate->parentParseState = NULL; - // override p_sourcetext with query_str to make parser_errposition() work correctly with errpos_ecb() pstate->p_sourcetext = query_str; cpstate->graph_name = graph_name; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 6e9a9b1b..f3c14b65 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1,35 +1,14 @@ #include "postgraph.h" -#include "access/nbtree.h" -#include "access/sysattr.h" #include "access/heapam.h" -#include "catalog/pg_amproc.h" -#include "catalog/pg_type_d.h" -#include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/nodes.h" #include "nodes/parsenodes.h" #include "nodes/pg_list.h" -#include "nodes/primnodes.h" -#include "optimizer/optimizer.h" -#include "parser/parse_clause.h" -#include "parser/parse_coerce.h" -#include "parser/parse_collate.h" -#include "parser/cypher_analyze.h" -#include "parser/parse_expr.h" -#include "parser/parse_func.h" -#include "parser/parse_node.h" -#include "parser/parse_oper.h" -#include "parser/parse_relation.h" -#include "parser/parse_target.h" #include "parser/parsetree.h" -#include "rewrite/rewriteHandler.h" -#include "utils/catcache.h" -#include "utils/typcache.h" -#include "utils/lsyscache.h" -#include "utils/rel.h" +#include "parser/cypher_analyze.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "commands/label_commands.h" @@ -40,22 +19,16 @@ #include "parser/cypher_item.h" #include "parser/cypher_parse_agg.h" #include "parser/cypher_parse_node.h" -#include "parser/cypher_transform_entity.h" #include "utils/ag_cache.h" #include "utils/ag_func.h" #include "utils/gtype.h" #include "utils/graphid.h" #include "utils/vertex.h" #include "utils/edge.h" -#include "utils/traversal.h" -#include "utils/variable_edge.h" - - typedef Query *(*transform_method)(cypher_parsestate *cpstate, cypher_clause *clause); // projection - static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, ParseExprKind expr_kind); static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, ParseExprKind expr_kind, const char *construct_name); static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause); @@ -63,10 +36,14 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, tra static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause); static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where); static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause); - +static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); +static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); +static Node *make_vertex_expr_with_edge(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi, ParseNamespaceItem *edge_pnsi); +static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, ParseNamespaceItem *r_pnsi, List **res_colnames, List **res_colvars); +static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, List **namespace) ; // create clause static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause); -static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, ParseNamespaceItem *r_pnsi, List **res_colnames, List **res_colvars); + // transform #define PREV_CYPHER_CLAUSE_ALIAS "_" @@ -76,8 +53,6 @@ static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, ParseNa prev_clause, NULL, add_rte_to_query) static ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, Alias *alias, bool add_rte_to_query); static Query *analyze_cypher_clause(transform_method transform, cypher_clause *clause, cypher_parsestate *parent_cpstate); -static void advance_transform_entities_to_next_clause(List *entities); -static ParseNamespaceItem *get_namespace_item(ParseState *pstate, RangeTblEntry *rte); static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); @@ -86,15 +61,11 @@ static char *make_property_alias(char *var_name); static char *make_startid_alias(char *var_name); static char *make_endid_alias(char *var_name); -static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); -static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); -static Node *make_vertex_expr_with_edge(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi, ParseNamespaceItem *edge_pnsi); +List *transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); -List * -transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); static char *make_vertex_adjlist_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); + char *str = palloc(strlen(var_name) + 8); str[0] = '_'; str[1] = 'a'; @@ -109,6 +80,7 @@ static char *make_vertex_adjlist_alias(char *var_name) { return str; } + /* * transform a cypher_clause */ @@ -134,26 +106,23 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); } - result->querySource = QSRC_ORIGINAL; result->canSetTag = true; return result; } - - - static FuncExpr *make_write_clause_function_placeholder(char *function_name, Node *clause_information) { StringInfo str = makeStringInfo(); - outNode(str, clause_information); - Const *c = makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false); - - Oid func_oid = get_ag_func_oid(function_name, 1, INTERNALOID); - - return makeFuncExpr(func_oid, GTYPEOID, list_make1(c), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + return makeFuncExpr( + get_ag_func_oid(function_name, 1, INTERNALOID), + GTYPEOID, + list_make1(makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); } static char *make_id_alias(char *var_name) { @@ -174,6 +143,7 @@ static char *make_id_alias(char *var_name) { return str; } + static char *make_startid_alias(char *var_name) { char *str = palloc0(strlen(var_name) + 8); @@ -212,7 +182,6 @@ static char *make_endid_alias(char *var_name) { return str; } - static char *make_property_alias(char *var_name) { char *str = palloc0(strlen(var_name) + 8); @@ -233,116 +202,73 @@ static char *make_property_alias(char *var_name) { } static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { - ParseState *pstate = (ParseState *)cpstate; - - Oid func_oid = get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID); - - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - - Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), - ObjectIdGetDatum(cpstate->graph_oid), false, true); - - Node * props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); + return makeFuncExpr( + get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID), + VERTEXOID, + list_make3( + scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1), + makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), + scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); - List *args = list_make3(id, graph_oid_const, props); - - FuncExpr *func_expr = makeFuncExpr(func_oid, VERTEXOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = -1; - - - return (Node *)func_expr; } - static Node *make_vertex_expr_with_edge(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi, ParseNamespaceItem *edge_pnsi) { - ParseState *pstate = (ParseState *)cpstate; - - Oid func_oid = get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID); - - Node *id = scanNSItemForColumn(pstate, edge_pnsi, 0, "endid", -1); - - Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), - ObjectIdGetDatum(cpstate->graph_oid), false, true); - - Node * props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); - - List *args = list_make3(id, graph_oid_const, props); - - FuncExpr *func_expr = makeFuncExpr(func_oid, VERTEXOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = -1; - - - return (Node *)func_expr; + return makeFuncExpr( + get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID), + VERTEXOID, + list_make3( + scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1), + makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), + scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); } - - static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { - ParseState *pstate = (ParseState *)cpstate; - - Oid func_oid = get_ag_func_oid("build_edge", 5, GRAPHIDOID, GRAPHIDOID, GRAPHIDOID, OIDOID, GTYPEOID); - - Node *id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); - - Node *start_id = scanNSItemForColumn(pstate, pnsi, 0, "startid", -1); - - Node *end_id = scanNSItemForColumn(pstate, pnsi, 0, "endid", -1); - - Const *graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), - ObjectIdGetDatum(cpstate->graph_oid), false, true); - - Node *props = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1); - - List *args = list_make5(id, start_id, end_id, graph_oid_const, props); - - FuncExpr *func_expr = makeFuncExpr(func_oid, EDGEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = -1; - - return (Node *)func_expr; + return makeFuncExpr( + get_ag_func_oid("build_edge", 5, GRAPHIDOID, GRAPHIDOID, GRAPHIDOID, OIDOID, GTYPEOID), + EDGEOID, + list_make5( + scanNSItemForColumn(cpstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1), + scanNSItemForColumn(cpstate, pnsi, 0, "startid", -1), + scanNSItemForColumn(cpstate, pnsi, 0, "endid", -1), + makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), + scanNSItemForColumn(cpstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); } - static Node * make_graphid_placeholder(cypher_parsestate *cpstate) { - - // typtypmod, typcollation, typlen, and typbyval of gtype are hard-coded. Const *c = makeConst(GRAPHIDOID, -1, InvalidOid, -1, 0, false, false); c->location = -1; - return (Node *)c; } static Node * make_int_placeholder(cypher_parsestate *cpstate) { - - Datum agt = integer_to_gtype(0); - - // typtypmod, typcollation, typlen, and typbyval of gtype are hard-coded. - Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, agt, false, false); + Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, integer_to_gtype(0), false, false); c->location = -1; - return (Node *)c; } + /* * This function is similar to transformFromClause() that is called with a * single RangeSubselect. */ -static ParseNamespaceItem * -transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, cypher_clause *clause, Alias *alias, bool add_rte_to_query, Query *query) { +static void +transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query) { ParseState *pstate = (ParseState *)cpstate; ParseExprKind old_expr_kind = pstate->p_expr_kind; bool lateral = pstate->p_lateral_active; + ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL), lateral, true); - if (!alias) - alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - - ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); - - /* - * NOTE: skip namespace conflicts check if the rte will be the only - * RangeTblEntry in pstate - */ if (list_length(pstate->p_rtable) > 1) { List *namespace = NULL; int rtindex = 0; @@ -359,56 +285,34 @@ transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, cypher_clause checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); } - //if (add_rte_to_query) - addNSItemToQuery(pstate, pnsi, true, false, true); - - return pnsi; + addNSItemToQuery(pstate, pnsi, true, false, true); } -#include "rewrite/rewriteHandler.h" -static Expr *add_volatile_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, GTYPEOID); - return (Expr *)makeFuncExpr(oid, GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +static Expr *add_volatile_wrapper(Expr *node) { + return (Expr *)makeFuncExpr(get_ag_func_oid("gtype_volatile_wrapper", 1, GTYPEOID), GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } static void validate_or_create_elabel(cypher_parsestate *cpstate, cypher_relationship *edge) { - ParseState *pstate = (ParseState *)cpstate; label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); - if (lcd && lcd->kind != LABEL_KIND_EDGE) { + if (lcd && lcd->kind != LABEL_KIND_EDGE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("label %s is for vertices, not edges", edge->label), - parser_errposition(pstate, edge->location))); - } else if (!lcd) { - List *parent; - RangeVar *rv; - - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_EDGE); - - parent = list_make1(rv); - - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, parent, NULL); - } + parser_errposition(cpstate, edge->location))); + else if (!lcd) + create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, list_make1(get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_EDGE)), NULL); + } static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *node) { - ParseState *pstate = (ParseState *)cpstate; label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); - if (lcd && lcd->kind != LABEL_KIND_VERTEX) { + if (lcd && lcd->kind != LABEL_KIND_VERTEX) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("label %s is for edges, not vertices", node->label), - parser_errposition(pstate, node->location))); - } else if (!lcd) { - List *parent; - RangeVar *rv; - - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_VERTEX); - - parent = list_make1(rv); - - create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, parent, NULL); - } + parser_errposition(cpstate, node->location))); + else if (!lcd) + create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, list_make1(get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_VERTEX)), NULL); } @@ -435,8 +339,6 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE; target_nodes->graph_oid = cpstate->graph_oid; - - ListCell *lc; foreach (lc, self->pattern) { cypher_path *path = lfirst(lc); @@ -470,7 +372,6 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause make_id_alias(get_next_default_alias(cpstate)), false)); - /* query->targetList = lappend(query->targetList, makeTargetEntry( @@ -521,32 +422,27 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause else edge->name = get_next_default_alias(cpstate); - if (edge->props) { - target->prop_attr_num = pstate->p_next_resno; + if (edge->props) query->targetList = lappend(query->targetList, makeTargetEntry( (Expr *)add_volatile_wrapper( transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), - pstate->p_next_resno++, + target->prop_attr_num = pstate->p_next_resno++, make_property_alias(edge->name), false)); - } else { + else target->prop_attr_num = InvalidAttrNumber; - } target->dir = edge->dir; if (edge->dir == CYPHER_REL_DIR_NONE) ereport(ERROR, (errmsg_internal("edges in CREATE must have a direction"))); - label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); target->relid = lcd->relation; - target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); + target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(target->relid), 1); - //target->prop_attr_num = InvalidAttrNumber; ccp->target_nodes = lappend(ccp->target_nodes, target); - } i++; @@ -555,25 +451,24 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause target_nodes->paths = lappend(target_nodes->paths, ccp); } - // Function for the set_rel_pathlist to capture - FuncExpr *func_expr = make_write_clause_function_placeholder(CREATE_CLAUSE_FUNCTION_NAME, target_nodes); - TargetEntry *te = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, "_create_clause", false); - query->targetList = lappend(query->targetList, te); + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)make_write_clause_function_placeholder(CREATE_CLAUSE_FUNCTION_NAME, target_nodes), + pstate->p_next_resno++, + "_create_clause", + false)); query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); { - Query *topquery; cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); - topquery = makeNode(Query); + Query *topquery = makeNode(Query); topquery->commandType = CMD_SELECT; topquery->targetList = NIL; - ParseState *pstate = (ParseState *) cpstate; - int rtindex; - - ParseNamespaceItem *pnsi = transform_cypher_clause_as_subquery_2(new_cpstate, clause, NULL, false, query); + transform_cypher_clause_as_subquery_2(new_cpstate, query); + topquery->rtable = new_cpstate->pstate.p_rtable; topquery->jointree = makeFromExpr(new_cpstate->pstate.p_joinlist, NULL); @@ -608,7 +503,7 @@ Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause if (self->real_group_clause != NIL) query->groupClause = transform_group_clause(cpstate, self->real_group_clause, &query->groupingSets, - &query->targetList, + &query->targetList, query->sortClause, EXPR_KIND_GROUP_BY); else if (groupClause != NIL) // auto GROUP BY query->groupClause = transform_group_clause(cpstate, groupClause, &query->groupingSets, &query->targetList, @@ -616,9 +511,9 @@ Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause else query->groupClause = NULL; - if (self->having) { + if (self->having) query->havingQual = transform_cypher_expr(cpstate, self->having, EXPR_KIND_HAVING); - } + // DISTINCT if (self->distinct) { query->distinctClause = transformDistinctClause(pstate, &query->targetList, query->sortClause, false); @@ -632,10 +527,8 @@ Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause query->limitOffset = transform_cypher_limit(cpstate, self->skip, EXPR_KIND_OFFSET, "SKIP"); query->limitCount = transform_cypher_limit(cpstate, self->limit, EXPR_KIND_LIMIT, "LIMIT"); - if (pstate->p_windowdefs != NIL) { - //ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("window functions are not done"))); + if (pstate->p_windowdefs != NIL) query->windowClause = transform_window_definitions(pstate, pstate->p_windowdefs, &query->targetList); - } query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, expr); @@ -654,26 +547,21 @@ Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause } -// see findTargetlistEntrySQL99() static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, ParseExprKind expr_kind) { - Node *expr; ListCell *lt; - TargetEntry *te; - expr = transform_cypher_expr(cpstate, node, expr_kind); + Node *expr = transform_cypher_expr(cpstate, node, expr_kind); foreach (lt, *target_list) { - Node *te_expr; - - te = lfirst(lt); - te_expr = strip_implicit_coercions((Node *)te->expr); + TargetEntry *te = lfirst(lt); + Node *te_expr = strip_implicit_coercions((Node *)te->expr); if (equal(expr, te_expr)) return te; } - te = transform_cypher_item(cpstate, node, expr, expr_kind, NULL, true); + TargetEntry *te = transform_cypher_item(cpstate, node, expr, expr_kind, NULL, true); *target_list = lappend(*target_list, te); @@ -681,19 +569,14 @@ static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *nod } // see transformSortClause() -List *transform_cypher_order_by(cypher_parsestate *cpstate, List *sort_items, List **target_list, +List *transform_cypher_order_by(cypher_parsestate *cpstate, List *sort_items,List **target_list, ParseExprKind expr_kind) { - ParseState *pstate = (ParseState *)cpstate; List *sort_list = NIL; ListCell *li; foreach (li, sort_items) { SortBy *sort_by = lfirst(li); - TargetEntry *te; - - te = find_target_list_entry(cpstate, sort_by->node, target_list, expr_kind); - - sort_list = addTargetToSortList(pstate, te, sort_list, *target_list, sort_by); + sort_list = addTargetToSortList(cpstate, find_target_list_entry(cpstate, sort_by->node, target_list, expr_kind), sort_list, *target_list, sort_by); } return sort_list; @@ -703,21 +586,16 @@ List *transform_cypher_order_by(cypher_parsestate *cpstate, List *sort_items, Li // see transformLimitClause() static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, ParseExprKind expr_kind, const char *construct_name) { - ParseState *pstate = (ParseState *)cpstate; - Node *qual; - if (!node) return NULL; - qual = transform_cypher_expr(cpstate, node, expr_kind); - - qual = coerce_to_specific_type(pstate, qual, INT8OID, construct_name); + Node *qual = coerce_to_specific_type(cpstate, transform_cypher_expr(cpstate, node, expr_kind), INT8OID, construct_name); // LIMIT can't refer to any variables of the current query. if (contain_vars_of_level(qual, 0)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("argument of %s must not contain variables", construct_name), - parser_errposition(pstate, locate_var_of_level(qual, 0)))); + parser_errposition(cpstate, locate_var_of_level(qual, 0)))); return qual; } @@ -747,40 +625,32 @@ Query *transform_cypher_with(cypher_parsestate *cpstate, cypher_clause *clause) } static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, transform_method transform, - cypher_clause *clause) -{ - ParseState *pstate = (ParseState *)cpstate; + cypher_clause *clause) { Query *query; cypher_match *self = (cypher_match *)clause->self; Node *where = self->where; if (where) { - int rtindex; - ParseNamespaceItem *pnsi; - query = makeNode(Query); query->commandType = CMD_SELECT; - pnsi = transform_cypher_clause_as_subquery(cpstate, transform, clause, NULL, true); - Assert(pnsi != NULL); - rtindex = list_length(pstate->p_rtable); - Assert(rtindex == 1); // rte is the only RangeTblEntry in pstate + ParseNamespaceItem *pnsi = transform_cypher_clause_as_subquery(cpstate, transform, clause, NULL, true); - query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1); + query->targetList = expandNSItemAttrs(cpstate, pnsi, 0, -1); - markTargetListOrigins(pstate, query->targetList); + markTargetListOrigins(cpstate, query->targetList); - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + query->rtable = get_parse_state(cpstate)->p_rtable; + query->jointree = makeFromExpr(get_parse_state(cpstate)->p_joinlist, NULL); - assign_query_collations(pstate, query); + assign_query_collations(cpstate, query); } else { query = transform(cpstate, clause); } - query->hasSubLinks = pstate->p_hasSubLinks; - query->hasTargetSRFs = pstate->p_hasTargetSRFs; - query->hasAggs = pstate->p_hasAggs; + query->hasSubLinks = get_parse_state(cpstate)->p_hasSubLinks; + query->hasTargetSRFs = get_parse_state(cpstate)->p_hasTargetSRFs; + query->hasAggs = get_parse_state(cpstate)->p_hasAggs; return query; } @@ -789,32 +659,17 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause * return transform_cypher_clause_with_where(cpstate, transform_cypher_match_pattern, clause); } -/* - * Transform the clause into a subquery. This subquery will be used - * in a join so setup the namespace item and the created the rtr - * for the join to use. - */ static Node *transform_clause_for_join(cypher_parsestate *cpstate, cypher_clause *clause, RangeTblEntry **rte, ParseNamespaceItem **nsitem, Alias* alias) { - RangeTblRef *rtr; - *nsitem = transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, clause, alias, false); *rte = (*nsitem)->p_rte; - rtr = makeNode(RangeTblRef); + RangeTblRef *rtr = makeNode(RangeTblRef); rtr->rtindex = (*nsitem)->p_rtindex; return (Node *) rtr; } -/* - * For cases where we need to join two subqueries together (OPTIONAL MATCH and - * MERGE) we need to take the columns available in each rte and merge them - * together. The l_rte has precedence when there is a conflict, because that - * means that the pattern create in the current clause is referencing a - * variable declared in a previous clause (the l_rte). The output is the - * res_colnames and res_colvars that are passed in. - */ static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, ParseNamespaceItem *r_pnsi, List **res_colnames, List **res_colvars) { List *l_colnames, *l_colvars; @@ -863,38 +718,23 @@ static RangeTblEntry *transform_cypher_match_clause(cypher_parsestate *cpstate, ParseState *pstate = (ParseState *) cpstate; JoinExpr* j = makeNode(JoinExpr); List *res_colnames = NIL, *res_colvars = NIL; - Alias *l_alias, *r_alias; ParseNamespaceItem *jnsitem; int i = 0; j->jointype = JOIN_INNER; - l_alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL); - j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, &l_nsitem, l_alias); + j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, &l_nsitem, makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL)); pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); - /* - * Remove the previous clause so when the transform_clause_for_join function - * transforms the OPTIONAL MATCH, the previous clause will not be transformed - * again. - */ + prevclause = clause->prev; clause->prev = NULL; - - //set the lateral flag to true pstate->p_lateral_active = true; - j->rarg = transform_clause_for_join(cpstate, clause, &r_rte, &r_nsitem, r_alias); + j->rarg = transform_clause_for_join(cpstate, clause, &r_rte, &r_nsitem, makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL)); - // we are done transform the lateral left join pstate->p_lateral_active = false; - - /* - * We are done with the previous clause in the transform phase, but - * reattach the previous clause for semantics. - */ clause->prev = prevclause; pstate->p_namespace = NIL; @@ -914,67 +754,42 @@ static RangeTblEntry *transform_cypher_match_clause(cypher_parsestate *cpstate, pstate->p_joinlist = lappend(pstate->p_joinlist, j); - // add jrte to column namespace only - addNSItemToQuery(pstate, jnsitem, false, false, true); + addNSItemToQuery(pstate, jnsitem, true, false, true); return jnsitem->p_rte; } static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause) { - ParseState *pstate = (ParseState *)cpstate; cypher_match *self = (cypher_match *)clause->self; Query *query; Node *where = self->where; Node *order_by = self->order_by; self->order_by = NULL; + query = makeNode(Query); query->commandType = CMD_SELECT; - // If there is no previous clause, transform to a general MATCH clause. - if (clause->prev && (self->order_by)) { - RangeTblEntry *rte = transform_cypher_match_clause(cpstate, clause); - - query->targetList = make_target_list_from_join(pstate, rte); - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - } else if (clause->prev) { - ParseNamespaceItem *pnsi = transform_prev_cypher_clause(cpstate, clause->prev, true); - - /* - * add all the target entries in rte to the current target list to pass - * all the variables that are introduced in the previous clause to the - * next clause - */ - query->targetList = expandNSItemAttrs(pstate, get_namespace_item(pstate, pnsi->p_rte), 0, -1); - transform_match_pattern(cpstate, query, self->pattern, where); - } else { - transform_match_pattern(cpstate, query, self->pattern, where); - } + if (clause->prev) + query->targetList = expandNSItemAttrs(cpstate, transform_prev_cypher_clause(cpstate, clause->prev, true), 0, -1); + transform_match_pattern(cpstate, query, self->pattern, where); // ORDER BY query->sortClause = transform_cypher_order_by(cpstate, order_by, &query->targetList, EXPR_KIND_ORDER_BY); - markTargetListOrigins(pstate, query->targetList); + markTargetListOrigins(cpstate, query->targetList); - query->hasSubLinks = pstate->p_hasSubLinks; - query->hasWindowFuncs = pstate->p_hasWindowFuncs; - query->hasTargetSRFs = pstate->p_hasTargetSRFs; - query->hasAggs = pstate->p_hasAggs; + query->hasSubLinks = get_parse_state(cpstate)->p_hasSubLinks; + query->hasWindowFuncs = get_parse_state(cpstate)->p_hasWindowFuncs; + query->hasTargetSRFs = get_parse_state(cpstate)->p_hasTargetSRFs; + query->hasAggs = get_parse_state(cpstate)->p_hasAggs; - assign_query_collations(pstate, query); + assign_query_collations(cpstate, query); return query; } - -static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace) ; -static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); - - static Node *make_null_const(int location) { - A_Const *n; - - n = makeNode(A_Const); + A_Const *n = makeNode(A_Const); n->val.type = T_Null; n->location = location; @@ -985,7 +800,7 @@ static A_Expr * makeSimpleCypherA_Expr(A_Expr_Kind kind, char *name, Node *lexpr, Node *rexpr, int location) { - A_Expr *a = makeNode(A_Expr); + A_Expr *a = makeNode(A_Expr); a->kind = kind; a->name = list_make2(makeString("postgraph"), makeString((char *) name)); @@ -995,19 +810,13 @@ makeSimpleCypherA_Expr(A_Expr_Kind kind, char *name, return a; } - - Alias *make_alias(char *name, List *colnames) { Alias *alias = makeNode(Alias); - alias->aliasname = name; alias->colnames = colnames; - return alias; } - - RangeFunction *make_range_function(FuncCall *func, Alias *alias, bool is_lateral, bool ordinality, bool is_rows_from) { RangeFunction *rf = makeNode(RangeFunction); @@ -1020,105 +829,46 @@ RangeFunction *make_range_function(FuncCall *func, Alias *alias, bool is_lateral return rf; } -static ParseNamespaceItem *add_srf_to_query(cypher_parsestate *cpstate, Node *n, char *var_name) { - RangeTblEntry *rte = NULL; - RangeTblRef *rtr; +static ParseNamespaceItem *add_srf_to_query(cypher_parsestate *cpstate, Node *n, char *var_name, List *colnames) { List *namespace = NULL; - int rtindex; - ParseState *pstate = (ParseState *)cpstate; - - Alias *alias = make_alias(var_name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); - //Alias *alias = make_alias(var_name, NIL); - RangeFunction *rf = make_range_function(n, alias, true, false, false); + RangeTblRef *rtr = transform_srf_function( + cpstate, + make_range_function(n, make_alias(var_name, colnames), true, false, false), + &namespace); - rtr = transform_srf_function(cpstate, rf, &rte, &rtindex, &namespace); - Assert(rtr != NULL); + setNamespaceLateralState(namespace, false, true); - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + get_parse_state(cpstate)->p_joinlist = lappend(get_parse_state(cpstate)->p_joinlist, rtr); + get_parse_state(cpstate)->p_namespace = list_concat(get_parse_state(cpstate)->p_namespace, namespace); - setNamespaceLateralState(namespace, true, true); - //((ParseNamespaceItem *)lfirst(list_head(namespace)))->p_names = alias;//list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties")); - pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); - pstate->p_namespace = list_concat(pstate->p_namespace, namespace); - - setNamespaceLateralState(pstate->p_namespace, true, true); - - return lfirst(list_head(namespace)); + return linitial(namespace); } - - -static ParseNamespaceItem *add_srf_to_query2(cypher_parsestate *cpstate, Node *n, char *var_name) { - RangeTblEntry *rte = NULL; - RangeTblRef *rtr; - List *namespace = NULL; - int rtindex; - ParseState *pstate = (ParseState *)cpstate; - - Alias *alias = make_alias(var_name, list_make2(makeString("edges"), makeString("endid"))); - //Alias *alias = make_alias(var_name, NIL); - RangeFunction *rf = make_range_function(n, alias, true, false, false); - - - rtr = transform_srf_function(cpstate, rf, &rte, &rtindex, &namespace); - Assert(rtr != NULL); - - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); - - setNamespaceLateralState(namespace, true, true); - //((ParseNamespaceItem *)lfirst(list_head(namespace)))->p_names = alias;//list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties")); - pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); - pstate->p_namespace = list_concat(pstate->p_namespace, namespace); - - setNamespaceLateralState(pstate->p_namespace, true, true); - - return lfirst(list_head(namespace)); -} - - - -// transform a function call appearing in FROM static ParseNamespaceItem *transformRangeFunction(cypher_parsestate *cpstate, RangeFunction *r) { - ParseState *pstate = NULL; List *funcexprs = NIL; List *funcnames = NIL; List *coldeflists = NIL; - bool is_lateral = false; - ListCell *lc = NULL; - ParseNamespaceItem *pnsi; - - pstate = &cpstate->pstate; + ListCell *lc; - Assert(!pstate->p_lateral_active); - pstate->p_lateral_active = true; + Assert(!get_parse_state(cpstate)->p_lateral_active); + get_parse_state(cpstate)->p_lateral_active = true; - // transform the raw expressions - foreach(lc, r->functions) - { + foreach(lc, r->functions) { List *pair = (List*)lfirst(lc); - Node *fexpr; - List *coldeflist; - Node *newfexpr; - Node *last_srf; - // Disassemble the function-call/column-def-list pairs Assert(list_length(pair) == 2); - fexpr = (Node*) linitial(pair); - coldeflist = (List*) lsecond(pair); - - // normal case ... - last_srf = pstate->p_last_srf; + Node *fexpr = (Node*) linitial(pair); + List *coldeflist = (List*) lsecond(pair); - // transform the function expression - newfexpr = transform_cypher_expr(cpstate, fexpr, EXPR_KIND_FROM_FUNCTION); + Node *last_srf = get_parse_state(cpstate)->p_last_srf; + Node *newfexpr = transform_cypher_expr(cpstate, fexpr, EXPR_KIND_FROM_FUNCTION); - // nodeFunctionscan.c requires SRFs to be at top level - if (pstate->p_last_srf != last_srf && pstate->p_last_srf != newfexpr) + if (get_parse_state(cpstate)->p_last_srf != last_srf && get_parse_state(cpstate)->p_last_srf != newfexpr) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-returning functions must appear at top level of FROM"), - parser_errposition(pstate, exprLocation(pstate->p_last_srf)))); + parser_errposition(cpstate, exprLocation(get_parse_state(cpstate)->p_last_srf)))); funcexprs = lappend(funcexprs, newfexpr); funcnames = lappend(funcnames, FigureColname(fexpr)); @@ -1127,58 +877,31 @@ static ParseNamespaceItem *transformRangeFunction(cypher_parsestate *cpstate, Ra ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple column definition lists are not allowed for the same function"), - parser_errposition(pstate, exprLocation((Node *) r->coldeflist)))); + parser_errposition(cpstate, exprLocation((Node *) r->coldeflist)))); coldeflists = lappend(coldeflists, coldeflist); } - pstate->p_lateral_active = false; + get_parse_state(cpstate)->p_lateral_active = false; - /* - * We must assign collations now so that the RTE exposes correct collation - * info for Vars created from it. - */ - assign_list_collations(pstate, funcexprs); + assign_list_collations(get_parse_state(cpstate), funcexprs); - // currently this is not used by the VLE Assert(r->coldeflist == NULL); - // mark the RTE as LATERAL - is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0); - - // build an RTE for the function - pnsi = addRangeTableEntryForFunction(pstate, funcnames, funcexprs, coldeflists, r, is_lateral, true); - - return pnsi; + return addRangeTableEntryForFunction(cpstate, funcnames, funcexprs, coldeflists, r, r->lateral || contain_vars_of_level((Node *) funcexprs, 0), true); } -static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, RangeTblEntry **top_rte, int *top_rti, List **namespace) { - ParseState *pstate = (ParseState *)cpstate; - - Assert(IsA(n, RangeFunction)); - - if (IsA(n, RangeFunction)) { - RangeTblRef *rtr; - RangeTblEntry *rte; - ParseNamespaceItem *nsitem; - int rtindex; - - nsitem = transformRangeFunction(cpstate, (RangeFunction *) n); - rte = nsitem->p_rte; - rtindex = list_length(pstate->p_rtable); - Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); - *top_rte = rte; - *top_rti = rtindex; - *namespace = list_make1(nsitem); - rtr = makeNode(RangeTblRef); - rtr->rtindex = rtindex; - return (Node *) rtr; - } - - return NULL; -} +static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, List **namespace) { + ParseNamespaceItem *pnsi = transformRangeFunction(cpstate, (RangeFunction *) n); + + RangeTblEntry *rte = pnsi->p_rte; + *namespace = list_make1(pnsi); + RangeTblRef *rtr = makeNode(RangeTblRef); + rtr->rtindex = list_length(get_parse_state(cpstate)->p_rtable); + return (Node *) rtr; +} // setNamespaceLateralState - subroutine to update LATERAL flags in a namespace list. static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok) { @@ -1203,6 +926,9 @@ static Node *make_int_const(int i, int location) { return (Node *)n; } +// only needed for this function +// TODO move +#include "access/nbtree.h" char *get_vertex_relation_name(cypher_parsestate *cpstate, char *label, bool is_default_label) { // XXX: LTree Labeling Project, first code is here Datum label_lquery; @@ -1243,13 +969,27 @@ char *get_vertex_relation_name(cypher_parsestate *cpstate, char *label, bool is_ } static ParseNamespaceItem * -add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node) +add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, List **quals) { - ParseState *pstate = (ParseState *)cpstate; - node->has_variable = false; - if (node->name) + node->declared_in_previous_clause = false; + if (node->name) { node->has_variable = true; + if (colNameToVar(cpstate, node->name, false, -1)) { + ereport(WARNING,(errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("here"))); + node->declared_in_previous_clause = true; + return refnameNamespaceItem(cpstate, NULL, PREV_CYPHER_CLAUSE_ALIAS, -1, NULL); + } + + int sublevels_up; + ParseNamespaceItem *pnsi = refnameNamespaceItem(cpstate, NULL, node->name, -1, NULL); + if (pnsi) { + node->in_join_tree = true; + return pnsi; + } + } + else node->name = get_next_default_alias(cpstate); @@ -1261,28 +1001,65 @@ add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node) node->in_join_tree = true; - ParseNamespaceItem *pnsi = addRangeTableEntry(pstate, + ParseNamespaceItem *pnsi = addRangeTableEntry(cpstate, makeRangeVar(get_graph_namespace_name(cpstate->graph_name), get_vertex_relation_name(cpstate, node->label, node->is_default_label), -1), makeAlias(node->name, list_make2(makeString("id"), makeString("properties"))), true, true); + setNamespaceLateralState(pnsi, false, true); +/* + RangeTblRef *rtr = transform_srf_function( + cpstate, + make_range_function(n, make_alias(var_name, colnames), true, false, false), + &namespace); + setNamespaceLateralState(namespace, true, true); - addNSItemToQuery(pstate, pnsi, true, true, true); + get_parse_state(cpstate)->p_joinlist = lappend(get_parse_state(cpstate)->p_joinlist, rtr); + get_parse_state(cpstate)->p_namespace = list_concat(get_parse_state(cpstate)->p_namespace, namespace); +*/ + addNSItemToQuery(cpstate, pnsi, true, true, true); return pnsi; } static ParseNamespaceItem * -add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, ParseNamespaceItem *edge_pnsi) +add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, ParseNamespaceItem *edge_pnsi, List **quals) { - ParseState *pstate = (ParseState *)cpstate; - node->has_variable = false; - if (node->name) + if (node->name) { node->has_variable = true; + + Var *var; + if (var = colNameToVar(cpstate, make_id_alias(node->name), false, -1)) { + ereport(WARNING,(errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("here"))); + node->declared_in_previous_clause = true; + + *quals = lappend(*quals, + makeSimpleCypherA_Expr(AEXPR_OP, "=", + (Node *)scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1), + (Node *)var, -1)); + + return refnameNamespaceItem(cpstate, NULL, PREV_CYPHER_CLAUSE_ALIAS, -1, NULL); + } + + int sublevels_up; + ParseNamespaceItem *pnsi = refnameNamespaceItem(cpstate, NULL, node->name, -1, NULL); + if (pnsi) { + node->in_join_tree = false; + node->pnsi = pnsi; + *quals = lappend(*quals, + makeSimpleCypherA_Expr(AEXPR_OP, "=", + (Node *)scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1), + (Node *)scanNSItemForColumn(cpstate, pnsi, 0, "id", -1), -1)); + + return pnsi; + } + } + else { node->in_join_tree = false; return NULL; @@ -1304,25 +1081,24 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n make_int_const(cpstate->graph_oid, -1), scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1)), COERCE_EXPLICIT_CALL, -1), - make_alias(node->name, - list_make1(makeString("properties"))), + make_alias(node->name, list_make1(makeString("properties"))), true, false, false)); - - addNSItemToQuery(pstate, pnsi, true, true, true); + setNamespaceLateralState(pnsi, false, true); + addNSItemToQuery(cpstate, pnsi, true, true, true); return pnsi; } static ParseNamespaceItem * -add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi) +add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, cypher_node *vertex) { - ParseState *pstate = (ParseState *)cpstate; - edge->has_variable = false; - if (edge->name) + if (edge->name) { edge->has_variable = true; + + } else edge->name = get_next_default_alias(cpstate); @@ -1334,22 +1110,28 @@ add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship else edge->label = AG_DEFAULT_LABEL_EDGE; - Node *id_field = scanNSItemForColumn(cpstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + Node *id_field; + if (!vertex->declared_in_previous_clause) + id_field = scanNSItemForColumn(cpstate, vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + else + id_field = colNameToVar(cpstate, make_id_alias(vertex->name), false, -1); + + //id_field = scanNSItemForColumn(cpstate, vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1); + + //if (id_field == NULL) + // id_field = colNameToVar(cpstate, make_id_alias(vertex->name), false, -1); FuncCall *fc = makeFuncCall( list_make2(makeString("postgraph"), makeString("edge_search")), list_make4(make_int_const(cpstate->graph_oid, -1), id_field, make_null_const(-1), make_null_const(-1)), COERCE_EXPLICIT_CALL, -1); - return add_srf_to_query(cpstate, fc, edge->name); + return add_srf_to_query(cpstate, fc, edge->name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); } - static ParseNamespaceItem * add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *prev_pnsi, char *prev_name) { - ParseState *pstate = (ParseState *)cpstate; - edge->has_variable = false; if (edge->name) edge->has_variable = true; @@ -1378,10 +1160,9 @@ add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cyphe list_make4(make_int_const(cpstate->graph_oid, -1), id_field, make_null_const(-1), make_null_const(-1)), COERCE_EXPLICIT_CALL, -1); - return add_srf_to_query(cpstate, fc, edge->name); + return add_srf_to_query(cpstate, fc, edge->name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); } - static ParseNamespaceItem * add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi) { @@ -1391,7 +1172,7 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela if (edge->name) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH variable edge variables are not supported"))); + errmsg("MATCH variable edge variables are not supported"))); else edge->name = get_next_default_alias(cpstate); @@ -1412,16 +1193,15 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela list_make4(make_int_const(cpstate->graph_oid, -1), id_field, idx->lidx, make_null_const(-1)), COERCE_EXPLICIT_CALL, -1); - return add_srf_to_query2(cpstate, fc, edge->name); + return add_srf_to_query(cpstate, fc, edge->name, list_make3(makeString("edges"), makeString("endid"), makeString("hashset"))); } - static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *query, cypher_node *left_vertex, cypher_relationship *edge, cypher_node *right_vertex) { ParseState *pstate = (ParseState *)cpstate; - // left vertex id field - if (left_vertex->in_join_tree) { + // left vertex id + if (left_vertex->in_join_tree && !left_vertex->declared_in_previous_clause) { query->targetList = lappend(query->targetList, makeTargetEntry( edge->dir == CYPHER_REL_DIR_RIGHT ? @@ -1452,7 +1232,7 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que } if (edge->varlen) { - // end id field + // end id query->targetList = lappend(query->targetList, makeTargetEntry( scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), @@ -1460,7 +1240,7 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que make_endid_alias(edge->name), false)); } else { - // id field + // id query->targetList = lappend(query->targetList, makeTargetEntry( scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_ID, -1), @@ -1468,7 +1248,7 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que make_id_alias(edge->name), false)); - // start id field + // start id query->targetList = lappend(query->targetList, makeTargetEntry( scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_START_ID, -1), @@ -1476,7 +1256,7 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que make_startid_alias(edge->name), false)); - // end id field + // end id query->targetList = lappend(query->targetList, makeTargetEntry( scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_END_ID, -1), @@ -1484,7 +1264,7 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que make_endid_alias(edge->name), false)); - // properties field + // properties query->targetList = lappend(query->targetList, makeTargetEntry( scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1), @@ -1493,7 +1273,7 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que false)); } - // id field + // id if (edge->has_variable) query->targetList = lappend(query->targetList, makeTargetEntry( @@ -1521,7 +1301,7 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que false)); // Vertex expression - if (right_vertex->has_variable) + if (right_vertex->has_variable && !right_vertex->declared_in_previous_clause) query->targetList = lappend(query->targetList, makeTargetEntry( edge->dir == CYPHER_REL_DIR_RIGHT ? @@ -1531,7 +1311,6 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que right_vertex->name, false)); } - } static void add_new_fields_to_target_list(cypher_parsestate *cpstate, Query *query, @@ -1618,10 +1397,9 @@ static void add_new_fields_to_target_list(cypher_parsestate *cpstate, Query *que right_vertex->name, false)); } - } static void -process_three_element_path(cypher_parsestate *cpstate, Query *query, cypher_path *path) +process_three_element_path(cypher_parsestate *cpstate, Query *query, cypher_path *path, List **quals) { cypher_node *start_vertex; cypher_node *end_vertex; @@ -1638,72 +1416,18 @@ process_three_element_path(cypher_parsestate *cpstate, Query *query, cypher_path errmsg("MATCH edge can't be bi directional"))); } - start_vertex->pnsi = add_vertex_to_query(cpstate, query, start_vertex); + start_vertex->pnsi = add_vertex_to_query(cpstate, query, start_vertex, quals); if (edge->varlen) edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, start_vertex->pnsi); else - edge->pnsi = add_edge_to_query(cpstate, query, edge, start_vertex->pnsi); + edge->pnsi = add_edge_to_query(cpstate, query, edge, start_vertex); - end_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, end_vertex, edge->pnsi); + end_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, end_vertex, edge->pnsi, quals); // SELECT fields add_all_fields_to_target_list(cpstate, query, linitial(path->path), edge, lthird(path->path)); } -static Query * -transform_cypher_clause_as_subquery_match(cypher_parsestate *cpstate, cypher_path *path) { - ParseState *pstate = (ParseState *)cpstate; - ParseExprKind old_expr_kind = pstate->p_expr_kind; - bool lateral = pstate->p_lateral_active; - - - /* - * If this is a WHERE, pass it through and set lateral to true because it - * needs to see what comes before it. - */ - - - cypher_parsestate *sub_cpstate; - Query *query; - ParseState *parent_pstate = (ParseState*)cpstate; - ParseState *sub_pstate; - - sub_cpstate = make_cypher_parsestate(cpstate); - sub_pstate = (ParseState*)sub_cpstate; - - - sub_pstate->p_expr_kind = pstate->p_expr_kind; - - - query = makeNode(Query); - query->commandType = CMD_SELECT; - - process_three_element_path(sub_cpstate, query, path); - - - - markTargetListOrigins(sub_pstate, query->targetList); - - query->rtable = pstate->p_rtable; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - - assign_query_collations(sub_cpstate, query); - - - free_cypher_parsestate(sub_cpstate); - - pstate->p_expr_kind = old_expr_kind; - - // if (!alias) - // alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - - // ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); - - // addNSItemToQuery(pstate, pnsi, true, false, true); - - return query; -} - typedef struct path_parts { int start; int end; @@ -1746,13 +1470,119 @@ static List *find_path_parts(cypher_path *path) { return parts; } +static void +process_path_part( + cypher_parsestate *cpstate, + Query *query, + cypher_path *path, + path_parts *pp, + List **quals, + cypher_relationship **prev_edge) +{ + ParseState *pstate = (ParseState *)cpstate; + + if (pp->dir == CYPHER_REL_DIR_RIGHT) { + cypher_node *left_vertex = list_nth(path->path, pp->start - 1); + left_vertex->pnsi = add_vertex_to_query(cpstate, query, left_vertex, quals); + + for (int i = pp->start; i < pp->end; i += 2) { + cypher_node *left_vertex = list_nth(path->path, i - 1); + cypher_relationship *edge = list_nth(path->path, i); + cypher_node *right_vertex = list_nth(path->path, i + 1); + + if (edge->varlen) + edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, left_vertex->pnsi); + else if (i == pp->start) + edge->pnsi = add_edge_to_query(cpstate, query, edge, left_vertex); + else { + cypher_relationship *prev = list_nth(path->path, i - 2); + edge->pnsi = add_edge_to_query_with_prev_edge(cpstate, query, edge, prev->pnsi, make_endid_alias(prev->name)); + } + right_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, right_vertex, edge->pnsi, quals); + + if (i == pp->start && *prev_edge) + *quals = lappend(*quals, + makeSimpleCypherA_Expr(AEXPR_OP, "=", + (Node *)scanNSItemForColumn(cpstate, (*prev_edge)->pnsi, 0, "endid", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "endid", -1), -1)); + + for (int j = 1; j < i; j += 2) { + cypher_relationship *earlier_edge = list_nth(path->path, j); + if (earlier_edge->varlen && !edge->varlen) + *quals = lappend(*quals, + makeSimpleCypherA_Expr(AEXPR_OP, "@>", + (Node *)scanNSItemForColumn(cpstate, earlier_edge->pnsi, 0, "hashset", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); + else + *quals = lappend(*quals, + makeSimpleCypherA_Expr(AEXPR_OP, "<>", + (Node *)scanNSItemForColumn(cpstate, earlier_edge->pnsi, 0, "id", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); + } + + if (pp->start == i) + add_all_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); + else + add_new_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); + + *prev_edge = edge; + } + } else if (pp->dir == CYPHER_REL_DIR_LEFT) { + cypher_node *right_vertex = list_nth(path->path, pp->end - 1); + right_vertex->pnsi = add_vertex_to_query(cpstate, query, right_vertex, quals); + + for (int i = pp->end; i > pp->start; i -= 2) { + cypher_node *left_vertex = list_nth(path->path, i - 3); + cypher_relationship *edge = list_nth(path->path, i - 2); + cypher_node *right_vertex = list_nth(path->path, i - 1); + + if (edge->varlen) + edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, right_vertex->pnsi); + else if (i == pp->end) + edge->pnsi = add_edge_to_query(cpstate, query, edge, right_vertex); + else { + cypher_relationship *prev = list_nth(path->path, i); + edge->pnsi = add_edge_to_query_with_prev_edge(cpstate, query, edge, prev->pnsi, make_endid_alias(prev->name)); + } + right_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, left_vertex, edge->pnsi, quals); + + if (i == pp->end && *prev_edge != NULL) { + *quals = lappend(*quals, + makeSimpleCypherA_Expr(AEXPR_OP, "=", + (Node *)scanNSItemForColumn(cpstate, (*prev_edge)->pnsi, 0, "endid", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "endid", -1), -1)); + } + + for (int p = 1; p < pp->start; p += 2) { + cypher_relationship *prev = list_nth(path->path, p); + *quals = lappend(*quals, + makeSimpleCypherA_Expr(AEXPR_OP, "<>", + (Node *)scanNSItemForColumn(cpstate, prev->pnsi, 0, "id", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); + } + + for (int k = pp->end; k > i; k -= 2) { + cypher_relationship *prev = list_nth(path->path, k - 2); + *quals = lappend(*quals, + makeSimpleCypherA_Expr(AEXPR_OP, "<>", + (Node *)scanNSItemForColumn(cpstate, prev->pnsi, 0, "id", -1), + (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); + } + + if (pp->start == i) + add_all_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); + else + add_new_fields_to_target_list(cpstate, query, right_vertex, edge, left_vertex); + + *prev_edge = edge; + } + } +} static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc; List *quals = NIL; - Expr *expr = NULL; - if (list_length(pattern) !=1 ) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -1764,7 +1594,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li if (list_length(path->path) == 1) { cypher_node *node = linitial(path->path); - node->pnsi = add_vertex_to_query(cpstate, query, node); + node->pnsi = add_vertex_to_query(cpstate, query, node, &quals); // left vertex id field query->targetList = lappend(query->targetList, makeTargetEntry( @@ -1788,147 +1618,29 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li pstate->p_next_resno++, node->name, false)); - } continue; } if (list_length(path->path) == 3) { - process_three_element_path(cpstate, query, path); + process_three_element_path(cpstate, query, path, &quals); } else { List *parts = find_path_parts(path); ListCell *lc; cypher_relationship *prev_edge = NULL; foreach (lc, parts) { path_parts *pp = (path_parts *) lfirst(lc); - - - if (pp->dir == CYPHER_REL_DIR_RIGHT) { - cypher_node *left_vertex = list_nth(path->path, pp->start - 1); - left_vertex->pnsi = add_vertex_to_query(cpstate, query, left_vertex); - - for (int i = pp->start; i < pp->end; i += 2) { - cypher_node *left_vertex = list_nth(path->path, i - 1); - cypher_relationship *edge = list_nth(path->path, i); - cypher_node *right_vertex = list_nth(path->path, i + 1); - - //left_vertex->pnsi = add_vertex_to_query(cpstate, query, left_vertex); - if (edge->varlen) - edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, left_vertex->pnsi); - else if ( i == pp->start) - edge->pnsi = add_edge_to_query(cpstate, query, edge, left_vertex->pnsi); - else { - cypher_relationship *prev_edge = list_nth(path->path, i - 2); - edge->pnsi = add_edge_to_query_with_prev_edge(cpstate, query, edge, prev_edge->pnsi, make_endid_alias(prev_edge->name)); - } - right_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, right_vertex, edge->pnsi); - - if (i == pp->start && prev_edge) { - ereport(WARNING, errmsg("start quals")); - quals = lappend(quals, - makeSimpleCypherA_Expr(AEXPR_OP, "=", - (Node *)scanNSItemForColumn(cpstate, prev_edge->pnsi, 0, "endid", -1), - (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "endid", -1), -1)); - } - - for (int j = 1; j < i; j += 2) { - cypher_relationship *earlier_edge = list_nth(path->path, j); - if (!earlier_edge->varlen) - quals = lappend(quals, - makeSimpleCypherA_Expr(AEXPR_OP, "<>", - (Node *)scanNSItemForColumn(cpstate, earlier_edge->pnsi, 0, "id", -1), - (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); - } - - // SELECT fields - if (pp->start == i) - add_all_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); - else - add_new_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); - - prev_edge = edge; - } - } else if (pp->dir == CYPHER_REL_DIR_LEFT) { - cypher_node *right_vertex = list_nth(path->path, pp->end - 1); - right_vertex->pnsi = add_vertex_to_query(cpstate, query, right_vertex); - - for (int i = pp->end; i > pp->start; i -= 2) { - cypher_node *left_vertex = list_nth(path->path, i - 3); - cypher_relationship *edge = list_nth(path->path, i - 2); - cypher_node *right_vertex = list_nth(path->path, i - 1); - - //left_vertex->pnsi = add_vertex_to_query(cpstate, query, left_vertex); - if (edge->varlen) - edge->pnsi = add_variable_edge_to_query(cpstate, query, edge, right_vertex->pnsi); - else if ( i == pp->end) - edge->pnsi = add_edge_to_query(cpstate, query, edge, right_vertex->pnsi); - else { - cypher_relationship *prev_edge = list_nth(path->path, i); - edge->pnsi = add_edge_to_query_with_prev_edge(cpstate, query, edge, prev_edge->pnsi, make_endid_alias(prev_edge->name)); - } - right_vertex->pnsi = add_vertex_retrieval_to_query(cpstate, query, left_vertex, edge->pnsi); - - if (i == pp->end && prev_edge != NULL) { - quals = lappend(quals, - makeSimpleCypherA_Expr(AEXPR_OP, "=", - (Node *)scanNSItemForColumn(cpstate, prev_edge->pnsi, 0, "endid", -1), - (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "endid", -1), -1)); - } - - for (int p = 1; p < pp->start; p += 2) { - cypher_relationship *prev_edge = list_nth(path->path, p); - quals = lappend(quals, - makeSimpleCypherA_Expr(AEXPR_OP, "<>", - (Node *)scanNSItemForColumn(cpstate, prev_edge->pnsi, 0, "id", -1), - (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); - } - - // 2. All previous edges in this path_part (pp) - for (int k = pp->end; k > i; k -= 2) { - cypher_relationship *prev_edge = list_nth(path->path, k - 2); - quals = lappend(quals, - makeSimpleCypherA_Expr(AEXPR_OP, "<>", - (Node *)scanNSItemForColumn(cpstate, prev_edge->pnsi, 0, "id", -1), - (Node *)scanNSItemForColumn(cpstate, edge->pnsi, 0, "id", -1), -1)); - } - - // SELECT fields - if (pp->start == i) - add_all_fields_to_target_list(cpstate, query, left_vertex, edge, right_vertex); - else - add_new_fields_to_target_list(cpstate, query, right_vertex, edge, left_vertex); - - prev_edge = edge; - } - } - + process_path_part(cpstate, query, path, pp, &quals, &prev_edge); } } - } + } // AND the quals for each path together - Expr *q = NULL; - - if (quals != NIL) { - ereport(WARNING, errmsg("quals")); - q = makeBoolExpr(AND_EXPR, quals, -1); - expr = (Expr *)sql_transform_expr(&cpstate->pstate, (Node *)q, EXPR_KIND_WHERE); - } - - /* - / property constraints on MATCH Pattern - if (cpstate->property_constraint_quals != NIL) { - Expr *prop_qual = makeBoolExpr(AND_EXPR, cpstate->property_constraint_quals, -1); - - if (expr == NULL) - expr = prop_qual; - else - expr = makeBoolExpr(AND_EXPR, list_make2(expr, prop_qual), -1); - } - */ + Expr *expr = NULL; + if (quals != NIL) + expr = (Expr *)sql_transform_expr(cpstate, (Node *)makeBoolExpr(AND_EXPR, quals, -1), EXPR_KIND_WHERE); - // WHERE Clause /* if (where != NULL) { @@ -1954,10 +1666,8 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li query->rtable = cpstate->pstate.p_rtable; query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr); - //query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)NULL); } - static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte) { List *targetlist = NIL; ListCell *lt; @@ -1966,162 +1676,68 @@ static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte) AssertArg(rte->rtekind == RTE_JOIN); forboth(lt, rte->joinaliasvars, ln, rte->eref->colnames) { - Var *varnode = lfirst(lt); - char *resname = strVal(lfirst(ln)); - TargetEntry *tmp; - - tmp = makeTargetEntry((Expr *) varnode, (AttrNumber) pstate->p_next_resno++, pstrdup(resname), false); - targetlist = lappend(targetlist, tmp); + targetlist = lappend(targetlist, + makeTargetEntry((Expr *) lfirst(lt), (AttrNumber) pstate->p_next_resno++, strVal(lfirst(ln)), false)); } return targetlist; } -/* - * This function is similar to transformFromClause() that is called with a - * single RangeSubselect. - */ static ParseNamespaceItem * transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, Alias *alias, bool add_rte_to_query) { - ParseState *pstate = (ParseState *)cpstate; - ParseExprKind old_expr_kind = pstate->p_expr_kind; - bool lateral = pstate->p_lateral_active; - - /* - * We allow expression kinds of none, where, and subselect. Others MAY need - * to be added depending. However, at this time, only these are needed. - */ - Assert(pstate->p_expr_kind == EXPR_KIND_NONE || pstate->p_expr_kind == EXPR_KIND_OTHER || - pstate->p_expr_kind == EXPR_KIND_WHERE || pstate->p_expr_kind == EXPR_KIND_FROM_SUBSELECT); - /* * As these are all sub queries, if this is just of type NONE, note it as a * SUBSELECT. Other types will be dealt with as needed. */ - if (pstate->p_expr_kind == EXPR_KIND_NONE) { - pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; - } else if (pstate->p_expr_kind == EXPR_KIND_OTHER) { + if (get_parse_state(cpstate)->p_expr_kind == EXPR_KIND_NONE) { + get_parse_state(cpstate)->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; + } else if (get_parse_state(cpstate)->p_expr_kind == EXPR_KIND_OTHER) { // this is a lateral subselect for the MERGE - pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; - lateral = true; + get_parse_state(cpstate)->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; + get_parse_state(cpstate)->p_lateral_active = true; } - /* - * If this is a WHERE, pass it through and set lateral to true because it - * needs to see what comes before it. - */ + + ParseExprKind old_expr_kind = get_parse_state(cpstate)->p_expr_kind; Query *query = analyze_cypher_clause(transform, clause, cpstate); - - pstate->p_expr_kind = old_expr_kind; + get_parse_state(cpstate)->p_expr_kind = old_expr_kind; if (!alias) alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); - ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); - - /* - * NOTE: skip namespace conflicts check if the rte will be the only - * RangeTblEntry in pstate - */ - if (list_length(pstate->p_rtable) > 1) { - List *namespace = NULL; - int rtindex = 0; - - rtindex = list_length(pstate->p_rtable); - - if (pnsi->p_rte != rt_fetch(rtindex, pstate->p_rtable)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("rte must be last entry in p_rtable"))); - - namespace = list_make1(pnsi); - - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); - } + ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(cpstate, query, alias, get_parse_state(cpstate)->p_lateral_active, true); if (add_rte_to_query) - addNSItemToQuery(pstate, pnsi, true, false, true); + addNSItemToQuery(cpstate, pnsi, true, false, true); return pnsi; } static Query *analyze_cypher_clause(transform_method transform, cypher_clause *clause, cypher_parsestate *parent_cpstate) { - cypher_parsestate *cpstate; - Query *query; - ParseState *parent_pstate = (ParseState*)parent_cpstate; - ParseState *pstate; + cypher_parsestate *cpstate = make_cypher_parsestate(parent_cpstate); - cpstate = make_cypher_parsestate(parent_cpstate); - pstate = (ParseState*)cpstate; + get_parse_state(cpstate)->p_expr_kind = get_parse_state(parent_cpstate)->p_expr_kind; - // copy the expr_kind down to the child - pstate->p_expr_kind = parent_pstate->p_expr_kind; - - if (pstate->p_expr_kind == EXPR_KIND_WHERE) { - cpstate->entities = list_concat(NIL, parent_cpstate->entities); - } - - query = transform(cpstate, clause); - - advance_transform_entities_to_next_clause(cpstate->entities); - - parent_cpstate->entities = list_concat(parent_cpstate->entities, cpstate->entities); + Query *query = transform(cpstate, clause); free_cypher_parsestate(cpstate); return query; } -/* - * When we are done transforming a clause, before transforming the next clause - * iterate through the transform entities and mark them as not belonging to - * the clause that is currently being transformed. - */ -static void advance_transform_entities_to_next_clause(List *entities) { - ListCell *lc; - - foreach (lc, entities) { - transform_entity *entity = lfirst(lc); - - entity->declared_in_current_clause = false; - } -} - -/* - * from postgresql parse_sub_analyze - * Entry point for recursively analyzing a sub-statement. - */ Query *cypher_parse_sub_analyze(Node *parseTree, cypher_parsestate *cpstate, CommonTableExpr *parentCTE, bool locked_from_parent, bool resolve_unknowns) { - ParseState *pstate = make_parsestate((ParseState*)cpstate); - cypher_clause *clause; - Query *query; + ParseState *pstate = make_cypher_parsestate((ParseState*)cpstate); pstate->p_parent_cte = parentCTE; pstate->p_locked_from_parent = locked_from_parent; pstate->p_resolve_unknowns = resolve_unknowns; - clause = palloc0(sizeof(cypher_clause)); + cypher_clause *clause = palloc0(sizeof(cypher_clause)); clause->self = parseTree; - query = transform_cypher_clause(cpstate, clause); + Query *query = transform_cypher_clause(pstate, clause); - free_parsestate(pstate); + free_cypher_parsestate(pstate); return query; -} - -/* - * Get a namespace item for the given rte. - */ -static ParseNamespaceItem *get_namespace_item(ParseState *pstate, RangeTblEntry *rte) { - ParseNamespaceItem *pnsi; - ListCell *l; - - foreach(l, pstate->p_namespace) { - pnsi = lfirst(l); - if (rte == pnsi->p_rte) - return pnsi; - } - - return NULL; -} +} \ No newline at end of file diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 9b53c50d..16b83ad9 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 PostGraphDB + * Copyright (C) 2023-2025 PostGraphDB * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c index d26b3ec5..22c5403f 100644 --- a/src/backend/parser/cypher_parse_node.c +++ b/src/backend/parser/cypher_parse_node.c @@ -30,8 +30,6 @@ #include "parser/cypher_parse_node.h" -static void errpos_ecb(void *arg); - // NOTE: sync the logic with make_parsestate() cypher_parsestate *make_cypher_parsestate(cypher_parsestate *parent_cpstate) { @@ -71,41 +69,6 @@ void free_cypher_parsestate(cypher_parsestate *cpstate) free_parsestate((ParseState *)cpstate); } -void setup_errpos_ecb(errpos_ecb_state *ecb_state, ParseState *pstate, - int query_loc) -{ - ecb_state->ecb.previous = error_context_stack; - ecb_state->ecb.callback = errpos_ecb; - ecb_state->ecb.arg = ecb_state; - ecb_state->pstate = pstate; - ecb_state->query_loc = query_loc; - - error_context_stack = &ecb_state->ecb; -} - -void cancel_errpos_ecb(errpos_ecb_state *ecb_state) -{ - error_context_stack = ecb_state->ecb.previous; -} - -/* - * adjust the current error position by adding the position of the current - * query which is a subquery of a parent query - */ -static void errpos_ecb(void *arg) -{ - errpos_ecb_state *ecb_state = arg; - int query_pos; - - if (geterrcode() == ERRCODE_QUERY_CANCELED) - return; - - Assert(ecb_state->query_loc > -1); - query_pos = pg_mbstrlen_with_len(ecb_state->pstate->p_sourcetext, - ecb_state->query_loc); - errposition(query_pos + geterrposition()); -} - /* * Generates a default alias name for when a query needs on and the parse * state does not provide one. diff --git a/src/backend/utils/adt/hashset.c b/src/backend/utils/adt/hashset.c new file mode 100644 index 00000000..ccdf26ef --- /dev/null +++ b/src/backend/utils/adt/hashset.c @@ -0,0 +1,303 @@ +#include "utils/hashset.h" + + + +PG_FUNCTION_INFO_V1(hashset_in); +Datum hashset_in(PG_FUNCTION_ARGS) +{ + ereport(WARNING, (errmsg("hashset_in not implemented"))); + PG_RETURN_NULL(); +} +PG_FUNCTION_INFO_V1(hashset_out); +Datum hashset_out(PG_FUNCTION_ARGS) +{ + ereport(WARNING, (errmsg("hashset_out not implemented"))); + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(hashset_send); +Datum hashset_send(PG_FUNCTION_ARGS) +{ + ereport(WARNING, (errmsg("hashset_send not implemented"))); + PG_RETURN_NULL(); +} + + +PG_FUNCTION_INFO_V1(hashset_recv); +Datum hashset_recv(PG_FUNCTION_ARGS) +{ + ereport(WARNING, (errmsg("hashset_recv not implemented"))); + PG_RETURN_NULL(); +} + + +PG_FUNCTION_INFO_V1(hashset_contains); +Datum hashset_contains(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(true); +// hashset *set = AG_GET_ARG_HASHSET_P(0); +// ereport(WARNING, (errmsg("contains check %i", searchSerializedHashSetInBuffer(set->data, set->data_size, AG_GETARG_GRAPHID(1))))); + // PG_RETURN_BOOL(searchSerializedHashSetInBuffer(set->data, set->data_size, AG_GETARG_GRAPHID(1))); +} + + +size_t getHashSetSize(const HashSetValue* set) { + size_t header_size = sizeof(int) * 2; + size_t index_table_size = (size_t)set->capacity * sizeof(SerializedBucketInfo); + size_t key_data_size = (size_t)set->size * sizeof(graphid); + return header_size + index_table_size + key_data_size; +} + +/** + * @brief Serializes a hash set into a buffer, preserving the bucket structure for faster searching. + * @param set The hash set to serialize. + * @param buffer_size A pointer to a size_t variable to store the size of the created buffer. + * @return A pointer to the newly allocated buffer, or NULL on failure. The caller is responsible for freeing this buffer. + */ +void serializeHashSetToBuffer(const HashSetValue* set, char* buffer, size_t buffer_size) +{ + size_t header_size = sizeof(int) * 2; + size_t index_table_size = (size_t)set->capacity * sizeof(SerializedBucketInfo); + size_t key_data_size = (size_t)set->size * sizeof(graphid); + + + + + char* ptr = buffer; + + // 1. Write metadata header + memcpy(ptr, &set->capacity, sizeof(int)); + ptr += sizeof(int); + memcpy(ptr, &set->size, sizeof(int)); + ptr += sizeof(int); + + // 2. Write the bucket index table and the key data + SerializedBucketInfo* index_table = (SerializedBucketInfo*)ptr; + ptr += index_table_size; // Move pointer past the index table for now + + char* key_data_ptr = ptr; + uint32_t current_key_offset = 0; + + for (int i = 0; i < set->capacity; i++) { + uint32_t key_count_in_bucket = 0; + HashSetNode* currentNode = set->buckets[i]; + + index_table[i].offset = current_key_offset; + + while (currentNode != NULL) { + memcpy(key_data_ptr, ¤tNode->key, sizeof(graphid)); + key_data_ptr += sizeof(graphid); + key_count_in_bucket++; + currentNode = currentNode->next; + } + index_table[i].count = key_count_in_bucket; + current_key_offset += key_count_in_bucket * sizeof(graphid); + } +} +/** + * @brief Deserializes a hash set from a memory buffer. + * @param buffer A pointer to the buffer containing the serialized data. + * @param buffer_size The size of the buffer. + * @return A pointer to the newly created HashSet, or NULL on failure. + */ +HashSetValue* deserializeHashSetFromBuffer(const char* buffer, size_t buffer_size) { + if (buffer == NULL || buffer_size < sizeof(int) * 2) return NULL; + + const char* ptr = buffer; + int capacity, size; + + // 1. Read metadata + memcpy(&capacity, ptr, sizeof(int)); + ptr += sizeof(int); + memcpy(&size, ptr, sizeof(int)); + ptr += sizeof(int); + + // 2. Create a new hash set + HashSetValue* set = createHashSet(capacity); + if (set == NULL) return NULL; + + // 3. Find the start of the key data and insert keys + ptr += (size_t)capacity * sizeof(SerializedBucketInfo); + for (int i = 0; i < size; i++) { + graphid key; + memcpy(&key, ptr, sizeof(graphid)); + ptr += sizeof(graphid); + insert(set, key); + } + + return set; +} + +/** + * @brief Searches for a key in a structured serialized buffer, leveraging the hash structure. + * @param buffer A pointer to the buffer containing the serialized data. + * @param buffer_size The size of the buffer. + * @param key The key to search for. + * @return true if the key is found, false otherwise. + */ +bool searchSerializedHashSetInBuffer(const char* buffer, size_t buffer_size, graphid key) { + + + const char* ptr = buffer; + int capacity; + + // 1. Read capacity to use the hash function + memcpy(&capacity, ptr, sizeof(int)); + + + // 2. Calculate the target bucket index + int bucket_index = hashFunction(key, capacity); + + // 3. Find the metadata for that bucket + const char* index_table_ptr = buffer + sizeof(int) * 2; + SerializedBucketInfo bucket_info; + memcpy(&bucket_info, index_table_ptr + (size_t)bucket_index * sizeof(SerializedBucketInfo), sizeof(SerializedBucketInfo)); + + // 4. Find the start of the key data for that bucket + const char* key_data_start = index_table_ptr + (size_t)capacity * sizeof(SerializedBucketInfo) + bucket_info.offset; + + // 5. Scan only the keys within that bucket + for (uint32_t i = 0; i < bucket_info.count; i++) { + graphid current_key; + memcpy(¤t_key, key_data_start + i * sizeof(graphid), sizeof(graphid)); + if (current_key == key) { + return true; + } + } + + return false; +} + +/** + * @brief Creates a new HashSetNode. + * @param key The graphid key for the new node. + * @return A pointer to the newly created HashSetNode. + */ +HashSetNode* createHashSetNode(graphid key) { + HashSetNode* newNode = (HashSetNode*)palloc(sizeof(HashSetNode)); + newNode->key = key; + newNode->next = NULL; + return newNode; +} + +/** + * @brief Creates and initializes a new HashSetValue. + * @param capacity The initial number of buckets for the hash set. + * @return A pointer to the newly created HashSetValue, or NULL if capacity is invalid. + */ +HashSetValue* createHashSet(int capacity) { + HashSetValue* set = (HashSetValue*)palloc(sizeof(HashSetValue)); + + set->capacity = capacity; + set->size = 0; + set->buckets = (HashSetNode**)palloc0(capacity * sizeof(HashSetNode*)); + + return set; +} + +/** + * @brief A simple hash function to map a key to a bucket index. + * @param key The key to hash. + * @param capacity The capacity of the hash set. + * @return The calculated index for the key. + */ +int hashFunction(graphid key, int capacity) { + return key % capacity; +} + +/** + * @brief Inserts a key into the hash set. + * @param set A pointer to the HashSetValue. + * @param key The key to insert. + */ +void insert(HashSetValue* set, graphid key) { + if (set == NULL) return; + + int index = hashFunction(key, set->capacity); + HashSetNode* currentNode = set->buckets[index]; + + while (currentNode != NULL) { + if (currentNode->key == key) { + return; + } + currentNode = currentNode->next; + } + + HashSetNode* newNode = createHashSetNode(key); + newNode->next = set->buckets[index]; + set->buckets[index] = newNode; + set->size++; +} + +/** + * @brief Checks if a key exists in the hash set. + * @param set A pointer to the HashSetValue. + * @param key The key to search for. + * @return true if the key is found, false otherwise. + */ +bool contains(HashSetValue* set, graphid key) { + if (set == NULL) return false; + + int index = hashFunction(key, set->capacity); + HashSetNode* currentNode = set->buckets[index]; + + while (currentNode != NULL) { + if (currentNode->key == key) { + return true; + } + currentNode = currentNode->next; + } + + return false; +} + +/** + * @brief Removes a key from the hash set. + * @param set A pointer to the HashSetValue. + * @param key The key to remove. + * @return true if the key was found and removed, false otherwise. + */ +bool removeElement(HashSetValue* set, graphid key) { + if (set == NULL) return false; + + int index = hashFunction(key, set->capacity); + HashSetNode* currentNode = set->buckets[index]; + HashSetNode* prevNode = NULL; + + while (currentNode != NULL) { + if (currentNode->key == key) { + if (prevNode == NULL) { + set->buckets[index] = currentNode->next; + } else { + prevNode->next = currentNode->next; + } + pfree(currentNode); + set->size--; + return true; + } + prevNode = currentNode; + currentNode = currentNode->next; + } + + return false; +} + +/** + * @brief Frees all memory associated with the hash set. + * @param set A pointer to the HashSetValue to destroy. + */ +void destroyHashSet(HashSetValue* set) { + if (set == NULL) return; + + for (int i = 0; i < set->capacity; i++) { + HashSetNode* currentNode = set->buckets[i]; + while (currentNode != NULL) { + HashSetNode* temp = currentNode; + currentNode = currentNode->next; + + pfree(temp); + } + } + pfree(set->buckets); + pfree(set); +} diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index eec3efcf..3c4fa761 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -49,6 +49,8 @@ #include "access/vertex.h" #include "utils/ag_cache.h" +#include "utils/hashset.h" + #include "ltree.h" @@ -111,6 +113,8 @@ typedef struct edge_search_cxt PG_FUNCTION_INFO_V1(edge_search); Datum edge_search(PG_FUNCTION_ARGS) { + //ereport(WARNING, (errmsg("edge_search called with graphid %lu", AG_GETARG_GRAPHID(1)))); + FuncCallContext *funcctx; if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -175,9 +179,10 @@ Datum edge_search(PG_FUNCTION_ARGS) values[1] = heap_getattr(heap_tuple, 2, RelationGetDescr(rel), &nulls[1]); values[2] = heap_getattr(heap_tuple, 3, RelationGetDescr(rel), &nulls[2]); values[3] = heap_getattr(heap_tuple, 4, RelationGetDescr(rel), &nulls[3]); - + //ereport(WARNING, (errmsg("edge_search sending graphid %lu", DATUM_GET_GRAPHID(values[2])))); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); } +/* // Node for the linked list to handle collisions (chaining) typedef struct HashSetNode { graphid key; @@ -185,148 +190,14 @@ typedef struct HashSetNode { } HashSetNode; // The main Hash Set structure -typedef struct HashSet { +typedef struct HashSetValue { HashSetNode** buckets; // Array of pointers to HashSetNodes (the buckets) int capacity; // The total number of buckets int size; // The current number of elements in the set -} HashSet; - - - -/** - * @brief Creates a new HashSetNode. - * @param key The graphid key for the new node. - * @return A pointer to the newly created HashSetNode. - */ -HashSetNode* createHashSetNode(graphid key) { - HashSetNode* newNode = (HashSetNode*)palloc(sizeof(HashSetNode)); - newNode->key = key; - newNode->next = NULL; - return newNode; -} - -/** - * @brief Creates and initializes a new HashSet. - * @param capacity The initial number of buckets for the hash set. - * @return A pointer to the newly created HashSet, or NULL if capacity is invalid. - */ -HashSet* createHashSet(int capacity) { - HashSet* set = (HashSet*)palloc(sizeof(HashSet)); - - set->capacity = capacity; - set->size = 0; - set->buckets = (HashSetNode**)palloc0(capacity * sizeof(HashSetNode*)); - - return set; -} +} HashSetValue; +*/ -/** - * @brief A simple hash function to map a key to a bucket index. - * @param key The key to hash. - * @param capacity The capacity of the hash set. - * @return The calculated index for the key. - */ -int hashFunction(graphid key, int capacity) { - return key % capacity; -} - -/** - * @brief Inserts a key into the hash set. - * @param set A pointer to the HashSet. - * @param key The key to insert. - */ -void insert(HashSet* set, graphid key) { - if (set == NULL) return; - - int index = hashFunction(key, set->capacity); - HashSetNode* currentNode = set->buckets[index]; - while (currentNode != NULL) { - if (currentNode->key == key) { - return; - } - currentNode = currentNode->next; - } - - HashSetNode* newNode = createHashSetNode(key); - newNode->next = set->buckets[index]; - set->buckets[index] = newNode; - set->size++; -} - -/** - * @brief Checks if a key exists in the hash set. - * @param set A pointer to the HashSet. - * @param key The key to search for. - * @return true if the key is found, false otherwise. - */ -bool contains(HashSet* set, graphid key) { - if (set == NULL) return false; - - int index = hashFunction(key, set->capacity); - HashSetNode* currentNode = set->buckets[index]; - - while (currentNode != NULL) { - if (currentNode->key == key) { - return true; - } - currentNode = currentNode->next; - } - - return false; -} - -/** - * @brief Removes a key from the hash set. - * @param set A pointer to the HashSet. - * @param key The key to remove. - * @return true if the key was found and removed, false otherwise. - */ -bool removeElement(HashSet* set, graphid key) { - if (set == NULL) return false; - - int index = hashFunction(key, set->capacity); - HashSetNode* currentNode = set->buckets[index]; - HashSetNode* prevNode = NULL; - - while (currentNode != NULL) { - if (currentNode->key == key) { - if (prevNode == NULL) { - set->buckets[index] = currentNode->next; - } else { - prevNode->next = currentNode->next; - } - // Use the environment-specific free function - pfree(currentNode); - set->size--; - return true; - } - prevNode = currentNode; - currentNode = currentNode->next; - } - - return false; -} - -/** - * @brief Frees all memory associated with the hash set. - * @param set A pointer to the HashSet to destroy. - */ -void destroyHashSet(HashSet* set) { - if (set == NULL) return; - - for (int i = 0; i < set->capacity; i++) { - HashSetNode* currentNode = set->buckets[i]; - while (currentNode != NULL) { - HashSetNode* temp = currentNode; - currentNode = currentNode->next; - // Use the environment-specific free function - pfree(temp); - } - } - pfree(set->buckets); - pfree(set); -} typedef struct variable_edge_stack @@ -349,9 +220,10 @@ typedef struct variable_edge_search_cxt TupleTableSlot *slot; int min; Oid graph_oid; - HashSet *hashSet; + HashSetValue *hashSet; variable_edge_stack *stack; graph_stack_count *current_path; + variable_edge_stack *edge_stack; int current_path_length; } variable_edge_search_cxt; @@ -396,6 +268,12 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) cxt->slot->tts_ops->materialize(cxt->slot); bool isnull; + graphid edge_id = DATUM_GET_GRAPHID( + heap_getattr( + cxt->slot->tts_ops->get_heap_tuple(cxt->slot), + 1, + RelationGetDescr(cxt->scan_desc->rs_rd), + &isnull)); graphid new_id = DATUM_GET_GRAPHID( heap_getattr( cxt->slot->tts_ops->get_heap_tuple(cxt->slot), @@ -403,9 +281,13 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) RelationGetDescr(cxt->scan_desc->rs_rd), &isnull)); - if (!contains(cxt->hashSet, new_id)) { + //ereport(WARNING, errmsg("trying to push %lu with edge id %lu", new_id, edge_id)); + if (!contains(cxt->hashSet, edge_id)) { + + //ereport(WARNING, errmsg("pushing %lu", new_id)); cxt->current_path[cxt->current_path_length].count++; variable_edge_stack_push(cxt->stack, new_id); + variable_edge_stack_push(cxt->edge_stack, edge_id); } } ReleaseTupleDesc(RelationGetDescr(cxt->rel)); @@ -416,6 +298,7 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) PG_FUNCTION_INFO_V1(variable_edge_search); Datum variable_edge_search(PG_FUNCTION_ARGS) { + //ereport(WARNING, (errmsg("variable_edge_search called with graphid %lu and min %d", AG_GETARG_GRAPHID(1), GT_ARG_TO_INT4_DATUM(2)))); FuncCallContext *funcctx; if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -425,9 +308,10 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - tupdesc = CreateTemplateTupleDesc(2); + tupdesc = CreateTemplateTupleDesc(3); TupleDescInitEntry(tupdesc, 1, "edges", VARIABLEEDGEOID, -1, 0); TupleDescInitEntry(tupdesc, 2, "endid", GRAPHIDOID, -1, 0); + TupleDescInitEntry(tupdesc, 3, "hset", HASHSETOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -440,11 +324,17 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) cxt->stack->top = 0; cxt->stack->capacity = 1024; + cxt->edge_stack = palloc0(sizeof(variable_edge_stack)); + cxt->edge_stack->array = palloc0(sizeof(graphid) * (1024)); + cxt->edge_stack->top = 0; + cxt->edge_stack->capacity = 1024; + + cxt->current_path = palloc(sizeof(graph_stack_count) * (cxt->min + 1)); cxt->current_path_length = 0; cxt->hashSet = createHashSet(1024); - insert(cxt->hashSet, GRAPHID_GET_DATUM(id)); + //insert(cxt->hashSet, GRAPHID_GET_DATUM(id)); scan_and_push_neighbors(cxt, id); funcctx->user_fctx = cxt; @@ -455,29 +345,37 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) variable_edge_search_cxt *cxt = (variable_edge_search_cxt *) funcctx->user_fctx; - while (cxt->hashSet->size > 0) { + //while (cxt->hashSet->size > 0) { + while (cxt->stack->top > 0) { graphid id; + graphid edge_id; - - if(!variable_edge_stack_pop(cxt->stack, &id)){ - //destroyHashSet(cxt->hashSet); + if(!variable_edge_stack_pop(cxt->stack, &id) || !variable_edge_stack_pop(cxt->edge_stack, &edge_id)) SRF_RETURN_DONE(funcctx); - } cxt->current_path[++cxt->current_path_length].count = 0; cxt->current_path[cxt->current_path_length].count = id; - insert(cxt->hashSet, GRAPHID_GET_DATUM(id)); - - if (cxt->hashSet->size == cxt->min + 1) { - Datum values[2]; - bool nulls[2]; + insert(cxt->hashSet, edge_id); + //ereport(WARNING, errmsg("cxt->hashSet->size %i, %lu", cxt->hashSet->size, id)); + if (cxt->hashSet->size == cxt->min) { + Datum values[3]; + bool nulls[3]; values[0] = NULL; nulls[0] = true; values[1] = GRAPHID_GET_DATUM(id); nulls[1] = false; - removeElement(cxt->hashSet, id); + + hashset *hset = palloc(sizeof(hashset) + getHashSetSize(cxt->hashSet)); + hset->data_size = getHashSetSize(cxt->hashSet); + serializeHashSetToBuffer(cxt->hashSet, hset->data, getHashSetSize(cxt->hashSet)); + SET_VARSIZE(hset, VARHDRSZ + sizeof(size_t) + hset->data_size); + + values[2] = HASHSET_P_GET_DATUM(hset); + nulls[2] = false; + removeElement(cxt->hashSet, edge_id); + //ereport(WARNING, errmsg("sending %lu", id)); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); } else { scan_and_push_neighbors(cxt, id); @@ -485,10 +383,11 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) if (cxt->current_path[++cxt->current_path_length].count == 0) { variable_edge_stack_pop(cxt->stack, &cxt->current_path[cxt->current_path_length--]); - removeElement(cxt->hashSet, id); + variable_edge_stack_pop(cxt->edge_stack, &edge_id); + removeElement(cxt->hashSet, edge_id); } } - //destroyHashSet(cxt->hashSet); + //destroyHashSetValue(cxt->hashSet); SRF_RETURN_DONE(funcctx); diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index fcc87b39..0a97ebf3 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -196,6 +196,7 @@ typedef struct cypher_node bool has_variable; bool is_default_label; bool in_join_tree; + bool declared_in_previous_clause; } cypher_node; typedef enum diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h index c0777906..b386196d 100644 --- a/src/include/parser/cypher_parse_node.h +++ b/src/include/parser/cypher_parse_node.h @@ -35,7 +35,6 @@ typedef struct cypher_parsestate uint32 graph_oid; Param *params; int default_alias_num; - List *entities; List *property_constraint_quals; /* * To flag when an aggregate has been found in an expression during an @@ -50,20 +49,12 @@ typedef struct cypher_parsestate Node *prop_name; } cypher_parsestate; -typedef struct errpos_ecb_state -{ - ErrorContextCallback ecb; - ParseState *pstate; // ParseState of query that has subquery being parsed - int query_loc; // location of subquery starting from p_sourcetext -} errpos_ecb_state; + cypher_parsestate *make_cypher_parsestate(cypher_parsestate *parent_cpstate); void free_cypher_parsestate(cypher_parsestate *cpstate); #define get_parse_state(cpstate) ((ParseState *)(cpstate)) -void setup_errpos_ecb(errpos_ecb_state *ecb_state, ParseState *pstate, - int query_loc); -void cancel_errpos_ecb(errpos_ecb_state *ecb_state); char *get_next_default_alias(cypher_parsestate *cpstate); #endif diff --git a/src/include/utils/gtype.h b/src/include/utils/gtype.h index 964e2c35..b53ec1f6 100644 --- a/src/include/utils/gtype.h +++ b/src/include/utils/gtype.h @@ -117,7 +117,6 @@ typedef enum #define GT_GIN_FLAG_HASHED 0x10 // OR'd into flag if value was hashed #define GT_GIN_MAX_LENGTH 125 // max length of text part before hashing -/* Convenience macros */ #define DATUM_GET_GTYPE_P(d) ((gtype *)PG_DETOAST_DATUM(d)) #define GTYPE_P_GET_DATUM(p) PointerGetDatum(p) #define AG_GET_ARG_GTYPE_P(x) DATUM_GET_GTYPE_P(PG_GETARG_DATUM(x)) diff --git a/src/include/utils/hashset.h b/src/include/utils/hashset.h new file mode 100644 index 00000000..21d1c871 --- /dev/null +++ b/src/include/utils/hashset.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2025 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +#ifndef POSTGRAPH_HASHSET_H +#define POSTGRAPH_HASHSET_H + +#include "utils/graphid.h" + +#define DATUM_GET_HASHSET_P(d) ((hashset *)PG_DETOAST_DATUM(d)) +#define HASHSET_P_GET_DATUM(p) PointerGetDatum(p) +#define AG_GET_ARG_HASHSET_P(x) DATUM_GET_HASHSET_P(PG_GETARG_DATUM(x)) +#define AG_RETURN_HASHSET_P(x) PG_RETURN_POINTER(x) + +typedef struct +{ + int32 vl_len_; + size_t data_size; + char data[FLEXIBLE_ARRAY_MEMBER]; +} hashset; + +// New struct for the serialized bucket metadata +typedef struct SerializedBucketInfo { + uint32_t offset; // Byte offset from the start of the key data block + uint32_t count; // Number of keys in this bucket +} SerializedBucketInfo; + +typedef struct HashSetNode { + graphid key; + struct HashSetNode* next; +} HashSetNode; + +typedef struct HashSetValue { + HashSetNode** buckets; + int capacity; + int size; +} HashSetValue; + + + +HashSetNode* createHashSetNode(graphid key); +HashSetValue* createHashSet(int capacity); +int hashFunction(graphid key, int capacity); +void insert(HashSetValue* set, graphid key); +bool contains(HashSetValue* set, graphid key); +bool removeElement(HashSetValue* set, graphid key); +void destroyHashSet(HashSetValue* set); +void printHashSet(HashSetValue* set); +void serializeHashSetToBuffer(const HashSetValue* set, char* buffer, size_t buffer_size); +HashSetValue* deserializeHashSetFromBuffer(const char* buffer, size_t buffer_size); +bool searchSerializedHashSetInBuffer(const char* buffer, size_t buffer_size, graphid key); + +// Oid accessors for HASHSET +#define HASHSETOID \ + (GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, CStringGetDatum("hashset"), ObjectIdGetDatum(postgraph_namespace_id()))) +#define HASHSETARRAYOID \ + (GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, CStringGetDatum("_hashset"), ObjectIdGetDatum(postgraph_namespace_id()))) + +#endif From e2a1e77e260b129bfd86e67f66322506537eb787 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Fri, 22 Aug 2025 02:33:20 -0700 Subject: [PATCH 40/56] Strip out transform_entity logic gone. was needed at one point, now it isn't should simplify everything in the parser --- Makefile | 1 - regress/sql/cypher_create.sql | 1 - src/backend/parser/cypher_analyze.c | 1 - src/backend/parser/cypher_expr.c | 18 +-- src/backend/parser/cypher_grouping.c | 1 - src/backend/parser/cypher_item.c | 39 +---- src/backend/parser/cypher_parse_node.c | 4 - src/backend/parser/cypher_transform_entity.c | 156 ------------------- src/backend/utils/edge_searching.c | 2 - src/include/parser/cypher_transform_entity.h | 100 ------------ 10 files changed, 6 insertions(+), 317 deletions(-) delete mode 100644 src/backend/parser/cypher_transform_entity.c delete mode 100644 src/include/parser/cypher_transform_entity.h diff --git a/Makefile b/Makefile index 5e2a7500..192d0861 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,6 @@ OBJS = src/backend/postgraph.o \ src/backend/parser/cypher_parse_agg.o \ src/backend/parser/cypher_parse_node.o \ src/backend/parser/cypher_parser.o \ - src/backend/parser/cypher_transform_entity.o \ src/backend/utils/adt/gtype.o \ src/backend/utils/adt/gtype_ext.o \ src/backend/utils/adt/gtype_gin.o \ diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 9228e776..9a10e8f5 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -86,7 +86,6 @@ EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; MATCH (q)-[]->(q)-[]->() RETURN 1; - EXPLAIN ANALYZE MATCH (q) MATCH (q)-[]->() diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index 59a3458e..ceb06326 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -1346,7 +1346,6 @@ analyze_cypher(List *stmt, ParseState *parent_pstate, const char *query_str, int cpstate->graph_oid = graph_oid; cpstate->params = params; cpstate->default_alias_num = 0; - cpstate->entities = NIL; Query *query = transform_cypher_clause(cpstate, clause); diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 16b83ad9..3bad05da 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -57,7 +57,6 @@ #include "parser/cypher_expr.h" #include "parser/cypher_item.h" #include "parser/cypher_parse_node.h" -#include "parser/cypher_transform_entity.h" #include "utils/ag_func.h" #include "utils/gtype.h" #include "utils/gtype_typecasting.h" @@ -1953,7 +1952,7 @@ transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { ParseNamespaceItem *pnsi; if (list_length(cref->fields) == 1) { - transform_entity *te; + field1 = (Node*)linitial(cref->fields); Assert(IsA(field1, String)); @@ -1963,11 +1962,7 @@ transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { if (node != NULL) return node; - te = find_variable(cpstate, colname) ; - if (te != NULL && te->expr != NULL) { - node = (Node *)te->expr; - return node; - } else { + { ParseNamespaceItem *nsitem = refnameNamespaceItem(pstate, NULL, colname, cref->location, &levels_up); if (nsitem) { @@ -1988,7 +1983,7 @@ transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { } Node *last_srf = pstate->p_last_srf; - transform_entity *te; + field1 = (Node*)linitial(cref->fields); Assert(IsA(field1, String)); @@ -1996,10 +1991,7 @@ transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { node = colNameToVar(pstate, colname, false, cref->location); if (node == NULL){ - te = find_variable(cpstate, colname) ; - if (te != NULL && te->expr != NULL) { - node = (Node *)te->expr; - } else { + ParseNamespaceItem *nsitem = refnameNamespaceItem(pstate, NULL, colname, cref->location, &levels_up); if (nsitem) { @@ -2009,7 +2001,7 @@ transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("could not find rte for %s", colname), parser_errposition(pstate, cref->location))); - } + } if (!node) diff --git a/src/backend/parser/cypher_grouping.c b/src/backend/parser/cypher_grouping.c index d1aa6d3c..18101509 100644 --- a/src/backend/parser/cypher_grouping.c +++ b/src/backend/parser/cypher_grouping.c @@ -41,7 +41,6 @@ #include "parser/cypher_item.h" #include "parser/cypher_parse_agg.h" #include "parser/cypher_parse_node.h" -#include "parser/cypher_transform_entity.h" #include "utils/ag_cache.h" #include "utils/ag_func.h" #include "utils/gtype.h" diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index a14b6ab1..e4e4747f 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -38,7 +38,6 @@ #include "parser/cypher_item.h" #include "parser/cypher_parse_node.h" -static List *ExpandAllTables(ParseState *pstate, int location); static List *expand_rel_attrs(ParseState *pstate, RangeTblEntry *rte, int rtindex, int sublevels_up, int location); @@ -206,7 +205,7 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, /* clear the exprHasAgg flag to check transform for an aggregate */ cpstate->exprHasAgg = false; - // if (item->name[0] == '_' && item->name[1] == '_') + //if (item->name[0] == '_' && item->name[1] == '_') // continue; /* transform the item */ te = transform_cypher_item(cpstate, item->val, NULL, expr_kind, item->name, false); @@ -236,42 +235,6 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, return target_list; } -/* - * From PG's ExpandAllTables() - * Transforms '*' (in the target list) into a list of targetlist entries. - */ -static List *ExpandAllTables(ParseState *pstate, int location) -{ - List *target = NIL; - bool found_table = false; - ListCell *l; - - foreach(l, pstate->p_namespace) - { - ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l); - - /* Ignore table-only items */ - if (!nsitem->p_cols_visible) - continue; - /* Should not have any lateral-only items when parsing targetlist */ - Assert(!nsitem->p_lateral_only); - /* Remember we found a p_cols_visible item */ - found_table = true; - - target = list_concat(target, expand_rel_attrs(pstate, nsitem->p_rte, - nsitem->p_rtindex, - 0, location)); - } - - /* Check for "RETURN *;" */ - if (!found_table) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("RETURN * without a pattern is not valid"), - parser_errposition(pstate, location))); - - return target; -} - /* * From PG's expandRelAttrs * Modified to exclude hidden variables and aliases in RETURN * diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c index 22c5403f..3803eace 100644 --- a/src/backend/parser/cypher_parse_node.c +++ b/src/backend/parser/cypher_parse_node.c @@ -78,18 +78,14 @@ char *get_next_default_alias(cypher_parsestate *cpstate) char *alias_name; int nlen = 0; - /* get the length of the combinded string */ nlen = snprintf(NULL, 0, "%s%d", AGE_DEFAULT_ALIAS_PREFIX, cpstate->default_alias_num); - /* allocate the space */ alias_name = palloc0(nlen + 1); - /* create the name */ snprintf(alias_name, nlen + 1, "%s%d", AGE_DEFAULT_ALIAS_PREFIX, cpstate->default_alias_num); - /* increment the default alias number */ cpstate->default_alias_num++; return alias_name; diff --git a/src/backend/parser/cypher_transform_entity.c b/src/backend/parser/cypher_transform_entity.c deleted file mode 100644 index 5b63244d..00000000 --- a/src/backend/parser/cypher_transform_entity.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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. - */ - -#include "postgres.h" - -#include "parser/cypher_transform_entity.h" - -// creates a transform entity -transform_entity *make_transform_entity(cypher_parsestate *cpstate, - enum transform_entity_type type, - Node *node, Expr *expr, char *alias) -{ - transform_entity *entity; - - entity = palloc0(sizeof(transform_entity)); - - entity->type = type; - if (type == ENT_VERTEX) - entity->entity.node = (cypher_node *)node; - else if (entity->type == ENT_EDGE || entity->type == ENT_VLE_EDGE) - entity->entity.rel = (cypher_relationship *)node; - else if (type == ENT_FUNC_CALL) - entity->entity.rel = NULL; - else - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unknown entity type"))); - - entity->alias = alias; - entity->declared_in_current_clause = true; - entity->expr = expr; - entity->in_join_tree = expr != NULL; - - return entity; -} - -enum transform_entity_type find_transform_entity_type(cypher_parsestate *cpstate, char *name) -{ - ListCell *lc; - - foreach(lc, cpstate->entities) - { - transform_entity *entity = lfirst(lc); - - if (entity->type == ENT_VERTEX && entity->entity.node->name != NULL && !strcmp(entity->entity.node->name, name)) - return ENT_VERTEX; - else if (entity->type == ENT_EDGE && entity->entity.node->name != NULL && !strcmp(entity->entity.rel->name, name)) - return ENT_EDGE; - else if (entity->type == ENT_VLE_EDGE && entity->entity.node->name != NULL && !strcmp(entity->entity.rel->name, name)) - return ENT_VLE_EDGE; - - } - - return -1; -} - - -/* - * Finds the transform_entity in the cypher_parstate for a the given name and - * type. - */ -transform_entity *find_transform_entity(cypher_parsestate *cpstate, - char *name, - enum transform_entity_type type) -{ - ListCell *lc; - - foreach(lc, cpstate->entities) - { - transform_entity *entity = lfirst(lc); - - - if (type == ENT_VERTEX && entity->entity.node->name) - { - if (!strcmp(entity->entity.node->name, name)) - return entity; - } - else if (type == ENT_EDGE || type == ENT_VLE_EDGE) - { - if (entity->entity.node->name && !strcmp(entity->entity.rel->name, name)) - return entity; - } - } - - return NULL; -} - -/* - * Iterate through the cypher_parsestate's transform_entities and returns - * the entity with name passed by name variable. - */ -transform_entity *find_variable(cypher_parsestate *cpstate, char *name) -{ - ListCell *lc; - - foreach (lc, cpstate->entities) - { - transform_entity *entity = lfirst(lc); - char *entity_name; - - if (entity->type == ENT_VERTEX) { - entity_name = entity->entity.node->name; - } else if (entity->type == ENT_EDGE || entity->type == ENT_VLE_EDGE) { - entity_name = entity->entity.rel->name; - } else if (entity->type == ENT_FUNC_CALL) { - entity_name = entity->alias; - }else { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unknown entity type"))); - } - - if (entity_name != NULL && !strcmp(name, entity_name)) - { - return entity; - } - } - - return NULL; -} - -// helper function that extracts the name associated with the transform_entity. -char *get_entity_name(transform_entity *entity) -{ - if (entity->type == ENT_EDGE || entity->type == ENT_VLE_EDGE) - { - return entity->entity.rel->name; - } - else if (entity->type == ENT_VERTEX) - { - return entity->entity.node->name; - } - else - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot get entity name from transform_entity type %i", - entity->type))); - } - - return NULL; -} diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 3c4fa761..ca6d0259 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -389,6 +389,4 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) } //destroyHashSetValue(cxt->hashSet); SRF_RETURN_DONE(funcctx); - - } diff --git a/src/include/parser/cypher_transform_entity.h b/src/include/parser/cypher_transform_entity.h deleted file mode 100644 index 5d015356..00000000 --- a/src/include/parser/cypher_transform_entity.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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. - */ - -#ifndef AG_CYPHER_TRANSFORM_ENTITY_H -#define AG_CYPHER_TRANSFORM_ENTITY_H - -#include "nodes/primnodes.h" -#include "parser/parse_node.h" - -#include "nodes/cypher_nodes.h" -#include "parser/cypher_parse_node.h" - -enum transform_entity_type -{ - ENT_VERTEX = 0x0, - ENT_EDGE, - ENT_VLE_EDGE, - ENT_FUNC_CALL -}; - -enum transform_entity_join_side -{ - JOIN_SIDE_LEFT = 0x0, - JOIN_SIDE_RIGHT -}; - -/* - * In the transformation stage, we need to track - * where a variable came from. When moving between - * clauses, Postgres parsestate and Query data structures - * are insufficient for some of the information we - * need. - */ -typedef struct -{ - // denotes whether this entity is a vertex or edge - enum transform_entity_type type; - - /* - * MATCH clauses are transformed into a select * FROM ... JOIN, etc - * We need to know wheter the table that this entity represents is - * part of the join tree. If a cypher_node does not meet the conditions - * set in INCLUDE_NODE_IN_JOIN_TREE. Then we can skip the node when - * constructing our join tree. The entities around this particular entity - * need to know this for the join to get properly constructed. - */ - bool in_join_tree; - - /* - * The parse data structure will be transformed into an Expr that represents - * the entity. When contructing the join tree, we need to know what it was - * turned into. If the entity was originally created in a previous clause, - * this will be a Var that we need to reference to extract the id, startid, - * endid for the join. If the entity was created in the current clause, then - * this will be a FuncExpr that we can reference to get the id, startid, and - * endid. - */ - Expr *expr; - - char *alias; - /* - * tells each clause whether this variable was - * declared by itself or a previous clause. - */ - bool declared_in_current_clause; - // The parse data structure that we transformed - union - { - cypher_node *node; - cypher_relationship *rel; - } entity; -} transform_entity; - -transform_entity *find_variable(cypher_parsestate *cpstate, char *name); -transform_entity *find_transform_entity(cypher_parsestate *cpstate, - char *name, - enum transform_entity_type type); -transform_entity *make_transform_entity(cypher_parsestate *cpstate, - enum transform_entity_type type, - Node *node, Expr *expr, char *alias); -char *get_entity_name(transform_entity *entity); -enum transform_entity_type find_transform_entity_type(cypher_parsestate *cpstate, char *name); - -#endif From 5656047e86f7c7b4ba62377d26ddc11e5563afd9 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Mon, 25 Aug 2025 00:23:51 -0700 Subject: [PATCH 41/56] Cleanup Expression logic --- Makefile | 3 - regress/sql/cypher_create.sql | 4 + src/backend/executor/cypher_create.c | 195 +- src/backend/parser/cypher_analyze.c | 3 + src/backend/parser/cypher_clause.c | 371 +-- src/backend/parser/cypher_expr.c | 2333 ++---------------- src/backend/parser/cypher_grouping.c | 873 ------- src/backend/parser/cypher_parse_agg.c | 546 ---- src/backend/parser/cypher_parse_node.c | 4 +- src/backend/parser/sql_expr.c | 3153 ------------------------ src/include/parser/cypher_clause.h | 6 +- src/include/parser/cypher_expr.h | 33 +- src/include/parser/cypher_parse_agg.h | 32 - 13 files changed, 405 insertions(+), 7151 deletions(-) delete mode 100644 src/backend/parser/cypher_grouping.c delete mode 100644 src/backend/parser/cypher_parse_agg.c delete mode 100644 src/backend/parser/sql_expr.c delete mode 100644 src/include/parser/cypher_parse_agg.h diff --git a/Makefile b/Makefile index 192d0861..911719f0 100644 --- a/Makefile +++ b/Makefile @@ -36,13 +36,10 @@ OBJS = src/backend/postgraph.o \ src/backend/parser/ag_scanner.o \ src/backend/parser/cypher_analyze.o \ src/backend/parser/cypher_clause.o \ - src/backend/parser/sql_expr.o \ src/backend/parser/cypher_expr.o \ src/backend/parser/cypher_gram.o \ - src/backend/parser/cypher_grouping.o \ src/backend/parser/cypher_item.o \ src/backend/parser/cypher_keywords.o \ - src/backend/parser/cypher_parse_agg.o \ src/backend/parser/cypher_parse_node.o \ src/backend/parser/cypher_parser.o \ src/backend/utils/adt/gtype.o \ diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 9a10e8f5..5b3de4b0 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -48,6 +48,10 @@ MATCH (a:test) RETURN a; CREATE ()-[]->(); +CREATE (a)-[]->(); + +CREATE (a)-[]->() RETURN a; + CREATE ()-[:elabel]->(); MATCH ()-[]->() RETURN 1; diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index f016cc30..194e161e 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -40,18 +40,11 @@ #include "utils/ag_cache.h" #include "utils/traversal.h" -/* - * When executing the children of the CREATE, SET, REMOVE, and - * DELETE clasues, we need to alter the command id in the estate - * and the snapshot. That way we can hide the modified tuples from - * the sub clauses that should not know what their parent clauses are - * doing. - */ -#define Increment_Estate_CommandId(estate) \ +#define AdvanceCmdId(estate) \ estate->es_output_cid++; \ estate->es_snapshot->curcid++; -#define Decrement_Estate_CommandId(estate) \ +#define RollbackCmdId(estate) \ estate->es_output_cid--; \ estate->es_snapshot->curcid--; @@ -71,23 +64,12 @@ typedef struct cypher_create_custom_scan_state static HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, - EState *estate); -static bool entity_exists(EState *estate, Oid graph_oid, graphid id); + EState *estate);\ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflags); static TupleTableSlot *exec_cypher_create(CustomScanState *node); static void end_cypher_create(CustomScanState *node); static void rescan_cypher_create(CustomScanState *node); -static void insert_edge(cypher_create_custom_scan_state *css, - cypher_target_node *node, Datum prev_vertex_id, - ListCell *next, List *list); - -static void insert_vertex(cypher_create_custom_scan_state *css, - cypher_target_node *node, ListCell *next, List *list); - -static void process_pattern(cypher_create_custom_scan_state *css); - - const CustomExecMethods cypher_create_exec_methods = {CREATE_SCAN_STATE_NAME, begin_cypher_create, exec_cypher_create, @@ -164,15 +146,13 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag cypher_create_path *path = linitial(css->pattern); - - css->vertex_ids = palloc(sizeof(graphid *) * list_length(css->pattern)); css->vertex_ids[0] = palloc(sizeof(graphid) * ((list_length(path->target_nodes)/ 2) + 1) ); if (path->target_nodes) { css->edge_ids = palloc(sizeof(graphid *) * list_length(css->pattern)); - css->edge_ids[0] = palloc(sizeof(graphid) * ((list_length(path->target_nodes)/ 2) ) ); - } + css->edge_ids[0] = palloc(sizeof(graphid) * ((list_length(path->target_nodes) / 2) ) ); + } ListCell *lc1; int i = 0; @@ -201,10 +181,8 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag cypher_node->adj_resultRelInfo = makeNode(ResultRelInfo); InitResultRelInfo(cypher_node->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); - // Open all indexes for the relation ExecOpenIndices(cypher_node->adj_resultRelInfo, false); - // Setup the relation's tuple slot cypher_node->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); @@ -215,49 +193,7 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag if (estate->es_output_cid == 0) estate->es_output_cid = estate->es_snapshot->curcid; - Increment_Estate_CommandId(estate); -} - -/* - * CREATE the vertices and edges for a CREATE clause pattern. - */ -static void process_pattern(cypher_create_custom_scan_state *css) -{ - ListCell *lc2; - - foreach (lc2, css->pattern) - { - cypher_create_path *path = lfirst(lc2); - - ListCell *lc = list_head(path->target_nodes); - - /* - * Create the first vertex. The create_vertex function will - * create the rest of the path, if necessary. - */ - insert_vertex(css, lfirst(lc), lnext(path->target_nodes, lc), path->target_nodes); - - /* - * If this path is a variable, take the list that was accumulated - * in the vertex/edge creation, create a path datum, and add to the - * scantuple slot. - */ - if (path->path_attr_num != InvalidAttrNumber) - { - TupleTableSlot *scantuple; - PlanState *ps; - Datum result; - - ps = css->css.ss.ps.lefttree; - scantuple = ps->ps_ExprContext->ecxt_scantuple; - - result = create_traversal(css->path_values); - scantuple->tts_values[path->path_attr_num - 1] = result; - scantuple->tts_isnull[path->path_attr_num - 1] = false; - } - - css->path_values = NIL; - } + AdvanceCmdId(estate); } static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) @@ -267,10 +203,11 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) EState *estate = css->css.ss.ps.state; ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - //econtext->ecxt_scantuple = - // csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; + + RollbackCmdId(estate); TupleTableSlot *slot = ExecProcNode(csnode->ss.ps.lefttree); + AdvanceCmdId(estate); + slot = csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; slot->tts_ops->materialize(slot); if (list_length(css->pattern) != 1) @@ -311,18 +248,27 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) // get the next graphid for this vertex. elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; elemTupleSlot->tts_isnull[0] = false; + if (node->id_attr_num != InvalidAttrNumber) { + slot->tts_values[node->id_attr_num - 1] = GRAPHID_GET_DATUM(css->vertex_ids[0][i]); + slot->tts_isnull[node->id_attr_num - 1] = false; + } // get the properties for this vertex if (node->prop_attr_num == InvalidAttrNumber) { elemTupleSlot->tts_values[1] = NULL; elemTupleSlot->tts_isnull[1] = true; } else { - //TupleTableSlot *scanTupleSlot = csnode->ss.ss_ScanTupleSlot; - //ereport(WARNING, errmsg("adsf %i", scanTupleSlot->tts_values[node->prop_attr_num])); - elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->prop_attr_num - 1]; } + if (node->tuple_position != InvalidAttrNumber) { + create_vertex( + GRAPHID_GET_DATUM(slot->tts_values[node->id_attr_num - 1]), + css->graph_oid, + node->prop_attr_num == InvalidAttrNumber? NULL: DATUM_GET_GTYPE_P(slot->tts_values[node->prop_attr_num - 1])); + } + // Insert the new vertex insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); @@ -341,7 +287,7 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; elemTupleSlot->tts_isnull[1] = false; - elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i+1 : i]; + elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; elemTupleSlot->tts_isnull[2] = false; @@ -350,8 +296,8 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) elemTupleSlot->tts_isnull[3] = true; } else { //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num]; - elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num]; + elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; } // Insert the new vertex @@ -430,52 +376,6 @@ Node *create_cypher_create_plan_state(CustomScan *cscan) return (Node *)cypher_css; } - -/* - * Creates the vertex entity, returns the vertex's id in case the caller is - * the create_edge function. - */ -static void insert_vertex(cypher_create_custom_scan_state *css, - cypher_target_node *node, ListCell *next, List *list) -{ - bool isNull; - Datum id; - EState *estate = css->css.ss.ps.state; - ExprContext *econtext = css->css.ss.ps.ps_ExprContext; - ResultRelInfo *resultRelInfo = node->resultRelInfo; - TupleTableSlot *elemTupleSlot = node->elemTupleSlot; - TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - - /* - * Vertices in a path might already exists. If they do get the id - * to pass to the edges before and after it. Otherwise, insert the - * new vertex into it's table and then pass the id along. - */ - ResultRelInfo **old_estate_es_result_relations_info = NULL; - - old_estate_es_result_relations_info = estate->es_result_relations; - - estate->es_result_relations = &resultRelInfo; - - ExecClearTuple(elemTupleSlot); - - // get the next graphid for this vertex. - id = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - elemTupleSlot->tts_values[0] = id; - elemTupleSlot->tts_isnull[0] = isNull; - - // get the properties for this vertex - elemTupleSlot->tts_values[1] = NULL; - elemTupleSlot->tts_isnull[1] = true; - - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - - /* restore the old result relation info */ - estate->es_result_relations = old_estate_es_result_relations_info; -} - - /* * Insert the edge/vertex tuple into the table and indices. Check that the * table's constraints have not been violated. @@ -511,48 +411,3 @@ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, return NULL; } - - -/* - * Find out if the entity still exists. This is for 'implicit' deletion - * of an entity. - */ -bool entity_exists(EState *estate, Oid graph_oid, graphid id) -{ - label_cache_data *label; - ScanKeyData scan_keys[1]; - TableScanDesc scan_desc; - HeapTuple tuple; - Relation rel; - bool result = true; - - /* - * Extract the label id from the graph id and get the table name - * the entity is part of. - */ - label = search_label_graph_oid_cache(graph_oid, GET_LABEL_ID(id)); - - // Setup the scan key to be the graphid - ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, - F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); - - rel = table_open(label->relation, RowExclusiveLock); - scan_desc = table_beginscan(rel, estate->es_snapshot, 1, scan_keys); - - tuple = heap_getnext(scan_desc, ForwardScanDirection); - - /* - * If a single tuple was returned, the tuple is still valid, otherwise' - * set to false. - */ - if (!HeapTupleIsValid(tuple)) - { - result = false; - } - - table_endscan(scan_desc); - table_close(rel, RowExclusiveLock); - - return result; -} - diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index ceb06326..532d5721 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -1216,6 +1216,7 @@ cypher_parse_analyze(RawStmt *parseTree, const char *sourceText, cpstate->graph_name = gcd->name.data; cpstate->graph_oid = graph_oid; + explainStmt->query = analyze_cypher(explainStmt->query, pstate, sourceText, 0, gcd->name.data, graph_oid, NULL); Query *result; @@ -1347,7 +1348,9 @@ analyze_cypher(List *stmt, ParseState *parent_pstate, const char *query_str, int cpstate->params = params; cpstate->default_alias_num = 0; + parsing_cypher = true; Query *query = transform_cypher_clause(cpstate, clause); + parsing_cypher = false; free_cypher_parsestate(cpstate); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index f3c14b65..746dbc93 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -17,7 +17,6 @@ #include "parser/cypher_clause.h" #include "parser/cypher_expr.h" #include "parser/cypher_item.h" -#include "parser/cypher_parse_agg.h" #include "parser/cypher_parse_node.h" #include "utils/ag_cache.h" #include "utils/ag_func.h" @@ -61,9 +60,6 @@ static char *make_property_alias(char *var_name); static char *make_startid_alias(char *var_name); static char *make_endid_alias(char *var_name); -List *transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); - - static char *make_vertex_adjlist_alias(char *var_name) { char *str = palloc(strlen(var_name) + 8); @@ -245,7 +241,7 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi static Node * make_graphid_placeholder(cypher_parsestate *cpstate) { - Const *c = makeConst(GRAPHIDOID, -1, InvalidOid, -1, 0, false, false); + Const *c = makeConst(GRAPHIDOID, -1, InvalidOid, sizeof(graphid), GRAPHID_GET_DATUM(0), false, true); c->location = -1; return (Node *)c; } @@ -269,22 +265,6 @@ transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query) ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL), lateral, true); - if (list_length(pstate->p_rtable) > 1) { - List *namespace = NULL; - int rtindex = 0; - - rtindex = list_length(pstate->p_rtable); - - if (pnsi->p_rte != rt_fetch(rtindex, pstate->p_rtable)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("rte must be last entry in p_rtable"))); - - namespace = list_make1(pnsi); - - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); - } - addNSItemToQuery(pstate, pnsi, true, false, true); } @@ -300,7 +280,9 @@ static void validate_or_create_elabel(cypher_parsestate *cpstate, cypher_relatio errmsg("label %s is for vertices, not edges", edge->label), parser_errposition(cpstate, edge->location))); else if (!lcd) - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, list_make1(get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_EDGE)), NULL); + create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, + list_make1(get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_EDGE)), + NULL); } @@ -316,6 +298,133 @@ static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *n } +static void +process_create_vertex( + cypher_parsestate *cpstate, + Query *query, + cypher_node *node, + cypher_create_path *ccp) +{ + ParseState *pstate = (ParseState *)cpstate; + cypher_target_node *target = make_ag_node(cypher_target_node); + + if (node->label) + validate_or_create_vlabel(cpstate, node); + else + node->label = AG_DEFAULT_LABEL_VERTEX; + + if (node->name) { + // /ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); + + if (colNameToVar(cpstate, node->name, false, -1)) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CREATE vertex variable %s already exists", node->name))); + + target->variable_name = node->name; + query->targetList = lappend(query->targetList, + makeTargetEntry( + make_graphid_placeholder(cpstate), + target->id_attr_num = pstate->p_next_resno++, + make_id_alias(node->name), + false)); + + if (node->props) { + query->targetList = lappend(query->targetList, + makeTargetEntry( + transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET), + target->prop_attr_num = pstate->p_next_resno++, + make_property_alias(node->name), + false)); + } else { + target->prop_attr_num = InvalidAttrNumber; + /*query->targetList = lappend(query->targetList, + makeTargetEntry( + make_int_placeholder(cpstate), + target->prop_attr_num = pstate->p_next_resno++, + make_property_alias(node->name), + false));*/ + } + + query->targetList = lappend(query->targetList, + makeTargetEntry( + make_int_placeholder(cpstate), + target->tuple_position = pstate->p_next_resno++, + node->name, + false)); + + } else { + node->name = get_next_default_alias(cpstate); + target->id_attr_num = InvalidAttrNumber; + target->tuple_position = InvalidAttrNumber; + + if (node->props) { + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)add_volatile_wrapper( + transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), + target->prop_attr_num = pstate->p_next_resno++, + make_property_alias(node->name), + false)); + } else { + target->prop_attr_num = InvalidAttrNumber; + } + + } + + + + label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); + + target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); + target->relid = lcd->relation; + target->adj_relid = lcd->vertex_adjlist; + + ccp->target_nodes = lappend(ccp->target_nodes, target); +} + +static void +process_create_edge( + cypher_parsestate *cpstate, + Query *query, + cypher_relationship *edge, + cypher_create_path *ccp) +{ + ParseState *pstate = (ParseState *)cpstate; + cypher_target_node *target = make_ag_node(cypher_target_node); + + if (edge->label) + validate_or_create_elabel(cpstate, edge); + else + edge->label = AG_DEFAULT_LABEL_EDGE; + + if (edge->name) + ereport(ERROR, (errmsg_internal("edges in CREATE cannot have variable names"))); + else + edge->name = get_next_default_alias(cpstate); + + if (edge->props) + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)add_volatile_wrapper( + transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), + target->prop_attr_num = pstate->p_next_resno++, + make_property_alias(edge->name), + false)); + else + target->prop_attr_num = InvalidAttrNumber; + + target->dir = edge->dir; + if (edge->dir == CYPHER_REL_DIR_NONE) + ereport(ERROR, (errmsg_internal("edges in CREATE must have a direction"))); + + label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); + target->relid = lcd->relation; + + target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(target->relid), 1); + + ccp->target_nodes = lappend(ccp->target_nodes, target); +} + static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause) { ParseState *pstate = (ParseState *)cpstate; cypher_create *self = (cypher_create *)clause->self; @@ -327,8 +436,8 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause if (clause->prev != NULL) ereport(ERROR, (errmsg_internal("CREATE doesn't work with previous clauses"))); - if (clause->next) - ereport(ERROR, (errmsg_internal("CREATE doesn't work with next clauses"))); + //if (clause->next) + // ereport(ERROR, (errmsg_internal("CREATE doesn't work with next clauses"))); if (list_length(self->pattern) != 1) ereport(ERROR, (errmsg_internal("CREATE doesn't work with patterns"))); @@ -352,97 +461,10 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause foreach (lc2, path->path) { if (i % 2 == 1) { cypher_node *node = (cypher_node *)lfirst(lc2); - - cypher_target_node *target = make_ag_node(cypher_target_node); - - if (node->label) - validate_or_create_vlabel(cpstate, node); - else - node->label = AG_DEFAULT_LABEL_VERTEX; - - if (node->name) { - ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); - - target->variable_name = node->name; - target->id_attr_num = pstate->p_next_resno; - query->targetList = lappend(query->targetList, - makeTargetEntry( - make_graphid_placeholder(cpstate), - pstate->p_next_resno++, - make_id_alias(get_next_default_alias(cpstate)), - false)); - -/* - query->targetList = lappend(query->targetList, - makeTargetEntry( - make_int_placeholder(cpstate), - pstate->p_next_resno++, - make_id_alias(get_next_default_alias(cpstate)), - false)); - } - - target->props_attr_num = list_length(query->targetList); -*/ - } else - node->name = get_next_default_alias(cpstate); - - if (node->props) { - target->prop_attr_num = pstate->p_next_resno; - query->targetList = lappend(query->targetList, - makeTargetEntry( - (Expr *)add_volatile_wrapper( - transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), - pstate->p_next_resno++, - make_property_alias(node->name), - false)); - - } else { - target->prop_attr_num = InvalidAttrNumber; - } - - - label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); - - target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); - target->relid = lcd->relation; - target->adj_relid = lcd->vertex_adjlist; - - ccp->target_nodes = lappend(ccp->target_nodes, target); - + process_create_vertex(cpstate, query, node, ccp); } else { cypher_relationship *edge = lfirst(lc2); - cypher_target_node *target = make_ag_node(cypher_target_node); - if (edge->label) - validate_or_create_elabel(cpstate, edge); - else - edge->label = AG_DEFAULT_LABEL_EDGE; - - if (edge->name) - ereport(ERROR, (errmsg_internal("edges in CREATE cannot have variable names"))); - else - edge->name = get_next_default_alias(cpstate); - - if (edge->props) - query->targetList = lappend(query->targetList, - makeTargetEntry( - (Expr *)add_volatile_wrapper( - transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), - target->prop_attr_num = pstate->p_next_resno++, - make_property_alias(edge->name), - false)); - else - target->prop_attr_num = InvalidAttrNumber; - - target->dir = edge->dir; - if (edge->dir == CYPHER_REL_DIR_NONE) - ereport(ERROR, (errmsg_internal("edges in CREATE must have a direction"))); - - label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); - target->relid = lcd->relation; - - target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(target->relid), 1); - - ccp->target_nodes = lappend(ccp->target_nodes, target); + process_create_edge(cpstate, query, edge, ccp); } i++; @@ -502,11 +524,11 @@ Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause query->sortClause = transform_cypher_order_by(cpstate, self->order_by, &query->targetList, EXPR_KIND_ORDER_BY); if (self->real_group_clause != NIL) - query->groupClause = transform_group_clause(cpstate, self->real_group_clause, &query->groupingSets, + query->groupClause = transformGroupClause(cpstate, self->real_group_clause, &query->groupingSets, &query->targetList, query->sortClause, EXPR_KIND_GROUP_BY); else if (groupClause != NIL) // auto GROUP BY - query->groupClause = transform_group_clause(cpstate, groupClause, &query->groupingSets, &query->targetList, + query->groupClause = transformGroupClause(cpstate, groupClause, &query->groupingSets, &query->targetList, query->sortClause, EXPR_KIND_GROUP_BY); else query->groupClause = NULL; @@ -528,7 +550,7 @@ Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause query->limitCount = transform_cypher_limit(cpstate, self->limit, EXPR_KIND_LIMIT, "LIMIT"); if (pstate->p_windowdefs != NIL) - query->windowClause = transform_window_definitions(pstate, pstate->p_windowdefs, &query->targetList); + query->windowClause = transformWindowDefinitions(pstate, pstate->p_windowdefs, &query->targetList); query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, expr); @@ -541,7 +563,7 @@ Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause // this must be done after collations, for reliable comparison of exprs if (pstate->p_hasAggs || query->groupClause || query->groupingSets || query->havingQual) - parse_check_aggregates(pstate, query); + parseCheckAggregates(pstate, query); return query; } @@ -582,7 +604,6 @@ List *transform_cypher_order_by(cypher_parsestate *cpstate, List *sort_items,Lis return sort_list; } - // see transformLimitClause() static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, ParseExprKind expr_kind, const char *construct_name) { @@ -903,7 +924,6 @@ static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, List ** return (Node *) rtr; } -// setNamespaceLateralState - subroutine to update LATERAL flags in a namespace list. static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok) { ListCell *lc; @@ -1009,17 +1029,7 @@ add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, true, true); setNamespaceLateralState(pnsi, false, true); -/* - RangeTblRef *rtr = transform_srf_function( - cpstate, - make_range_function(n, make_alias(var_name, colnames), true, false, false), - &namespace); - setNamespaceLateralState(namespace, true, true); - - get_parse_state(cpstate)->p_joinlist = lappend(get_parse_state(cpstate)->p_joinlist, rtr); - get_parse_state(cpstate)->p_namespace = list_concat(get_parse_state(cpstate)->p_namespace, namespace); -*/ addNSItemToQuery(cpstate, pnsi, true, true, true); return pnsi; @@ -1034,8 +1044,8 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n Var *var; if (var = colNameToVar(cpstate, make_id_alias(node->name), false, -1)) { - ereport(WARNING,(errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("here"))); +// ereport(WARNING,(errcode(ERRCODE_UNDEFINED_SCHEMA), +// errmsg("here"))); node->declared_in_previous_clause = true; *quals = lappend(*quals, @@ -1057,7 +1067,7 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n (Node *)scanNSItemForColumn(cpstate, pnsi, 0, "id", -1), -1)); return pnsi; - } + } } else { @@ -1096,10 +1106,13 @@ add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship { edge->has_variable = false; if (edge->name) { + if (colNameToVar(cpstate, make_id_alias(edge->name), false, -1) || refnameNamespaceItem(cpstate, NULL, edge->name, -1, NULL)) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH edge variable %s already exists", edge->name))); + edge->has_variable = true; - } - else + } else edge->name = get_next_default_alias(cpstate); bool is_default_label = true; @@ -1110,32 +1123,33 @@ add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship else edge->label = AG_DEFAULT_LABEL_EDGE; - Node *id_field; - if (!vertex->declared_in_previous_clause) - id_field = scanNSItemForColumn(cpstate, vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - else - id_field = colNameToVar(cpstate, make_id_alias(vertex->name), false, -1); - - //id_field = scanNSItemForColumn(cpstate, vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - - //if (id_field == NULL) - // id_field = colNameToVar(cpstate, make_id_alias(vertex->name), false, -1); - - FuncCall *fc = makeFuncCall( - list_make2(makeString("postgraph"), makeString("edge_search")), - list_make4(make_int_const(cpstate->graph_oid, -1), id_field, make_null_const(-1), make_null_const(-1)), - COERCE_EXPLICIT_CALL, -1); - - return add_srf_to_query(cpstate, fc, edge->name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); + return add_srf_to_query( + cpstate, + makeFuncCall( + list_make2(makeString("postgraph"), makeString("edge_search")), + list_make4( + make_int_const(cpstate->graph_oid, -1), + vertex->declared_in_previous_clause ? + colNameToVar(cpstate, make_id_alias(vertex->name), false, -1) : + scanNSItemForColumn(cpstate, vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1), + make_null_const(-1), + make_null_const(-1)), + COERCE_EXPLICIT_CALL, -1), + edge->name, + list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); } static ParseNamespaceItem * add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *prev_pnsi, char *prev_name) { edge->has_variable = false; - if (edge->name) + if (edge->name){ + if (colNameToVar(cpstate, make_id_alias(edge->name), false, -1) || refnameNamespaceItem(cpstate, NULL, edge->name, -1, NULL)) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH edge variable %s already exists", edge->name))); + edge->has_variable = true; - else + } else edge->name = get_next_default_alias(cpstate); bool is_default_label = true; @@ -1146,28 +1160,22 @@ add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cyphe else edge->label = AG_DEFAULT_LABEL_EDGE; - Node *id_field; - if(edge->dir != CYPHER_REL_DIR_NONE) - id_field = scanNSItemForColumn(cpstate, prev_pnsi, 0, "endid", -1); - else + if(edge->dir == CYPHER_REL_DIR_NONE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH edge can't be bi directional"))); - - - FuncCall *fc = makeFuncCall( - list_make2(makeString("postgraph"), makeString("edge_search")), - list_make4(make_int_const(cpstate->graph_oid, -1), id_field, make_null_const(-1), make_null_const(-1)), - COERCE_EXPLICIT_CALL, -1); - - return add_srf_to_query(cpstate, fc, edge->name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); + return add_srf_to_query( + cpstate, + makeFuncCall( + list_make2(makeString("postgraph"), makeString("edge_search")), + list_make4(make_int_const(cpstate->graph_oid, -1), scanNSItemForColumn(cpstate, prev_pnsi, 0, "endid", -1), make_null_const(-1), make_null_const(-1)), + COERCE_EXPLICIT_CALL, -1), + edge->name, + list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); } static ParseNamespaceItem * -add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi) -{ - ParseState *pstate = (ParseState *)cpstate; - +add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi) { edge->has_variable = false; if (edge->name) ereport(ERROR, @@ -1184,16 +1192,18 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela else edge->label = AG_DEFAULT_LABEL_EDGE; - Node *id_field = scanNSItemForColumn(cpstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - - A_Indices *idx= edge->varlen; - - FuncCall *fc = makeFuncCall( - list_make2(makeString("postgraph"), makeString("variable_edge_search")), - list_make4(make_int_const(cpstate->graph_oid, -1), id_field, idx->lidx, make_null_const(-1)), - COERCE_EXPLICIT_CALL, -1); - - return add_srf_to_query(cpstate, fc, edge->name, list_make3(makeString("edges"), makeString("endid"), makeString("hashset"))); + return add_srf_to_query( + cpstate, + makeFuncCall( + list_make2(makeString("postgraph"), makeString("variable_edge_search")), + list_make4( + make_int_const(cpstate->graph_oid, -1), + scanNSItemForColumn(cpstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1), + ((A_Indices *)edge->varlen)->lidx, + make_null_const(-1)), + COERCE_EXPLICIT_CALL, -1), + edge->name, + list_make3(makeString("edges"), makeString("endid"), makeString("hashset"))); } static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *query, @@ -1228,7 +1238,6 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que pstate->p_next_resno++, left_vertex->name, false)); - } if (edge->varlen) { @@ -1639,7 +1648,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li // AND the quals for each path together Expr *expr = NULL; if (quals != NIL) - expr = (Expr *)sql_transform_expr(cpstate, (Node *)makeBoolExpr(AND_EXPR, quals, -1), EXPR_KIND_WHERE); + expr = (Expr *)transformExpr(cpstate, (Node *)makeBoolExpr(AND_EXPR, quals, -1), EXPR_KIND_WHERE); // WHERE Clause /* diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 3bad05da..8f13e9cf 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -85,1754 +85,123 @@ static Node *transform_func_call(cypher_parsestate *cpstate, FuncCall *fn); static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, ColumnRef *cr); static Node *transformSQLValueFunction(cypher_parsestate *cpstate, SQLValueFunction *svf); static List *make_qualified_function_name(cypher_parsestate *cpstate, List *lst, List *targs); -static void unify_hypothetical_args(ParseState *pstate, List *fargs, int numAggregatedArgs, - Oid *actual_arg_types, Oid *declared_arg_types); - - -typedef struct -{ - ParseState *pstate; - int min_varlevel; - int min_agglevel; - int sublevels_up; -} check_agg_arguments_context; - -typedef struct -{ - ParseState *pstate; - Query *qry; - bool hasJoinRTEs; - List *groupClauses; - List *groupClauseCommonVars; - bool have_non_var_grouping; - List **func_grouped_rels; - int sublevels_up; - bool in_agg_direct_args; -} check_ungrouped_columns_context; -static int check_agg_arguments(ParseState *pstate, List *directargs, List *args, Expr *filter); -static bool check_agg_arguments_walker(Node *node, check_agg_arguments_context *context); -static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry, - List *groupClauses, List *groupClauseCommonVars, - bool have_non_var_grouping, List **func_grouped_rels); -static bool check_ungrouped_columns_walker(Node *node, check_ungrouped_columns_context *context); -static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry, - List *groupClauses, bool hasJoinRTEs, - bool have_non_var_grouping); -static bool finalize_grouping_exprs_walker(Node *node, check_ungrouped_columns_context *context); -static void check_agglevels_and_constraints(ParseState *pstate, Node *expr); -static List *expand_groupingset_node(GroupingSet *gs); -static Node *make_agg_arg(Oid argtype, Oid argcollation); - static char *make_property_alias(char *var_name) { char *str = palloc(strlen(var_name) + 8); str[0] = '_'; - str[1] = 'p'; - str[2] = 'r'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\n'; - - return str; -} - - -Node * -transform_cypher_expr(cypher_parsestate *cpstate, Node *expr, ParseExprKind expr_kind) { - ParseState *pstate = (ParseState *)cpstate; - ParseExprKind old_expr_kind; - Node *result; - - // save and restore identity of expression type we're parsing - Assert(expr_kind != EXPR_KIND_NONE); - old_expr_kind = pstate->p_expr_kind; - pstate->p_expr_kind = expr_kind; - - result = transform_cypher_expr_recurse(cpstate, expr); - - pstate->p_expr_kind = old_expr_kind; - - return result; -} - -static Node * -transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr) { - if (!expr) - return NULL; - - check_stack_depth(); - - switch (nodeTag(expr)) - { - case T_A_Const: - return transform_a_const(cpstate, (A_Const *)expr); - case T_ColumnRef: - return transform_column_ref(cpstate, (ColumnRef *)expr); - case T_A_Indirection: - return transform_a_indirection(cpstate, (A_Indirection *)expr); - case T_A_Expr: - { - A_Expr *a = (A_Expr *)expr; - - switch (a->kind) - { - case AEXPR_OP: - return transform_a_expr_op(cpstate, a); - default: - ereport(ERROR, (errmsg_internal("unrecognized A_Expr kind: %d", a->kind))); - } - } - case T_BoolExpr: - return transform_bool_expr(cpstate, (BoolExpr *)expr); - case T_NullTest: - { - NullTest *n = (NullTest *)expr; - - n->arg = (Expr *)transform_cypher_expr_recurse(cpstate, (Node *)n->arg); - n->argisrow = type_is_rowtype(exprType((Node *)n->arg)); - - return expr; - } - case T_CaseExpr: - return transform_case_expr(cpstate, (CaseExpr *) expr); - case T_CaseTestExpr: - return expr; - case T_CoalesceExpr: - return transform_coalesce_expr(cpstate, (CoalesceExpr *) expr); - case T_ExtensibleNode: - if (is_ag_node(expr, cypher_bool_const)) - return transform_cypher_bool_const(cpstate, (cypher_bool_const *)expr); - if (is_ag_node(expr, cypher_inet_const)) - return transform_cypher_inet_const(cpstate, (cypher_inet_const *)expr); - if (is_ag_node(expr, cypher_integer_const)) - return transform_cypher_integer_const(cpstate, (cypher_integer_const *)expr); - if (is_ag_node(expr, cypher_param)) - return transform_cypher_param(cpstate, (cypher_param *)expr); - if (is_ag_node(expr, cypher_map)) - return transform_cypher_map(cpstate, (cypher_map *)expr); - if (is_ag_node(expr, cypher_list)) - return transform_cypher_list(cpstate, (cypher_list *)expr); - if (is_ag_node(expr, cypher_string_match)) - return transform_cypher_string_match(cpstate, (cypher_string_match *)expr); - if (is_ag_node(expr, cypher_typecast)) - return transform_cypher_typecast(cpstate, (cypher_typecast *)expr); - - ereport(ERROR, (errmsg_internal("unrecognized ExtensibleNode: %s", - ((ExtensibleNode *)expr)->extnodename))); - - case T_FuncCall: - return transform_func_call(cpstate, (FuncCall *)expr); - case T_SubLink: - return transform_sub_link(cpstate, (SubLink *)expr); - case T_SQLValueFunction: - return transformSQLValueFunction(cpstate, (SQLValueFunction *)expr); - case T_String: - return makeConst(GTYPEOID, -1, InvalidOid, -1, string_to_gtype(strVal(expr)), false, false); - case T_Var: - { - return (Node *) expr; - } - default: - ereport(ERROR, (errmsg_internal("unrecognized node type: %d", nodeTag(expr)))); - } - - return NULL; -} - -/* - * Aggregate functions and grouping operations (which are combined in the spec - * as ) are very similar with regard to level and - * nesting restrictions (though we allow a lot more things than the spec does). - * Centralise those restrictions here. - */ -static void -check_agglevels_and_constraints(ParseState *pstate, Node *expr) -{ - List *directargs = NIL; - List *args = NIL; - Expr *filter = NULL; - int min_varlevel; - int location = -1; - Index *p_levelsup; - const char *err; - bool errkind; - bool isAgg = IsA(expr, Aggref); - - if (isAgg) - { - Aggref *agg = (Aggref *) expr; - - directargs = agg->aggdirectargs; - args = agg->args; - filter = agg->aggfilter; - location = agg->location; - p_levelsup = &agg->agglevelsup; - } - else - { - GroupingFunc *grp = (GroupingFunc *) expr; - - args = grp->args; - location = grp->location; - p_levelsup = &grp->agglevelsup; - } - - /* - * Check the arguments to compute the aggregate's level and detect - * improper nesting. - */ - min_varlevel = check_agg_arguments(pstate, - directargs, - args, - filter); - - *p_levelsup = min_varlevel; - - /* Mark the correct pstate level as having aggregates */ - while (min_varlevel-- > 0) - pstate = pstate->parentParseState; - pstate->p_hasAggs = true; - - /* - * Check to see if the aggregate function is in an invalid place within - * its aggregation query. - * - * For brevity we support two schemes for reporting an error here: set - * "err" to a custom message, or set "errkind" true if the error context - * is sufficiently identified by what ParseExprKindName will return, *and* - * what it will return is just a SQL keyword. (Otherwise, use a custom - * message to avoid creating translation problems.) - */ - err = NULL; - errkind = false; - switch (pstate->p_expr_kind) - { - case EXPR_KIND_NONE: - Assert(false); /* can't happen */ - break; - case EXPR_KIND_OTHER: - - /* - * Accept aggregate/grouping here; caller must throw error if - * wanted - */ - break; - case EXPR_KIND_JOIN_ON: - case EXPR_KIND_JOIN_USING: - if (isAgg) - err = _("aggregate functions are not allowed in JOIN conditions"); - else - err = _("grouping operations are not allowed in JOIN conditions"); - - break; - case EXPR_KIND_FROM_SUBSELECT: - /* Should only be possible in a LATERAL subquery */ - Assert(pstate->p_lateral_active); - - /* - * Aggregate/grouping scope rules make it worth being explicit - * here - */ - if (isAgg) - err = _("aggregate functions are not allowed in FROM clause of their own query level"); - else - err = _("grouping operations are not allowed in FROM clause of their own query level"); - - break; - case EXPR_KIND_FROM_FUNCTION: - if (isAgg) - err = _("aggregate functions are not allowed in functions in FROM"); - else - err = _("grouping operations are not allowed in functions in FROM"); - - break; - case EXPR_KIND_WHERE: - errkind = true; - break; - case EXPR_KIND_POLICY: - if (isAgg) - err = _("aggregate functions are not allowed in policy expressions"); - else - err = _("grouping operations are not allowed in policy expressions"); - - break; - case EXPR_KIND_HAVING: - /* okay */ - break; - case EXPR_KIND_FILTER: - errkind = true; - break; - case EXPR_KIND_WINDOW_PARTITION: - /* okay */ - break; - case EXPR_KIND_WINDOW_ORDER: - /* okay */ - break; - case EXPR_KIND_WINDOW_FRAME_RANGE: - if (isAgg) - err = _("aggregate functions are not allowed in window RANGE"); - else - err = _("grouping operations are not allowed in window RANGE"); - - break; - case EXPR_KIND_WINDOW_FRAME_ROWS: - if (isAgg) - err = _("aggregate functions are not allowed in window ROWS"); - else - err = _("grouping operations are not allowed in window ROWS"); - - break; - case EXPR_KIND_WINDOW_FRAME_GROUPS: - if (isAgg) - err = _("aggregate functions are not allowed in window GROUPS"); - else - err = _("grouping operations are not allowed in window GROUPS"); - - break; - case EXPR_KIND_SELECT_TARGET: - /* okay */ - break; - case EXPR_KIND_INSERT_TARGET: - case EXPR_KIND_UPDATE_SOURCE: - case EXPR_KIND_UPDATE_TARGET: - errkind = true; - break; - case EXPR_KIND_GROUP_BY: - errkind = true; - break; - case EXPR_KIND_ORDER_BY: - /* okay */ - break; - case EXPR_KIND_DISTINCT_ON: - /* okay */ - break; - case EXPR_KIND_LIMIT: - case EXPR_KIND_OFFSET: - errkind = true; - break; - case EXPR_KIND_RETURNING: - errkind = true; - break; - case EXPR_KIND_VALUES: - case EXPR_KIND_VALUES_SINGLE: - errkind = true; - break; - case EXPR_KIND_CHECK_CONSTRAINT: - case EXPR_KIND_DOMAIN_CHECK: - if (isAgg) - err = _("aggregate functions are not allowed in check constraints"); - else - err = _("grouping operations are not allowed in check constraints"); - - break; - case EXPR_KIND_COLUMN_DEFAULT: - case EXPR_KIND_FUNCTION_DEFAULT: - - if (isAgg) - err = _("aggregate functions are not allowed in DEFAULT expressions"); - else - err = _("grouping operations are not allowed in DEFAULT expressions"); - - break; - case EXPR_KIND_INDEX_EXPRESSION: - if (isAgg) - err = _("aggregate functions are not allowed in index expressions"); - else - err = _("grouping operations are not allowed in index expressions"); - - break; - case EXPR_KIND_INDEX_PREDICATE: - if (isAgg) - err = _("aggregate functions are not allowed in index predicates"); - else - err = _("grouping operations are not allowed in index predicates"); - - break; - case EXPR_KIND_STATS_EXPRESSION: - if (isAgg) - err = _("aggregate functions are not allowed in statistics expressions"); - else - err = _("grouping operations are not allowed in statistics expressions"); - - break; - case EXPR_KIND_ALTER_COL_TRANSFORM: - if (isAgg) - err = _("aggregate functions are not allowed in transform expressions"); - else - err = _("grouping operations are not allowed in transform expressions"); - - break; - case EXPR_KIND_EXECUTE_PARAMETER: - if (isAgg) - err = _("aggregate functions are not allowed in EXECUTE parameters"); - else - err = _("grouping operations are not allowed in EXECUTE parameters"); - - break; - case EXPR_KIND_TRIGGER_WHEN: - if (isAgg) - err = _("aggregate functions are not allowed in trigger WHEN conditions"); - else - err = _("grouping operations are not allowed in trigger WHEN conditions"); - - break; - case EXPR_KIND_PARTITION_BOUND: - if (isAgg) - err = _("aggregate functions are not allowed in partition bound"); - else - err = _("grouping operations are not allowed in partition bound"); - - break; - case EXPR_KIND_PARTITION_EXPRESSION: - if (isAgg) - err = _("aggregate functions are not allowed in partition key expressions"); - else - err = _("grouping operations are not allowed in partition key expressions"); - - break; - case EXPR_KIND_GENERATED_COLUMN: - - if (isAgg) - err = _("aggregate functions are not allowed in column generation expressions"); - else - err = _("grouping operations are not allowed in column generation expressions"); - - break; - - case EXPR_KIND_CALL_ARGUMENT: - if (isAgg) - err = _("aggregate functions are not allowed in CALL arguments"); - else - err = _("grouping operations are not allowed in CALL arguments"); - - break; - - case EXPR_KIND_COPY_WHERE: - if (isAgg) - err = _("aggregate functions are not allowed in COPY FROM WHERE conditions"); - else - err = _("grouping operations are not allowed in COPY FROM WHERE conditions"); - - break; - - case EXPR_KIND_CYCLE_MARK: - errkind = true; - break; - - /* - * There is intentionally no default: case here, so that the - * compiler will warn if we add a new ParseExprKind without - * extending this switch. If we do see an unrecognized value at - * runtime, the behavior will be the same as for EXPR_KIND_OTHER, - * which is sane anyway. - */ - } - - if (err) - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg_internal("%s", err), - parser_errposition(pstate, location))); - - if (errkind) - { - if (isAgg) - /* translator: %s is name of a SQL construct, eg GROUP BY */ - err = _("aggregate functions are not allowed in %s"); - else - /* translator: %s is name of a SQL construct, eg GROUP BY */ - err = _("grouping operations are not allowed in %s"); - - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg_internal(err, - ParseExprKindName(pstate->p_expr_kind)), - parser_errposition(pstate, location))); - } -} - - - -/* - * check_agg_arguments - * Scan the arguments of an aggregate function to determine the - * aggregate's semantic level (zero is the current select's level, - * one is its parent, etc). - * - * The aggregate's level is the same as the level of the lowest-level variable - * or aggregate in its aggregated arguments (including any ORDER BY columns) - * or filter expression; or if it contains no variables at all, we presume it - * to be local. - * - * Vars/Aggs in direct arguments are *not* counted towards determining the - * agg's level, as those arguments aren't evaluated per-row but only - * per-group, and so in some sense aren't really agg arguments. However, - * this can mean that we decide an agg is upper-level even when its direct - * args contain lower-level Vars/Aggs, and that case has to be disallowed. - * (This is a little strange, but the SQL standard seems pretty definite that - * direct args are not to be considered when setting the agg's level.) - * - * We also take this opportunity to detect any aggregates or window functions - * nested within the arguments. We can throw error immediately if we find - * a window function. Aggregates are a bit trickier because it's only an - * error if the inner aggregate is of the same semantic level as the outer, - * which we can't know until we finish scanning the arguments. - */ -static int -check_agg_arguments(ParseState *pstate, - List *directargs, - List *args, - Expr *filter) -{ - int agglevel; - check_agg_arguments_context context; - - context.pstate = pstate; - context.min_varlevel = -1; /* signifies nothing found yet */ - context.min_agglevel = -1; - context.sublevels_up = 0; - - (void) check_agg_arguments_walker((Node *) args, &context); - (void) check_agg_arguments_walker((Node *) filter, &context); - - /* - * If we found no vars nor aggs at all, it's a level-zero aggregate; - * otherwise, its level is the minimum of vars or aggs. - */ - if (context.min_varlevel < 0) - { - if (context.min_agglevel < 0) - agglevel = 0; - else - agglevel = context.min_agglevel; - } - else if (context.min_agglevel < 0) - agglevel = context.min_varlevel; - else - agglevel = Min(context.min_varlevel, context.min_agglevel); - - /* - * If there's a nested aggregate of the same semantic level, complain. - */ - if (agglevel == context.min_agglevel) - { - int aggloc; - - aggloc = locate_agg_of_level((Node *) args, agglevel); - if (aggloc < 0) - aggloc = locate_agg_of_level((Node *) filter, agglevel); - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg("aggregate function calls cannot be nested"), - parser_errposition(pstate, aggloc))); - } - - /* - * Now check for vars/aggs in the direct arguments, and throw error if - * needed. Note that we allow a Var of the agg's semantic level, but not - * an Agg of that level. In principle such Aggs could probably be - * supported, but it would create an ordering dependency among the - * aggregates at execution time. Since the case appears neither to be - * required by spec nor particularly useful, we just treat it as a - * nested-aggregate situation. - */ - if (directargs) - { - context.min_varlevel = -1; - context.min_agglevel = -1; - (void) check_agg_arguments_walker((Node *) directargs, &context); - if (context.min_varlevel >= 0 && context.min_varlevel < agglevel) - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg("outer-level aggregate cannot contain a lower-level variable in its direct arguments"), - parser_errposition(pstate, - locate_var_of_level((Node *) directargs, - context.min_varlevel)))); - if (context.min_agglevel >= 0 && context.min_agglevel <= agglevel) - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg("aggregate function calls cannot be nested"), - parser_errposition(pstate, - locate_agg_of_level((Node *) directargs, - context.min_agglevel)))); - } - return agglevel; -} - -static bool -check_agg_arguments_walker(Node *node, - check_agg_arguments_context *context) -{ - if (node == NULL) - return false; - if (IsA(node, Var)) - { - int varlevelsup = ((Var *) node)->varlevelsup; - - /* convert levelsup to frame of reference of original query */ - varlevelsup -= context->sublevels_up; - /* ignore local vars of subqueries */ - if (varlevelsup >= 0) - { - if (context->min_varlevel < 0 || - context->min_varlevel > varlevelsup) - context->min_varlevel = varlevelsup; - } - return false; - } - if (IsA(node, Aggref)) - { - int agglevelsup = ((Aggref *) node)->agglevelsup; - - /* convert levelsup to frame of reference of original query */ - agglevelsup -= context->sublevels_up; - /* ignore local aggs of subqueries */ - if (agglevelsup >= 0) - { - if (context->min_agglevel < 0 || - context->min_agglevel > agglevelsup) - context->min_agglevel = agglevelsup; - } - /* Continue and descend into subtree */ - } - if (IsA(node, GroupingFunc)) - { - int agglevelsup = ((GroupingFunc *) node)->agglevelsup; - - /* convert levelsup to frame of reference of original query */ - agglevelsup -= context->sublevels_up; - /* ignore local aggs of subqueries */ - if (agglevelsup >= 0) - { - if (context->min_agglevel < 0 || - context->min_agglevel > agglevelsup) - context->min_agglevel = agglevelsup; - } - /* Continue and descend into subtree */ - } - - /* - * SRFs and window functions can be rejected immediately, unless we are - * within a sub-select within the aggregate's arguments; in that case - * they're OK. - */ - if (context->sublevels_up == 0) - { - if ((IsA(node, FuncExpr) && ((FuncExpr *) node)->funcretset) || - (IsA(node, OpExpr) && ((OpExpr *) node)->opretset)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("aggregate function calls cannot contain set-returning function calls"), - errhint("You might be able to move the set-returning function into a LATERAL FROM item."), - parser_errposition(context->pstate, exprLocation(node)))); - if (IsA(node, WindowFunc)) - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg("aggregate function calls cannot contain window function calls"), - parser_errposition(context->pstate, - ((WindowFunc *) node)->location))); - } - if (IsA(node, Query)) - { - /* Recurse into subselects */ - bool result; - - context->sublevels_up++; - result = query_tree_walker((Query *) node, - check_agg_arguments_walker, - (void *) context, - 0); - context->sublevels_up--; - return result; - } - - return expression_tree_walker(node, - check_agg_arguments_walker, - (void *) context); -} - -/* - * transformAggregateCall - - * Finish initial transformation of an aggregate call - * - * parse_func.c has recognized the function as an aggregate, and has set up - * all the fields of the Aggref except aggargtypes, aggdirectargs, args, - * aggorder, aggdistinct and agglevelsup. The passed-in args list has been - * through standard expression transformation and type coercion to match the - * agg's declared arg types, while the passed-in aggorder list hasn't been - * transformed at all. - * - * Here we separate the args list into direct and aggregated args, storing the - * former in agg->aggdirectargs and the latter in agg->args. The regular - * args, but not the direct args, are converted into a targetlist by inserting - * TargetEntry nodes. We then transform the aggorder and agg_distinct - * specifications to produce lists of SortGroupClause nodes for agg->aggorder - * and agg->aggdistinct. (For a regular aggregate, this might result in - * adding resjunk expressions to the targetlist; but for ordered-set - * aggregates the aggorder list will always be one-to-one with the aggregated - * args.) - * - * We must also determine which query level the aggregate actually belongs to, - * set agglevelsup accordingly, and mark p_hasAggs true in the corresponding - * pstate level. - */ -void -transform_aggregate_call(ParseState *pstate, Aggref *agg, - List *args, List *aggorder, bool agg_distinct) -{ - List *argtypes = NIL; - List *tlist = NIL; - List *torder = NIL; - List *tdistinct = NIL; - AttrNumber attno = 1; - int save_next_resno; - ListCell *lc; - - if (AGGKIND_IS_ORDERED_SET(agg->aggkind)) - { - /* - * For an ordered-set agg, the args list includes direct args and - * aggregated args; we must split them apart. - */ - int numDirectArgs = list_length(args) - list_length(aggorder); - List *aargs; - ListCell *lc2; - - Assert(numDirectArgs >= 0); - - aargs = list_copy_tail(args, numDirectArgs); - agg->aggdirectargs = list_truncate(args, numDirectArgs); - - /* - * Build a tlist from the aggregated args, and make a sortlist entry - * for each one. Note that the expressions in the SortBy nodes are - * ignored (they are the raw versions of the transformed args); we are - * just looking at the sort information in the SortBy nodes. - */ - forboth(lc, aargs, lc2, aggorder) - { - Expr *arg = (Expr *) lfirst(lc); - SortBy *sortby = (SortBy *) lfirst(lc2); - TargetEntry *tle; - - /* We don't bother to assign column names to the entries */ - tle = makeTargetEntry(arg, attno++, NULL, false); - tlist = lappend(tlist, tle); - - torder = addTargetToSortList(pstate, tle, - torder, tlist, sortby); - } - - /* Never any DISTINCT in an ordered-set agg */ - Assert(!agg_distinct); - } - else - { - /* Regular aggregate, so it has no direct args */ - agg->aggdirectargs = NIL; - - /* - * Transform the plain list of Exprs into a targetlist. - */ - foreach(lc, args) - { - Expr *arg = (Expr *) lfirst(lc); - TargetEntry *tle; - - /* We don't bother to assign column names to the entries */ - tle = makeTargetEntry(arg, attno++, NULL, false); - tlist = lappend(tlist, tle); - } - - /* - * If we have an ORDER BY, transform it. This will add columns to the - * tlist if they appear in ORDER BY but weren't already in the arg - * list. They will be marked resjunk = true so we can tell them apart - * from regular aggregate arguments later. - * - * We need to mess with p_next_resno since it will be used to number - * any new targetlist entries. - */ - save_next_resno = pstate->p_next_resno; - pstate->p_next_resno = attno; - - torder = transform_cypher_order_by(pstate, - aggorder, - &tlist, - EXPR_KIND_ORDER_BY, - true /* force SQL99 rules */ ); - - /* - * If we have DISTINCT, transform that to produce a distinctList. - */ - if (agg_distinct) - { - tdistinct = transformDistinctClause(pstate, &tlist, torder, true); - - /* - * Remove this check if executor support for hashed distinct for - * aggregates is ever added. - */ - foreach(lc, tdistinct) - { - SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc); - - if (!OidIsValid(sortcl->sortop)) - { - Node *expr = get_sortgroupclause_expr(sortcl, tlist); - - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("could not identify an ordering operator for type %s", - format_type_be(exprType(expr))), - errdetail("Aggregates with DISTINCT must be able to sort their inputs."), - parser_errposition(pstate, exprLocation(expr)))); - } - } - } - - pstate->p_next_resno = save_next_resno; - } - - /* Update the Aggref with the transformation results */ - agg->args = tlist; - agg->aggorder = torder; - agg->aggdistinct = tdistinct; - - /* - * Now build the aggargtypes list with the type OIDs of the direct and - * aggregated args, ignoring any resjunk entries that might have been - * added by ORDER BY/DISTINCT processing. We can't do this earlier - * because said processing can modify some args' data types, in particular - * by resolving previously-unresolved "unknown" literals. - */ - foreach(lc, agg->aggdirectargs) - { - Expr *arg = (Expr *) lfirst(lc); - - argtypes = lappend_oid(argtypes, exprType((Node *) arg)); - } - foreach(lc, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(lc); - - if (tle->resjunk) - continue; /* ignore junk */ - argtypes = lappend_oid(argtypes, exprType((Node *) tle->expr)); - } - agg->aggargtypes = argtypes; - - check_agglevels_and_constraints(pstate, (Node *) agg); -} - -/* - * transformGroupingFunc - * Transform a GROUPING expression - * - * GROUPING() behaves very like an aggregate. Processing of levels and nesting - * is done as for aggregates. We set p_hasAggs for these expressions too. - */ -Node * -transformGroupingFunc(ParseState *pstate, GroupingFunc *p) -{ - ListCell *lc; - List *args = p->args; - List *result_list = NIL; - GroupingFunc *result = makeNode(GroupingFunc); - - if (list_length(args) > 31) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg("GROUPING must have fewer than 32 arguments"), - parser_errposition(pstate, p->location))); - - foreach(lc, args) - { - Node *current_result; - - current_result = sql_transform_expr(pstate, (Node *) lfirst(lc), pstate->p_expr_kind); - - /* acceptability of expressions is checked later */ - - result_list = lappend(result_list, current_result); - } - - result->args = result_list; - result->location = p->location; - - check_agglevels_and_constraints(pstate, (Node *) result); - - return (Node *) result; -} - -/* - * Parse a function call - * - * For historical reasons, Postgres tries to treat the notations tab.col - * and col(tab) as equivalent: if a single-argument function call has an - * argument of complex type and the (unqualified) function name matches - * any attribute of the type, we can interpret it as a column projection. - * Conversely a function of a single complex-type argument can be written - * like a column reference, allowing functions to act like computed columns. - * - * If both interpretations are possible, we prefer the one matching the - * syntactic form, but otherwise the form does not matter. - * - * Hence, both cases come through here. If fn is null, we're dealing with - * column syntax not function syntax. In the function-syntax case, - * the FuncCall struct is needed to carry various decoration that applies - * to aggregate and window functions. - * - * Also, when fn is null, we return NULL on failure rather than - * reporting a no-such-function error. - * - * The argument expressions (in fargs) must have been transformed - * already. However, nothing in *fn has been transformed. - * - * last_srf should be a copy of pstate->p_last_srf from just before we - * started transforming fargs. If the caller knows that fargs couldn't - * contain any SRF calls, last_srf can just be pstate->p_last_srf. - * - * proc_call is true if we are considering a CALL statement, so that the - * name must resolve to a procedure name, not anything else. This flag - * also specifies that the argument list includes any OUT-mode arguments. - */ -Node * -parse_func_or_column(ParseState *pstate, List *funcname, List *fargs, - Node *last_srf, FuncCall *fn, bool proc_call, int location) -{ - bool is_column = (fn == NULL); - List *agg_order = (fn ? fn->agg_order : NIL); - Expr *agg_filter = NULL; - WindowDef *over = (fn ? fn->over : NULL); - bool agg_within_group = (fn ? fn->agg_within_group : false); - bool agg_star = (fn ? fn->agg_star : false); - bool agg_distinct = (fn ? fn->agg_distinct : false); - bool func_variadic = (fn ? fn->func_variadic : false); - CoercionForm funcformat = (fn ? fn->funcformat : COERCE_EXPLICIT_CALL); - bool could_be_projection; - Oid rettype; - Oid funcid; - ListCell *l; - Node *first_arg = NULL; - int nargs; - int nargsplusdefs; - Oid actual_arg_types[FUNC_MAX_ARGS]; - Oid *declared_arg_types; - List *argnames; - List *argdefaults; - Node *retval; - bool retset; - int nvargs; - Oid vatype; - FuncDetailCode fdresult; - char aggkind = 0; - ParseCallbackState pcbstate; - - /* - * If there's an aggregate filter, transform it using sql_transform_where_clause - */ - if (fn && fn->agg_filter != NULL) - agg_filter = (Expr *) transform_cypher_expr(pstate, fn->agg_filter, EXPR_KIND_FILTER); -// agg_filter = (Expr *) sql_transform_where_clause(pstate, fn->agg_filter, EXPR_KIND_FILTER, "FILTER"); - - /* - * Most of the rest of the parser just assumes that functions do not have - * more than FUNC_MAX_ARGS parameters. We have to test here to protect - * against array overruns, etc. Of course, this may not be a function, - * but the test doesn't hurt. - */ - if (list_length(fargs) > FUNC_MAX_ARGS) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg_plural("cannot pass more than %d argument to a function", - "cannot pass more than %d arguments to a function", - FUNC_MAX_ARGS, - FUNC_MAX_ARGS), - parser_errposition(pstate, location))); - - /* - * Extract arg type info in preparation for function lookup. - * - * If any arguments are Param markers of type VOID, we discard them from - * the parameter list. This is a hack to allow the JDBC driver to not have - * to distinguish "input" and "output" parameter symbols while parsing - * function-call constructs. Don't do this if dealing with column syntax, - * nor if we had WITHIN GROUP (because in that case it's critical to keep - * the argument count unchanged). - */ - nargs = 0; - foreach(l, fargs) - { - Node *arg = lfirst(l); - Oid argtype = exprType(arg); - - if (argtype == VOIDOID && IsA(arg, Param) && - !is_column && !agg_within_group) - { - fargs = foreach_delete_current(fargs, l); - continue; - } - - actual_arg_types[nargs++] = argtype; - } - - /* - * Check for named arguments; if there are any, build a list of names. - * - * We allow mixed notation (some named and some not), but only with all - * the named parameters after all the unnamed ones. So the name list - * corresponds to the last N actual parameters and we don't need any extra - * bookkeeping to match things up. - */ - argnames = NIL; - foreach(l, fargs) - { - Node *arg = lfirst(l); - - if (IsA(arg, NamedArgExpr)) - { - NamedArgExpr *na = (NamedArgExpr *) arg; - ListCell *lc; - - /* Reject duplicate arg names */ - foreach(lc, argnames) - { - if (strcmp(na->name, (char *) lfirst(lc)) == 0) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("argument name \"%s\" used more than once", - na->name), - parser_errposition(pstate, na->location))); - } - argnames = lappend(argnames, na->name); - } - else - { - if (argnames != NIL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("positional argument cannot follow named argument"), - parser_errposition(pstate, exprLocation(arg)))); - } - } - - if (fargs) - { - first_arg = linitial(fargs); - Assert(first_arg != NULL); - } - - /* - * Decide whether it's legitimate to consider the construct to be a column - * projection. For that, there has to be a single argument of complex - * type, the function name must not be qualified, and there cannot be any - * syntactic decoration that'd require it to be a function (such as - * aggregate or variadic decoration, or named arguments). - */ - could_be_projection = (nargs == 1 && !proc_call && - agg_order == NIL && agg_filter == NULL && - !agg_star && !agg_distinct && over == NULL && - !func_variadic && argnames == NIL && - list_length(funcname) == 1 && - (actual_arg_types[0] == RECORDOID || - ISCOMPLEX(actual_arg_types[0]))); - - /* - * If it's column syntax, check for column projection case first. - */ - if (could_be_projection && is_column) - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("ParseComplexProjection not Implemented"))); -/* - retval = ParseComplexProjection(pstate, - strVal(linitial(funcname)), - first_arg, - location); - if (retval) - return retval; -*/ - /* - * If ParseComplexProjection doesn't recognize it as a projection, - * just press on. - */ - } - - /* - * func_get_detail looks up the function in the catalogs, does - * disambiguation for polymorphic functions, handles inheritance, and - * returns the funcid and type and set or singleton status of the - * function's return value. It also returns the true argument types to - * the function. - * - * Note: for a named-notation or variadic function call, the reported - * "true" types aren't really what is in pg_proc: the types are reordered - * to match the given argument order of named arguments, and a variadic - * argument is replaced by a suitable number of copies of its element - * type. We'll fix up the variadic case below. We may also have to deal - * with default arguments. - */ - - setup_parser_errposition_callback(&pcbstate, pstate, location); - - fdresult = func_get_detail(funcname, fargs, argnames, nargs, - actual_arg_types, - !func_variadic, true, proc_call, - &funcid, &rettype, &retset, - &nvargs, &vatype, - &declared_arg_types, &argdefaults); - - cancel_parser_errposition_callback(&pcbstate); - - /* - * Check for various wrong-kind-of-routine cases. - */ - - /* If this is a CALL, reject things that aren't procedures */ - if (proc_call && - (fdresult == FUNCDETAIL_NORMAL || - fdresult == FUNCDETAIL_AGGREGATE || - fdresult == FUNCDETAIL_WINDOWFUNC || - fdresult == FUNCDETAIL_COERCION)) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("%s is not a procedure", - func_signature_string(funcname, nargs, - argnames, - actual_arg_types)), - errhint("To call a function, use SELECT."), - parser_errposition(pstate, location))); - /* Conversely, if not a CALL, reject procedures */ - if (fdresult == FUNCDETAIL_PROCEDURE && !proc_call) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("%s is a procedure", - func_signature_string(funcname, nargs, - argnames, - actual_arg_types)), - errhint("To call a procedure, use CALL."), - parser_errposition(pstate, location))); - - if (fdresult == FUNCDETAIL_NORMAL || - fdresult == FUNCDETAIL_PROCEDURE || - fdresult == FUNCDETAIL_COERCION) - { - /* - * In these cases, complain if there was anything indicating it must - * be an aggregate or window function. - */ - if (agg_star) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("%s(*) specified, but %s is not an aggregate function", - NameListToString(funcname), - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (agg_distinct) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("DISTINCT specified, but %s is not an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (agg_within_group) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("WITHIN GROUP specified, but %s is not an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (agg_order != NIL) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("ORDER BY specified, but %s is not an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (agg_filter) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("FILTER specified, but %s is not an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (over) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("OVER specified, but %s is not a window function nor an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - } - - /* - * So far so good, so do some fdresult-type-specific processing. - */ - if (fdresult == FUNCDETAIL_NORMAL || fdresult == FUNCDETAIL_PROCEDURE) - { - /* Nothing special to do for these cases. */ - } - else if (fdresult == FUNCDETAIL_AGGREGATE) - { - /* - * It's an aggregate; fetch needed info from the pg_aggregate entry. - */ - HeapTuple tup; - Form_pg_aggregate classForm; - int catDirectArgs; - - tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid)); - if (!HeapTupleIsValid(tup)) /* should not happen */ - elog(ERROR, "cache lookup failed for aggregate %u", funcid); - classForm = (Form_pg_aggregate) GETSTRUCT(tup); - aggkind = classForm->aggkind; - catDirectArgs = classForm->aggnumdirectargs; - ReleaseSysCache(tup); - - /* Now check various disallowed cases. */ - if (AGGKIND_IS_ORDERED_SET(aggkind)) - { - int numAggregatedArgs; - int numDirectArgs; - - if (!agg_within_group) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("WITHIN GROUP is required for ordered-set aggregate %s", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (over) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("OVER is not supported for ordered-set aggregate %s", - NameListToString(funcname)), - parser_errposition(pstate, location))); - /* gram.y rejects DISTINCT + WITHIN GROUP */ - Assert(!agg_distinct); - /* gram.y rejects VARIADIC + WITHIN GROUP */ - Assert(!func_variadic); - - /* - * Since func_get_detail was working with an undifferentiated list - * of arguments, it might have selected an aggregate that doesn't - * really match because it requires a different division of direct - * and aggregated arguments. Check that the number of direct - * arguments is actually OK; if not, throw an "undefined function" - * error, similarly to the case where a misplaced ORDER BY is used - * in a regular aggregate call. - */ - numAggregatedArgs = list_length(agg_order); - numDirectArgs = nargs - numAggregatedArgs; - Assert(numDirectArgs >= 0); - - if (!OidIsValid(vatype)) - { - /* Test is simple if aggregate isn't variadic */ - if (numDirectArgs != catDirectArgs) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(funcname, nargs, - argnames, - actual_arg_types)), - errhint_plural("There is an ordered-set aggregate %s, but it requires %d direct argument, not %d.", - "There is an ordered-set aggregate %s, but it requires %d direct arguments, not %d.", - catDirectArgs, - NameListToString(funcname), - catDirectArgs, numDirectArgs), - parser_errposition(pstate, location))); - } - else - { - /* - * If it's variadic, we have two cases depending on whether - * the agg was "... ORDER BY VARIADIC" or "..., VARIADIC ORDER - * BY VARIADIC". It's the latter if catDirectArgs equals - * pronargs; to save a catalog lookup, we reverse-engineer - * pronargs from the info we got from func_get_detail. - */ - int pronargs; - - pronargs = nargs; - if (nvargs > 1) - pronargs -= nvargs - 1; - if (catDirectArgs < pronargs) - { - /* VARIADIC isn't part of direct args, so still easy */ - if (numDirectArgs != catDirectArgs) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(funcname, nargs, - argnames, - actual_arg_types)), - errhint_plural("There is an ordered-set aggregate %s, but it requires %d direct argument, not %d.", - "There is an ordered-set aggregate %s, but it requires %d direct arguments, not %d.", - catDirectArgs, - NameListToString(funcname), - catDirectArgs, numDirectArgs), - parser_errposition(pstate, location))); - } - else - { - /* - * Both direct and aggregated args were declared variadic. - * For a standard ordered-set aggregate, it's okay as long - * as there aren't too few direct args. For a - * hypothetical-set aggregate, we assume that the - * hypothetical arguments are those that matched the - * variadic parameter; there must be just as many of them - * as there are aggregated arguments. - */ - if (aggkind == AGGKIND_HYPOTHETICAL) - { - if (nvargs != 2 * numAggregatedArgs) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(funcname, nargs, - argnames, - actual_arg_types)), - errhint("To use the hypothetical-set aggregate %s, the number of hypothetical direct arguments (here %d) must match the number of ordering columns (here %d).", - NameListToString(funcname), - nvargs - numAggregatedArgs, numAggregatedArgs), - parser_errposition(pstate, location))); - } - else - { - if (nvargs <= numAggregatedArgs) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(funcname, nargs, - argnames, - actual_arg_types)), - errhint_plural("There is an ordered-set aggregate %s, but it requires at least %d direct argument.", - "There is an ordered-set aggregate %s, but it requires at least %d direct arguments.", - catDirectArgs, - NameListToString(funcname), - catDirectArgs), - parser_errposition(pstate, location))); - } - } - } - - /* Check type matching of hypothetical arguments */ - if (aggkind == AGGKIND_HYPOTHETICAL) -/* ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("unify_hypothetical_args not implemented")));*/ - unify_hypothetical_args(pstate, fargs, numAggregatedArgs, - actual_arg_types, declared_arg_types); - } - else - { - /* Normal aggregate, so it can't have WITHIN GROUP */ - if (agg_within_group) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("%s is not an ordered-set aggregate, so it cannot have WITHIN GROUP", - NameListToString(funcname)), - parser_errposition(pstate, location))); - } - } - else if (fdresult == FUNCDETAIL_WINDOWFUNC) - { - /* - * True window functions must be called with a window definition. - */ - if (!over) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("window function %s requires an OVER clause", - NameListToString(funcname)), - parser_errposition(pstate, location))); - /* And, per spec, WITHIN GROUP isn't allowed */ - if (agg_within_group) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("window function %s cannot have WITHIN GROUP", - NameListToString(funcname)), - parser_errposition(pstate, location))); - } - else if (fdresult == FUNCDETAIL_COERCION) - { - /* - * We interpreted it as a type coercion. coerce_type can handle these - * cases, so why duplicate code... - */ - return coerce_type(pstate, linitial(fargs), - actual_arg_types[0], rettype, -1, - COERCION_EXPLICIT, COERCE_EXPLICIT_CALL, location); - } - else if (fdresult == FUNCDETAIL_MULTIPLE) - { - /* - * We found multiple possible functional matches. If we are dealing - * with attribute notation, return failure, letting the caller report - * "no such column" (we already determined there wasn't one). If - * dealing with function notation, report "ambiguous function", - * regardless of whether there's also a column by this name. - */ - if (is_column) - return NULL; - - if (proc_call) - ereport(ERROR, - (errcode(ERRCODE_AMBIGUOUS_FUNCTION), - errmsg("procedure %s is not unique", - func_signature_string(funcname, nargs, argnames, - actual_arg_types)), - errhint("Could not choose a best candidate procedure. " - "You might need to add explicit type casts."), - parser_errposition(pstate, location))); - else - ereport(ERROR, - (errcode(ERRCODE_AMBIGUOUS_FUNCTION), - errmsg("function %s is not unique", - func_signature_string(funcname, nargs, argnames, - actual_arg_types)), - errhint("Could not choose a best candidate function. " - "You might need to add explicit type casts."), - parser_errposition(pstate, location))); - } - else - { - /* - * Not found as a function. If we are dealing with attribute - * notation, return failure, letting the caller report "no such - * column" (we already determined there wasn't one). - */ - if (is_column) - return NULL; - - /* - * Check for column projection interpretation, since we didn't before. - */ - if (could_be_projection) - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("ParseComplexProjection not Implemented"))); -/* - retval = ParseComplexProjection(pstate, - strVal(linitial(funcname)), - first_arg, - location); - if (retval) - return retval; -*/ - } - - /* - * No function, and no column either. Since we're dealing with - * function notation, report "function does not exist". - */ - if (list_length(agg_order) > 1 && !agg_within_group) - { - /* It's agg(x, ORDER BY y,z) ... perhaps misplaced ORDER BY */ - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(funcname, nargs, argnames, - actual_arg_types)), - errhint("No aggregate function matches the given name and argument types. " - "Perhaps you misplaced ORDER BY; ORDER BY must appear " - "after all regular arguments of the aggregate."), - parser_errposition(pstate, location))); - } - else if (proc_call) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("procedure %s does not exist", - func_signature_string(funcname, nargs, argnames, - actual_arg_types)), - errhint("No procedure matches the given name and argument types. " - "You might need to add explicit type casts."), - parser_errposition(pstate, location))); - else - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(funcname, nargs, argnames, - actual_arg_types)), - errhint("No function matches the given name and argument types. " - "You might need to add explicit type casts."), - parser_errposition(pstate, location))); - } - - /* - * If there are default arguments, we have to include their types in - * actual_arg_types for the purpose of checking generic type consistency. - * However, we do NOT put them into the generated parse node, because - * their actual values might change before the query gets run. The - * planner has to insert the up-to-date values at plan time. - */ - nargsplusdefs = nargs; - foreach(l, argdefaults) - { - Node *expr = (Node *) lfirst(l); - - /* probably shouldn't happen ... */ - if (nargsplusdefs >= FUNC_MAX_ARGS) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg_plural("cannot pass more than %d argument to a function", - "cannot pass more than %d arguments to a function", - FUNC_MAX_ARGS, - FUNC_MAX_ARGS), - parser_errposition(pstate, location))); - - actual_arg_types[nargsplusdefs++] = exprType(expr); - } - - /* - * enforce consistency with polymorphic argument and return types, - * possibly adjusting return type or declared_arg_types (which will be - * used as the cast destination by make_fn_arguments) - */ - rettype = enforce_generic_type_consistency(actual_arg_types, - declared_arg_types, - nargsplusdefs, - rettype, - false); + str[1] = 'p'; + str[2] = 'r'; + str[3] = '_'; - /* perform the necessary typecasting of arguments */ - make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types); + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; - /* - * If the function isn't actually variadic, forget any VARIADIC decoration - * on the call. (Perhaps we should throw an error instead, but - * historically we've allowed people to write that.) - */ - if (!OidIsValid(vatype)) - { - Assert(nvargs == 0); - func_variadic = false; - } + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\n'; - /* - * If it's a variadic function call, transform the last nvargs arguments - * into an array --- unless it's an "any" variadic. - */ - if (nvargs > 0 && vatype != ANYOID) - { - ArrayExpr *newa = makeNode(ArrayExpr); - int non_var_args = nargs - nvargs; - List *vargs; - - Assert(non_var_args >= 0); - vargs = list_copy_tail(fargs, non_var_args); - fargs = list_truncate(fargs, non_var_args); - - newa->elements = vargs; - /* assume all the variadic arguments were coerced to the same type */ - newa->element_typeid = exprType((Node *) linitial(vargs)); - newa->array_typeid = get_array_type(newa->element_typeid); - if (!OidIsValid(newa->array_typeid)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find array type for data type %s", - format_type_be(newa->element_typeid)), - parser_errposition(pstate, exprLocation((Node *) vargs)))); - /* array_collid will be set by parse_collate.c */ - newa->multidims = false; - newa->location = exprLocation((Node *) vargs); - - fargs = lappend(fargs, newa); - - /* We could not have had VARIADIC marking before ... */ - Assert(!func_variadic); - /* ... but now, it's a VARIADIC call */ - func_variadic = true; - } + return str; +} - /* - * If an "any" variadic is called with explicit VARIADIC marking, insist - * that the variadic parameter be of some array type. - */ - if (nargs > 0 && vatype == ANYOID && func_variadic) - { - Oid va_arr_typid = actual_arg_types[nargs - 1]; - if (!OidIsValid(get_base_element_type(va_arr_typid))) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("VARIADIC argument must be an array"), - parser_errposition(pstate, - exprLocation((Node *) llast(fargs))))); - } +Node * +transform_cypher_expr(cypher_parsestate *cpstate, Node *expr, ParseExprKind expr_kind) { + ParseExprKind old_expr_kind = get_parse_state(cpstate)->p_expr_kind; + get_parse_state(cpstate)->p_expr_kind = expr_kind; - /* if it returns a set, check that's OK */ - if (retset) - check_srf_call_placement(pstate, last_srf, location); + Node *result = transform_cypher_expr_recurse(cpstate, expr); - /* build the appropriate output structure */ - if (fdresult == FUNCDETAIL_NORMAL || fdresult == FUNCDETAIL_PROCEDURE) - { - FuncExpr *funcexpr = makeNode(FuncExpr); - - funcexpr->funcid = funcid; - funcexpr->funcresulttype = rettype; - funcexpr->funcretset = retset; - funcexpr->funcvariadic = func_variadic; - funcexpr->funcformat = funcformat; - /* funccollid and inputcollid will be set by parse_collate.c */ - funcexpr->args = fargs; - funcexpr->location = location; - - retval = (Node *) funcexpr; - } - else if (fdresult == FUNCDETAIL_AGGREGATE && !over) - { - /* aggregate function */ - Aggref *aggref = makeNode(Aggref); - - aggref->aggfnoid = funcid; - aggref->aggtype = rettype; - /* aggcollid and inputcollid will be set by parse_collate.c */ - aggref->aggtranstype = InvalidOid; /* will be set by planner */ - /* aggargtypes will be set by transformAggregateCall */ - /* aggdirectargs and args will be set by transformAggregateCall */ - /* aggorder and aggdistinct will be set by transformAggregateCall */ - aggref->aggfilter = agg_filter; - aggref->aggstar = agg_star; - aggref->aggvariadic = func_variadic; - aggref->aggkind = aggkind; - /* agglevelsup will be set by transformAggregateCall */ - aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */ - aggref->aggno = -1; /* planner will set aggno and aggtransno */ - aggref->aggtransno = -1; - aggref->location = location; + get_parse_state(cpstate)->p_expr_kind = old_expr_kind; - /* - * Reject attempt to call a parameterless aggregate without (*) - * syntax. This is mere pedantry but some folks insisted ... - */ - if (fargs == NIL && !agg_star && !agg_within_group) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("%s(*) must be used to call a parameterless aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); + return result; +} - if (retset) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("aggregates cannot return sets"), - parser_errposition(pstate, location))); +static Node * +transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr) { + if (!expr) + return NULL; - /* - * We might want to support named arguments later, but disallow it for - * now. We'd need to figure out the parsed representation (should the - * NamedArgExprs go above or below the TargetEntry nodes?) and then - * teach the planner to reorder the list properly. Or maybe we could - * make transformAggregateCall do that? However, if you'd also like - * to allow default arguments for aggregates, we'd need to do it in - * planning to avoid semantic problems. - */ - if (argnames != NIL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("aggregates cannot use named arguments"), - parser_errposition(pstate, location))); + check_stack_depth(); - /* parse_agg.c does additional aggregate-specific processing */ - transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct); + switch (nodeTag(expr)) + { + case T_A_Const: + return transform_a_const(cpstate, (A_Const *)expr); + case T_ColumnRef: + return transform_column_ref(cpstate, (ColumnRef *)expr); + case T_A_Indirection: + return transform_a_indirection(cpstate, (A_Indirection *)expr); + case T_A_Expr: + { + A_Expr *a = (A_Expr *)expr; - retval = (Node *) aggref; + switch (a->kind) + { + case AEXPR_OP: + return transform_a_expr_op(cpstate, a); + default: + ereport(ERROR, (errmsg_internal("unrecognized A_Expr kind: %d", a->kind))); + } } - else + case T_BoolExpr: + return transform_bool_expr(cpstate, (BoolExpr *)expr); + case T_NullTest: { - /* window function */ - WindowFunc *wfunc = makeNode(WindowFunc); - - Assert(over); /* lack of this was checked above */ - Assert(!agg_within_group); /* also checked above */ - - wfunc->winfnoid = funcid; - wfunc->wintype = rettype; - /* wincollid and inputcollid will be set by parse_collate.c */ - wfunc->args = fargs; - /* winref will be set by transformWindowFuncCall */ - wfunc->winstar = agg_star; - wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE); - wfunc->aggfilter = agg_filter; - wfunc->location = location; - - /* - * agg_star is allowed for aggregate functions but distinct isn't - */ - if (agg_distinct) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("DISTINCT is not implemented for window functions"), - parser_errposition(pstate, location))); - - /* - * Reject attempt to call a parameterless aggregate without (*) - * syntax. This is mere pedantry but some folks insisted ... - */ - if (wfunc->winagg && fargs == NIL && !agg_star) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("%s(*) must be used to call a parameterless aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - - /* - * ordered aggs not allowed in windows yet - */ - if (agg_order != NIL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("aggregate ORDER BY is not implemented for window functions"), - parser_errposition(pstate, location))); - - /* - * FILTER is not yet supported with true window functions - */ - if (!wfunc->winagg && agg_filter) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("FILTER is not implemented for non-aggregate window functions"), - parser_errposition(pstate, location))); + NullTest *n = (NullTest *)expr; - /* - * Window functions can't either take or return sets - */ - if (pstate->p_last_srf != last_srf) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("window function calls cannot contain set-returning function calls"), - errhint("You might be able to move the set-returning function into a LATERAL FROM item."), - parser_errposition(pstate, - exprLocation(pstate->p_last_srf)))); + n->arg = (Expr *)transform_cypher_expr_recurse(cpstate, (Node *)n->arg); + n->argisrow = type_is_rowtype(exprType((Node *)n->arg)); - if (retset) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("window functions cannot return sets"), - parser_errposition(pstate, location))); + return expr; + } + case T_CaseExpr: + return transform_case_expr(cpstate, (CaseExpr *) expr); + case T_CaseTestExpr: + return expr; + case T_CoalesceExpr: + return transform_coalesce_expr(cpstate, (CoalesceExpr *) expr); + case T_ExtensibleNode: + if (is_ag_node(expr, cypher_bool_const)) + return transform_cypher_bool_const(cpstate, (cypher_bool_const *)expr); + if (is_ag_node(expr, cypher_inet_const)) + return transform_cypher_inet_const(cpstate, (cypher_inet_const *)expr); + if (is_ag_node(expr, cypher_integer_const)) + return transform_cypher_integer_const(cpstate, (cypher_integer_const *)expr); + if (is_ag_node(expr, cypher_param)) + return transform_cypher_param(cpstate, (cypher_param *)expr); + if (is_ag_node(expr, cypher_map)) + return transform_cypher_map(cpstate, (cypher_map *)expr); + if (is_ag_node(expr, cypher_list)) + return transform_cypher_list(cpstate, (cypher_list *)expr); + if (is_ag_node(expr, cypher_string_match)) + return transform_cypher_string_match(cpstate, (cypher_string_match *)expr); + if (is_ag_node(expr, cypher_typecast)) + return transform_cypher_typecast(cpstate, (cypher_typecast *)expr); - /* parse_agg.c does additional window-func-specific processing */ - transformWindowFuncCall(pstate, wfunc, over); + ereport(ERROR, (errmsg_internal("unrecognized ExtensibleNode: %s", + ((ExtensibleNode *)expr)->extnodename))); - retval = (Node *) wfunc; + case T_FuncCall: + return transform_func_call(cpstate, (FuncCall *)expr); + case T_SubLink: + return transform_sub_link(cpstate, (SubLink *)expr); + case T_SQLValueFunction: + return transformSQLValueFunction(cpstate, (SQLValueFunction *)expr); + case T_String: + return makeConst(GTYPEOID, -1, InvalidOid, -1, string_to_gtype(strVal(expr)), false, false); + case T_Var: + { + return (Node *) expr; + } + default: + ereport(ERROR, (errmsg_internal("unrecognized node type: %d", nodeTag(expr)))); } - /* if it returns a set, remember it for error checks at higher levels */ - if (retset) - pstate->p_last_srf = retval; - - return retval; + return NULL; } - static Node * transform_a_const(cypher_parsestate *cpstate, A_Const *ac) { Value *v = &ac->val; @@ -1877,67 +246,6 @@ transform_a_const(cypher_parsestate *cpstate, A_Const *ac) { return (Node *)c; } -static Node * -transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem, - int sublevels_up, int location) -{ - /* - * Build the appropriate referencing node. Normally this can be a - * whole-row Var, but if the nsitem is a JOIN USING alias then it contains - * only a subset of the columns of the underlying join RTE, so that will - * not work. Instead we immediately expand the reference into a RowExpr. - * Since the JOIN USING's common columns are fully determined at this - * point, there seems no harm in expanding it now rather than during - * planning. - * - * Note that if the RTE is a function returning scalar, we create just a - * plain reference to the function value, not a composite containing a - * single column. This is pretty inconsistent at first sight, but it's - * what we've done historically. One argument for it is that "rel" and - * "rel.*" mean the same thing for composite relations, so why not for - * scalar functions... - */ - if (nsitem->p_names == nsitem->p_rte->eref) - { - Var *result; - - result = makeWholeRowVar(nsitem->p_rte, nsitem->p_rtindex, - sublevels_up, true); - - /* location is not filled in by makeWholeRowVar */ - result->location = location; - - /* mark relation as requiring whole-row SELECT access */ - markVarForSelectPriv(pstate, result); - - return (Node *) result; - } - else - { - RowExpr *rowexpr; - List *fields; - - /* - * We want only as many columns as are listed in p_names->colnames, - * and we should use those names not whatever possibly-aliased names - * are in the RTE. We needn't worry about marking the RTE for SELECT - * access, as the common columns are surely so marked already. - */ - expandRTE(nsitem->p_rte, nsitem->p_rtindex, - sublevels_up, location, false, - NULL, &fields); - rowexpr = makeNode(RowExpr); - rowexpr->args = list_truncate(fields, - list_length(nsitem->p_names->colnames)); - rowexpr->row_typeid = RECORDOID; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(nsitem->p_names->colnames); - rowexpr->location = location; - - return (Node *) rowexpr; - } -} - static Node * transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { @@ -1959,25 +267,20 @@ transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { colname = strVal(field1); node = colNameToVar(pstate, colname, false, cref->location); + if (node != NULL) return node; - { - ParseNamespaceItem *nsitem = refnameNamespaceItem(pstate, NULL, colname, cref->location, &levels_up); - - if (nsitem) { - return transformWholeRowRef(pstate, nsitem, levels_up, cref->location); - } - - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("could not find rte for %s", colname), - parser_errposition(pstate, cref->location))); + { + // ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), + // errmsg("could not find rte for %s", colname), + // parser_errposition(pstate, cref->location))); } - if (!node) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("variable `%s` does not exist", colname), - parser_errposition(pstate, cref->location))); + //if (!node) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("variable `%s` does not exist", colname), + parser_errposition(pstate, cref->location))); return node; } @@ -1990,20 +293,7 @@ transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { colname = strVal(field1); node = colNameToVar(pstate, colname, false, cref->location); - if (node == NULL){ - - ParseNamespaceItem *nsitem = refnameNamespaceItem(pstate, NULL, colname, cref->location, &levels_up); - - if (nsitem) { - return transformWholeRowRef(pstate, nsitem, levels_up, cref->location); - } - - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("could not find rte for %s", colname), - parser_errposition(pstate, cref->location))); - - - } + if (!node) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -2064,15 +354,12 @@ transform_column_ref(cypher_parsestate *cpstate, ColumnRef *cref) { */ static bool is_a_valid_operator(List *lst) { - if (list_length(lst) != 1) - return false; - - if (!IsA(linitial(lst), String)) + if (list_length(lst) != 1 || !IsA(linitial(lst), String)) return false; - Value *str = linitial(lst); + char *str = strVal(linitial(lst)); - if (!strcmp(str->val.str, "?") || !strcmp(str->val.str, "?|") || !strcmp(str->val.str, "?&")) + if (!strcmp(str, "?") || !strcmp(str, "?|") || !strcmp(str, "?&")) return true; return false; @@ -2115,7 +402,6 @@ transform_bool_expr(cypher_parsestate *cpstate, BoolExpr *expr) { ParseState *pstate = (ParseState *)cpstate; List *args = NIL; const char *opname; - ListCell *la; switch (expr->boolop) { @@ -2134,6 +420,7 @@ transform_bool_expr(cypher_parsestate *cpstate, BoolExpr *expr) { return NULL; } + ListCell *la; foreach (la, expr->args) { Node *arg = lfirst(la); @@ -2149,55 +436,34 @@ transform_bool_expr(cypher_parsestate *cpstate, BoolExpr *expr) { static Node * transform_cypher_bool_const(cypher_parsestate *cpstate, cypher_bool_const *b) { - - Datum agt = boolean_to_gtype(b->boolean); - - // typtypmod, typcollation, typlen, and typbyval of gtype are hard-coded. - Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, agt, false, false); - c->location = b->location; - - return (Node *)c; + return (Node *)makeConst(GTYPEOID, -1, InvalidOid, -1, boolean_to_gtype(b->boolean), false, false); } - static Node * transform_cypher_inet_const(cypher_parsestate *cpstate, cypher_inet_const *inet) { - - Datum agt = _gtype_toinet(GTYPE_P_GET_DATUM(string_to_gtype(inet->inet))); - - // typtypmod, typcollation, typlen, and typbyval of gtype are hard-coded. - Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, agt, false, false); - c->location = inet->location; - - return (Node *)c; + return makeConst(GTYPEOID, -1, InvalidOid, -1, _gtype_toinet(GTYPE_P_GET_DATUM(string_to_gtype(inet->inet))), false, false); } static Node * transform_cypher_integer_const(cypher_parsestate *cpstate, cypher_integer_const *i) { - - Datum agt = integer_to_gtype(i->integer); - - // typtypmod, typcollation, typlen, and typbyval of gtype are hard-coded. - Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, agt, false, false); - c->location = i->location; - - return (Node *)c; + return makeConst(GTYPEOID, -1, InvalidOid, -1, integer_to_gtype(i->integer), false, false); } static Node * transform_cypher_param(cypher_parsestate *cpstate, cypher_param *p) { - ParseState *pstate = (ParseState *)cpstate; - Const *const_str; - if (!cpstate->params) ereport( ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("parameters argument is missing from cypher() function call"), - parser_errposition(pstate, p->location))); - - const_str = makeConst(GTYPEOID, -1, InvalidOid, -1, string_to_gtype(p->name), false, false); - - return (Node *)make_op(pstate, list_make1(makeString("->")), (Node *)cpstate->params, (Node *)const_str, pstate->p_last_srf, -1); + parser_errposition(cpstate, p->location))); + + return (Node *)make_op( + cpstate, + list_make1(makeString("->")), + (Node *)cpstate->params, + (Node *)makeConst(GTYPEOID, -1, InvalidOid, -1, string_to_gtype(p->name), false, false), + get_parse_state(cpstate)->p_last_srf, + -1); } static Node * @@ -2244,16 +510,11 @@ transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl) { args = lappend(args, transform_cypher_expr_recurse(cpstate, lfirst(lc))); } - Oid oid; - if (list_length(args) == 0) - oid = get_ag_func_oid("gtype_build_list", 0); - else - oid = get_ag_func_oid("gtype_build_list", 1, ANYOID); - - FuncExpr *expr = makeFuncExpr(oid, GTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - expr->location = cl->location; - - return (Node *)expr; + return makeFuncExpr( + list_length(args) == 0 ? + get_ag_func_oid("gtype_build_list", 0) : + get_ag_func_oid("gtype_build_list", 1, ANYOID), + GTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } /* @@ -2277,11 +538,9 @@ transform_column_ref_for_indirection(cypher_parsestate *cpstate, ColumnRef *cr) // locate the referenced RTE pnsi = refnameNamespaceItem(pstate, NULL, relname, cr->location, &levels_up); - // This column ref is referencing something that was created in a previous query and is a variable. if (!pnsi) return transform_cypher_expr_recurse(cpstate, (Node *)cr); - // try to identify the properties column of the RTE node = scanNSItemForColumn(pstate, pnsi, 0, "properties", cr->location); if (!node) @@ -2312,7 +571,6 @@ transform_a_indirection(cypher_parsestate *cpstate, A_Indirection *a_ind) { else cur = transform_cypher_expr_recurse(cpstate, a_ind->arg); - // iterate through each indirection value ListCell *lc; foreach (lc, a_ind->indirection) { @@ -2321,50 +579,47 @@ transform_a_indirection(cypher_parsestate *cpstate, A_Indirection *a_ind) { if (is_a_slice(node)) { A_Indices *indices = (A_Indices *)node; - List *args = list_make1(cur); - - // lower bound - if (!indices->lidx) - args = lappend(args, makeConst(GTYPEOID, -1, InvalidOid, -1, (Datum)NULL, true, false)); - else - args = lappend(args, transform_cypher_expr_recurse(cpstate, indices->lidx)); - - // upper bound - if (!indices->uidx) - args = lappend(args, makeConst(GTYPEOID, -1, InvalidOid, -1, (Datum)NULL, true, false)); - else - args = lappend(args, transform_cypher_expr_recurse(cpstate, indices->uidx)); - - Oid oid = get_ag_func_oid("gtype_access_slice", 3, GTYPEOID, GTYPEOID, GTYPEOID); - FuncExpr *func_expr = makeFuncExpr(oid, GTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = exprLocation(cur); - - cur = (Node *)func_expr; + cur = makeFuncExpr( + get_ag_func_oid("gtype_access_slice", 3, GTYPEOID, GTYPEOID, GTYPEOID), + GTYPEOID, + list_make3( + cur, + indices->lidx ? transform_cypher_expr_recurse(cpstate, indices->lidx) : makeConst(GTYPEOID, -1, InvalidOid, -1, (Datum)NULL, true, false), + indices->uidx ? transform_cypher_expr_recurse(cpstate, indices->uidx) : makeConst(GTYPEOID, -1, InvalidOid, -1, (Datum)NULL, true, false) + ), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); } else if (IsA(node, A_Indices)) { // a[i] A_Indices *indices = (A_Indices *)node; - node = transform_cypher_expr_recurse(cpstate, indices->uidx); - cur = (Node *)make_op(pstate, list_make1(makeString("->")), cur, node, pstate->p_last_srf, -1); + cur = (Node *)make_op( + pstate, + list_make1(makeString("->")), + cur, + transform_cypher_expr_recurse(cpstate, indices->uidx), pstate->p_last_srf, -1); } else if (IsA(node, ColumnRef)) { // a.i - ColumnRef *cr = (ColumnRef*)node; - List *fields = cr->fields; - Value *string = linitial(fields); - - Const *const_str = makeConst(GTYPEOID, -1, InvalidOid, -1, string_to_gtype(strVal(string)), false, false); - - cur = (Node *)make_op(pstate, list_make1(makeString("->")), cur, (Node *)const_str, pstate->p_last_srf, -1); + ColumnRef *cr = (ColumnRef *)node; + cur = (Node *)make_op( + pstate, + list_make1(makeString("->")), + cur, + makeConst(GTYPEOID, -1, InvalidOid, -1, string_to_gtype(strVal(linitial(cr->fields))), false, false), + pstate->p_last_srf, + -1 + ); } else { ereport(ERROR, (errmsg("invalid indirection node %d", nodeTag(node)))); } - // TODO: Add Regression Tests if (pstate->p_last_srf != last_srf) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-returning functions are not allowed in indirection"), + errmsg("set-returning functions are not allowed in -> operator"), + errhint("Use UNWIND to expand the result set of the function first"), parser_errposition(pstate, exprLocation(pstate->p_last_srf)))); } @@ -2390,15 +645,14 @@ transform_cypher_string_match(cypher_parsestate *cpstate, cypher_string_match *c ereport(ERROR, (errmsg_internal("unknown Cypher string match operation"))); } - Oid oid = get_ag_func_oid(func_name, 2, GTYPEOID, GTYPEOID); - - List *args = list_make2(transform_cypher_expr_recurse(cpstate, csm_node->lhs), - transform_cypher_expr_recurse(cpstate, csm_node->rhs)); - - FuncExpr *expr = makeFuncExpr(oid, GTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - expr->location = csm_node->location; - - return (Node *)expr; + return makeFuncExpr( + get_ag_func_oid(func_name, 2, GTYPEOID, GTYPEOID), + GTYPEOID, + list_make2(transform_cypher_expr_recurse(cpstate, csm_node->lhs), + transform_cypher_expr_recurse(cpstate, csm_node->rhs)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); } /* @@ -2407,7 +661,6 @@ transform_cypher_string_match(cypher_parsestate *cpstate, cypher_string_match *c static Node * transform_cypher_typecast(cypher_parsestate *cpstate, cypher_typecast *ctypecast) { List *fname; - FuncCall *fnode; /* verify input parameter */ Assert (cpstate != NULL); @@ -2474,97 +727,11 @@ transform_cypher_typecast(cypher_parsestate *cpstate, cypher_typecast *ctypecast else ereport(ERROR, (errmsg_internal("typecast \'%s\' not supported", ctypecast->typecast))); - // make a function call node - fnode = makeFuncCall(fname, list_make1(ctypecast->expr), COERCE_SQL_SYNTAX, ctypecast->location); - return transform_func_call(cpstate, fnode); + return transform_func_call(cpstate, makeFuncCall(fname, list_make1(ctypecast->expr), COERCE_SQL_SYNTAX, ctypecast->location)); } -/* - * unify_hypothetical_args() - * - * Ensure that each hypothetical direct argument of a hypothetical-set - * aggregate has the same type as the corresponding aggregated argument. - * Modify the expressions in the fargs list, if necessary, and update - * actual_arg_types[]. - * - * If the agg declared its args non-ANY (even ANYELEMENT), we need only a - * sanity check that the declared types match; make_fn_arguments will coerce - * the actual arguments to match the declared ones. But if the declaration - * is ANY, nothing will happen in make_fn_arguments, so we need to fix any - * mismatch here. We use the same type resolution logic as UNION etc. - */ -static void -unify_hypothetical_args(ParseState *pstate, - List *fargs, - int numAggregatedArgs, - Oid *actual_arg_types, - Oid *declared_arg_types) -{ - int numDirectArgs, - numNonHypotheticalArgs; - int hargpos; - - numDirectArgs = list_length(fargs) - numAggregatedArgs; - numNonHypotheticalArgs = numDirectArgs - numAggregatedArgs; - /* safety check (should only trigger with a misdeclared agg) */ - if (numNonHypotheticalArgs < 0) - elog(ERROR, "incorrect number of arguments to hypothetical-set aggregate"); - - /* Check each hypothetical arg and corresponding aggregated arg */ - for (hargpos = numNonHypotheticalArgs; hargpos < numDirectArgs; hargpos++) - { - int aargpos = numDirectArgs + (hargpos - numNonHypotheticalArgs); - ListCell *harg = list_nth_cell(fargs, hargpos); - ListCell *aarg = list_nth_cell(fargs, aargpos); - Oid commontype; - int32 commontypmod; - - /* A mismatch means AggregateCreate didn't check properly ... */ - if (declared_arg_types[hargpos] != declared_arg_types[aargpos]) - elog(ERROR, "hypothetical-set aggregate has inconsistent declared argument types"); - - /* No need to unify if make_fn_arguments will coerce */ - if (declared_arg_types[hargpos] != ANYOID) - continue; - - /* - * Select common type, giving preference to the aggregated argument's - * type (we'd rather coerce the direct argument once than coerce all - * the aggregated values). - */ - commontype = select_common_type(pstate, - list_make2(lfirst(aarg), lfirst(harg)), - "WITHIN GROUP", - NULL); - commontypmod = select_common_typmod(pstate, - list_make2(lfirst(aarg), lfirst(harg)), - commontype); - - /* - * Perform the coercions. We don't need to worry about NamedArgExprs - * here because they aren't supported with aggregates. - */ - lfirst(harg) = coerce_type(pstate, - (Node *) lfirst(harg), - actual_arg_types[hargpos], - commontype, commontypmod, - COERCION_IMPLICIT, - COERCE_IMPLICIT_CAST, - -1); - actual_arg_types[hargpos] = commontype; - lfirst(aarg) = coerce_type(pstate, - (Node *) lfirst(aarg), - actual_arg_types[aargpos], - commontype, commontypmod, - COERCION_IMPLICIT, - COERCE_IMPLICIT_CAST, - -1); - actual_arg_types[aargpos] = commontype; - } -} - static List * make_qualified_function_name(cypher_parsestate *cpstate, List *lst, List *targs) { char *name = ((Value*)linitial(lst))->val.str; @@ -2630,7 +797,7 @@ transform_func_call(cypher_parsestate *cpstate, FuncCall *fn) { fname = fn->funcname; // Passed our new fname to the normal function transform logic - Node *retval = parse_func_or_column(pstate, fname, args, last_srf, fn, false, fn->location); + Node *retval = ParseFuncOrColumn(pstate, fname, args, last_srf, fn, false, fn->location); if (list_length(fn->funcname) == 1) { @@ -3004,28 +1171,6 @@ make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rarg return (Node *) rcexpr; } -/* - * sql_transform_where_clause - - * Transform the qualification and make sure it is of type boolean. - * Used for WHERE and allied clauses. - * - * constructName does not affect the semantics, but is used in error messages - */ -Node * -sql_transform_where_clause(ParseState *pstate, Node *clause, - ParseExprKind exprKind, const char *constructName) -{ - Node *qual; - - if (clause == NULL) - return NULL; - - qual = sql_transform_expr(pstate, clause, exprKind); - - qual = coerce_to_boolean(pstate, qual, constructName); - - return qual; -} /* * Transform a FOR [KEY] UPDATE/SHARE clause * @@ -3236,147 +1381,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, } } } -/* - * sql_transform_select_stmt - - * transforms a Select Statement - * - * Note: this covers only cases with no set operations and no VALUES lists; - * see below for the other cases. - */ -static Query * -sql_transform_select_stmt(ParseState *pstate, SelectStmt *stmt) -{ - Query *qry = makeNode(Query); - Node *qual; - ListCell *l; - - qry->commandType = CMD_SELECT; - - /* process the WITH clause independently of all else */ - if (stmt->withClause) - { - qry->hasRecursive = stmt->withClause->recursive; - qry->cteList = transformWithClause(pstate, stmt->withClause); - qry->hasModifyingCTE = pstate->p_hasModifyingCTE; - } - - /* Complain if we get called from someplace where INTO is not allowed */ - if (stmt->intoClause) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("SELECT ... INTO is not allowed here"), - parser_errposition(pstate, - exprLocation((Node *) stmt->intoClause)))); - - /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */ - pstate->p_locking_clause = stmt->lockingClause; - - /* make WINDOW info available for window functions, too */ - pstate->p_windowdefs = stmt->windowClause; - - /* process the FROM clause */ - transformFromClause(pstate, stmt->fromClause); - - /* transform targetlist */ - qry->targetList = transformTargetList(pstate, stmt->targetList, - EXPR_KIND_SELECT_TARGET); - - /* mark column origins */ - markTargetListOrigins(pstate, qry->targetList); - - /* transform WHERE */ - qual = sql_transform_where_clause(pstate, stmt->whereClause, - EXPR_KIND_WHERE, "WHERE"); - - /* initial processing of HAVING clause is much like WHERE clause */ - qry->havingQual = sql_transform_where_clause(pstate, stmt->havingClause, - EXPR_KIND_HAVING, "HAVING"); - - /* - * Transform sorting/grouping stuff. Do ORDER BY first because both - * transformGroupClause and transformDistinctClause need the results. Note - * that these functions can also change the targetList, so it's passed to - * them by reference. - */ - qry->sortClause = transformSortClause(pstate, - stmt->sortClause, - &qry->targetList, - EXPR_KIND_ORDER_BY, - false /* allow SQL92 rules */ ); - - qry->groupClause = transformGroupClause(pstate, - stmt->groupClause, - &qry->groupingSets, - &qry->targetList, - qry->sortClause, - EXPR_KIND_GROUP_BY, - false /* allow SQL92 rules */ ); - qry->groupDistinct = stmt->groupDistinct; - - if (stmt->distinctClause == NIL) - { - qry->distinctClause = NIL; - qry->hasDistinctOn = false; - } - else if (linitial(stmt->distinctClause) == NULL) - { - /* We had SELECT DISTINCT */ - qry->distinctClause = transformDistinctClause(pstate, - &qry->targetList, - qry->sortClause, - false); - qry->hasDistinctOn = false; - } - else - { - /* We had SELECT DISTINCT ON */ - qry->distinctClause = transformDistinctOnClause(pstate, - stmt->distinctClause, - &qry->targetList, - qry->sortClause); - qry->hasDistinctOn = true; - } - - /* transform LIMIT */ - qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, - EXPR_KIND_OFFSET, "OFFSET", - stmt->limitOption); - qry->limitCount = transformLimitClause(pstate, stmt->limitCount, - EXPR_KIND_LIMIT, "LIMIT", - stmt->limitOption); - qry->limitOption = stmt->limitOption; - - /* transform window clauses after we have seen all window functions */ - qry->windowClause = transformWindowDefinitions(pstate, - pstate->p_windowdefs, - &qry->targetList); - - /* resolve any still-unresolved output columns as being type text */ - if (pstate->p_resolve_unknowns) - resolveTargetListUnknowns(pstate, qry->targetList); - - qry->rtable = pstate->p_rtable; - qry->jointree = makeFromExpr(pstate->p_joinlist, qual); - - qry->hasSubLinks = pstate->p_hasSubLinks; - qry->hasWindowFuncs = pstate->p_hasWindowFuncs; - qry->hasTargetSRFs = pstate->p_hasTargetSRFs; - qry->hasAggs = pstate->p_hasAggs; - - foreach(l, stmt->lockingClause) - { - transformLockingClause(pstate, qry, - (LockingClause *) lfirst(l), false); - } - - assign_query_collations(pstate, qry); - - /* this must be done after collations, for reliable comparison of exprs */ - if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual) - parseCheckAggregates(pstate, qry); - - return qry; -} /* @@ -3403,9 +1407,11 @@ sql_parse_sub_analyze(Node *parseTree, ParseState *parentParseState, if (n->valuesLists) elog(ERROR, "unexpected non-SELECT command in SubLink"); //query = transformValuesClause(pstate, n); - else if (n->op == SETOP_NONE) - query = sql_transform_select_stmt(pstate, n); - else + else if (n->op == SETOP_NONE) { + parsing_cypher = false; + query = transformSelectStmt(pstate, n); + parsing_cypher = true; + } else elog(ERROR, "unexpected non-SELECT command in SubLink"); //query = transformSetOperationStmt(pstate, n); } @@ -3507,9 +1513,11 @@ transform_sub_link(cypher_parsestate *cpstate, SubLink *sublink) { Query *query; - if (IsA(sublink->subselect, SelectStmt)) - query = sql_parse_sub_analyze(sublink->subselect, pstate, NULL, false, true); - else + if (IsA(sublink->subselect, SelectStmt)) { + parsing_cypher = false; + query = sql_parse_sub_analyze(sublink->subselect, pstate, NULL, false, true); + parsing_cypher = true; + } else query = cypher_parse_sub_analyze(sublink->subselect, cpstate, NULL, false, true); if (!IsA(query, Query) || query->commandType != CMD_SELECT) @@ -3683,3 +1691,6 @@ transformSQLValueFunction(cypher_parsestate *cpstate, SQLValueFunction *svf) return result; } +Node *cypher_columnref_hook (ParseState *pstate, ColumnRef *cref) { + return NULL; +} \ No newline at end of file diff --git a/src/backend/parser/cypher_grouping.c b/src/backend/parser/cypher_grouping.c deleted file mode 100644 index 18101509..00000000 --- a/src/backend/parser/cypher_grouping.c +++ /dev/null @@ -1,873 +0,0 @@ - -#include "postgraph.h" - -#include "access/nbtree.h" -#include "access/sysattr.h" -#include "access/heapam.h" -#include "catalog/pg_amproc.h" -#include "catalog/pg_type_d.h" -#include "miscadmin.h" -#include "nodes/makefuncs.h" -#include "nodes/nodeFuncs.h" -#include "nodes/nodes.h" -#include "nodes/parsenodes.h" -#include "nodes/pg_list.h" -#include "nodes/primnodes.h" -#include "optimizer/optimizer.h" -#include "parser/parse_clause.h" -#include "parser/parse_coerce.h" -#include "parser/parse_collate.h" -#include "parser/cypher_analyze.h" -#include "parser/parse_expr.h" -#include "parser/parse_func.h" -#include "parser/parse_node.h" -#include "parser/parse_oper.h" -#include "parser/parse_relation.h" -#include "parser/parse_target.h" -#include "parser/parsetree.h" -#include "rewrite/rewriteHandler.h" -#include "utils/catcache.h" -#include "utils/typcache.h" -#include "utils/lsyscache.h" -#include "utils/rel.h" - -#include "catalog/ag_graph.h" -#include "catalog/ag_label.h" -#include "commands/label_commands.h" -#include "nodes/ag_nodes.h" -#include "nodes/cypher_nodes.h" -#include "parser/cypher_clause.h" -#include "parser/cypher_expr.h" -#include "parser/cypher_item.h" -#include "parser/cypher_parse_agg.h" -#include "parser/cypher_parse_node.h" -#include "utils/ag_cache.h" -#include "utils/ag_func.h" -#include "utils/gtype.h" -#include "utils/graphid.h" -#include "utils/vertex.h" -#include "utils/edge.h" -#include "utils/traversal.h" -#include "utils/variable_edge.h" - -static WindowClause * -findWindowClause(List *wclist, const char *name); - static Node *flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets); -static Index transform_group_clause_expr(List **flatresult, Bitmapset *seen_local, cypher_parsestate *cpstate, Node *gexpr, List **targetlist, List *sortClause, ParseExprKind exprKind, bool toplevel); -static List *add_target_to_group_list(cypher_parsestate *cpstate, TargetEntry *tle, List *grouplist, List *targetlist, int location); -static Node * -transformGroupingSet(List **flatresult, ParseState *pstate, GroupingSet *gset, List **targetlist, List *sortClause, - ParseExprKind exprKind, bool useSQL99, bool toplevel); - static Node * -transform_frame_offset(ParseState *pstate, int frameOptions, - Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc, - Node *clause); -static void -checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName); - - List * transform_group_clause(cypher_parsestate *cpstate, List *grouplist, List **groupingSets, - List **targetlist, List *sortClause, ParseExprKind exprKind) { - ParseState *pstate = (ParseState *)cpstate; - List *result = NIL; - List *flat_grouplist; - List *gsets = NIL; - ListCell *gl; - bool hasGroupingSets = false; - Bitmapset *seen_local = NULL; - - /* - * Recursively flatten implicit RowExprs. (Technically this is only needed - * for GROUP BY, per the syntax rules for grouping sets, but we do it - * anyway.) - */ - flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist, true, &hasGroupingSets); - - /* - * If the list is now empty, but hasGroupingSets is true, it's because we - * elided redundant empty grouping sets. Restore a single empty grouping - * set to leave a canonical form: GROUP BY () - */ - - if (flat_grouplist == NIL && hasGroupingSets) - flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY, NIL, exprLocation((Node *) grouplist))); - - - foreach(gl, flat_grouplist) { - Node *gexpr = (Node *) lfirst(gl); - - if (IsA(gexpr, GroupingSet)) { - GroupingSet *gset = (GroupingSet *) gexpr; - - switch (gset->kind) - { - case GROUPING_SET_EMPTY: - gsets = lappend(gsets, gset); - break; - case GROUPING_SET_SIMPLE: - /* can't happen */ - Assert(false); - break; - case GROUPING_SET_SETS: - case GROUPING_SET_CUBE: - case GROUPING_SET_ROLLUP: - gsets = lappend(gsets, - transformGroupingSet(&result, pstate, gset, targetlist, sortClause, exprKind, true, true)); - break; - } - - } else { - Index ref = transform_group_clause_expr(&result, seen_local, cpstate, gexpr, targetlist, - sortClause, exprKind, true); - if (ref > 0) { - seen_local = bms_add_member(seen_local, ref); - if (hasGroupingSets) { - seen_local = bms_add_member(seen_local, ref); - if (hasGroupingSets) - gsets = lappend(gsets, - makeGroupingSet(GROUPING_SET_SIMPLE, list_make1_int(ref), exprLocation(gexpr))); - - } - } - } - } - - - if (groupingSets) - *groupingSets = gsets; - - return result; -} - - -/* - * findWindowClause - * Find the named WindowClause in the list, or return NULL if not there - */ -static WindowClause * -findWindowClause(List *wclist, const char *name) -{ - ListCell *l; - - foreach(l, wclist) - { - WindowClause *wc = (WindowClause *) lfirst(l); - - if (wc->name && strcmp(wc->name, name) == 0) - return wc; - } - - return NULL; -} -/* - * transformWindowDefinitions - - * transform window definitions (WindowDef to WindowClause) - */ -List * -transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist) -{ - List *result = NIL; - Index winref = 0; - ListCell *lc; - - foreach(lc, windowdefs) - { - WindowDef *windef = (WindowDef *) lfirst(lc); - WindowClause *refwc = NULL; - List *partitionClause; - List *orderClause; - Oid rangeopfamily = InvalidOid; - Oid rangeopcintype = InvalidOid; - WindowClause *wc; - - winref++; - - /* - * Check for duplicate window names. - */ - if (windef->name && findWindowClause(result, windef->name) != NULL) - ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("window \"%s\" is already defined", windef->name), - parser_errposition(pstate, windef->location))); - - /* - * If it references a previous window, look that up. - */ - if (windef->refname) - { - refwc = findWindowClause(result, windef->refname); - if (refwc == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("window \"%s\" does not exist", - windef->refname), - parser_errposition(pstate, windef->location))); - } - - /* - * Transform PARTITION and ORDER specs, if any. These are treated - * almost exactly like top-level GROUP BY and ORDER BY clauses, - * including the special handling of nondefault operator semantics. - */ - orderClause = transform_cypher_order_by(pstate, windef->orderClause, targetlist, EXPR_KIND_ORDER_BY); - partitionClause = transform_group_clause(pstate, windef->partitionClause, NULL, targetlist, - orderClause, EXPR_KIND_GROUP_BY); - - /* - * And prepare the new WindowClause. - */ - wc = makeNode(WindowClause); - wc->name = windef->name; - wc->refname = windef->refname; - - /* - * Per spec, a windowdef that references a previous one copies the - * previous partition clause (and mustn't specify its own). It can - * specify its own ordering clause, but only if the previous one had - * none. It always specifies its own frame clause, and the previous - * one must not have a frame clause. Yeah, it's bizarre that each of - * these cases works differently, but SQL:2008 says so; see 7.11 - * syntax rule 10 and general rule 1. The frame - * clause rule is especially bizarre because it makes "OVER foo" - * different from "OVER (foo)", and requires the latter to throw an - * error if foo has a nondefault frame clause. Well, ours not to - * reason why, but we do go out of our way to throw a useful error - * message for such cases. - */ - if (refwc) - { - if (partitionClause) - ereport(ERROR, - (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot override PARTITION BY clause of window \"%s\"", windef->refname), - parser_errposition(pstate, windef->location))); - wc->partitionClause = copyObject(refwc->partitionClause); - } - else - wc->partitionClause = partitionClause; - if (refwc) { - if (orderClause && refwc->orderClause) - ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot override ORDER BY clause of window \"%s\"", windef->refname), - parser_errposition(pstate, windef->location))); - if (orderClause) { - wc->orderClause = orderClause; - wc->copiedOrder = false; - } else { - wc->orderClause = copyObject(refwc->orderClause); - wc->copiedOrder = true; - } - } else { - wc->orderClause = orderClause; - wc->copiedOrder = false; - } - if (refwc && refwc->frameOptions != FRAMEOPTION_DEFAULTS) { - /* - * Use this message if this is a WINDOW clause, or if it's an OVER - * clause that includes ORDER BY or framing clauses. (We already - * rejected PARTITION BY above, so no need to check that.) - */ - if (windef->name || orderClause || windef->frameOptions != FRAMEOPTION_DEFAULTS) - ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot copy window \"%s\" because it has a frame clause", windef->refname), - parser_errposition(pstate, windef->location))); - /* Else this clause is just OVER (foo), so say this: */ - ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("cannot copy window \"%s\" because it has a frame clause", windef->refname), - errhint("Omit the parentheses in this OVER clause."), - parser_errposition(pstate, windef->location))); - } - wc->frameOptions = windef->frameOptions; - - /* - * RANGE offset PRECEDING/FOLLOWING requires exactly one ORDER BY - * column; check that and get its sort opfamily info. - */ - if ((wc->frameOptions & FRAMEOPTION_RANGE) && - (wc->frameOptions & (FRAMEOPTION_START_OFFSET | FRAMEOPTION_END_OFFSET))) - { - SortGroupClause *sortcl; - Node *sortkey; - int16 rangestrategy; - - if (list_length(wc->orderClause) != 1) - ereport(ERROR, - (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column"), - parser_errposition(pstate, windef->location))); - sortcl = castNode(SortGroupClause, linitial(wc->orderClause)); - sortkey = get_sortgroupclause_expr(sortcl, *targetlist); - /* Find the sort operator in pg_amop */ - if (!get_ordering_op_properties(sortcl->sortop, - &rangeopfamily, - &rangeopcintype, - &rangestrategy)) - elog(ERROR, "operator %u is not a valid ordering operator", - sortcl->sortop); - /* Record properties of sort ordering */ - wc->inRangeColl = exprCollation(sortkey); - wc->inRangeAsc = (rangestrategy == BTLessStrategyNumber); - wc->inRangeNullsFirst = sortcl->nulls_first; - } - - /* Per spec, GROUPS mode requires an ORDER BY clause */ - if (wc->frameOptions & FRAMEOPTION_GROUPS) - { - if (wc->orderClause == NIL) - ereport(ERROR, - (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("GROUPS mode requires an ORDER BY clause"), - parser_errposition(pstate, windef->location))); - } - - /* Process frame offset expressions */ - wc->startOffset = transform_frame_offset(pstate, wc->frameOptions, - rangeopfamily, rangeopcintype, - &wc->startInRangeFunc, - windef->startOffset); - wc->endOffset = transform_frame_offset(pstate, wc->frameOptions, - rangeopfamily, rangeopcintype, - &wc->endInRangeFunc, - windef->endOffset); - wc->winref = winref; - - result = lappend(result, wc); - } - - return result; -} - - - -/* - * transformFrameOffset - * Process a window frame offset expression - * - * In RANGE mode, rangeopfamily is the sort opfamily for the input ORDER BY - * column, and rangeopcintype is the input data type the sort operator is - * registered with. We expect the in_range function to be registered with - * that same type. (In binary-compatible cases, it might be different from - * the input column's actual type, so we can't use that for the lookups.) - * We'll return the OID of the in_range function to *inRangeFunc. - */ -static Node * -transform_frame_offset(ParseState *pstate, int frameOptions, - Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc, - Node *clause) -{ - const char *constructName = NULL; - Node *node; - - *inRangeFunc = InvalidOid; /* default result */ - - /* Quick exit if no offset expression */ - if (clause == NULL) - return NULL; - - if (frameOptions & FRAMEOPTION_ROWS) - { - /* Transform the raw expression tree */ - node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_ROWS); - - /* - * Like LIMIT clause, simply coerce to int8 - */ - constructName = "ROWS"; - node = coerce_to_specific_type(pstate, node, INT8OID, constructName); - } - else if (frameOptions & FRAMEOPTION_RANGE) - { - /* - * We must look up the in_range support function that's to be used, - * possibly choosing one of several, and coerce the "offset" value to - * the appropriate input type. - */ - Oid nodeType; - Oid preferredType; - int nfuncs = 0; - int nmatches = 0; - Oid selectedType = InvalidOid; - Oid selectedFunc = InvalidOid; - CatCList *proclist; - int i; - - /* Transform the raw expression tree */ - node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE); - nodeType = exprType(node); - - /* - * If there are multiple candidates, we'll prefer the one that exactly - * matches nodeType; or if nodeType is as yet unknown, prefer the one - * that exactly matches the sort column type. (The second rule is - * like what we do for "known_type operator unknown".) - */ - preferredType = (nodeType != UNKNOWNOID) ? nodeType : rangeopcintype; - - /* Find the in_range support functions applicable to this case */ - proclist = SearchSysCacheList2(AMPROCNUM, - ObjectIdGetDatum(rangeopfamily), - ObjectIdGetDatum(rangeopcintype)); - for (i = 0; i < proclist->n_members; i++) - { - HeapTuple proctup = &proclist->members[i]->tuple; - Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); - - /* The search will find all support proc types; ignore others */ - if (procform->amprocnum != BTINRANGE_PROC) - continue; - nfuncs++; - - /* Ignore function if given value can't be coerced to that type */ - if (!can_coerce_type(1, &nodeType, &procform->amprocrighttype, - COERCION_IMPLICIT)) - continue; - nmatches++; - - /* Remember preferred match, or any match if didn't find that */ - if (selectedType != preferredType) - { - selectedType = procform->amprocrighttype; - selectedFunc = procform->amproc; - } - } - ReleaseCatCacheList(proclist); - - /* - * Throw error if needed. It seems worth taking the trouble to - * distinguish "no support at all" from "you didn't match any - * available offset type". - */ - if (nfuncs == 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s", - format_type_be(rangeopcintype)), - parser_errposition(pstate, exprLocation(node)))); - if (nmatches == 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s and offset type %s", - format_type_be(rangeopcintype), - format_type_be(nodeType)), - errhint("Cast the offset value to an appropriate type."), - parser_errposition(pstate, exprLocation(node)))); - if (nmatches != 1 && selectedType != preferredType) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RANGE with offset PRECEDING/FOLLOWING has multiple interpretations for column type %s and offset type %s", - format_type_be(rangeopcintype), - format_type_be(nodeType)), - errhint("Cast the offset value to the exact intended type."), - parser_errposition(pstate, exprLocation(node)))); - - /* OK, coerce the offset to the right type */ - constructName = "RANGE"; - node = coerce_to_specific_type(pstate, node, - selectedType, constructName); - *inRangeFunc = selectedFunc; - } - else if (frameOptions & FRAMEOPTION_GROUPS) - { - /* Transform the raw expression tree */ - node = sql_transform_expr(pstate, clause, EXPR_KIND_WINDOW_FRAME_GROUPS); - - /* - * Like LIMIT clause, simply coerce to int8 - */ - constructName = "GROUPS"; - node = coerce_to_specific_type(pstate, node, INT8OID, constructName); - } - else - { - Assert(false); - node = NULL; - } - - /* Disallow variables in frame offsets */ - checkExprIsVarFree(pstate, node, constructName); - - return node; -} - - -/* - * checkExprIsVarFree - * Check that given expr has no Vars of the current query level - * (aggregates and window functions should have been rejected already). - * - * This is used to check expressions that have to have a consistent value - * across all rows of the query, such as a LIMIT. Arguably it should reject - * volatile functions, too, but we don't do that --- whatever value the - * function gives on first execution is what you get. - * - * constructName does not affect the semantics, but is used in error messages - */ -static void -checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName) -{ - if (contain_vars_of_level(n, 0)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("argument of %s must not contain variables", constructName), - parser_errposition(pstate, locate_var_of_level(n, 0)))); - } -} - -// from PG's addTargetToGroupList -static List *add_target_to_group_list(cypher_parsestate *cpstate, TargetEntry *tle, List *grouplist, List *targetlist, int location) { - ParseState *pstate = (ParseState *)cpstate; - Oid restype = exprType((Node *) tle->expr); - - // if tlist item is an UNKNOWN literal, change it to TEXT - if (restype == UNKNOWNOID) { - tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, restype, GTYPEOID, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); - restype = GTYPEOID; - } - - // avoid making duplicate grouplist entries - if (!targetIsInSortList(tle, InvalidOid, grouplist)) { - SortGroupClause *grpcl = makeNode(SortGroupClause); - Oid sortop; - Oid eqop; - bool hashable; - ParseCallbackState pcbstate; - - // determine the eqop and optional sortop - setup_parser_errposition_callback(&pcbstate, pstate, location); - get_sort_group_operators(restype, false, true, false, &sortop, &eqop, NULL, &hashable); - cancel_parser_errposition_callback(&pcbstate); - - grpcl->tleSortGroupRef = assignSortGroupRef(tle, targetlist); - grpcl->eqop = eqop; - grpcl->sortop = sortop; - grpcl->nulls_first = false; // OK with or without sortop - grpcl->hashable = hashable; - - grouplist = lappend(grouplist, grpcl); - } - - return grouplist; -} - -// see findTargetlistEntrySQL99() -static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, - ParseExprKind expr_kind) { - Node *expr; - ListCell *lt; - TargetEntry *te; - - expr = transform_cypher_expr(cpstate, node, expr_kind); - - foreach (lt, *target_list) { - Node *te_expr; - - te = lfirst(lt); - te_expr = strip_implicit_coercions((Node *)te->expr); - - if (equal(expr, te_expr)) - return te; - } - - te = transform_cypher_item(cpstate, node, expr, expr_kind, NULL, true); - - *target_list = lappend(*target_list, te); - - return te; -} -// from PG's transformGroupClauseExpr -static Index transform_group_clause_expr(List **flatresult, Bitmapset *seen_local, cypher_parsestate *cpstate, Node *gexpr, List **targetlist, List *sortClause, ParseExprKind exprKind, bool toplevel) { - TargetEntry *tle = NULL; - bool found = false; - - tle = find_target_list_entry(cpstate, gexpr, targetlist, exprKind); - - if (tle->ressortgroupref > 0) { - ListCell *sl; - - /* - * Eliminate duplicates (GROUP BY x, x) but only at local level. - * (Duplicates in grouping sets can affect the number of returned - * rows, so can't be dropped indiscriminately.) - * - * Since we don't care about anything except the sortgroupref, we can - * use a bitmapset rather than scanning lists. - */ - if (bms_is_member(tle->ressortgroupref, seen_local)) - return 0; - - /* - * If we're already in the flat clause list, we don't need to consider - * adding ourselves again. - */ - found = targetIsInSortList(tle, InvalidOid, *flatresult); - if (found) - return tle->ressortgroupref; - - /* - * If the GROUP BY tlist entry also appears in ORDER BY, copy operator - * info from the (first) matching ORDER BY item. This means that if - * you write something like "GROUP BY foo ORDER BY foo USING <<<", the - * GROUP BY operation silently takes on the equality semantics implied - * by the ORDER BY. There are two reasons to do this: it improves the - * odds that we can implement both GROUP BY and ORDER BY with a single - * sort step, and it allows the user to choose the equality semantics - * used by GROUP BY, should she be working with a datatype that has - * more than one equality operator. - * - * If we're in a grouping set, though, we force our requested ordering - * to be NULLS LAST, because if we have any hope of using a sorted agg - * for the job, we're going to be tacking on generated NULL values - * after the corresponding groups. If the user demands nulls first, - * another sort step is going to be inevitable, but that's the - * planner's problem. - */ - foreach(sl, sortClause) { - SortGroupClause *sc = (SortGroupClause *) lfirst(sl); - - if (sc->tleSortGroupRef == tle->ressortgroupref) { - SortGroupClause *grpc = copyObject(sc); - - if (!toplevel) - grpc->nulls_first = false; - *flatresult = lappend(*flatresult, grpc); - found = true; - break; - } - } - } - - /* - * If no match in ORDER BY, just add it to the result using default - * sort/group semantics. - */ - if (!found) - *flatresult = add_target_to_group_list(cpstate, tle, *flatresult, *targetlist, exprLocation(gexpr)); - - // _something_ must have assigned us a sortgroupref by now... - - return tle->ressortgroupref; -} - -/* - * Transform a list of expressions within a GROUP BY clause or grouping set. - * - * The list of expressions belongs to a single clause within which duplicates - * can be safely eliminated. - * - * Returns an integer list of ressortgroupref values. - * - * flatresult reference to flat list of SortGroupClause nodes - * pstate ParseState - * list nodes to transform - * targetlist reference to TargetEntry list - * sortClause ORDER BY clause (SortGroupClause nodes) - * exprKind expression kind - * useSQL99 SQL99 rather than SQL92 syntax - * toplevel false if within any grouping set - */ -static List * transformGroupClauseList(List **flatresult, ParseState *pstate, List *list, List **targetlist, - List *sortClause, ParseExprKind exprKind, bool useSQL99, bool toplevel) { - Bitmapset *seen_local = NULL; - List *result = NIL; - ListCell *gl; - - foreach(gl, list) - { - Node *gexpr = (Node *) lfirst(gl); - - Index ref = transform_group_clause_expr(flatresult, seen_local, pstate, gexpr, targetlist, sortClause, exprKind, toplevel); - - if (ref > 0) { - seen_local = bms_add_member(seen_local, ref); - result = lappend_int(result, ref); - } - } - - return result; -} - -/* - * Transform a grouping set and (recursively) its content. - * - * The grouping set might be a GROUPING SETS node with other grouping sets - * inside it, but SETS within SETS have already been flattened out before - * reaching here. - * - * Returns the transformed node, which now contains SIMPLE nodes with lists - * of ressortgrouprefs rather than expressions. - * - * flatresult reference to flat list of SortGroupClause nodes - * pstate ParseState - * gset grouping set to transform - * targetlist reference to TargetEntry list - * sortClause ORDER BY clause (SortGroupClause nodes) - * exprKind expression kind - * useSQL99 SQL99 rather than SQL92 syntax - * toplevel false if within any grouping set - */ -static Node * -transformGroupingSet(List **flatresult, ParseState *pstate, GroupingSet *gset, List **targetlist, List *sortClause, - ParseExprKind exprKind, bool useSQL99, bool toplevel) { - ListCell *gl; - List *content = NIL; - - Assert(toplevel || gset->kind != GROUPING_SET_SETS); - - foreach(gl, gset->content) - { - Node *n = lfirst(gl); - - if (IsA(n, List)) - { - List *l = transformGroupClauseList(flatresult, pstate, (List *) n, targetlist, sortClause, - exprKind, useSQL99, false); - - content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE, l, exprLocation(n))); - } - else if (IsA(n, GroupingSet)) - { - GroupingSet *gset2 = (GroupingSet *) lfirst(gl); - - content = lappend(content, transformGroupingSet(flatresult, pstate, gset2, targetlist, sortClause, - exprKind, useSQL99, false)); - } - else - { - Index ref = transform_group_clause_expr(flatresult, NULL, pstate, n, targetlist, sortClause, - exprKind, false); - - content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE, list_make1_int(ref), exprLocation(n))); - } - } - - /* Arbitrarily cap the size of CUBE, which has exponential growth */ - if (gset->kind == GROUPING_SET_CUBE) - { - if (list_length(content) > 12) - ereport(ERROR, (errcode(ERRCODE_TOO_MANY_COLUMNS), - errmsg("CUBE is limited to 12 elements"), - parser_errposition(pstate, gset->location))); - } - - return (Node *) makeGroupingSet(gset->kind, content, gset->location); -} - - - - -/*------------------------------------------------------------------------- - * Flatten out parenthesized sublists in grouping lists, and some cases - * of nested grouping sets. - * - * Inside a grouping set (ROLLUP, CUBE, or GROUPING SETS), we expect the - * content to be nested no more than 2 deep: i.e. ROLLUP((a,b),(c,d)) is - * ok, but ROLLUP((a,(b,c)),d) is flattened to ((a,b,c),d), which we then - * (later) normalize to ((a,b,c),(d)). - * - * CUBE or ROLLUP can be nested inside GROUPING SETS (but not the reverse), - * and we leave that alone if we find it. But if we see GROUPING SETS inside - * GROUPING SETS, we can flatten and normalize as follows: - * GROUPING SETS (a, (b,c), GROUPING SETS ((c,d),(e)), (f,g)) - * becomes - * GROUPING SETS ((a), (b,c), (c,d), (e), (f,g)) - * - * This is per the spec's syntax transformations, but these are the only such - * transformations we do in parse analysis, so that queries retain the - * originally specified grouping set syntax for CUBE and ROLLUP as much as - * possible when deparsed. (Full expansion of the result into a list of - * grouping sets is left to the planner.) - * - * When we're done, the resulting list should contain only these possible - * elements: - * - an expression - * - a CUBE or ROLLUP with a list of expressions nested 2 deep - * - a GROUPING SET containing any of: - * - expression lists - * - empty grouping sets - * - CUBE or ROLLUP nodes with lists nested 2 deep - * The return is a new list, but doesn't deep-copy the old nodes except for - * GroupingSet nodes. - * - * As a side effect, flag whether the list has any GroupingSet nodes. - *------------------------------------------------------------------------- - */ -static Node *flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets) { - // just in case of pathological input - check_stack_depth(); - - if (expr == (Node *) NIL) - return (Node *) NIL; - - switch (expr->type) - { - case T_RowExpr: - { - RowExpr *r = (RowExpr *) expr; - - if (r->row_format == COERCE_IMPLICIT_CAST) - return flatten_grouping_sets((Node *) r->args, false, NULL); - break; - } - case T_GroupingSet: - { - GroupingSet *gset = (GroupingSet *) expr; - ListCell *l2; - List *result_set = NIL; - - if (hasGroupingSets) - *hasGroupingSets = true; - - /* - * at the top level, we skip over all empty grouping sets; the - * caller can supply the canonical GROUP BY () if nothing is - * left. - */ - if (toplevel && gset->kind == GROUPING_SET_EMPTY) - return (Node *) NIL; - - foreach(l2, gset->content) { - Node *n1 = lfirst(l2); - Node *n2 = flatten_grouping_sets(n1, false, NULL); - - if (IsA(n1, GroupingSet) && ((GroupingSet *) n1)->kind == GROUPING_SET_SETS) - result_set = list_concat(result_set, (List *) n2); - else - result_set = lappend(result_set, n2); - } - - /* - * At top level, keep the grouping set node; but if we're in a - * nested grouping set, then we need to concat the flattened - * result into the outer list if it's simply nested. - */ - - if (toplevel || (gset->kind != GROUPING_SET_SETS)) - return (Node *) makeGroupingSet(gset->kind, result_set, gset->location); - else - return (Node *) result_set; - - break; - } - case T_List: - { - List *result = NIL; - ListCell *l; - - foreach(l, (List *) expr) { - Node *n = NULL; - - n = flatten_grouping_sets(lfirst(l), toplevel, hasGroupingSets); - - if (n != (Node *) NIL) { - if (IsA(n, List)) - result = list_concat(result, (List *) n); - else - result = lappend(result, n); - } - } - return (Node *) result; - } - default: - break; - } - return expr; -} diff --git a/src/backend/parser/cypher_parse_agg.c b/src/backend/parser/cypher_parse_agg.c deleted file mode 100644 index b44b49a7..00000000 --- a/src/backend/parser/cypher_parse_agg.c +++ /dev/null @@ -1,546 +0,0 @@ -/* - * For PostgreSQL Database Management System: - * (formerly known as Postgres, then as Postgres95) - * - * Portions Copyright (c) 1996-2010, The PostgreSQL Global Development Group - * - * Portions Copyright (c) 1994, The Regents of the University of California - * - * Permission to use, copy, modify, and distribute this software and its documentation for any purpose, - * without fee, and without a written agreement is hereby granted, provided that the above copyright notice - * and this paragraph and the following two paragraphs appear in all copies. - * - * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, - * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, - * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY - * OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - * - * THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA - * HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - */ - -#include "postgres.h" - -#include "catalog/pg_constraint.h" -#include "nodes/nodeFuncs.h" -#include "optimizer/tlist.h" -#include "optimizer/optimizer.h" -#include "parser/cypher_parse_agg.h" -#include "parser/parsetree.h" -#include "rewrite/rewriteManip.h" - -#include "parser/cypher_parse_node.h" - -typedef struct -{ - ParseState *pstate; - int min_varlevel; - int min_agglevel; - int sublevels_up; -} check_agg_arguments_context; - -typedef struct -{ - ParseState *pstate; - Query *qry; - PlannerInfo *root; - List *groupClauses; - List *groupClauseCommonVars; - bool have_non_var_grouping; - List **func_grouped_rels; - int sublevels_up; - bool in_agg_direct_args; -} check_ungrouped_columns_context; - -static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry, - List *groupClauses, List *groupClauseVars, - bool have_non_var_grouping, - List **func_grouped_rels); -static bool check_ungrouped_columns_walker(Node *node, check_ungrouped_columns_context *context); -static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry, - List *groupClauses, PlannerInfo *root, - bool have_non_var_grouping); -static bool finalize_grouping_exprs_walker(Node *node, check_ungrouped_columns_context *context); - -/* - * From PG's parseCheckAggregates - * - * Check for aggregates where they shouldn't be and improper grouping. - * This function should be called after the target list and qualifications - * are finalized. - * - * Misplaced aggregates are now mostly detected in transformAggregateCall, - * but it seems more robust to check for aggregates in recursive queries - * only after everything is finalized. In any case it's hard to detect - * improper grouping on-the-fly, so we have to make another pass over the - * query for that. - */ -void parse_check_aggregates(ParseState *pstate, Query *qry) { - List *gset_common = NIL; - List *groupClauses = NIL; - List *groupClauseCommonVars = NIL; - bool have_non_var_grouping; - List *func_grouped_rels = NIL; - ListCell *l; - bool hasJoinRTEs; - bool hasSelfRefRTEs; - PlannerInfo *root = NULL; - Node *clause; - - /* This should only be called if we found aggregates or grouping */ - Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual || qry->groupingSets); - - /* - * Scan the range table to see if there are JOIN or self-reference CTE - * entries. We'll need this info below. - */ - hasJoinRTEs = hasSelfRefRTEs = false; - foreach(l, pstate->p_rtable) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); - - if (rte->rtekind == RTE_JOIN) - hasJoinRTEs = true; - else if (rte->rtekind == RTE_CTE && rte->self_reference) - hasSelfRefRTEs = true; - } - - /* - * Build a list of the acceptable GROUP BY expressions for use by - * check_ungrouped_columns(). - * - * We get the TLE, not just the expr, because GROUPING wants to know the - * sortgroupref. - */ - foreach(l, qry->groupClause) { - SortGroupClause *grpcl = (SortGroupClause *) lfirst(l); - TargetEntry *expr = get_sortgroupclause_tle(grpcl, qry->targetList); - - if (!expr) - continue; /* probably cannot happen */ - - groupClauses = lcons(expr, groupClauses); - } - - /* - * If there are join alias vars involved, we have to flatten them to the - * underlying vars, so that aliased and unaliased vars will be correctly - * taken as equal. We can skip the expense of doing this if no rangetable - * entries are RTE_JOIN kind. We use the planner's flatten_join_alias_vars - * routine to do the flattening; it wants a PlannerInfo root node, which - * fortunately can be mostly dummy. - */ - if (hasJoinRTEs) { - root = makeNode(PlannerInfo); - root->parse = qry; - root->planner_cxt = CurrentMemoryContext; - root->hasJoinRTEs = true; - - groupClauses = (List *) flatten_join_alias_vars((Query*)root, (Node *) groupClauses); - } - - /* - * Detect whether any of the grouping expressions aren't simple Vars; if - * they're all Vars then we don't have to work so hard in the recursive - * scans. (Note we have to flatten aliases before this.) - * - * Track Vars that are included in all grouping sets separately in - * groupClauseCommonVars, since these are the only ones we can use to - * check for functional dependencies. - */ - have_non_var_grouping = false; - foreach(l, groupClauses) { - TargetEntry *tle = lfirst(l); - - if (!IsA(tle->expr, Var)) - have_non_var_grouping = true; - else if (!qry->groupingSets || list_member_int(gset_common, tle->ressortgroupref)) - groupClauseCommonVars = lappend(groupClauseCommonVars, tle->expr); - } - - /* - * Check the targetlist and HAVING clause for ungrouped variables. - * - * Note: because we check resjunk tlist elements as well as regular ones, - * this will also find ungrouped variables that came from ORDER BY and - * WINDOW clauses. For that matter, it's also going to examine the - * grouping expressions themselves --- but they'll all pass the test ... - * - * We also finalize GROUPING expressions, but for that we need to traverse - * the original (unflattened) clause in order to modify nodes. - */ - clause = (Node *) qry->targetList; - finalize_grouping_exprs(clause, pstate, qry, groupClauses, root, have_non_var_grouping); - if (hasJoinRTEs) - clause = flatten_join_alias_vars((Query*)root, clause); - - check_ungrouped_columns(clause, pstate, qry, groupClauses, - groupClauseCommonVars, have_non_var_grouping, &func_grouped_rels); - - clause = (Node *) qry->havingQual; - finalize_grouping_exprs(clause, pstate, qry, groupClauses, root, have_non_var_grouping); - - if (hasJoinRTEs) - clause = flatten_join_alias_vars((Query*)root, clause); - - check_ungrouped_columns(clause, pstate, qry, groupClauses, groupClauseCommonVars, have_non_var_grouping, - &func_grouped_rels); - - /* - * Per spec, aggregates can't appear in a recursive term. - */ - if (pstate->p_hasAggs && hasSelfRefRTEs) - ereport(ERROR, - (errcode(ERRCODE_INVALID_RECURSION), - errmsg("aggregate functions are not allowed in a recursive query's recursive term"), - parser_errposition(pstate, locate_agg_of_level((Node *) qry, 0)))); -} - -/* - * check_ungrouped_columns - - * - * Scan the given expression tree for ungrouped variables (variables - * that are not listed in the groupClauses list and are not within - * the arguments of aggregate functions). Emit a suitable error message - * if any are found. - * - * NOTE: we assume that the given clause has been transformed suitably for - * parser output. This means we can use expression_tree_walker. - * - * NOTE: we recognize grouping expressions in the main query, but only - * grouping Vars in subqueries. For example, this will be rejected, - * although it could be allowed: - * SELECT (SELECT x FROM bar where y = (foo.a + foo.b)) - * FROM foo - * GROUP BY a + b; - * The difficulty is the need to account for different sublevels_up. - * This appears to require a whole custom version of equal(), which is - * way more pain than the feature seems worth. - */ -static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry, - List *groupClauses, List *groupClauseCommonVars, - bool have_non_var_grouping, - List **func_grouped_rels) { - check_ungrouped_columns_context context; - - context.pstate = pstate; - context.qry = qry; - context.root = NULL; - context.groupClauses = groupClauses; - context.groupClauseCommonVars = groupClauseCommonVars; - context.have_non_var_grouping = have_non_var_grouping; - context.func_grouped_rels = func_grouped_rels; - context.sublevels_up = 0; - context.in_agg_direct_args = false; - check_ungrouped_columns_walker(node, &context); -} - -static bool check_ungrouped_columns_walker(Node *node, check_ungrouped_columns_context *context) { - ListCell *gl; - - if (node == NULL) - return false; - - if (IsA(node, Const) || IsA(node, Param)) - return false; /* constants are always acceptable */ - - if (IsA(node, Aggref)) { - Aggref *agg = (Aggref *) node; - - if ((int) agg->agglevelsup == context->sublevels_up) { - /* - * If we find an aggregate call of the original level, do not - * recurse into its normal arguments, ORDER BY arguments, or - * filter; ungrouped vars there are not an error. But we should - * check direct arguments as though they weren't in an aggregate. - * We set a special flag in the context to help produce a useful - * error message for ungrouped vars in direct arguments. - */ - bool result; - - Assert(!context->in_agg_direct_args); - context->in_agg_direct_args = true; - result = check_ungrouped_columns_walker((Node *) agg->aggdirectargs, context); - context->in_agg_direct_args = false; - return result; - } - - /* - * We can skip recursing into aggregates of higher levels altogether, - * since they could not possibly contain Vars of concern to us (see - * transformAggregateCall). We do need to look at aggregates of lower - * levels, however. - */ - if ((int) agg->agglevelsup > context->sublevels_up) - return false; - } - - if (IsA(node, GroupingFunc)) { - GroupingFunc *grp = (GroupingFunc *) node; - - /* handled GroupingFunc separately, no need to recheck at this level */ - - if ((int) grp->agglevelsup >= context->sublevels_up) - return false; - } - - /* - * If we have any GROUP BY items that are not simple Vars, check to see if - * subexpression as a whole matches any GROUP BY item. We need to do this - * at every recursion level so that we recognize GROUPed-BY expressions - * before reaching variables within them. But this only works at the outer - * query level, as noted above. - */ - if (context->have_non_var_grouping && context->sublevels_up == 0) { - foreach(gl, context->groupClauses) { - TargetEntry *tle = lfirst(gl); - - if (equal(node, tle->expr)) - return false; /* acceptable, do not descend more */ - } - } - - /* - * If we have an ungrouped Var of the original query level, we have a - * failure. Vars below the original query level are not a problem, and - * neither are Vars from above it. (If such Vars are ungrouped as far as - * their own query level is concerned, that's someone else's problem...) - */ - if (IsA(node, Var)) - { - Var *var = (Var *) node; - RangeTblEntry *rte; - char *attname; - - if (var->varlevelsup != context->sublevels_up) - return false; /* it's not local to my query, ignore */ - - /* - * Check for a match, if we didn't do it above. - */ - if (!context->have_non_var_grouping || context->sublevels_up != 0) { - foreach(gl, context->groupClauses) { - Var *gvar = (Var *) ((TargetEntry *) lfirst(gl))->expr; - - if (IsA(gvar, Var) && - gvar->varno == var->varno && - gvar->varattno == var->varattno && - gvar->varlevelsup == 0) - return false; /* acceptable, we're okay */ - } - } - - /* - * Check whether the Var is known functionally dependent on the GROUP - * BY columns. If so, we can allow the Var to be used, because the - * grouping is really a no-op for this table. However, this deduction - * depends on one or more constraints of the table, so we have to add - * those constraints to the query's constraintDeps list, because it's - * not semantically valid anymore if the constraint(s) get dropped. - * (Therefore, this check must be the last-ditch effort before raising - * error: we don't want to add dependencies unnecessarily.) - * - * Because this is a pretty expensive check, and will have the same - * outcome for all columns of a table, we remember which RTEs we've - * already proven functional dependency for in the func_grouped_rels - * list. This test also prevents us from adding duplicate entries to - * the constraintDeps list. - */ - if (list_member_int(*context->func_grouped_rels, var->varno)) - return false; /* previously proven acceptable */ - - Assert(var->varno > 0 && - (int) var->varno <= list_length(context->pstate->p_rtable)); - rte = rt_fetch(var->varno, context->pstate->p_rtable); - if (rte->rtekind == RTE_RELATION) { - if (check_functional_grouping(rte->relid, var->varno, 0, context->groupClauseCommonVars, - &context->qry->constraintDeps)) { - *context->func_grouped_rels = lappend_int(*context->func_grouped_rels, - var->varno); - return false; /* acceptable */ - } - } - - /* Found an ungrouped local variable; generate error message */ - attname = get_rte_attribute_name(rte, var->varattno); - if (context->sublevels_up == 0) { - /*cypher_parsestate *cpstate = context->pstate; - Query *query = context->qry; - List *targetList = query->targetList; - - int resno = cpstate->pstate.p_next_resno++; - - - TargetEntry *te = makeTargetEntry(var, resno, attname, false); - targetList = lappend(targetList, te); -*/ - - ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), - errmsg("\"%s\" must be either part of an explicitly listed key or used inside an aggregate function", attname), context->in_agg_direct_args ? - errdetail("Direct arguments of an ordered-set aggregate must use only grouped columns.") : 0, parser_errposition(context->pstate, var->location))); - - - } else - ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), - errmsg("subquery uses ungrouped column \"%s.%s\" from outer query", - rte->eref->aliasname, attname), - parser_errposition(context->pstate, var->location))); - } - - if (IsA(node, Query)) { - /* Recurse into subselects */ - bool result; - - context->sublevels_up++; - result = query_tree_walker((Query *) node, check_ungrouped_columns_walker, (void *) context, 0); - context->sublevels_up--; - return result; - } - - return expression_tree_walker(node, check_ungrouped_columns_walker, (void *) context); -} - -/* - * finalize_grouping_exprs - - * Scan the given expression tree for GROUPING() and related calls, - * and validate and process their arguments. - * - * This is split out from check_ungrouped_columns above because it needs - * to modify the nodes (which it does in-place, not via a mutator) while - * check_ungrouped_columns may see only a copy of the original thanks to - * flattening of join alias vars. So here, we flatten each individual - * GROUPING argument as we see it before comparing it. - */ -static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry, - List *groupClauses, PlannerInfo *root, bool have_non_var_grouping) { - check_ungrouped_columns_context context; - - context.pstate = pstate; - context.qry = qry; - context.root = root; - context.groupClauses = groupClauses; - context.groupClauseCommonVars = NIL; - context.have_non_var_grouping = have_non_var_grouping; - context.func_grouped_rels = NULL; - context.sublevels_up = 0; - context.in_agg_direct_args = false; - finalize_grouping_exprs_walker(node, &context); -} - -static bool -finalize_grouping_exprs_walker(Node *node, check_ungrouped_columns_context *context) { - ListCell *gl; - - if (node == NULL) - return false; - if (IsA(node, Const) || IsA(node, Param)) - return false; /* constants are always acceptable */ - - if (IsA(node, Aggref)) { - Aggref *agg = (Aggref *) node; - - if ((int) agg->agglevelsup == context->sublevels_up) { - /* - * If we find an aggregate call of the original level, do not - * recurse into its normal arguments, ORDER BY arguments, or - * filter; GROUPING exprs of this level are not allowed there. But - * check direct arguments as though they weren't in an aggregate. - */ - bool result; - - Assert(!context->in_agg_direct_args); - context->in_agg_direct_args = true; - result = finalize_grouping_exprs_walker((Node *) agg->aggdirectargs, context); - context->in_agg_direct_args = false; - return result; - } - - /* - * We can skip recursing into aggregates of higher levels altogether, - * since they could not possibly contain exprs of concern to us (see - * transformAggregateCall). We do need to look at aggregates of lower - * levels, however. - */ - if ((int) agg->agglevelsup > context->sublevels_up) - return false; - } - - if (IsA(node, GroupingFunc)) { - GroupingFunc *grp = (GroupingFunc *) node; - - /* - * We only need to check GroupingFunc nodes at the exact level to - * which they belong, since they cannot mix levels in arguments. - */ - - if ((int) grp->agglevelsup == context->sublevels_up) { - ListCell *lc; - List *ref_list = NIL; - - foreach(lc, grp->args) { - Node *expr = lfirst(lc); - Index ref = 0; - - if (context->root) - expr = flatten_join_alias_vars((Query*)context->root, expr); - - /* - * Each expression must match a grouping entry at the current - * query level. Unlike the general expression case, we don't - * allow functional dependencies or outer references. - */ - if (IsA(expr, Var)) { - Var *var = (Var *) expr; - - if (var->varlevelsup == context->sublevels_up) { - foreach(gl, context->groupClauses) { - TargetEntry *tle = lfirst(gl); - Var *gvar = (Var *) tle->expr; - - if (IsA(gvar, Var) && gvar->varno == var->varno && - gvar->varattno == var->varattno && gvar->varlevelsup == 0) { - ref = tle->ressortgroupref; - break; - } - } - } - } - else if (context->have_non_var_grouping && context->sublevels_up == 0) { - foreach(gl, context->groupClauses) { - TargetEntry *tle = lfirst(gl); - - if (equal(expr, tle->expr)) { - ref = tle->ressortgroupref; - break; - } - } - } - - if (ref == 0) - ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), - errmsg("arguments to GROUPING must be grouping expressions of the associated query level"), parser_errposition(context->pstate, - exprLocation(expr)))); - - ref_list = lappend_int(ref_list, ref); - } - - grp->refs = ref_list; - } - - if ((int) grp->agglevelsup > context->sublevels_up) - return false; - } - - if (IsA(node, Query)) { - /* Recurse into subselects */ - bool result; - - context->sublevels_up++; - result = query_tree_walker((Query *) node, finalize_grouping_exprs_walker, (void *) context, 0); - context->sublevels_up--; - return result; - } - return expression_tree_walker(node, finalize_grouping_exprs_walker, (void *) context); -} diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c index 3803eace..27c66258 100644 --- a/src/backend/parser/cypher_parse_node.c +++ b/src/backend/parser/cypher_parse_node.c @@ -26,6 +26,7 @@ #include "mb/pg_wchar.h" #include "nodes/primnodes.h" +#include "parser/cypher_expr.h" #include "parser/parse_node.h" #include "parser/cypher_parse_node.h" @@ -45,12 +46,13 @@ cypher_parsestate *make_cypher_parsestate(cypher_parsestate *parent_cpstate) pstate->parentParseState = parent_pstate; pstate->p_next_resno = 1; pstate->p_resolve_unknowns = true; + pstate->p_pre_columnref_hook = cypher_columnref_hook; if (parent_cpstate) { pstate->p_sourcetext = parent_pstate->p_sourcetext; pstate->p_queryEnv = parent_pstate->p_queryEnv; - pstate->p_pre_columnref_hook = parent_pstate->p_pre_columnref_hook; + //pstate->p_pre_columnref_hook = parent_pstate->p_pre_columnref_hook; pstate->p_post_columnref_hook = parent_pstate->p_post_columnref_hook; pstate->p_paramref_hook = parent_pstate->p_paramref_hook; pstate->p_coerce_param_hook = parent_pstate->p_coerce_param_hook; diff --git a/src/backend/parser/sql_expr.c b/src/backend/parser/sql_expr.c deleted file mode 100644 index 5b8381cb..00000000 --- a/src/backend/parser/sql_expr.c +++ /dev/null @@ -1,3153 +0,0 @@ -/*------------------------------------------------------------------------- - * - * parse_expr.c - * handle expressions in parser - * - * Copyright (C) 2023-2024 PostGraphDB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/backend/parser/parse_expr.c - * - *------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "catalog/pg_type.h" -#include "commands/dbcommands.h" -#include "miscadmin.h" -#include "nodes/makefuncs.h" -#include "nodes/nodeFuncs.h" -#include "optimizer/optimizer.h" -#include "parser/analyze.h" -#include "parser/parse_agg.h" -#include "parser/parse_clause.h" -#include "parser/parse_coerce.h" -#include "parser/parse_collate.h" -#include "parser/parse_expr.h" -#include "parser/parse_func.h" -#include "parser/parse_oper.h" -#include "parser/parse_relation.h" -#include "parser/parse_target.h" -#include "parser/parse_type.h" -#include "utils/builtins.h" -#include "utils/date.h" -#include "utils/lsyscache.h" -#include "utils/timestamp.h" -#include "utils/xml.h" - -#include "utils/gtype.h" - - -Node *sql_transform_expr_recurse(ParseState *pstate, Node *expr); -static Node *transformParamRef(ParseState *pstate, ParamRef *pref); -static Node *transformAExprOp(ParseState *pstate, A_Expr *a); -static Node *transformAExprOpAny(ParseState *pstate, A_Expr *a); -static Node *transformAExprOpAll(ParseState *pstate, A_Expr *a); -static Node *transformAExprDistinct(ParseState *pstate, A_Expr *a); -static Node *transformAExprNullIf(ParseState *pstate, A_Expr *a); -static Node *transformAExprIn(ParseState *pstate, A_Expr *a); -static Node *transformAExprBetween(ParseState *pstate, A_Expr *a); -static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a); -static Node *transformFuncCall(ParseState *pstate, FuncCall *fn); -static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref); -static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c); -static Node *transformSubLink(ParseState *pstate, SubLink *sublink); -static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, - Oid array_type, Oid element_type, int32 typmod); -static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault); -static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c); -static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m); -static Node *transformSQLValueFunction(ParseState *pstate, - SQLValueFunction *svf); -static Node *transformXmlExpr(ParseState *pstate, XmlExpr *x); -static Node *transformXmlSerialize(ParseState *pstate, XmlSerialize *xs); -static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b); -static Node *transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr); -static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref); -static Node *transformWholeRowRef(ParseState *pstate, - ParseNamespaceItem *nsitem, - int sublevels_up, int location); -static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); -static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); -static Node *transformCollateClause(ParseState *pstate, CollateClause *c); -static Node *make_row_comparison_op(ParseState *pstate, List *opname, - List *largs, List *rargs, int location); -static Node *make_row_distinct_op(ParseState *pstate, List *opname, - RowExpr *lrow, RowExpr *rrow, int location); -static Expr *make_distinct_op(ParseState *pstate, List *opname, - Node *ltree, Node *rtree, int location); -static Node *make_nulltest_from_distinct(ParseState *pstate, - A_Expr *distincta, Node *arg); - - -/* - * sql_transform_expr - - * Analyze and transform expressions. Type checking and type casting is - * done here. This processing converts the raw grammar output into - * expression trees with fully determined semantics. - */ -Node * -sql_transform_expr(ParseState *pstate, Node *expr, ParseExprKind exprKind) -{ - Node *result; - ParseExprKind sv_expr_kind; - - // Save and restore identity of expression type we're parsing - Assert(exprKind != EXPR_KIND_NONE); - sv_expr_kind = pstate->p_expr_kind; - pstate->p_expr_kind = exprKind; - - result = sql_transform_expr_recurse(pstate, expr); - - pstate->p_expr_kind = sv_expr_kind; - - return result; -} - -Node * -sql_transform_expr_recurse(ParseState *pstate, Node *expr) -{ - Node *result; - - if (expr == NULL) - return NULL; - - /* Guard against stack overflow due to overly complex expressions */ - check_stack_depth(); - - switch (nodeTag(expr)) - { - case T_ColumnRef: - result = transformColumnRef(pstate, (ColumnRef *) expr); - break; - - case T_ParamRef: - result = transformParamRef(pstate, (ParamRef *) expr); - break; - - case T_A_Const: - { - A_Const *con = (A_Const *) expr; - Value *val = &con->val; - - result = (Node *) make_const(pstate, val, con->location); - break; - } - - case T_A_Indirection: - result = transformIndirection(pstate, (A_Indirection *) expr); - break; - - case T_A_ArrayExpr: - result = transformArrayExpr(pstate, (A_ArrayExpr *) expr, - InvalidOid, InvalidOid, -1); - break; - - case T_TypeCast: - result = transformTypeCast(pstate, (TypeCast *) expr); - break; - - case T_CollateClause: - result = transformCollateClause(pstate, (CollateClause *) expr); - break; - - case T_A_Expr: - { - A_Expr *a = (A_Expr *) expr; - - switch (a->kind) - { - case AEXPR_OP: - result = transformAExprOp(pstate, a); - break; - case AEXPR_OP_ANY: - result = transformAExprOpAny(pstate, a); - break; - case AEXPR_OP_ALL: - result = transformAExprOpAll(pstate, a); - break; - case AEXPR_DISTINCT: - case AEXPR_NOT_DISTINCT: - result = transformAExprDistinct(pstate, a); - break; - case AEXPR_NULLIF: - result = transformAExprNullIf(pstate, a); - break; - case AEXPR_IN: - result = transformAExprIn(pstate, a); - break; - case AEXPR_LIKE: - case AEXPR_ILIKE: - case AEXPR_SIMILAR: - /* we can transform these just like AEXPR_OP */ - result = transformAExprOp(pstate, a); - break; - case AEXPR_BETWEEN: - case AEXPR_NOT_BETWEEN: - case AEXPR_BETWEEN_SYM: - case AEXPR_NOT_BETWEEN_SYM: - result = transformAExprBetween(pstate, a); - break; - default: - elog(ERROR, "unrecognized A_Expr kind: %d", a->kind); - result = NULL; /* keep compiler quiet */ - break; - } - break; - } - - case T_BoolExpr: - result = transformBoolExpr(pstate, (BoolExpr *) expr); - break; - - case T_FuncCall: - result = transformFuncCall(pstate, (FuncCall *) expr); - break; - - case T_MultiAssignRef: - result = transformMultiAssignRef(pstate, (MultiAssignRef *) expr); - break; - - case T_GroupingFunc: - result = transformGroupingFunc(pstate, (GroupingFunc *) expr); - break; - - case T_NamedArgExpr: - { - NamedArgExpr *na = (NamedArgExpr *) expr; - - na->arg = (Expr *) sql_transform_expr_recurse(pstate, (Node *) na->arg); - result = expr; - break; - } - - case T_SubLink: - result = transformSubLink(pstate, (SubLink *) expr); - break; - - case T_CaseExpr: - result = transformCaseExpr(pstate, (CaseExpr *) expr); - break; - - case T_RowExpr: - result = transformRowExpr(pstate, (RowExpr *) expr, false); - break; - - case T_CoalesceExpr: - result = transformCoalesceExpr(pstate, (CoalesceExpr *) expr); - break; - - case T_MinMaxExpr: - result = transformMinMaxExpr(pstate, (MinMaxExpr *) expr); - break; - - case T_SQLValueFunction: - result = transformSQLValueFunction(pstate, - (SQLValueFunction *) expr); - break; - - case T_XmlExpr: - result = transformXmlExpr(pstate, (XmlExpr *) expr); - break; - - case T_XmlSerialize: - result = transformXmlSerialize(pstate, (XmlSerialize *) expr); - break; - - case T_NullTest: - { - NullTest *n = (NullTest *) expr; - - n->arg = (Expr *) sql_transform_expr_recurse(pstate, (Node *) n->arg); - /* the argument can be any type, so don't coerce it */ - n->argisrow = type_is_rowtype(exprType((Node *) n->arg)); - result = expr; - break; - } - - case T_BooleanTest: - result = transformBooleanTest(pstate, (BooleanTest *) expr); - break; - - case T_CurrentOfExpr: - result = transformCurrentOfExpr(pstate, (CurrentOfExpr *) expr); - break; - - /* - * In all places where DEFAULT is legal, the caller should have - * processed it rather than passing it to sql_transform_expr(). - */ - case T_SetToDefault: - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("DEFAULT is not allowed in this context"), - parser_errposition(pstate, - ((SetToDefault *) expr)->location))); - break; - - /* - * CaseTestExpr doesn't require any processing; it is only - * injected into parse trees in a fully-formed state. - * - * Ordinarily we should not see a Var here, but it is convenient - * for transformJoinUsingClause() to create untransformed operator - * trees containing already-transformed Vars. The best - * alternative would be to deconstruct and reconstruct column - * references, which seems expensively pointless. So allow it. - */ - case T_CaseTestExpr: - case T_Var: - { - result = (Node *) expr; - break; - } - - default: - /* should not reach here */ - elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); - result = NULL; /* keep compiler quiet */ - break; - } - - return result; -} - -/* - * helper routine for delivering "column does not exist" error message - * - * (Usually we don't have to work this hard, but the general case of field - * selection from an arbitrary node needs it.) - */ -static void -unknown_attribute(ParseState *pstate, Node *relref, const char *attname, - int location) -{ - RangeTblEntry *rte; - - if (IsA(relref, Var) && - ((Var *) relref)->varattno == InvalidAttrNumber) - { - /* Reference the RTE by alias not by actual table name */ - rte = GetRTEByRangeTablePosn(pstate, - ((Var *) relref)->varno, - ((Var *) relref)->varlevelsup); - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column %s.%s does not exist", - rte->eref->aliasname, attname), - parser_errposition(pstate, location))); - } - else - { - /* Have to do it by reference to the type of the expression */ - Oid relTypeId = exprType(relref); - - if (ISCOMPLEX(relTypeId)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" not found in data type %s", - attname, format_type_be(relTypeId)), - parser_errposition(pstate, location))); - else if (relTypeId == RECORDOID) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("could not identify column \"%s\" in record data type", - attname), - parser_errposition(pstate, location))); - else - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("column notation .%s applied to type %s, " - "which is not a composite type", - attname, format_type_be(relTypeId)), - parser_errposition(pstate, location))); - } -} - -static Node * -transformIndirection(ParseState *pstate, A_Indirection *ind) -{ - Node *last_srf = pstate->p_last_srf; - Node *result = sql_transform_expr_recurse(pstate, ind->arg); - List *subscripts = NIL; - int location = exprLocation(result); - ListCell *i; - - if (IsA(ind->arg, String)) { - ParseNamespaceItem *pnsi; - - char *relname = strVal(ind->arg); - int levels_up; - // locate the referenced RTE - pnsi = refnameNamespaceItem(pstate, NULL, relname, -1, &levels_up); - - // This column ref is referencing something that was created in a previous query and is a variable. - if (pnsi) { - // try to identify the properties column of the RTE - Node *node = scanNSItemForColumn(pstate, pnsi, 0, "properties", -1); - if (node) - result = node; - } - } - - /* - * We have to split any field-selection operations apart from - * subscripting. Adjacent A_Indices nodes have to be treated as a single - * multidimensional subscript operation. - */ - foreach(i, ind->indirection) - { - Node *n = lfirst(i); - - if (IsA(n, A_Indices)) - subscripts = lappend(subscripts, n); - else if (IsA(n, A_Star)) - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("row expansion via \"*\" is not supported here"), - parser_errposition(pstate, location))); - } - else - { - Node *newresult; - - Assert(IsA(n, String)); - - /* process subscripts before this field selection */ - if (subscripts) - result = (Node *) transformContainerSubscripts(pstate, - result, - exprType(result), - exprTypmod(result), - subscripts, - false); - subscripts = NIL; - - newresult = ParseFuncOrColumn(pstate, - list_make1(n), - list_make1(result), - last_srf, - NULL, - false, - location); - if (newresult == NULL) - unknown_attribute(pstate, result, strVal(n), location); - result = newresult; - } - } - /* process trailing subscripts, if any */ - if (subscripts) - result = (Node *) transformContainerSubscripts(pstate, - result, - exprType(result), - exprTypmod(result), - subscripts, - false); - - return result; -} - -/* - * Transform a ColumnRef. - * - * If you find yourself changing this code, see also ExpandColumnRefStar. - */ -static Node * -transformColumnRef(ParseState *pstate, ColumnRef *cref) -{ - Node *node = NULL; - char *nspname = NULL; - char *relname = NULL; - char *colname = NULL; - ParseNamespaceItem *nsitem; - int levels_up; - enum - { - CRERR_NO_COLUMN, - CRERR_NO_RTE, - CRERR_WRONG_DB, - CRERR_TOO_MANY - } crerr = CRERR_NO_COLUMN; - const char *err; - - /* - * Check to see if the column reference is in an invalid place within the - * query. We allow column references in most places, except in default - * expressions and partition bound expressions. - */ - err = NULL; - switch (pstate->p_expr_kind) - { - case EXPR_KIND_NONE: - Assert(false); /* can't happen */ - break; - case EXPR_KIND_OTHER: - case EXPR_KIND_JOIN_ON: - case EXPR_KIND_JOIN_USING: - case EXPR_KIND_FROM_SUBSELECT: - case EXPR_KIND_FROM_FUNCTION: - case EXPR_KIND_WHERE: - case EXPR_KIND_POLICY: - case EXPR_KIND_HAVING: - case EXPR_KIND_FILTER: - case EXPR_KIND_WINDOW_PARTITION: - case EXPR_KIND_WINDOW_ORDER: - case EXPR_KIND_WINDOW_FRAME_RANGE: - case EXPR_KIND_WINDOW_FRAME_ROWS: - case EXPR_KIND_WINDOW_FRAME_GROUPS: - case EXPR_KIND_SELECT_TARGET: - case EXPR_KIND_INSERT_TARGET: - case EXPR_KIND_UPDATE_SOURCE: - case EXPR_KIND_UPDATE_TARGET: - case EXPR_KIND_GROUP_BY: - case EXPR_KIND_ORDER_BY: - case EXPR_KIND_DISTINCT_ON: - case EXPR_KIND_LIMIT: - case EXPR_KIND_OFFSET: - case EXPR_KIND_RETURNING: - case EXPR_KIND_VALUES: - case EXPR_KIND_VALUES_SINGLE: - case EXPR_KIND_CHECK_CONSTRAINT: - case EXPR_KIND_DOMAIN_CHECK: - case EXPR_KIND_FUNCTION_DEFAULT: - case EXPR_KIND_INDEX_EXPRESSION: - case EXPR_KIND_INDEX_PREDICATE: - case EXPR_KIND_STATS_EXPRESSION: - case EXPR_KIND_ALTER_COL_TRANSFORM: - case EXPR_KIND_EXECUTE_PARAMETER: - case EXPR_KIND_TRIGGER_WHEN: - case EXPR_KIND_PARTITION_EXPRESSION: - case EXPR_KIND_CALL_ARGUMENT: - case EXPR_KIND_COPY_WHERE: - case EXPR_KIND_GENERATED_COLUMN: - case EXPR_KIND_CYCLE_MARK: - /* okay */ - break; - - case EXPR_KIND_COLUMN_DEFAULT: - err = _("cannot use column reference in DEFAULT expression"); - break; - case EXPR_KIND_PARTITION_BOUND: - err = _("cannot use column reference in partition bound expression"); - break; - - /* - * There is intentionally no default: case here, so that the - * compiler will warn if we add a new ParseExprKind without - * extending this switch. If we do see an unrecognized value at - * runtime, the behavior will be the same as for EXPR_KIND_OTHER, - * which is sane anyway. - */ - } - if (err) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("%s", err), - parser_errposition(pstate, cref->location))); - - /* - * Give the PreParseColumnRefHook, if any, first shot. If it returns - * non-null then that's all, folks. - */ - if (pstate->p_pre_columnref_hook != NULL) - { - node = pstate->p_pre_columnref_hook(pstate, cref); - if (node != NULL) - return node; - } - - /*---------- - * The allowed syntaxes are: - * - * A First try to resolve as unqualified column name; - * if no luck, try to resolve as unqualified table name (A.*). - * A.B A is an unqualified table name; B is either a - * column or function name (trying column name first). - * A.B.C schema A, table B, col or func name C. - * A.B.C.D catalog A, schema B, table C, col or func D. - * A.* A is an unqualified table name; means whole-row value. - * A.B.* whole-row value of table B in schema A. - * A.B.C.* whole-row value of table C in schema B in catalog A. - * - * We do not need to cope with bare "*"; that will only be accepted by - * the grammar at the top level of a SELECT list, and transformTargetList - * will take care of it before it ever gets here. Also, "A.*" etc will - * be expanded by transformTargetList if they appear at SELECT top level, - * so here we are only going to see them as function or operator inputs. - * - * Currently, if a catalog name is given then it must equal the current - * database name; we check it here and then discard it. - *---------- - */ - switch (list_length(cref->fields)) - { - case 1: - { - Node *field1 = (Node *) linitial(cref->fields); - - Assert(IsA(field1, String)); - colname = strVal(field1); - - /* Try to identify as an unqualified column */ - node = colNameToVar(pstate, colname, false, cref->location); - - if (node == NULL) - { - /* - * Not known as a column of any range-table entry. - * - * Try to find the name as a relation. Note that only - * relations already entered into the rangetable will be - * recognized. - * - * This is a hack for backwards compatibility with - * PostQUEL-inspired syntax. The preferred form now is - * "rel.*". - */ - nsitem = refnameNamespaceItem(pstate, NULL, colname, - cref->location, - &levels_up); - if (nsitem) - node = transformWholeRowRef(pstate, nsitem, levels_up, - cref->location); - } - break; - } - case 2: - { - Node *field1 = (Node *) linitial(cref->fields); - Node *field2 = (Node *) lsecond(cref->fields); - - Assert(IsA(field1, String)); - relname = strVal(field1); - - /* Locate the referenced nsitem */ - nsitem = refnameNamespaceItem(pstate, nspname, relname, - cref->location, - &levels_up); - if (nsitem == NULL) - { - crerr = CRERR_NO_RTE; - break; - } - - /* Whole-row reference? */ - if (IsA(field2, A_Star)) - { - node = transformWholeRowRef(pstate, nsitem, levels_up, - cref->location); - break; - } - - Assert(IsA(field2, String)); - colname = strVal(field2); - - /* Try to identify as a column of the nsitem */ - node = scanNSItemForColumn(pstate, nsitem, levels_up, colname, - cref->location); - if (node == NULL) - { - /* Try it as a function call on the whole row */ - node = transformWholeRowRef(pstate, nsitem, levels_up, - cref->location); - node = ParseFuncOrColumn(pstate, - list_make1(makeString(colname)), - list_make1(node), - pstate->p_last_srf, - NULL, - false, - cref->location); - } - - if (!node) { - Node *prop = scanNSItemForColumn(pstate, nsitem, levels_up, "properties", cref->location); - if (prop){ - Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, string_to_gtype(colname), false, false); - node = (Node *)make_op(pstate, list_make2(makeString("postgraph"), makeString("->")), prop, c, pstate->p_last_srf, -1); - } - } - break; - } - case 3: - { - Node *field1 = (Node *) linitial(cref->fields); - Node *field2 = (Node *) lsecond(cref->fields); - Node *field3 = (Node *) lthird(cref->fields); - - Assert(IsA(field1, String)); - nspname = strVal(field1); - Assert(IsA(field2, String)); - relname = strVal(field2); - - /* Locate the referenced nsitem */ - nsitem = refnameNamespaceItem(pstate, nspname, relname, - cref->location, - &levels_up); - if (nsitem == NULL) - { - crerr = CRERR_NO_RTE; - break; - } - - /* Whole-row reference? */ - if (IsA(field3, A_Star)) - { - node = transformWholeRowRef(pstate, nsitem, levels_up, - cref->location); - break; - } - - Assert(IsA(field3, String)); - colname = strVal(field3); - - /* Try to identify as a column of the nsitem */ - node = scanNSItemForColumn(pstate, nsitem, levels_up, colname, - cref->location); - if (node == NULL) - { - /* Try it as a function call on the whole row */ - node = transformWholeRowRef(pstate, nsitem, levels_up, - cref->location); - node = ParseFuncOrColumn(pstate, - list_make1(makeString(colname)), - list_make1(node), - pstate->p_last_srf, - NULL, - false, - cref->location); - } - break; - } - case 4: - { - Node *field1 = (Node *) linitial(cref->fields); - Node *field2 = (Node *) lsecond(cref->fields); - Node *field3 = (Node *) lthird(cref->fields); - Node *field4 = (Node *) lfourth(cref->fields); - char *catname; - - Assert(IsA(field1, String)); - catname = strVal(field1); - Assert(IsA(field2, String)); - nspname = strVal(field2); - Assert(IsA(field3, String)); - relname = strVal(field3); - - /* - * We check the catalog name and then ignore it. - */ - if (strcmp(catname, get_database_name(MyDatabaseId)) != 0) - { - crerr = CRERR_WRONG_DB; - break; - } - - /* Locate the referenced nsitem */ - nsitem = refnameNamespaceItem(pstate, nspname, relname, - cref->location, - &levels_up); - if (nsitem == NULL) - { - crerr = CRERR_NO_RTE; - break; - } - - /* Whole-row reference? */ - if (IsA(field4, A_Star)) - { - node = transformWholeRowRef(pstate, nsitem, levels_up, - cref->location); - break; - } - - Assert(IsA(field4, String)); - colname = strVal(field4); - - /* Try to identify as a column of the nsitem */ - node = scanNSItemForColumn(pstate, nsitem, levels_up, colname, - cref->location); - if (node == NULL) - { - /* Try it as a function call on the whole row */ - node = transformWholeRowRef(pstate, nsitem, levels_up, - cref->location); - node = ParseFuncOrColumn(pstate, - list_make1(makeString(colname)), - list_make1(node), - pstate->p_last_srf, - NULL, - false, - cref->location); - } - break; - } - default: - crerr = CRERR_TOO_MANY; /* too many dotted names */ - break; - } - - /* - * Now give the PostParseColumnRefHook, if any, a chance. We pass the - * translation-so-far so that it can throw an error if it wishes in the - * case that it has a conflicting interpretation of the ColumnRef. (If it - * just translates anyway, we'll throw an error, because we can't undo - * whatever effects the preceding steps may have had on the pstate.) If it - * returns NULL, use the standard translation, or throw a suitable error - * if there is none. - */ - if (pstate->p_post_columnref_hook != NULL) - { - Node *hookresult; - - hookresult = pstate->p_post_columnref_hook(pstate, cref, node); - if (node == NULL) - node = hookresult; - else if (hookresult != NULL) - ereport(ERROR, - (errcode(ERRCODE_AMBIGUOUS_COLUMN), - errmsg("column reference \"%s\" is ambiguous", - NameListToString(cref->fields)), - parser_errposition(pstate, cref->location))); - } - - /* - * Throw error if no translation found. - */ - if (node == NULL) - { - switch (crerr) - { - case CRERR_NO_COLUMN: - errorMissingColumn(pstate, relname, colname, cref->location); - break; - case CRERR_NO_RTE: - errorMissingRTE(pstate, makeRangeVar(nspname, relname, - cref->location)); - break; - case CRERR_WRONG_DB: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cross-database references are not implemented: %s", - NameListToString(cref->fields)), - parser_errposition(pstate, cref->location))); - break; - case CRERR_TOO_MANY: - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("improper qualified name (too many dotted names): %s", - NameListToString(cref->fields)), - parser_errposition(pstate, cref->location))); - break; - } - } - - return node; -} - -static Node * -transformParamRef(ParseState *pstate, ParamRef *pref) -{ - Node *result; - - /* - * The core parser knows nothing about Params. If a hook is supplied, - * call it. If not, or if the hook returns NULL, throw a generic error. - */ - if (pstate->p_paramref_hook != NULL) - result = pstate->p_paramref_hook(pstate, pref); - else - result = NULL; - - if (result == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_PARAMETER), - errmsg("there is no parameter $%d", pref->number), - parser_errposition(pstate, pref->location))); - - return result; -} - -/* Test whether an a_expr is a plain NULL constant or not */ -static bool -exprIsNullConstant(Node *arg) -{ - if (arg && IsA(arg, A_Const)) - { - A_Const *con = (A_Const *) arg; - - if (con->val.type == T_Null) - return true; - } - return false; -} - -static Node * -transformAExprOp(ParseState *pstate, A_Expr *a) -{ - Node *lexpr = a->lexpr; - Node *rexpr = a->rexpr; - Node *result; - - /* - * Special-case "foo = NULL" and "NULL = foo" for compatibility with - * standards-broken products (like Microsoft's). Turn these into IS NULL - * exprs. (If either side is a CaseTestExpr, then the expression was - * generated internally from a CASE-WHEN expression, and - * transform_null_equals does not apply.) - */ - if (Transform_null_equals && - list_length(a->name) == 1 && - strcmp(strVal(linitial(a->name)), "=") == 0 && - (exprIsNullConstant(lexpr) || exprIsNullConstant(rexpr)) && - (!IsA(lexpr, CaseTestExpr) && !IsA(rexpr, CaseTestExpr))) - { - NullTest *n = makeNode(NullTest); - - n->nulltesttype = IS_NULL; - n->location = a->location; - - if (exprIsNullConstant(lexpr)) - n->arg = (Expr *) rexpr; - else - n->arg = (Expr *) lexpr; - - result = sql_transform_expr_recurse(pstate, (Node *) n); - } - else if (lexpr && IsA(lexpr, RowExpr) && - rexpr && IsA(rexpr, SubLink) && - ((SubLink *) rexpr)->subLinkType == EXPR_SUBLINK) - { - /* - * Convert "row op subselect" into a ROWCOMPARE sublink. Formerly the - * grammar did this, but now that a row construct is allowed anywhere - * in expressions, it's easier to do it here. - */ - SubLink *s = (SubLink *) rexpr; - - s->subLinkType = ROWCOMPARE_SUBLINK; - s->testexpr = lexpr; - s->operName = a->name; - s->location = a->location; - result = sql_transform_expr_recurse(pstate, (Node *) s); - } - else if (lexpr && IsA(lexpr, RowExpr) && - rexpr && IsA(rexpr, RowExpr)) - { - /* ROW() op ROW() is handled specially */ - lexpr = sql_transform_expr_recurse(pstate, lexpr); - rexpr = sql_transform_expr_recurse(pstate, rexpr); - - result = make_row_comparison_op(pstate, - a->name, - castNode(RowExpr, lexpr)->args, - castNode(RowExpr, rexpr)->args, - a->location); - } - else - { - /* Ordinary scalar operator */ - Node *last_srf = pstate->p_last_srf; - - lexpr = sql_transform_expr_recurse(pstate, lexpr); - rexpr = sql_transform_expr_recurse(pstate, rexpr); - - result = (Node *) make_op(pstate, - a->name, - lexpr, - rexpr, - last_srf, - a->location); - } - - return result; -} - -static Node * -transformAExprOpAny(ParseState *pstate, A_Expr *a) -{ - Node *lexpr = sql_transform_expr_recurse(pstate, a->lexpr); - Node *rexpr = sql_transform_expr_recurse(pstate, a->rexpr); - - return (Node *) make_scalar_array_op(pstate, - a->name, - true, - lexpr, - rexpr, - a->location); -} - -static Node * -transformAExprOpAll(ParseState *pstate, A_Expr *a) -{ - Node *lexpr = sql_transform_expr_recurse(pstate, a->lexpr); - Node *rexpr = sql_transform_expr_recurse(pstate, a->rexpr); - - return (Node *) make_scalar_array_op(pstate, - a->name, - false, - lexpr, - rexpr, - a->location); -} - -static Node * -transformAExprDistinct(ParseState *pstate, A_Expr *a) -{ - Node *lexpr = a->lexpr; - Node *rexpr = a->rexpr; - Node *result; - - /* - * If either input is an undecorated NULL literal, transform to a NullTest - * on the other input. That's simpler to process than a full DistinctExpr, - * and it avoids needing to require that the datatype have an = operator. - */ - if (exprIsNullConstant(rexpr)) - return make_nulltest_from_distinct(pstate, a, lexpr); - if (exprIsNullConstant(lexpr)) - return make_nulltest_from_distinct(pstate, a, rexpr); - - lexpr = sql_transform_expr_recurse(pstate, lexpr); - rexpr = sql_transform_expr_recurse(pstate, rexpr); - - if (lexpr && IsA(lexpr, RowExpr) && - rexpr && IsA(rexpr, RowExpr)) - { - /* ROW() op ROW() is handled specially */ - result = make_row_distinct_op(pstate, a->name, - (RowExpr *) lexpr, - (RowExpr *) rexpr, - a->location); - } - else - { - /* Ordinary scalar operator */ - result = (Node *) make_distinct_op(pstate, - a->name, - lexpr, - rexpr, - a->location); - } - - /* - * If it's NOT DISTINCT, we first build a DistinctExpr and then stick a - * NOT on top. - */ - if (a->kind == AEXPR_NOT_DISTINCT) - result = (Node *) makeBoolExpr(NOT_EXPR, - list_make1(result), - a->location); - - return result; -} - -static Node * -transformAExprNullIf(ParseState *pstate, A_Expr *a) -{ - Node *lexpr = sql_transform_expr_recurse(pstate, a->lexpr); - Node *rexpr = sql_transform_expr_recurse(pstate, a->rexpr); - OpExpr *result; - - result = (OpExpr *) make_op(pstate, - a->name, - lexpr, - rexpr, - pstate->p_last_srf, - a->location); - - /* - * The comparison operator itself should yield boolean ... - */ - if (result->opresulttype != BOOLOID) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("NULLIF requires = operator to yield boolean"), - parser_errposition(pstate, a->location))); - if (result->opretset) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - /* translator: %s is name of a SQL construct, eg NULLIF */ - errmsg("%s must not return a set", "NULLIF"), - parser_errposition(pstate, a->location))); - - /* - * ... but the NullIfExpr will yield the first operand's type. - */ - result->opresulttype = exprType((Node *) linitial(result->args)); - - /* - * We rely on NullIfExpr and OpExpr being the same struct - */ - NodeSetTag(result, T_NullIfExpr); - - return (Node *) result; -} - -static Node * -transformAExprIn(ParseState *pstate, A_Expr *a) -{ - Node *result = NULL; - Node *lexpr; - List *rexprs; - List *rvars; - List *rnonvars; - bool useOr; - ListCell *l; - - /* - * If the operator is <>, combine with AND not OR. - */ - if (strcmp(strVal(linitial(a->name)), "<>") == 0) - useOr = false; - else - useOr = true; - - /* - * We try to generate a ScalarArrayOpExpr from IN/NOT IN, but this is only - * possible if there is a suitable array type available. If not, we fall - * back to a boolean condition tree with multiple copies of the lefthand - * expression. Also, any IN-list items that contain Vars are handled as - * separate boolean conditions, because that gives the planner more scope - * for optimization on such clauses. - * - * First step: transform all the inputs, and detect whether any contain - * Vars. - */ - lexpr = sql_transform_expr_recurse(pstate, a->lexpr); - rexprs = rvars = rnonvars = NIL; - foreach(l, (List *) a->rexpr) - { - Node *rexpr = sql_transform_expr_recurse(pstate, lfirst(l)); - - rexprs = lappend(rexprs, rexpr); - if (contain_vars_of_level(rexpr, 0)) - rvars = lappend(rvars, rexpr); - else - rnonvars = lappend(rnonvars, rexpr); - } - - /* - * ScalarArrayOpExpr is only going to be useful if there's more than one - * non-Var righthand item. - */ - if (list_length(rnonvars) > 1) - { - List *allexprs; - Oid scalar_type; - Oid array_type; - - /* - * Try to select a common type for the array elements. Note that - * since the LHS' type is first in the list, it will be preferred when - * there is doubt (eg, when all the RHS items are unknown literals). - * - * Note: use list_concat here not lcons, to avoid damaging rnonvars. - */ - allexprs = list_concat(list_make1(lexpr), rnonvars); - scalar_type = select_common_type(pstate, allexprs, NULL, NULL); - - /* We have to verify that the selected type actually works */ - if (OidIsValid(scalar_type) && - !verify_common_type(scalar_type, allexprs)) - scalar_type = InvalidOid; - - /* - * Do we have an array type to use? Aside from the case where there - * isn't one, we don't risk using ScalarArrayOpExpr when the common - * type is RECORD, because the RowExpr comparison logic below can cope - * with some cases of non-identical row types. - */ - if (OidIsValid(scalar_type) && scalar_type != RECORDOID) - array_type = get_array_type(scalar_type); - else - array_type = InvalidOid; - if (array_type != InvalidOid) - { - /* - * OK: coerce all the right-hand non-Var inputs to the common type - * and build an ArrayExpr for them. - */ - List *aexprs; - ArrayExpr *newa; - - aexprs = NIL; - foreach(l, rnonvars) - { - Node *rexpr = (Node *) lfirst(l); - - rexpr = coerce_to_common_type(pstate, rexpr, - scalar_type, - "IN"); - aexprs = lappend(aexprs, rexpr); - } - newa = makeNode(ArrayExpr); - newa->array_typeid = array_type; - /* array_collid will be set by parse_collate.c */ - newa->element_typeid = scalar_type; - newa->elements = aexprs; - newa->multidims = false; - newa->location = -1; - - result = (Node *) make_scalar_array_op(pstate, - a->name, - useOr, - lexpr, - (Node *) newa, - a->location); - - /* Consider only the Vars (if any) in the loop below */ - rexprs = rvars; - } - } - - /* - * Must do it the hard way, ie, with a boolean expression tree. - */ - foreach(l, rexprs) - { - Node *rexpr = (Node *) lfirst(l); - Node *cmp; - - if (IsA(lexpr, RowExpr) && - IsA(rexpr, RowExpr)) - { - /* ROW() op ROW() is handled specially */ - cmp = make_row_comparison_op(pstate, - a->name, - copyObject(((RowExpr *) lexpr)->args), - ((RowExpr *) rexpr)->args, - a->location); - } - else - { - /* Ordinary scalar operator */ - cmp = (Node *) make_op(pstate, - a->name, - copyObject(lexpr), - rexpr, - pstate->p_last_srf, - a->location); - } - - cmp = coerce_to_boolean(pstate, cmp, "IN"); - if (result == NULL) - result = cmp; - else - result = (Node *) makeBoolExpr(useOr ? OR_EXPR : AND_EXPR, - list_make2(result, cmp), - a->location); - } - - return result; -} - -static Node * -transformAExprBetween(ParseState *pstate, A_Expr *a) -{ - Node *aexpr; - Node *bexpr; - Node *cexpr; - Node *result; - Node *sub1; - Node *sub2; - List *args; - - /* Deconstruct A_Expr into three subexprs */ - aexpr = a->lexpr; - args = castNode(List, a->rexpr); - Assert(list_length(args) == 2); - bexpr = (Node *) linitial(args); - cexpr = (Node *) lsecond(args); - - /* - * Build the equivalent comparison expression. Make copies of - * multiply-referenced subexpressions for safety. (XXX this is really - * wrong since it results in multiple runtime evaluations of what may be - * volatile expressions ...) - * - * Ideally we would not use hard-wired operators here but instead use - * opclasses. However, mixed data types and other issues make this - * difficult: - * http://archives.postgresql.org/pgsql-hackers/2008-08/msg01142.php - */ - switch (a->kind) - { - case AEXPR_BETWEEN: - args = list_make2(makeSimpleA_Expr(AEXPR_OP, ">=", - aexpr, bexpr, - a->location), - makeSimpleA_Expr(AEXPR_OP, "<=", - copyObject(aexpr), cexpr, - a->location)); - result = (Node *) makeBoolExpr(AND_EXPR, args, a->location); - break; - case AEXPR_NOT_BETWEEN: - args = list_make2(makeSimpleA_Expr(AEXPR_OP, "<", - aexpr, bexpr, - a->location), - makeSimpleA_Expr(AEXPR_OP, ">", - copyObject(aexpr), cexpr, - a->location)); - result = (Node *) makeBoolExpr(OR_EXPR, args, a->location); - break; - case AEXPR_BETWEEN_SYM: - args = list_make2(makeSimpleA_Expr(AEXPR_OP, ">=", - aexpr, bexpr, - a->location), - makeSimpleA_Expr(AEXPR_OP, "<=", - copyObject(aexpr), cexpr, - a->location)); - sub1 = (Node *) makeBoolExpr(AND_EXPR, args, a->location); - args = list_make2(makeSimpleA_Expr(AEXPR_OP, ">=", - copyObject(aexpr), copyObject(cexpr), - a->location), - makeSimpleA_Expr(AEXPR_OP, "<=", - copyObject(aexpr), copyObject(bexpr), - a->location)); - sub2 = (Node *) makeBoolExpr(AND_EXPR, args, a->location); - args = list_make2(sub1, sub2); - result = (Node *) makeBoolExpr(OR_EXPR, args, a->location); - break; - case AEXPR_NOT_BETWEEN_SYM: - args = list_make2(makeSimpleA_Expr(AEXPR_OP, "<", - aexpr, bexpr, - a->location), - makeSimpleA_Expr(AEXPR_OP, ">", - copyObject(aexpr), cexpr, - a->location)); - sub1 = (Node *) makeBoolExpr(OR_EXPR, args, a->location); - args = list_make2(makeSimpleA_Expr(AEXPR_OP, "<", - copyObject(aexpr), copyObject(cexpr), - a->location), - makeSimpleA_Expr(AEXPR_OP, ">", - copyObject(aexpr), copyObject(bexpr), - a->location)); - sub2 = (Node *) makeBoolExpr(OR_EXPR, args, a->location); - args = list_make2(sub1, sub2); - result = (Node *) makeBoolExpr(AND_EXPR, args, a->location); - break; - default: - elog(ERROR, "unrecognized A_Expr kind: %d", a->kind); - result = NULL; /* keep compiler quiet */ - break; - } - - return sql_transform_expr_recurse(pstate, result); -} - -static Node * -transformBoolExpr(ParseState *pstate, BoolExpr *a) -{ - List *args = NIL; - const char *opname; - ListCell *lc; - - switch (a->boolop) - { - case AND_EXPR: - opname = "AND"; - break; - case OR_EXPR: - opname = "OR"; - break; - case NOT_EXPR: - opname = "NOT"; - break; - default: - elog(ERROR, "unrecognized boolop: %d", (int) a->boolop); - opname = NULL; /* keep compiler quiet */ - break; - } - - foreach(lc, a->args) - { - Node *arg = (Node *) lfirst(lc); - - arg = sql_transform_expr_recurse(pstate, arg); - arg = coerce_to_boolean(pstate, arg, opname); - args = lappend(args, arg); - } - - return (Node *) makeBoolExpr(a->boolop, args, a->location); -} - -static Node * -transformFuncCall(ParseState *pstate, FuncCall *fn) -{ - Node *last_srf = pstate->p_last_srf; - List *targs; - ListCell *args; - - /* Transform the list of arguments ... */ - targs = NIL; - foreach(args, fn->args) - { - targs = lappend(targs, sql_transform_expr_recurse(pstate, - (Node *) lfirst(args))); - } - - /* - * When WITHIN GROUP is used, we treat its ORDER BY expressions as - * additional arguments to the function, for purposes of function lookup - * and argument type coercion. So, transform each such expression and add - * them to the targs list. We don't explicitly mark where each argument - * came from, but ParseFuncOrColumn can tell what's what by reference to - * list_length(fn->agg_order). - */ - if (fn->agg_within_group) - { - Assert(fn->agg_order != NIL); - foreach(args, fn->agg_order) - { - SortBy *arg = (SortBy *) lfirst(args); - - targs = lappend(targs, sql_transform_expr(pstate, arg->node, - EXPR_KIND_ORDER_BY)); - } - } - - /* ... and hand off to ParseFuncOrColumn */ - return ParseFuncOrColumn(pstate, - fn->funcname, - targs, - last_srf, - fn, - false, - fn->location); -} - -static Node * -transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref) -{ - SubLink *sublink; - RowExpr *rexpr; - Query *qtree; - TargetEntry *tle; - - /* We should only see this in first-stage processing of UPDATE tlists */ - Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE); - - /* We only need to transform the source if this is the first column */ - if (maref->colno == 1) - { - /* - * For now, we only allow EXPR SubLinks and RowExprs as the source of - * an UPDATE multiassignment. This is sufficient to cover interesting - * cases; at worst, someone would have to write (SELECT * FROM expr) - * to expand a composite-returning expression of another form. - */ - if (IsA(maref->source, SubLink) && - ((SubLink *) maref->source)->subLinkType == EXPR_SUBLINK) - { - /* Relabel it as a MULTIEXPR_SUBLINK */ - sublink = (SubLink *) maref->source; - sublink->subLinkType = MULTIEXPR_SUBLINK; - /* And transform it */ - sublink = (SubLink *) sql_transform_expr_recurse(pstate, - (Node *) sublink); - - qtree = castNode(Query, sublink->subselect); - - /* Check subquery returns required number of columns */ - if (count_nonjunk_tlist_entries(qtree->targetList) != maref->ncolumns) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("number of columns does not match number of values"), - parser_errposition(pstate, sublink->location))); - - /* - * Build a resjunk tlist item containing the MULTIEXPR SubLink, - * and add it to pstate->p_multiassign_exprs, whence it will later - * get appended to the completed targetlist. We needn't worry - * about selecting a resno for it; transformUpdateStmt will do - * that. - */ - tle = makeTargetEntry((Expr *) sublink, 0, NULL, true); - pstate->p_multiassign_exprs = lappend(pstate->p_multiassign_exprs, - tle); - - /* - * Assign a unique-within-this-targetlist ID to the MULTIEXPR - * SubLink. We can just use its position in the - * p_multiassign_exprs list. - */ - sublink->subLinkId = list_length(pstate->p_multiassign_exprs); - } - else if (IsA(maref->source, RowExpr)) - { - /* Transform the RowExpr, allowing SetToDefault items */ - rexpr = (RowExpr *) transformRowExpr(pstate, - (RowExpr *) maref->source, - true); - - /* Check it returns required number of columns */ - if (list_length(rexpr->args) != maref->ncolumns) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("number of columns does not match number of values"), - parser_errposition(pstate, rexpr->location))); - - /* - * Temporarily append it to p_multiassign_exprs, so we can get it - * back when we come back here for additional columns. - */ - tle = makeTargetEntry((Expr *) rexpr, 0, NULL, true); - pstate->p_multiassign_exprs = lappend(pstate->p_multiassign_exprs, - tle); - } - else - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("source for a multiple-column UPDATE item must be a sub-SELECT or ROW() expression"), - parser_errposition(pstate, exprLocation(maref->source)))); - } - else - { - /* - * Second or later column in a multiassignment. Re-fetch the - * transformed SubLink or RowExpr, which we assume is still the last - * entry in p_multiassign_exprs. - */ - Assert(pstate->p_multiassign_exprs != NIL); - tle = (TargetEntry *) llast(pstate->p_multiassign_exprs); - } - - /* - * Emit the appropriate output expression for the current column - */ - if (IsA(tle->expr, SubLink)) - { - Param *param; - - sublink = (SubLink *) tle->expr; - Assert(sublink->subLinkType == MULTIEXPR_SUBLINK); - qtree = castNode(Query, sublink->subselect); - - /* Build a Param representing the current subquery output column */ - tle = (TargetEntry *) list_nth(qtree->targetList, maref->colno - 1); - Assert(!tle->resjunk); - - param = makeNode(Param); - param->paramkind = PARAM_MULTIEXPR; - param->paramid = (sublink->subLinkId << 16) | maref->colno; - param->paramtype = exprType((Node *) tle->expr); - param->paramtypmod = exprTypmod((Node *) tle->expr); - param->paramcollid = exprCollation((Node *) tle->expr); - param->location = exprLocation((Node *) tle->expr); - - return (Node *) param; - } - - if (IsA(tle->expr, RowExpr)) - { - Node *result; - - rexpr = (RowExpr *) tle->expr; - - /* Just extract and return the next element of the RowExpr */ - result = (Node *) list_nth(rexpr->args, maref->colno - 1); - - /* - * If we're at the last column, delete the RowExpr from - * p_multiassign_exprs; we don't need it anymore, and don't want it in - * the finished UPDATE tlist. We assume this is still the last entry - * in p_multiassign_exprs. - */ - if (maref->colno == maref->ncolumns) - pstate->p_multiassign_exprs = - list_delete_last(pstate->p_multiassign_exprs); - - return result; - } - - elog(ERROR, "unexpected expr type in multiassign list"); - return NULL; /* keep compiler quiet */ -} - -static Node * -transformCaseExpr(ParseState *pstate, CaseExpr *c) -{ - CaseExpr *newc = makeNode(CaseExpr); - Node *last_srf = pstate->p_last_srf; - Node *arg; - CaseTestExpr *placeholder; - List *newargs; - List *resultexprs; - ListCell *l; - Node *defresult; - Oid ptype; - - /* transform the test expression, if any */ - arg = sql_transform_expr_recurse(pstate, (Node *) c->arg); - - /* generate placeholder for test expression */ - if (arg) - { - /* - * If test expression is an untyped literal, force it to text. We have - * to do something now because we won't be able to do this coercion on - * the placeholder. This is not as flexible as what was done in 7.4 - * and before, but it's good enough to handle the sort of silly coding - * commonly seen. - */ - if (exprType(arg) == UNKNOWNOID) - arg = coerce_to_common_type(pstate, arg, TEXTOID, "CASE"); - - /* - * Run collation assignment on the test expression so that we know - * what collation to mark the placeholder with. In principle we could - * leave it to parse_collate.c to do that later, but propagating the - * result to the CaseTestExpr would be unnecessarily complicated. - */ - assign_expr_collations(pstate, arg); - - placeholder = makeNode(CaseTestExpr); - placeholder->typeId = exprType(arg); - placeholder->typeMod = exprTypmod(arg); - placeholder->collation = exprCollation(arg); - } - else - placeholder = NULL; - - newc->arg = (Expr *) arg; - - /* transform the list of arguments */ - newargs = NIL; - resultexprs = NIL; - foreach(l, c->args) - { - CaseWhen *w = lfirst_node(CaseWhen, l); - CaseWhen *neww = makeNode(CaseWhen); - Node *warg; - - warg = (Node *) w->expr; - if (placeholder) - { - /* shorthand form was specified, so expand... */ - warg = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", - (Node *) placeholder, - warg, - w->location); - } - neww->expr = (Expr *) sql_transform_expr_recurse(pstate, warg); - - neww->expr = (Expr *) coerce_to_boolean(pstate, - (Node *) neww->expr, - "CASE/WHEN"); - - warg = (Node *) w->result; - neww->result = (Expr *) sql_transform_expr_recurse(pstate, warg); - neww->location = w->location; - - newargs = lappend(newargs, neww); - resultexprs = lappend(resultexprs, neww->result); - } - - newc->args = newargs; - - /* transform the default clause */ - defresult = (Node *) c->defresult; - if (defresult == NULL) - { - A_Const *n = makeNode(A_Const); - - n->val.type = T_Null; - n->location = -1; - defresult = (Node *) n; - } - newc->defresult = (Expr *) sql_transform_expr_recurse(pstate, defresult); - - /* - * Note: default result is considered the most significant type in - * determining preferred type. This is how the code worked before, but it - * seems a little bogus to me --- tgl - */ - resultexprs = lcons(newc->defresult, resultexprs); - - ptype = select_common_type(pstate, resultexprs, "CASE", NULL); - Assert(OidIsValid(ptype)); - newc->casetype = ptype; - /* casecollid will be set by parse_collate.c */ - - /* Convert default result clause, if necessary */ - newc->defresult = (Expr *) - coerce_to_common_type(pstate, - (Node *) newc->defresult, - ptype, - "CASE/ELSE"); - - /* Convert when-clause results, if necessary */ - foreach(l, newc->args) - { - CaseWhen *w = (CaseWhen *) lfirst(l); - - w->result = (Expr *) - coerce_to_common_type(pstate, - (Node *) w->result, - ptype, - "CASE/WHEN"); - } - - /* if any subexpression contained a SRF, complain */ - if (pstate->p_last_srf != last_srf) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - /* translator: %s is name of a SQL construct, eg GROUP BY */ - errmsg("set-returning functions are not allowed in %s", - "CASE"), - errhint("You might be able to move the set-returning function into a LATERAL FROM item."), - parser_errposition(pstate, - exprLocation(pstate->p_last_srf)))); - - newc->location = c->location; - - return (Node *) newc; -} - -static Node * -transformSubLink(ParseState *pstate, SubLink *sublink) -{ - Node *result = (Node *) sublink; - Query *qtree; - const char *err; - - /* - * Check to see if the sublink is in an invalid place within the query. We - * allow sublinks everywhere in SELECT/INSERT/UPDATE/DELETE, but generally - * not in utility statements. - */ - err = NULL; - switch (pstate->p_expr_kind) - { - case EXPR_KIND_NONE: - Assert(false); /* can't happen */ - break; - case EXPR_KIND_OTHER: - /* Accept sublink here; caller must throw error if wanted */ - break; - case EXPR_KIND_JOIN_ON: - case EXPR_KIND_JOIN_USING: - case EXPR_KIND_FROM_SUBSELECT: - case EXPR_KIND_FROM_FUNCTION: - case EXPR_KIND_WHERE: - case EXPR_KIND_POLICY: - case EXPR_KIND_HAVING: - case EXPR_KIND_FILTER: - case EXPR_KIND_WINDOW_PARTITION: - case EXPR_KIND_WINDOW_ORDER: - case EXPR_KIND_WINDOW_FRAME_RANGE: - case EXPR_KIND_WINDOW_FRAME_ROWS: - case EXPR_KIND_WINDOW_FRAME_GROUPS: - case EXPR_KIND_SELECT_TARGET: - case EXPR_KIND_INSERT_TARGET: - case EXPR_KIND_UPDATE_SOURCE: - case EXPR_KIND_UPDATE_TARGET: - case EXPR_KIND_GROUP_BY: - case EXPR_KIND_ORDER_BY: - case EXPR_KIND_DISTINCT_ON: - case EXPR_KIND_LIMIT: - case EXPR_KIND_OFFSET: - case EXPR_KIND_RETURNING: - case EXPR_KIND_VALUES: - case EXPR_KIND_VALUES_SINGLE: - case EXPR_KIND_CYCLE_MARK: - /* okay */ - break; - case EXPR_KIND_CHECK_CONSTRAINT: - case EXPR_KIND_DOMAIN_CHECK: - err = _("cannot use subquery in check constraint"); - break; - case EXPR_KIND_COLUMN_DEFAULT: - case EXPR_KIND_FUNCTION_DEFAULT: - err = _("cannot use subquery in DEFAULT expression"); - break; - case EXPR_KIND_INDEX_EXPRESSION: - err = _("cannot use subquery in index expression"); - break; - case EXPR_KIND_INDEX_PREDICATE: - err = _("cannot use subquery in index predicate"); - break; - case EXPR_KIND_STATS_EXPRESSION: - err = _("cannot use subquery in statistics expression"); - break; - case EXPR_KIND_ALTER_COL_TRANSFORM: - err = _("cannot use subquery in transform expression"); - break; - case EXPR_KIND_EXECUTE_PARAMETER: - err = _("cannot use subquery in EXECUTE parameter"); - break; - case EXPR_KIND_TRIGGER_WHEN: - err = _("cannot use subquery in trigger WHEN condition"); - break; - case EXPR_KIND_PARTITION_BOUND: - err = _("cannot use subquery in partition bound"); - break; - case EXPR_KIND_PARTITION_EXPRESSION: - err = _("cannot use subquery in partition key expression"); - break; - case EXPR_KIND_CALL_ARGUMENT: - err = _("cannot use subquery in CALL argument"); - break; - case EXPR_KIND_COPY_WHERE: - err = _("cannot use subquery in COPY FROM WHERE condition"); - break; - case EXPR_KIND_GENERATED_COLUMN: - err = _("cannot use subquery in column generation expression"); - break; - - /* - * There is intentionally no default: case here, so that the - * compiler will warn if we add a new ParseExprKind without - * extending this switch. If we do see an unrecognized value at - * runtime, the behavior will be the same as for EXPR_KIND_OTHER, - * which is sane anyway. - */ - } - if (err) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg_internal("%s", err), - parser_errposition(pstate, sublink->location))); - - pstate->p_hasSubLinks = true; - - /* - * OK, let's transform the sub-SELECT. - */ - qtree = parse_sub_analyze(sublink->subselect, pstate, NULL, false, true); - - /* - * Check that we got a SELECT. Anything else should be impossible given - * restrictions of the grammar, but check anyway. - */ - if (!IsA(qtree, Query) || - qtree->commandType != CMD_SELECT) - elog(ERROR, "unexpected non-SELECT command in SubLink"); - - sublink->subselect = (Node *) qtree; - - if (sublink->subLinkType == EXISTS_SUBLINK) - { - /* - * EXISTS needs no test expression or combining operator. These fields - * should be null already, but make sure. - */ - sublink->testexpr = NULL; - sublink->operName = NIL; - } - else if (sublink->subLinkType == EXPR_SUBLINK || - sublink->subLinkType == ARRAY_SUBLINK) - { - /* - * Make sure the subselect delivers a single column (ignoring resjunk - * targets). - */ - if (count_nonjunk_tlist_entries(qtree->targetList) != 1) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("subquery must return only one column"), - parser_errposition(pstate, sublink->location))); - - /* - * EXPR and ARRAY need no test expression or combining operator. These - * fields should be null already, but make sure. - */ - sublink->testexpr = NULL; - sublink->operName = NIL; - } - else if (sublink->subLinkType == MULTIEXPR_SUBLINK) - { - /* Same as EXPR case, except no restriction on number of columns */ - sublink->testexpr = NULL; - sublink->operName = NIL; - } - else - { - /* ALL, ANY, or ROWCOMPARE: generate row-comparing expression */ - Node *lefthand; - List *left_list; - List *right_list; - ListCell *l; - - /* - * If the source was "x IN (select)", convert to "x = ANY (select)". - */ - if (sublink->operName == NIL) - sublink->operName = list_make1(makeString("=")); - - /* - * Transform lefthand expression, and convert to a list - */ - lefthand = sql_transform_expr_recurse(pstate, sublink->testexpr); - if (lefthand && IsA(lefthand, RowExpr)) - left_list = ((RowExpr *) lefthand)->args; - else - left_list = list_make1(lefthand); - - /* - * Build a list of PARAM_SUBLINK nodes representing the output columns - * of the subquery. - */ - right_list = NIL; - foreach(l, qtree->targetList) - { - TargetEntry *tent = (TargetEntry *) lfirst(l); - Param *param; - - if (tent->resjunk) - continue; - - param = makeNode(Param); - param->paramkind = PARAM_SUBLINK; - param->paramid = tent->resno; - param->paramtype = exprType((Node *) tent->expr); - param->paramtypmod = exprTypmod((Node *) tent->expr); - param->paramcollid = exprCollation((Node *) tent->expr); - param->location = -1; - - right_list = lappend(right_list, param); - } - - /* - * We could rely on make_row_comparison_op to complain if the list - * lengths differ, but we prefer to generate a more specific error - * message. - */ - if (list_length(left_list) < list_length(right_list)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("subquery has too many columns"), - parser_errposition(pstate, sublink->location))); - if (list_length(left_list) > list_length(right_list)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("subquery has too few columns"), - parser_errposition(pstate, sublink->location))); - - /* - * Identify the combining operator(s) and generate a suitable - * row-comparison expression. - */ - sublink->testexpr = make_row_comparison_op(pstate, - sublink->operName, - left_list, - right_list, - sublink->location); - } - - return result; -} - -/* - * transformArrayExpr - * - * If the caller specifies the target type, the resulting array will - * be of exactly that type. Otherwise we try to infer a common type - * for the elements using select_common_type(). - */ -static Node * -transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, - Oid array_type, Oid element_type, int32 typmod) -{ - ArrayExpr *newa = makeNode(ArrayExpr); - List *newelems = NIL; - List *newcoercedelems = NIL; - ListCell *element; - Oid coerce_type; - bool coerce_hard; - - /* - * Transform the element expressions - * - * Assume that the array is one-dimensional unless we find an array-type - * element expression. - */ - newa->multidims = false; - foreach(element, a->elements) - { - Node *e = (Node *) lfirst(element); - Node *newe; - - /* - * If an element is itself an A_ArrayExpr, recurse directly so that we - * can pass down any target type we were given. - */ - if (IsA(e, A_ArrayExpr)) - { - newe = transformArrayExpr(pstate, - (A_ArrayExpr *) e, - array_type, - element_type, - typmod); - /* we certainly have an array here */ - Assert(array_type == InvalidOid || array_type == exprType(newe)); - newa->multidims = true; - } - else - { - newe = sql_transform_expr_recurse(pstate, e); - - /* - * Check for sub-array expressions, if we haven't already found - * one. - */ - if (!newa->multidims && type_is_array(exprType(newe))) - newa->multidims = true; - } - - newelems = lappend(newelems, newe); - } - - /* - * Select a target type for the elements. - * - * If we haven't been given a target array type, we must try to deduce a - * common type based on the types of the individual elements present. - */ - if (OidIsValid(array_type)) - { - /* Caller must ensure array_type matches element_type */ - Assert(OidIsValid(element_type)); - coerce_type = (newa->multidims ? array_type : element_type); - coerce_hard = true; - } - else - { - /* Can't handle an empty array without a target type */ - if (newelems == NIL) - ereport(ERROR, - (errcode(ERRCODE_INDETERMINATE_DATATYPE), - errmsg("cannot determine type of empty array"), - errhint("Explicitly cast to the desired type, " - "for example ARRAY[]::integer[]."), - parser_errposition(pstate, a->location))); - - /* Select a common type for the elements */ - coerce_type = select_common_type(pstate, newelems, "ARRAY", NULL); - - if (newa->multidims) - { - array_type = coerce_type; - element_type = get_element_type(array_type); - if (!OidIsValid(element_type)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find element type for data type %s", - format_type_be(array_type)), - parser_errposition(pstate, a->location))); - } - else - { - element_type = coerce_type; - array_type = get_array_type(element_type); - if (!OidIsValid(array_type)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find array type for data type %s", - format_type_be(element_type)), - parser_errposition(pstate, a->location))); - } - coerce_hard = false; - } - - /* - * Coerce elements to target type - * - * If the array has been explicitly cast, then the elements are in turn - * explicitly coerced. - * - * If the array's type was merely derived from the common type of its - * elements, then the elements are implicitly coerced to the common type. - * This is consistent with other uses of select_common_type(). - */ - foreach(element, newelems) - { - Node *e = (Node *) lfirst(element); - Node *newe; - - if (coerce_hard) - { - newe = coerce_to_target_type(pstate, e, - exprType(e), - coerce_type, - typmod, - COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, - -1); - if (newe == NULL) - ereport(ERROR, - (errcode(ERRCODE_CANNOT_COERCE), - errmsg("cannot cast type %s to %s", - format_type_be(exprType(e)), - format_type_be(coerce_type)), - parser_errposition(pstate, exprLocation(e)))); - } - else - newe = coerce_to_common_type(pstate, e, - coerce_type, - "ARRAY"); - newcoercedelems = lappend(newcoercedelems, newe); - } - - newa->array_typeid = array_type; - /* array_collid will be set by parse_collate.c */ - newa->element_typeid = element_type; - newa->elements = newcoercedelems; - newa->location = a->location; - - return (Node *) newa; -} - -static Node * -transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault) -{ - RowExpr *newr; - char fname[16]; - int fnum; - - newr = makeNode(RowExpr); - - /* Transform the field expressions */ - newr->args = transformExpressionList(pstate, r->args, - pstate->p_expr_kind, allowDefault); - - /* Disallow more columns than will fit in a tuple */ - if (list_length(newr->args) > MaxTupleAttributeNumber) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_COLUMNS), - errmsg("ROW expressions can have at most %d entries", - MaxTupleAttributeNumber), - parser_errposition(pstate, r->location))); - - /* Barring later casting, we consider the type RECORD */ - newr->row_typeid = RECORDOID; - newr->row_format = COERCE_IMPLICIT_CAST; - - /* ROW() has anonymous columns, so invent some field names */ - newr->colnames = NIL; - for (fnum = 1; fnum <= list_length(newr->args); fnum++) - { - snprintf(fname, sizeof(fname), "f%d", fnum); - newr->colnames = lappend(newr->colnames, makeString(pstrdup(fname))); - } - - newr->location = r->location; - - return (Node *) newr; -} - -static Node * -transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c) -{ - CoalesceExpr *newc = makeNode(CoalesceExpr); - Node *last_srf = pstate->p_last_srf; - List *newargs = NIL; - List *newcoercedargs = NIL; - ListCell *args; - - foreach(args, c->args) - { - Node *e = (Node *) lfirst(args); - Node *newe; - - newe = sql_transform_expr_recurse(pstate, e); - newargs = lappend(newargs, newe); - } - - newc->coalescetype = select_common_type(pstate, newargs, "COALESCE", NULL); - /* coalescecollid will be set by parse_collate.c */ - - /* Convert arguments if necessary */ - foreach(args, newargs) - { - Node *e = (Node *) lfirst(args); - Node *newe; - - newe = coerce_to_common_type(pstate, e, - newc->coalescetype, - "COALESCE"); - newcoercedargs = lappend(newcoercedargs, newe); - } - - /* if any subexpression contained a SRF, complain */ - if (pstate->p_last_srf != last_srf) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - /* translator: %s is name of a SQL construct, eg GROUP BY */ - errmsg("set-returning functions are not allowed in %s", - "COALESCE"), - errhint("You might be able to move the set-returning function into a LATERAL FROM item."), - parser_errposition(pstate, - exprLocation(pstate->p_last_srf)))); - - newc->args = newcoercedargs; - newc->location = c->location; - return (Node *) newc; -} - -static Node * -transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m) -{ - MinMaxExpr *newm = makeNode(MinMaxExpr); - List *newargs = NIL; - List *newcoercedargs = NIL; - const char *funcname = (m->op == IS_GREATEST) ? "GREATEST" : "LEAST"; - ListCell *args; - - newm->op = m->op; - foreach(args, m->args) - { - Node *e = (Node *) lfirst(args); - Node *newe; - - newe = sql_transform_expr_recurse(pstate, e); - newargs = lappend(newargs, newe); - } - - newm->minmaxtype = select_common_type(pstate, newargs, funcname, NULL); - /* minmaxcollid and inputcollid will be set by parse_collate.c */ - - /* Convert arguments if necessary */ - foreach(args, newargs) - { - Node *e = (Node *) lfirst(args); - Node *newe; - - newe = coerce_to_common_type(pstate, e, - newm->minmaxtype, - funcname); - newcoercedargs = lappend(newcoercedargs, newe); - } - - newm->args = newcoercedargs; - newm->location = m->location; - return (Node *) newm; -} - -static Node * -transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf) -{ - /* - * All we need to do is insert the correct result type and (where needed) - * validate the typmod, so we just modify the node in-place. - */ - switch (svf->op) - { - case SVFOP_CURRENT_DATE: - svf->type = DATEOID; - break; - case SVFOP_CURRENT_TIME: - svf->type = TIMETZOID; - break; - case SVFOP_CURRENT_TIME_N: - svf->type = TIMETZOID; - svf->typmod = anytime_typmod_check(true, svf->typmod); - break; - case SVFOP_CURRENT_TIMESTAMP: - svf->type = TIMESTAMPTZOID; - break; - case SVFOP_CURRENT_TIMESTAMP_N: - svf->type = TIMESTAMPTZOID; - svf->typmod = anytimestamp_typmod_check(true, svf->typmod); - break; - case SVFOP_LOCALTIME: - svf->type = TIMEOID; - break; - case SVFOP_LOCALTIME_N: - svf->type = TIMEOID; - svf->typmod = anytime_typmod_check(false, svf->typmod); - break; - case SVFOP_LOCALTIMESTAMP: - svf->type = TIMESTAMPOID; - break; - case SVFOP_LOCALTIMESTAMP_N: - svf->type = TIMESTAMPOID; - svf->typmod = anytimestamp_typmod_check(false, svf->typmod); - break; - case SVFOP_CURRENT_ROLE: - case SVFOP_CURRENT_USER: - case SVFOP_USER: - case SVFOP_SESSION_USER: - case SVFOP_CURRENT_CATALOG: - case SVFOP_CURRENT_SCHEMA: - svf->type = NAMEOID; - break; - } - - return (Node *) svf; -} - -static Node * -transformXmlExpr(ParseState *pstate, XmlExpr *x) -{ - XmlExpr *newx; - ListCell *lc; - int i; - - newx = makeNode(XmlExpr); - newx->op = x->op; - if (x->name) - newx->name = map_sql_identifier_to_xml_name(x->name, false, false); - else - newx->name = NULL; - newx->xmloption = x->xmloption; - newx->type = XMLOID; /* this just marks the node as transformed */ - newx->typmod = -1; - newx->location = x->location; - - /* - * gram.y built the named args as a list of ResTarget. Transform each, - * and break the names out as a separate list. - */ - newx->named_args = NIL; - newx->arg_names = NIL; - - foreach(lc, x->named_args) - { - ResTarget *r = lfirst_node(ResTarget, lc); - Node *expr; - char *argname; - - expr = sql_transform_expr_recurse(pstate, r->val); - - if (r->name) - argname = map_sql_identifier_to_xml_name(r->name, false, false); - else if (IsA(r->val, ColumnRef)) - argname = map_sql_identifier_to_xml_name(FigureColname(r->val), - true, false); - else - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - x->op == IS_XMLELEMENT - ? errmsg("unnamed XML attribute value must be a column reference") - : errmsg("unnamed XML element value must be a column reference"), - parser_errposition(pstate, r->location))); - argname = NULL; /* keep compiler quiet */ - } - - /* reject duplicate argnames in XMLELEMENT only */ - if (x->op == IS_XMLELEMENT) - { - ListCell *lc2; - - foreach(lc2, newx->arg_names) - { - if (strcmp(argname, strVal(lfirst(lc2))) == 0) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("XML attribute name \"%s\" appears more than once", - argname), - parser_errposition(pstate, r->location))); - } - } - - newx->named_args = lappend(newx->named_args, expr); - newx->arg_names = lappend(newx->arg_names, makeString(argname)); - } - - /* The other arguments are of varying types depending on the function */ - newx->args = NIL; - i = 0; - foreach(lc, x->args) - { - Node *e = (Node *) lfirst(lc); - Node *newe; - - newe = sql_transform_expr_recurse(pstate, e); - switch (x->op) - { - case IS_XMLCONCAT: - newe = coerce_to_specific_type(pstate, newe, XMLOID, - "XMLCONCAT"); - break; - case IS_XMLELEMENT: - /* no coercion necessary */ - break; - case IS_XMLFOREST: - newe = coerce_to_specific_type(pstate, newe, XMLOID, - "XMLFOREST"); - break; - case IS_XMLPARSE: - if (i == 0) - newe = coerce_to_specific_type(pstate, newe, TEXTOID, - "XMLPARSE"); - else - newe = coerce_to_boolean(pstate, newe, "XMLPARSE"); - break; - case IS_XMLPI: - newe = coerce_to_specific_type(pstate, newe, TEXTOID, - "XMLPI"); - break; - case IS_XMLROOT: - if (i == 0) - newe = coerce_to_specific_type(pstate, newe, XMLOID, - "XMLROOT"); - else if (i == 1) - newe = coerce_to_specific_type(pstate, newe, TEXTOID, - "XMLROOT"); - else - newe = coerce_to_specific_type(pstate, newe, INT4OID, - "XMLROOT"); - break; - case IS_XMLSERIALIZE: - /* not handled here */ - Assert(false); - break; - case IS_DOCUMENT: - newe = coerce_to_specific_type(pstate, newe, XMLOID, - "IS DOCUMENT"); - break; - } - newx->args = lappend(newx->args, newe); - i++; - } - - return (Node *) newx; -} - -static Node * -transformXmlSerialize(ParseState *pstate, XmlSerialize *xs) -{ - Node *result; - XmlExpr *xexpr; - Oid targetType; - int32 targetTypmod; - - xexpr = makeNode(XmlExpr); - xexpr->op = IS_XMLSERIALIZE; - xexpr->args = list_make1(coerce_to_specific_type(pstate, - sql_transform_expr_recurse(pstate, xs->expr), - XMLOID, - "XMLSERIALIZE")); - - typenameTypeIdAndMod(pstate, xs->typeName, &targetType, &targetTypmod); - - xexpr->xmloption = xs->xmloption; - xexpr->location = xs->location; - /* We actually only need these to be able to parse back the expression. */ - xexpr->type = targetType; - xexpr->typmod = targetTypmod; - - /* - * The actual target type is determined this way. SQL allows char and - * varchar as target types. We allow anything that can be cast implicitly - * from text. This way, user-defined text-like data types automatically - * fit in. - */ - result = coerce_to_target_type(pstate, (Node *) xexpr, - TEXTOID, targetType, targetTypmod, - COERCION_IMPLICIT, - COERCE_IMPLICIT_CAST, - -1); - if (result == NULL) - ereport(ERROR, - (errcode(ERRCODE_CANNOT_COERCE), - errmsg("cannot cast XMLSERIALIZE result to %s", - format_type_be(targetType)), - parser_errposition(pstate, xexpr->location))); - return result; -} - -static Node * -transformBooleanTest(ParseState *pstate, BooleanTest *b) -{ - const char *clausename; - - switch (b->booltesttype) - { - case IS_TRUE: - clausename = "IS TRUE"; - break; - case IS_NOT_TRUE: - clausename = "IS NOT TRUE"; - break; - case IS_FALSE: - clausename = "IS FALSE"; - break; - case IS_NOT_FALSE: - clausename = "IS NOT FALSE"; - break; - case IS_UNKNOWN: - clausename = "IS UNKNOWN"; - break; - case IS_NOT_UNKNOWN: - clausename = "IS NOT UNKNOWN"; - break; - default: - elog(ERROR, "unrecognized booltesttype: %d", - (int) b->booltesttype); - clausename = NULL; /* keep compiler quiet */ - } - - b->arg = (Expr *) sql_transform_expr_recurse(pstate, (Node *) b->arg); - - b->arg = (Expr *) coerce_to_boolean(pstate, - (Node *) b->arg, - clausename); - - return (Node *) b; -} - -static Node * -transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr) -{ - /* CURRENT OF can only appear at top level of UPDATE/DELETE */ - Assert(pstate->p_target_nsitem != NULL); - cexpr->cvarno = pstate->p_target_nsitem->p_rtindex; - - /* - * Check to see if the cursor name matches a parameter of type REFCURSOR. - * If so, replace the raw name reference with a parameter reference. (This - * is a hack for the convenience of plpgsql.) - */ - if (cexpr->cursor_name != NULL) /* in case already transformed */ - { - ColumnRef *cref = makeNode(ColumnRef); - Node *node = NULL; - - /* Build an unqualified ColumnRef with the given name */ - cref->fields = list_make1(makeString(cexpr->cursor_name)); - cref->location = -1; - - /* See if there is a translation available from a parser hook */ - if (pstate->p_pre_columnref_hook != NULL) - node = pstate->p_pre_columnref_hook(pstate, cref); - if (node == NULL && pstate->p_post_columnref_hook != NULL) - node = pstate->p_post_columnref_hook(pstate, cref, NULL); - - /* - * XXX Should we throw an error if we get a translation that isn't a - * refcursor Param? For now it seems best to silently ignore false - * matches. - */ - if (node != NULL && IsA(node, Param)) - { - Param *p = (Param *) node; - - if (p->paramkind == PARAM_EXTERN && - p->paramtype == REFCURSOROID) - { - /* Matches, so convert CURRENT OF to a param reference */ - cexpr->cursor_name = NULL; - cexpr->cursor_param = p->paramid; - } - } - } - - return (Node *) cexpr; -} - -/* - * Construct a whole-row reference to represent the notation "relation.*". - */ -static Node * -transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem, - int sublevels_up, int location) -{ - /* - * Build the appropriate referencing node. Normally this can be a - * whole-row Var, but if the nsitem is a JOIN USING alias then it contains - * only a subset of the columns of the underlying join RTE, so that will - * not work. Instead we immediately expand the reference into a RowExpr. - * Since the JOIN USING's common columns are fully determined at this - * point, there seems no harm in expanding it now rather than during - * planning. - * - * Note that if the RTE is a function returning scalar, we create just a - * plain reference to the function value, not a composite containing a - * single column. This is pretty inconsistent at first sight, but it's - * what we've done historically. One argument for it is that "rel" and - * "rel.*" mean the same thing for composite relations, so why not for - * scalar functions... - */ - if (nsitem->p_names == nsitem->p_rte->eref) - { - Var *result; - - result = makeWholeRowVar(nsitem->p_rte, nsitem->p_rtindex, - sublevels_up, true); - - /* location is not filled in by makeWholeRowVar */ - result->location = location; - - /* mark relation as requiring whole-row SELECT access */ - markVarForSelectPriv(pstate, result); - - return (Node *) result; - } - else - { - RowExpr *rowexpr; - List *fields; - - /* - * We want only as many columns as are listed in p_names->colnames, - * and we should use those names not whatever possibly-aliased names - * are in the RTE. We needn't worry about marking the RTE for SELECT - * access, as the common columns are surely so marked already. - */ - expandRTE(nsitem->p_rte, nsitem->p_rtindex, - sublevels_up, location, false, - NULL, &fields); - rowexpr = makeNode(RowExpr); - rowexpr->args = list_truncate(fields, - list_length(nsitem->p_names->colnames)); - rowexpr->row_typeid = RECORDOID; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(nsitem->p_names->colnames); - rowexpr->location = location; - - return (Node *) rowexpr; - } -} - -/* - * Handle an explicit CAST construct. - * - * Transform the argument, look up the type name, and apply any necessary - * coercion function(s). - */ -static Node * -transformTypeCast(ParseState *pstate, TypeCast *tc) -{ - Node *result; - Node *arg = tc->arg; - Node *expr; - Oid inputType; - Oid targetType; - int32 targetTypmod; - int location; - - /* Look up the type name first */ - typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod); - - /* - * If the subject of the typecast is an ARRAY[] construct and the target - * type is an array type, we invoke transformArrayExpr() directly so that - * we can pass down the type information. This avoids some cases where - * transformArrayExpr() might not infer the correct type. Otherwise, just - * transform the argument normally. - */ - if (IsA(arg, A_ArrayExpr)) - { - Oid targetBaseType; - int32 targetBaseTypmod; - Oid elementType; - - /* - * If target is a domain over array, work with the base array type - * here. Below, we'll cast the array type to the domain. In the - * usual case that the target is not a domain, the remaining steps - * will be a no-op. - */ - targetBaseTypmod = targetTypmod; - targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod); - elementType = get_element_type(targetBaseType); - if (OidIsValid(elementType)) - { - expr = transformArrayExpr(pstate, - (A_ArrayExpr *) arg, - targetBaseType, - elementType, - targetBaseTypmod); - } - else - expr = sql_transform_expr_recurse(pstate, arg); - } - else - expr = sql_transform_expr_recurse(pstate, arg); - - inputType = exprType(expr); - if (inputType == InvalidOid) - return expr; /* do nothing if NULL input */ - - /* - * Location of the coercion is preferentially the location of the :: or - * CAST symbol, but if there is none then use the location of the type - * name (this can happen in TypeName 'string' syntax, for instance). - */ - location = tc->location; - if (location < 0) - location = tc->typeName->location; - - result = coerce_to_target_type(pstate, expr, inputType, - targetType, targetTypmod, - COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, - location); - if (result == NULL) - ereport(ERROR, - (errcode(ERRCODE_CANNOT_COERCE), - errmsg("cannot cast type %s to %s", - format_type_be(inputType), - format_type_be(targetType)), - parser_coercion_errposition(pstate, location, expr))); - - return result; -} - -/* - * Handle an explicit COLLATE clause. - * - * Transform the argument, and look up the collation name. - */ -static Node * -transformCollateClause(ParseState *pstate, CollateClause *c) -{ - CollateExpr *newc; - Oid argtype; - - newc = makeNode(CollateExpr); - newc->arg = (Expr *) sql_transform_expr_recurse(pstate, c->arg); - - argtype = exprType((Node *) newc->arg); - - /* - * The unknown type is not collatable, but coerce_type() takes care of it - * separately, so we'll let it go here. - */ - if (!type_is_collatable(argtype) && argtype != UNKNOWNOID) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("collations are not supported by type %s", - format_type_be(argtype)), - parser_errposition(pstate, c->location))); - - newc->collOid = LookupCollation(pstate, c->collname, c->location); - newc->location = c->location; - - return (Node *) newc; -} - -/* - * Transform a "row compare-op row" construct - * - * The inputs are lists of already-transformed expressions. - * As with coerce_type, pstate may be NULL if no special unknown-Param - * processing is wanted. - * - * The output may be a single OpExpr, an AND or OR combination of OpExprs, - * or a RowCompareExpr. In all cases it is guaranteed to return boolean. - * The AND, OR, and RowCompareExpr cases further imply things about the - * behavior of the operators (ie, they behave as =, <>, or < <= > >=). - */ -static Node * -make_row_comparison_op(ParseState *pstate, List *opname, - List *largs, List *rargs, int location) -{ - RowCompareExpr *rcexpr; - RowCompareType rctype; - List *opexprs; - List *opnos; - List *opfamilies; - ListCell *l, - *r; - List **opinfo_lists; - Bitmapset *strats; - int nopers; - int i; - - nopers = list_length(largs); - if (nopers != list_length(rargs)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("unequal number of entries in row expressions"), - parser_errposition(pstate, location))); - - /* - * We can't compare zero-length rows because there is no principled basis - * for figuring out what the operator is. - */ - if (nopers == 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot compare rows of zero length"), - parser_errposition(pstate, location))); - - /* - * Identify all the pairwise operators, using make_op so that behavior is - * the same as in the simple scalar case. - */ - opexprs = NIL; - forboth(l, largs, r, rargs) - { - Node *larg = (Node *) lfirst(l); - Node *rarg = (Node *) lfirst(r); - OpExpr *cmp; - - cmp = castNode(OpExpr, make_op(pstate, opname, larg, rarg, - pstate->p_last_srf, location)); - - /* - * We don't use coerce_to_boolean here because we insist on the - * operator yielding boolean directly, not via coercion. If it - * doesn't yield bool it won't be in any index opfamilies... - */ - if (cmp->opresulttype != BOOLOID) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("row comparison operator must yield type boolean, " - "not type %s", - format_type_be(cmp->opresulttype)), - parser_errposition(pstate, location))); - if (expression_returns_set((Node *) cmp)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("row comparison operator must not return a set"), - parser_errposition(pstate, location))); - opexprs = lappend(opexprs, cmp); - } - - /* - * If rows are length 1, just return the single operator. In this case we - * don't insist on identifying btree semantics for the operator (but we - * still require it to return boolean). - */ - if (nopers == 1) - return (Node *) linitial(opexprs); - - /* - * Now we must determine which row comparison semantics (= <> < <= > >=) - * apply to this set of operators. We look for btree opfamilies - * containing the operators, and see which interpretations (strategy - * numbers) exist for each operator. - */ - opinfo_lists = (List **) palloc(nopers * sizeof(List *)); - strats = NULL; - i = 0; - foreach(l, opexprs) - { - Oid opno = ((OpExpr *) lfirst(l))->opno; - Bitmapset *this_strats; - ListCell *j; - - opinfo_lists[i] = get_op_btree_interpretation(opno); - - /* - * convert strategy numbers into a Bitmapset to make the intersection - * calculation easy. - */ - this_strats = NULL; - foreach(j, opinfo_lists[i]) - { - OpBtreeInterpretation *opinfo = lfirst(j); - - this_strats = bms_add_member(this_strats, opinfo->strategy); - } - if (i == 0) - strats = this_strats; - else - strats = bms_int_members(strats, this_strats); - i++; - } - - /* - * If there are multiple common interpretations, we may use any one of - * them ... this coding arbitrarily picks the lowest btree strategy - * number. - */ - i = bms_first_member(strats); - if (i < 0) - { - /* No common interpretation, so fail */ - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("could not determine interpretation of row comparison operator %s", - strVal(llast(opname))), - errhint("Row comparison operators must be associated with btree operator families."), - parser_errposition(pstate, location))); - } - rctype = (RowCompareType) i; - - /* - * For = and <> cases, we just combine the pairwise operators with AND or - * OR respectively. - */ - if (rctype == ROWCOMPARE_EQ) - return (Node *) makeBoolExpr(AND_EXPR, opexprs, location); - if (rctype == ROWCOMPARE_NE) - return (Node *) makeBoolExpr(OR_EXPR, opexprs, location); - - /* - * Otherwise we need to choose exactly which opfamily to associate with - * each operator. - */ - opfamilies = NIL; - for (i = 0; i < nopers; i++) - { - Oid opfamily = InvalidOid; - ListCell *j; - - foreach(j, opinfo_lists[i]) - { - OpBtreeInterpretation *opinfo = lfirst(j); - - if (opinfo->strategy == rctype) - { - opfamily = opinfo->opfamily_id; - break; - } - } - if (OidIsValid(opfamily)) - opfamilies = lappend_oid(opfamilies, opfamily); - else /* should not happen */ - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("could not determine interpretation of row comparison operator %s", - strVal(llast(opname))), - errdetail("There are multiple equally-plausible candidates."), - parser_errposition(pstate, location))); - } - - /* - * Now deconstruct the OpExprs and create a RowCompareExpr. - * - * Note: can't just reuse the passed largs/rargs lists, because of - * possibility that make_op inserted coercion operations. - */ - opnos = NIL; - largs = NIL; - rargs = NIL; - foreach(l, opexprs) - { - OpExpr *cmp = (OpExpr *) lfirst(l); - - opnos = lappend_oid(opnos, cmp->opno); - largs = lappend(largs, linitial(cmp->args)); - rargs = lappend(rargs, lsecond(cmp->args)); - } - - rcexpr = makeNode(RowCompareExpr); - rcexpr->rctype = rctype; - rcexpr->opnos = opnos; - rcexpr->opfamilies = opfamilies; - rcexpr->inputcollids = NIL; /* assign_expr_collations will fix this */ - rcexpr->largs = largs; - rcexpr->rargs = rargs; - - return (Node *) rcexpr; -} - -/* - * Transform a "row IS DISTINCT FROM row" construct - * - * The input RowExprs are already transformed - */ -static Node * -make_row_distinct_op(ParseState *pstate, List *opname, - RowExpr *lrow, RowExpr *rrow, - int location) -{ - Node *result = NULL; - List *largs = lrow->args; - List *rargs = rrow->args; - ListCell *l, - *r; - - if (list_length(largs) != list_length(rargs)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("unequal number of entries in row expressions"), - parser_errposition(pstate, location))); - - forboth(l, largs, r, rargs) - { - Node *larg = (Node *) lfirst(l); - Node *rarg = (Node *) lfirst(r); - Node *cmp; - - cmp = (Node *) make_distinct_op(pstate, opname, larg, rarg, location); - if (result == NULL) - result = cmp; - else - result = (Node *) makeBoolExpr(OR_EXPR, - list_make2(result, cmp), - location); - } - - if (result == NULL) - { - /* zero-length rows? Generate constant FALSE */ - result = makeBoolConst(false, false); - } - - return result; -} - -/* - * make the node for an IS DISTINCT FROM operator - */ -static Expr * -make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, - int location) -{ - Expr *result; - - result = make_op(pstate, opname, ltree, rtree, - pstate->p_last_srf, location); - if (((OpExpr *) result)->opresulttype != BOOLOID) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("IS DISTINCT FROM requires = operator to yield boolean"), - parser_errposition(pstate, location))); - if (((OpExpr *) result)->opretset) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - /* translator: %s is name of a SQL construct, eg NULLIF */ - errmsg("%s must not return a set", "IS DISTINCT FROM"), - parser_errposition(pstate, location))); - - /* - * We rely on DistinctExpr and OpExpr being same struct - */ - NodeSetTag(result, T_DistinctExpr); - - return result; -} - -/* - * Produce a NullTest node from an IS [NOT] DISTINCT FROM NULL construct - * - * "arg" is the untransformed other argument - */ -static Node * -make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg) -{ - NullTest *nt = makeNode(NullTest); - - nt->arg = (Expr *) sql_transform_expr_recurse(pstate, arg); - /* the argument can be any type, so don't coerce it */ - if (distincta->kind == AEXPR_NOT_DISTINCT) - nt->nulltesttype = IS_NULL; - else - nt->nulltesttype = IS_NOT_NULL; - /* argisrow = false is correct whether or not arg is composite */ - nt->argisrow = false; - nt->location = distincta->location; - return (Node *) nt; -} - -/* - * Produce a string identifying an expression by kind. - * - * Note: when practical, use a simple SQL keyword for the result. If that - * doesn't work well, check call sites to see whether custom error message - * strings are required. - */ -const char * -ParseExprKindName(ParseExprKind exprKind) -{ - switch (exprKind) - { - case EXPR_KIND_NONE: - return "invalid expression context"; - case EXPR_KIND_OTHER: - return "extension expression"; - case EXPR_KIND_JOIN_ON: - return "JOIN/ON"; - case EXPR_KIND_JOIN_USING: - return "JOIN/USING"; - case EXPR_KIND_FROM_SUBSELECT: - return "sub-SELECT in FROM"; - case EXPR_KIND_FROM_FUNCTION: - return "function in FROM"; - case EXPR_KIND_WHERE: - return "WHERE"; - case EXPR_KIND_POLICY: - return "POLICY"; - case EXPR_KIND_HAVING: - return "HAVING"; - case EXPR_KIND_FILTER: - return "FILTER"; - case EXPR_KIND_WINDOW_PARTITION: - return "window PARTITION BY"; - case EXPR_KIND_WINDOW_ORDER: - return "window ORDER BY"; - case EXPR_KIND_WINDOW_FRAME_RANGE: - return "window RANGE"; - case EXPR_KIND_WINDOW_FRAME_ROWS: - return "window ROWS"; - case EXPR_KIND_WINDOW_FRAME_GROUPS: - return "window GROUPS"; - case EXPR_KIND_SELECT_TARGET: - return "SELECT"; - case EXPR_KIND_INSERT_TARGET: - return "INSERT"; - case EXPR_KIND_UPDATE_SOURCE: - case EXPR_KIND_UPDATE_TARGET: - return "UPDATE"; - case EXPR_KIND_GROUP_BY: - return "GROUP BY"; - case EXPR_KIND_ORDER_BY: - return "ORDER BY"; - case EXPR_KIND_DISTINCT_ON: - return "DISTINCT ON"; - case EXPR_KIND_LIMIT: - return "LIMIT"; - case EXPR_KIND_OFFSET: - return "OFFSET"; - case EXPR_KIND_RETURNING: - return "RETURNING"; - case EXPR_KIND_VALUES: - case EXPR_KIND_VALUES_SINGLE: - return "VALUES"; - case EXPR_KIND_CHECK_CONSTRAINT: - case EXPR_KIND_DOMAIN_CHECK: - return "CHECK"; - case EXPR_KIND_COLUMN_DEFAULT: - case EXPR_KIND_FUNCTION_DEFAULT: - return "DEFAULT"; - case EXPR_KIND_INDEX_EXPRESSION: - return "index expression"; - case EXPR_KIND_INDEX_PREDICATE: - return "index predicate"; - case EXPR_KIND_STATS_EXPRESSION: - return "statistics expression"; - case EXPR_KIND_ALTER_COL_TRANSFORM: - return "USING"; - case EXPR_KIND_EXECUTE_PARAMETER: - return "EXECUTE"; - case EXPR_KIND_TRIGGER_WHEN: - return "WHEN"; - case EXPR_KIND_PARTITION_BOUND: - return "partition bound"; - case EXPR_KIND_PARTITION_EXPRESSION: - return "PARTITION BY"; - case EXPR_KIND_CALL_ARGUMENT: - return "CALL"; - case EXPR_KIND_COPY_WHERE: - return "WHERE"; - case EXPR_KIND_GENERATED_COLUMN: - return "GENERATED AS"; - case EXPR_KIND_CYCLE_MARK: - return "CYCLE"; - - /* - * There is intentionally no default: case here, so that the - * compiler will warn if we add a new ParseExprKind without - * extending this switch. If we do see an unrecognized value at - * runtime, we'll fall through to the "unrecognized" return. - */ - } - return "unrecognized expression kind"; -} diff --git a/src/include/parser/cypher_clause.h b/src/include/parser/cypher_clause.h index 47293465..a231a583 100644 --- a/src/include/parser/cypher_clause.h +++ b/src/include/parser/cypher_clause.h @@ -28,6 +28,8 @@ typedef struct cypher_clause cypher_clause; +static bool parsing_cypher = false; + struct cypher_clause { cypher_clause *next; //next clause @@ -41,10 +43,6 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause); Query *transform_cypher_with(cypher_parsestate *cpstate, cypher_clause *clause); -List * -transform_window_definitions(ParseState *pstate, List *windowdefs, List **targetlist); -List * transform_group_clause(cypher_parsestate *cpstate, List *grouplist, List **groupingSets, - List **targetlist, List *sortClause, ParseExprKind exprKind); Query *cypher_parse_sub_analyze(Node *parseTree, cypher_parsestate *cpstate, CommonTableExpr *parentCTE, diff --git a/src/include/parser/cypher_expr.h b/src/include/parser/cypher_expr.h index 8a3ba51f..1de1ab3b 100644 --- a/src/include/parser/cypher_expr.h +++ b/src/include/parser/cypher_expr.h @@ -1,29 +1,6 @@ -/* - * For PostgreSQL Database Management System: - * (formerly known as Postgres, then as Postgres95) - * - * Portions Copyright (c) 1996-2010, The PostgreSQL Global Development Group - * - * Portions Copyright (c) 1994, The Regents of the University of California - * - * Permission to use, copy, modify, and distribute this software and its documentation for any purpose, - * without fee, and without a written agreement is hereby granted, provided that the above copyright notice - * and this paragraph and the following two paragraphs appear in all copies. - * - * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, - * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, - * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY - * OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - * - * THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA - * HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - */ -#ifndef AG_CYPHER_EXPR_H -#define AG_CYPHER_EXPR_H +#ifndef POSTGRAPH_CYPHER_EXPR_H +#define POSTGRAPH_CYPHER_EXPR_H #include "nodes/nodes.h" #include "parser/parse_node.h" @@ -32,9 +9,11 @@ Node *transform_cypher_expr(cypher_parsestate *cpstate, Node *expr, ParseExprKind expr_kind); -Node * -sql_transform_expr(ParseState *pstate, Node *expr, ParseExprKind exprKind) ; + List * expand_NS_Item_Attrs(ParseState *pstate, ParseNamespaceItem *nsitem, int sublevels_up, int location); + +Node *cypher_columnref_hook (ParseState *pstate, ColumnRef *cref); + #endif diff --git a/src/include/parser/cypher_parse_agg.h b/src/include/parser/cypher_parse_agg.h deleted file mode 100644 index dec228e6..00000000 --- a/src/include/parser/cypher_parse_agg.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * For PostgreSQL Database Management System: - * (formerly known as Postgres, then as Postgres95) - * - * Portions Copyright (c) 1996-2010, The PostgreSQL Global Development Group - * - * Portions Copyright (c) 1994, The Regents of the University of California - * - * Permission to use, copy, modify, and distribute this software and its documentation for any purpose, - * without fee, and without a written agreement is hereby granted, provided that the above copyright notice - * and this paragraph and the following two paragraphs appear in all copies. - * - * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, - * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, - * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY - * OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - * - * THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA - * HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - */ - -#ifndef CYPHER_PARSE_AGG_H -#define CYPHER_PARSE_AGG_H - -#include "parser/parse_node.h" - -extern void parse_check_aggregates(ParseState *pstate, Query *qry); - -#endif /* CYPHER_PARSE_AGG_H */ From 58b0a8e9d16561ec734787eae1eb854a07c718bf Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Mon, 25 Aug 2025 21:18:37 -0700 Subject: [PATCH 42/56] Support Vertex and Edge Variables in CREATE --- postgraph--0.1.0.sql | 1 + regress/sql/cypher_create.sql | 17 +++ src/backend/executor/cypher_create.c | 198 +++++++++++++++------------ src/backend/parser/cypher_clause.c | 101 ++++++++++---- src/backend/utils/adt/gtype.c | 192 +++----------------------- src/backend/utils/adt/gtype_parser.c | 93 ------------- src/include/utils/gtype.h | 1 - src/include/utils/gtype_parser.h | 1 - 8 files changed, 227 insertions(+), 377 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 1e092745..0ec26c68 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -3531,6 +3531,7 @@ CREATE FUNCTION gtype_in_operator(gtype, gtype) RETURNS bool LANGUAGE c IMMUTABLE +RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 5b3de4b0..e8cdc578 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -52,6 +52,23 @@ CREATE (a)-[]->(); CREATE (a)-[]->() RETURN a; +CREATE (a)-[]->(b) RETURN a; + +CREATE (a {msg: 'hello vertex out'}) RETURN a; + +CREATE (a {msg: 'hello vertex out'}) RETURN a.msg; + +CREATE (a {msg: 'hello vertex out'})-[]->(b) RETURN a; + + +CREATE (a)-[e]->(b) RETURN e; + +CREATE (a)-[e {msg: 'hello edge'}]->(b) RETURN e; + + +CREATE ()-[{msg: 'hello edge'}]->(); +SELECT * FROM cypher_create._adj__ag_label_vertex; + CREATE ()-[:elabel]->(); MATCH ()-[]->() RETURN 1; diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 194e161e..7359a05f 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -133,13 +133,13 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag ExecGetResultType(node->ss.ps.lefttree), &TTSOpsHeapTuple); - /* + if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) { TupleDesc tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } - */ + if (list_length(css->pattern) != 1) ereport(ERROR, (errmsg_internal("executor create found a multi pattern"))); @@ -203,111 +203,141 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) EState *estate = css->css.ss.ps.state; ExprContext *econtext = css->css.ss.ps.ps_ExprContext; + while (true) { + RollbackCmdId(estate); + TupleTableSlot *return_slot = ExecProcNode(csnode->ss.ps.lefttree); + AdvanceCmdId(estate); + + if (TupIsNull(return_slot)) + return return_slot; + // if ((return_slot->tts_flags = return_slot & TTS_FLAG_EMPTY)) + // return NULL; + econtext->ecxt_scantuple = + csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; + TupleTableSlot *slot = econtext->ecxt_scantuple;//csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; + //slot->tts_ops->materialize(slot); + + if (list_length(css->pattern) != 1) + ereport(ERROR, (errmsg_internal("executor create found a multi pattern"))); + + cypher_create_path *path = linitial(css->pattern); + + ListCell *lc; + int i = 1; + foreach(lc, path->target_nodes) { + cypher_target_node *node = (cypher_target_node *)lfirst(lc); + bool isNull; + if(i % 2 == 1) + css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + else { + css->edge_ids[0][(i-1)/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + + } + i++; + } - RollbackCmdId(estate); - TupleTableSlot *slot = ExecProcNode(csnode->ss.ps.lefttree); - AdvanceCmdId(estate); + for (int i = 0; i < list_length(path->target_nodes)/2 + 1; i++) { + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); + + ResultRelInfo *resultRelInfo = node->resultRelInfo; + TupleTableSlot *elemTupleSlot = node->elemTupleSlot; - slot = csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - slot->tts_ops->materialize(slot); - if (list_length(css->pattern) != 1) - ereport(ERROR, (errmsg_internal("executor create found a multi pattern"))); + ResultRelInfo **old_estate_es_result_relations_info = NULL; - cypher_create_path *path = linitial(css->pattern); + /* save the old result relation info */ + old_estate_es_result_relations_info = estate->es_result_relations; - ListCell *lc; - int i = 1; - foreach(lc, path->target_nodes) { - cypher_target_node *node = (cypher_target_node *)lfirst(lc); - bool isNull; - if(i % 2 == 1) - css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - else { - css->edge_ids[0][(i-1)/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - - } - i++; - - } + estate->es_result_relations = &resultRelInfo; - for (int i = 0; i < list_length(path->target_nodes)/2 + 1; i++) { - cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); - - ResultRelInfo *resultRelInfo = node->resultRelInfo; - TupleTableSlot *elemTupleSlot = node->elemTupleSlot; + ExecClearTuple(elemTupleSlot); + + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; + if (node->id_attr_num != InvalidAttrNumber) { + slot->tts_values[node->id_attr_num - 1] = GRAPHID_GET_DATUM(css->vertex_ids[0][i]); + slot->tts_isnull[node->id_attr_num - 1] = false; + } - ResultRelInfo **old_estate_es_result_relations_info = NULL; + // get the properties for this vertex + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + } else { + elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->prop_attr_num - 1]; + } - /* save the old result relation info */ - old_estate_es_result_relations_info = estate->es_result_relations; + if (node->tuple_position != InvalidAttrNumber) { + slot->tts_values[node->tuple_position - 1] = VERTEX_GET_DATUM(create_vertex( + slot->tts_values[node->id_attr_num - 1], + css->graph_oid, + elemTupleSlot->tts_isnull[1] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[1]))); + slot->tts_isnull[node->tuple_position - 1] = false; + } - estate->es_result_relations = &resultRelInfo; + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - ExecClearTuple(elemTupleSlot); - // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; - elemTupleSlot->tts_isnull[0] = false; - if (node->id_attr_num != InvalidAttrNumber) { - slot->tts_values[node->id_attr_num - 1] = GRAPHID_GET_DATUM(css->vertex_ids[0][i]); - slot->tts_isnull[node->id_attr_num - 1] = false; - } + if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { - // get the properties for this vertex - if (node->prop_attr_num == InvalidAttrNumber) { - elemTupleSlot->tts_values[1] = NULL; - elemTupleSlot->tts_isnull[1] = true; - } else { - elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; - elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->prop_attr_num - 1]; - } + + resultRelInfo = node->adj_resultRelInfo; + elemTupleSlot = node->adj_elemTupleSlot; - if (node->tuple_position != InvalidAttrNumber) { - create_vertex( - GRAPHID_GET_DATUM(slot->tts_values[node->id_attr_num - 1]), - css->graph_oid, - node->prop_attr_num == InvalidAttrNumber? NULL: DATUM_GET_GTYPE_P(slot->tts_values[node->prop_attr_num - 1])); - } + estate->es_result_relations = &resultRelInfo; - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + ExecClearTuple(elemTupleSlot); + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, (i * 2) + 1); + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; + + elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; + elemTupleSlot->tts_isnull[1] = false; + + elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; + elemTupleSlot->tts_isnull[2] = false; - if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { - resultRelInfo = node->adj_resultRelInfo; - elemTupleSlot = node->adj_elemTupleSlot; - estate->es_result_relations = &resultRelInfo; + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[3] = NULL; + elemTupleSlot->tts_isnull[3] = true; + } else { + //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; + elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; + } - ExecClearTuple(elemTupleSlot); + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; - elemTupleSlot->tts_isnull[0] = false; - - elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; - elemTupleSlot->tts_isnull[1] = false; - - elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; - elemTupleSlot->tts_isnull[2] = false; + if (node->tuple_position != InvalidAttrNumber) { + slot->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(create_edge( + css->edge_ids[0][i], + css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1], + css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i], + css->graph_oid, + elemTupleSlot->tts_isnull[3] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[3]))); + slot->tts_isnull[node->tuple_position - 1] = false; + } - if (node->prop_attr_num == InvalidAttrNumber) { - elemTupleSlot->tts_values[3] = NULL; - elemTupleSlot->tts_isnull[3] = true; - } else { - //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; - elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; } + /* restore the old result relation info */ + estate->es_result_relations = old_estate_es_result_relations_info; + } - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + // if(CYPHER_CLAUSE_IS_TERMINAL(css->flags)) + // return NULL; + if(!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) { + econtext->ecxt_scantuple = ExecProject(csnode->ss.ps.lefttree->ps_ProjInfo); + return ExecProject(csnode->ss.ps.ps_ProjInfo); + // return return_slot; } - /* restore the old result relation info */ - estate->es_result_relations = old_estate_es_result_relations_info; - } - return NULL; + } } static void end_cypher_create(CustomScanState *node) diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 746dbc93..9564c68d 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -241,16 +241,22 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi static Node * make_graphid_placeholder(cypher_parsestate *cpstate) { - Const *c = makeConst(GRAPHIDOID, -1, InvalidOid, sizeof(graphid), GRAPHID_GET_DATUM(0), false, true); - c->location = -1; - return (Node *)c; + return makeConst(GRAPHIDOID, -1, InvalidOid, sizeof(graphid), GRAPHID_GET_DATUM(0), false, true); } static Node * -make_int_placeholder(cypher_parsestate *cpstate) { - Const *c = makeConst(GTYPEOID, -1, InvalidOid, -1, integer_to_gtype(0), false, false); - c->location = -1; - return (Node *)c; +make_gtype_placeholder(cypher_parsestate *cpstate) { + return makeConst(GTYPEOID, -1, InvalidOid, -1, integer_to_gtype(0), false, false); +} + +static Node * +make_edge_placeholder(cypher_parsestate *cpstate) { + return makeConst(EDGEOID, -1, InvalidOid, -1, create_edge(0, 0, 0, cpstate->graph_oid, NULL), false, false); +} + +static Node * +make_vertex_placeholder(cypher_parsestate *cpstate) { + return makeConst(VERTEXOID, -1, InvalidOid, -1, create_vertex(0, cpstate->graph_oid, NULL), false, false); } /* @@ -266,6 +272,8 @@ transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query) ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL), lateral, true); addNSItemToQuery(pstate, pnsi, true, false, true); + + //query->targetList = list_concat(query->targetList, expandNSItemAttrs(pstate, pnsi, 0, -1)); } static Expr *add_volatile_wrapper(Expr *node) { @@ -331,7 +339,7 @@ process_create_vertex( if (node->props) { query->targetList = lappend(query->targetList, makeTargetEntry( - transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET), + add_volatile_wrapper(transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), target->prop_attr_num = pstate->p_next_resno++, make_property_alias(node->name), false)); @@ -339,7 +347,7 @@ process_create_vertex( target->prop_attr_num = InvalidAttrNumber; /*query->targetList = lappend(query->targetList, makeTargetEntry( - make_int_placeholder(cpstate), + make_gtype_placeholder(cpstate), target->prop_attr_num = pstate->p_next_resno++, make_property_alias(node->name), false));*/ @@ -347,7 +355,7 @@ process_create_vertex( query->targetList = lappend(query->targetList, makeTargetEntry( - make_int_placeholder(cpstate), + make_vertex_placeholder(cpstate), target->tuple_position = pstate->p_next_resno++, node->name, false)); @@ -371,8 +379,6 @@ process_create_vertex( } - - label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); @@ -397,21 +403,63 @@ process_create_edge( else edge->label = AG_DEFAULT_LABEL_EDGE; - if (edge->name) - ereport(ERROR, (errmsg_internal("edges in CREATE cannot have variable names"))); - else - edge->name = get_next_default_alias(cpstate); - if (edge->props) + if (edge->name) { + // /ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); + + if (colNameToVar(cpstate, edge->name, false, -1)) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CREATE edge variable %s already exists", edge->name))); + + target->variable_name = edge->name; query->targetList = lappend(query->targetList, makeTargetEntry( - (Expr *)add_volatile_wrapper( - transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), - target->prop_attr_num = pstate->p_next_resno++, - make_property_alias(edge->name), + make_graphid_placeholder(cpstate), + target->id_attr_num = pstate->p_next_resno++, + make_id_alias(edge->name), false)); - else - target->prop_attr_num = InvalidAttrNumber; + + if (edge->props) { + query->targetList = lappend(query->targetList, + makeTargetEntry( + transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET), + target->prop_attr_num = pstate->p_next_resno++, + make_property_alias(edge->name), + false)); + } else { + target->prop_attr_num = InvalidAttrNumber; + /*query->targetList = lappend(query->targetList, + makeTargetEntry( + make_gtype_placeholder(cpstate), + target->prop_attr_num = pstate->p_next_resno++, + make_property_alias(edge->name), + false));*/ + } + + query->targetList = lappend(query->targetList, + makeTargetEntry( + make_edge_placeholder(cpstate), + target->tuple_position = pstate->p_next_resno++, + edge->name, + false)); + + } else { + edge->name = get_next_default_alias(cpstate); + target->id_attr_num = InvalidAttrNumber; + target->tuple_position = InvalidAttrNumber; + + if (edge->props) { + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)add_volatile_wrapper( + transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), + target->prop_attr_num = pstate->p_next_resno++, + make_property_alias(edge->name), + false)); + } else { + target->prop_attr_num = InvalidAttrNumber; + } + } target->dir = edge->dir; if (edge->dir == CYPHER_REL_DIR_NONE) @@ -448,6 +496,9 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE; target_nodes->graph_oid = cpstate->graph_oid; + if (!clause->next) + target_nodes->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; + ListCell *lc; foreach (lc, self->pattern) { cypher_path *path = lfirst(lc); @@ -483,6 +534,10 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + if (clause->next) + return query; + + { cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); Query *topquery = makeNode(Query); diff --git a/src/backend/utils/adt/gtype.c b/src/backend/utils/adt/gtype.c index d3040a8f..f66c8140 100644 --- a/src/backend/utils/adt/gtype.c +++ b/src/backend/utils/adt/gtype.c @@ -610,13 +610,13 @@ static void gtype_in_scalar(void *pstate, char *token, gtype_token_type tokentyp tokentype = GTYPE_TOKEN_TIMESTAMP; else if (len == 11 && pg_strcasecmp(annotation, "timestamptz") == 0) tokentype = GTYPE_TOKEN_TIMESTAMPTZ; - else if (len == 4 && pg_strcasecmp(annotation, "date") == 0) + else if (len == 4 && pg_strcasecmp(annotation, "date") == 0) tokentype = GTYPE_TOKEN_DATE; else if (len == 4 && pg_strcasecmp(annotation, "time") == 0) tokentype = GTYPE_TOKEN_TIME; else if (len == 6 && pg_strcasecmp(annotation, "timetz") == 0) tokentype = GTYPE_TOKEN_TIMETZ; - else if (len == 8 && pg_strcasecmp(annotation, "interval") == 0) + else if (len == 8 && pg_strcasecmp(annotation, "interval") == 0) tokentype = GTYPE_TOKEN_INTERVAL; else if (len == 4 && pg_strcasecmp(annotation, "inet") == 0) tokentype = GTYPE_TOKEN_INET; @@ -719,7 +719,7 @@ static void gtype_in_scalar(void *pstate, char *token, gtype_token_type tokentyp v.type = AGTV_INET; i = DatumGetInetPP(DirectFunctionCall1(inet_in, CStringGetDatum(token))); - memcpy(&v.val.inet, i, sizeof(char) * 22); + memcpy(&v.val.inet, i, sizeof(char) * 22); break; } case GTYPE_TOKEN_CIDR: @@ -1831,27 +1831,16 @@ Datum gtype_access_slice(PG_FUNCTION_ARGS) } PG_FUNCTION_INFO_V1(gtype_in_operator); -/* - * Execute function for IN operator - */ -Datum gtype_in_operator(PG_FUNCTION_ARGS) -{ - gtype *agt_array, *agt_item; - gtype_iterator *it_array, *it_item; +Datum gtype_in_operator(PG_FUNCTION_ARGS) { gtype_value agtv_item, agtv_elem; - uint32 array_size = 0; - bool result = false; - uint32 i = 0; - - if (PG_ARGISNULL(1)) - PG_RETURN_NULL(); - - agt_array = AG_GET_ARG_GTYPE_P(1); - if (!AGT_ROOT_IS_ARRAY(agt_array)) + + + gtype *arr = AG_GET_ARG_GTYPE_P(1); + if (!AGT_ROOT_IS_ARRAY(arr)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("object of IN must be a list"))); - it_array = gtype_iterator_init(&agt_array->root); + gtype_iterator *it_array = gtype_iterator_init(&arr->root); gtype_iterator_next(&it_array, &agtv_elem, false); if (agtv_elem.type == AGTV_ARRAY && agtv_elem.val.array.raw_scalar) { gtype_iterator_next(&it_array, &agtv_elem, false); @@ -1862,15 +1851,8 @@ Datum gtype_in_operator(PG_FUNCTION_ARGS) errmsg("object of IN must be a list"))); } - array_size = AGT_ROOT_COUNT(agt_array); - - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - agt_item = AG_GET_ARG_GTYPE_P(0); - - it_item = gtype_iterator_init(&agt_item->root); - + gtype *agt_item = AG_GET_ARG_GTYPE_P(0); + gtype_iterator *it_item = gtype_iterator_init(&agt_item->root); gtype_iterator_next(&it_item, &agtv_item, false); if (agtv_item.type == AGTV_ARRAY && agtv_item.val.array.raw_scalar) { gtype_iterator_next(&it_item, &agtv_item, false); @@ -1879,15 +1861,19 @@ Datum gtype_in_operator(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - for (i = 0; i < array_size && !result; i++) + for (int i = 0; i < AGT_ROOT_COUNT(arr); i++) { + bool result = false; gtype_iterator_next(&it_array, &agtv_elem, true); if (!IS_A_GTYPE_SCALAR(&agtv_item) && !IS_A_GTYPE_SCALAR(&agtv_elem)) result = (compare_gtype_containers_orderability( &agt_item->root, agtv_elem.val.binary.data) == 0); else if (IS_A_GTYPE_SCALAR(&agtv_item) && IS_A_GTYPE_SCALAR(&agtv_elem) && agtv_item.type == agtv_elem.type) result = (compare_gtype_scalar_values(&agtv_item, &agtv_elem) == 0); + + if (result) + PG_RETURN_BOOL(true); } - return result; + PG_RETURN_BOOL(false); } PG_FUNCTION_INFO_V1(gtype_string_match_starts_with); @@ -2117,150 +2103,6 @@ Datum gtype_reverse(PG_FUNCTION_ARGS) } } -/* - * For the given properties, update the property with the key equal - * to var_name with the value defined in new_v. If the remove_property - * flag is set, simply remove the property with the given property - * name instead. - */ -gtype_value *alter_property_value(gtype *properties, char *var_name, - gtype *new_v, bool remove_property) -{ - gtype_iterator *it; - gtype_iterator_token tok = WGT_DONE; - gtype_parse_state *parse_state = NULL; - gtype_value *r; - gtype *prop_gtype; - gtype_value *parsed_gtype_value = NULL; - bool found; - - // if no properties, return NULL - if (properties == NULL) - return NULL; - - // if properties is not an object, throw an error - if (!GTYPE_CONTAINER_IS_OBJECT(&properties->root))//->type != AGTV_OBJECT) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("can only update objects"))); - - r = palloc0(sizeof(gtype_value)); - - prop_gtype = properties; - it = gtype_iterator_init(&prop_gtype->root); - tok = gtype_iterator_next(&it, r, true); - - parsed_gtype_value = push_gtype_value(&parse_state, tok, tok < WGT_BEGIN_ARRAY ? r : NULL); - - /* - * If the new value is NULL, this is equivalent to the remove_property - * flag set to true. - */ - if (new_v == NULL) - remove_property = true; - - found = false; - while (true) { - char *str; - - tok = gtype_iterator_next(&it, r, true); - - if (tok == WGT_DONE || tok == WGT_END_OBJECT) - break; - - str = pnstrdup(r->val.string.val, r->val.string.len); - - /* - * Check the key value, if it is equal to the passed in - * var_name, replace the value for this key with the passed - * in gtype. Otherwise pass the existing value to the - * new properties gtype_value. - */ - if (strcmp(str, var_name)) { - // push the key - parsed_gtype_value = push_gtype_value(&parse_state, tok, tok < WGT_BEGIN_ARRAY ? r : NULL); - - // get the value and push the value - tok = gtype_iterator_next(&it, r, true); - parsed_gtype_value = push_gtype_value(&parse_state, tok, r); - } else { - gtype_value *new_gtype_value_v; - - // if the remove flag is set, don't push the key or any value - if(remove_property) { - // skip the value - tok = gtype_iterator_next(&it, r, true); - continue; - } - - // push the key - parsed_gtype_value = push_gtype_value(&parse_state, tok, tok < WGT_BEGIN_ARRAY ? r : NULL); - - // skip the existing value for the key - tok = gtype_iterator_next(&it, r, true); - - /* - * If the the new gtype is scalar, push the gtype_value to the - * parse state. If the gtype is an object or array convert the - * gtype to a binary gtype_value to pass to the parse_state. - * This will save uncessary deserialization and serialization - * logic from running. - */ - if (GTYPE_CONTAINER_IS_SCALAR(&new_v->root)) { - //get the scalar value and push as the value - new_gtype_value_v = get_ith_gtype_value_from_container(&new_v->root, 0); - - parsed_gtype_value = push_gtype_value(&parse_state, WGT_VALUE, new_gtype_value_v); - } else { - gtype_value *result = palloc(sizeof(gtype_value)); - - result->type = AGTV_BINARY; - result->val.binary.len = GTYPE_CONTAINER_SIZE(&new_v->root); - result->val.binary.data = &new_v->root; - parsed_gtype_value = push_gtype_value(&parse_state, WGT_VALUE, result); - } - - found = true; - } - } - - /* - * If we have not found the property and we aren't trying to remove it, - * add the key/value pair now. - */ - if (!found && !remove_property) { - gtype_value *new_gtype_value_v; - gtype_value *key = string_to_gtype_value(var_name); - - // push the new key - parsed_gtype_value = push_gtype_value(&parse_state, WGT_KEY, key); - - /* - * If the the new gtype is scalar, push the gtype_value to the - * parse state. If the gtype is an object or array convert the - * gtype to a binary gtype_value to pass to the parse_state. - * This will save uncessary deserialization and serialization - * logic from running. - */ - if (GTYPE_CONTAINER_IS_SCALAR(&new_v->root)) { - new_gtype_value_v = get_ith_gtype_value_from_container(&new_v->root, 0); - - // convert the gtype array or object to a binary gtype_value - parsed_gtype_value = push_gtype_value(&parse_state, WGT_VALUE, new_gtype_value_v); - } else { - gtype_value *result = palloc(sizeof(gtype_value)); - - result->type = AGTV_BINARY; - result->val.binary.len = GTYPE_CONTAINER_SIZE(&new_v->root); - result->val.binary.data = &new_v->root; - parsed_gtype_value = push_gtype_value(&parse_state, WGT_VALUE, result); - } - } - - // push the end object token to parse state - parsed_gtype_value = push_gtype_value(&parse_state, WGT_END_OBJECT, NULL); - - return parsed_gtype_value; -} /* * Transfer function for age_sum(gtype, gtype). diff --git a/src/backend/utils/adt/gtype_parser.c b/src/backend/utils/adt/gtype_parser.c index 73cebbc1..2f2b6844 100644 --- a/src/backend/utils/adt/gtype_parser.c +++ b/src/backend/utils/adt/gtype_parser.c @@ -1253,96 +1253,3 @@ static char *extract_mb_char(char *s) { return res; } - -/* - * Encode 'value' of datetime type 'typid' into gtype string in ISO format - * using optionally preallocated buffer 'buf'. - */ -char *gtype_encode_date_time(char *buf, Datum value, Oid typid) { - if (!buf) - buf = palloc(MAXDATELEN + 1); - - switch (typid) - { - case DATEOID: - { - DateADT date; - struct pg_tm tm; - - date = DatumGetDateADT(value); - - /* Same as date_out(), but forcing DateStyle */ - if (DATE_NOT_FINITE(date)) { - EncodeSpecialDate(date, buf); - } else { - j2date(date + POSTGRES_EPOCH_JDATE, &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); - EncodeDateOnly(&tm, USE_XSD_DATES, buf); - } - } - break; - case TIMEOID: - { - TimeADT time = DatumGetTimeADT(value); - struct pg_tm tt, *tm = &tt; - fsec_t fsec; - - /* Same as time_out(), but forcing DateStyle */ - time2tm(time, tm, &fsec); - EncodeTimeOnly(tm, fsec, false, 0, USE_XSD_DATES, buf); - } - break; - case TIMETZOID: - { - TimeTzADT *time = DatumGetTimeTzADTP(value); - struct pg_tm tt, *tm = &tt; - fsec_t fsec; - int tz; - - /* Same as timetz_out(), but forcing DateStyle */ - timetz2tm(time, tm, &fsec, &tz); - EncodeTimeOnly(tm, fsec, true, tz, USE_XSD_DATES, buf); - } - break; - case TIMESTAMPOID: - { - Timestamp timestamp; - struct pg_tm tm; - fsec_t fsec; - - timestamp = DatumGetTimestamp(value); - /* Same as timestamp_out(), but forcing DateStyle */ - if (TIMESTAMP_NOT_FINITE(timestamp)) - EncodeSpecialTimestamp(timestamp, buf); - else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) - EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); - else - ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - } - break; - case TIMESTAMPTZOID: - { - TimestampTz timestamp; - struct pg_tm tm; - int tz; - fsec_t fsec; - const char *tzn = NULL; - - timestamp = DatumGetTimestampTz(value); - /* Same as timestamptz_out(), but forcing DateStyle */ - if (TIMESTAMP_NOT_FINITE(timestamp)) - EncodeSpecialTimestamp(timestamp, buf); - else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) - EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); - else - ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - } - break; - default: - elog(ERROR, "unknown gtype value datetime type oid %d", typid); - return NULL; - } - - return buf; -} diff --git a/src/include/utils/gtype.h b/src/include/utils/gtype.h index b53ec1f6..fb1ac3ba 100644 --- a/src/include/utils/gtype.h +++ b/src/include/utils/gtype.h @@ -656,7 +656,6 @@ void uniqueify_gtype_object(gtype_value *object); char *gtype_value_type_to_string(enum gtype_value_type type); bool is_decimal_needed(char *numstr); int compare_gtype_scalar_values(gtype_value *a, gtype_value *b); -gtype_value *alter_property_value(gtype *properties, char *var_name, gtype *new_v, bool remove_property); bool is_gtype_null(gtype *agt_arg); gtype_value *string_to_gtype_value(char *s); void add_gtype(Datum val, bool is_null, gtype_in_state *result, Oid val_type, bool key_scalar); diff --git a/src/include/utils/gtype_parser.h b/src/include/utils/gtype_parser.h index f1798770..69834b3b 100644 --- a/src/include/utils/gtype_parser.h +++ b/src/include/utils/gtype_parser.h @@ -152,6 +152,5 @@ gtype_lex_context *make_gtype_lex_context_cstring_len(char *str, int len, */ extern bool is_valid_gtype_number(const char *str, int len); -extern char *gtype_encode_date_time(char *buf, Datum value, Oid typid); #endif From 6a7f88c006dae2ba0941e096e689b5256318e65f Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 20 Sep 2025 07:23:06 -0700 Subject: [PATCH 43/56] Fix Vertex creation of to vertices in MAtch --- postgraph--0.1.0.sql | 12 +++++++++- src/backend/parser/cypher_clause.c | 3 --- src/backend/utils/adt/vertex.c | 2 +- src/backend/utils/edge_searching.c | 36 ++++++++++++++++++++++++++++-- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 0ec26c68..32a233ba 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -442,7 +442,7 @@ AS 'MODULE_PATHNAME'; CREATE FUNCTION retrieve_vertex(graph_oid gtype, id graphid) -RETURNS TABLE (properties gtype) +RETURNS gtype RETURNS NULL ON NULL INPUT STABLE PARALLEL SAFE @@ -450,6 +450,16 @@ LANGUAGE C COST 5000 AS 'MODULE_PATHNAME'; + +--CREATE FUNCTION retrieve_vertex(graph_oid gtype, id graphid) +--RETURNS TABLE (properties gtype) +--RETURNS NULL ON NULL INPUT +--STABLE +--PARALLEL SAFE +--LANGUAGE C +--COST 5000 +--AS 'MODULE_PATHNAME'; + -- -- There are times when the optimizer might eliminate -- functions we need. Wrap the function with this to diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 9564c68d..2ea46930 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1099,8 +1099,6 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n Var *var; if (var = colNameToVar(cpstate, make_id_alias(node->name), false, -1)) { -// ereport(WARNING,(errcode(ERRCODE_UNDEFINED_SCHEMA), -// errmsg("here"))); node->declared_in_previous_clause = true; *quals = lappend(*quals, @@ -1111,7 +1109,6 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n return refnameNamespaceItem(cpstate, NULL, PREV_CYPHER_CLAUSE_ALIAS, -1, NULL); } - int sublevels_up; ParseNamespaceItem *pnsi = refnameNamespaceItem(cpstate, NULL, node->name, -1, NULL); if (pnsi) { node->in_join_tree = false; diff --git a/src/backend/utils/adt/vertex.c b/src/backend/utils/adt/vertex.c index 0c24ebea..35cef948 100644 --- a/src/backend/utils/adt/vertex.c +++ b/src/backend/utils/adt/vertex.c @@ -127,7 +127,7 @@ create_vertex(graphid id, Oid graph_oid, gtype *properties) { append_to_buffer(&buffer, (char *)&graph_oid, sizeof(Oid)); // properties - if (properties == NULL) { + if (!properties) { gtype_in_state result; memset(&result, 0, sizeof(gtype_in_state)); diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index ca6d0259..08a775cc 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -70,7 +70,7 @@ static Datum get_vertex(Oid graph_oid, int64 graphid, bool *isnull) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); Datum properties = heap_getattr(tuple, 2, RelationGetDescr(rel), isnull); - + //*isnull = false; table_endscan(scan_desc); table_close(rel, ShareLock); @@ -79,6 +79,38 @@ static Datum get_vertex(Oid graph_oid, int64 graphid, bool *isnull) +PG_FUNCTION_INFO_V1(retrieve_vertex); +Datum retrieve_vertex(PG_FUNCTION_ARGS) { + Oid graph_oid = GT_ARG_TO_INT4_DATUM(0); + int64 graphid = AG_GETARG_GRAPHID(1); + bool isnull; + + label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, (graphid >> ENTRY_ID_BITS)); + + ScanKeyData scan_keys[1]; + ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_OIDEQ, Int64GetDatum(graphid)); + + Relation rel = table_open(lcd->relation, ShareLock); + + TableScanDesc scan_desc = table_beginscan(rel, GetActiveSnapshot(), 1, scan_keys); + + HeapTuple tuple; + if (!HeapTupleIsValid(tuple = heap_getnext(scan_desc, ForwardScanDirection))) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); + + Datum properties = heap_getattr(tuple, 2, RelationGetDescr(rel), &isnull); + + table_endscan(scan_desc); + table_close(rel, ShareLock); + + if (isnull) + PG_RETURN_NULL(); + + AG_RETURN_GTYPE_P(properties); + +} + +/* PG_FUNCTION_INFO_V1(retrieve_vertex); Datum retrieve_vertex(PG_FUNCTION_ARGS) { @@ -100,7 +132,7 @@ Datum retrieve_vertex(PG_FUNCTION_ARGS) { SRF_RETURN_DONE(funcctx); } - +*/ typedef struct edge_search_cxt { From ca9b8e182cb6a49fe671cade854bffc53186351b Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 20 Sep 2025 10:30:49 -0500 Subject: [PATCH 44/56] Support an upper limit --- regress/sql/cypher_create.sql | 3 + src/backend/parser/cypher_clause.c | 2 +- src/backend/utils/edge_searching.c | 90 ++++++------------------------ 3 files changed, 21 insertions(+), 74 deletions(-) diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index e8cdc578..a4db5e56 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -159,6 +159,9 @@ MATCH (a) RETURN a; MATCH ()-[*1]->() RETURN 1; MATCH ()-[]->() RETURN 1; MATCH ()-[*2]->() RETURN 1; + +MATCH ()-[*1..2]->() RETURN 1; + -- -- Clean up -- diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 2ea46930..37c48e01 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1252,7 +1252,7 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela make_int_const(cpstate->graph_oid, -1), scanNSItemForColumn(cpstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1), ((A_Indices *)edge->varlen)->lidx, - make_null_const(-1)), + ((A_Indices *)edge->varlen)->uidx), COERCE_EXPLICIT_CALL, -1), edge->name, list_make3(makeString("edges"), makeString("endid"), makeString("hashset"))); diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 08a775cc..35d5c372 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -51,34 +51,8 @@ #include "utils/ag_cache.h" #include "utils/hashset.h" - #include "ltree.h" -static Datum get_vertex(Oid graph_oid, int64 graphid, bool *isnull) -{ - label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, (graphid >> ENTRY_ID_BITS)); - - ScanKeyData scan_keys[1]; - ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_OIDEQ, Int64GetDatum(graphid)); - - Relation rel = table_open(lcd->relation, ShareLock); - - TableScanDesc scan_desc = table_beginscan(rel, GetActiveSnapshot(), 1, scan_keys); - - HeapTuple tuple; - if (!HeapTupleIsValid(tuple = heap_getnext(scan_desc, ForwardScanDirection))) - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); - - Datum properties = heap_getattr(tuple, 2, RelationGetDescr(rel), isnull); - //*isnull = false; - table_endscan(scan_desc); - table_close(rel, ShareLock); - - return properties; -} - - - PG_FUNCTION_INFO_V1(retrieve_vertex); Datum retrieve_vertex(PG_FUNCTION_ARGS) { Oid graph_oid = GT_ARG_TO_INT4_DATUM(0); @@ -110,30 +84,6 @@ Datum retrieve_vertex(PG_FUNCTION_ARGS) { } -/* -PG_FUNCTION_INFO_V1(retrieve_vertex); -Datum retrieve_vertex(PG_FUNCTION_ARGS) { - - FuncCallContext *funcctx; - if (SRF_IS_FIRSTCALL()) { - funcctx = SRF_FIRSTCALL_INIT(); - - TupleDesc tupdesc = CreateTemplateTupleDesc(1); - TupleDescInitEntry(tupdesc, 1, "properties", GTYPEOID, -1, 0); - funcctx->tuple_desc = BlessTupleDesc(tupdesc); - - funcctx = SRF_PERCALL_SETUP(); - - Datum values[1]; - bool nulls[1]; - values[0] = get_vertex(GT_ARG_TO_INT4_DATUM(0), AG_GETARG_GRAPHID(1), &nulls[0]); - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); - } - - SRF_RETURN_DONE(funcctx); -} -*/ - typedef struct edge_search_cxt { ScanKey scanKey; @@ -145,8 +95,6 @@ typedef struct edge_search_cxt PG_FUNCTION_INFO_V1(edge_search); Datum edge_search(PG_FUNCTION_ARGS) { - //ereport(WARNING, (errmsg("edge_search called with graphid %lu", AG_GETARG_GRAPHID(1)))); - FuncCallContext *funcctx; if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -214,23 +162,6 @@ Datum edge_search(PG_FUNCTION_ARGS) //ereport(WARNING, (errmsg("edge_search sending graphid %lu", DATUM_GET_GRAPHID(values[2])))); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); } -/* -// Node for the linked list to handle collisions (chaining) -typedef struct HashSetNode { - graphid key; - struct HashSetNode* next; -} HashSetNode; - -// The main Hash Set structure -typedef struct HashSetValue { - HashSetNode** buckets; // Array of pointers to HashSetNodes (the buckets) - int capacity; // The total number of buckets - int size; // The current number of elements in the set -} HashSetValue; -*/ - - - typedef struct variable_edge_stack { @@ -251,6 +182,7 @@ typedef struct variable_edge_search_cxt Relation rel; TupleTableSlot *slot; int min; + int max; Oid graph_oid; HashSetValue *hashSet; variable_edge_stack *stack; @@ -351,6 +283,11 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) cxt->graph_oid = GT_ARG_TO_INT4_DATUM(0); cxt->min = GT_ARG_TO_INT4_DATUM(2); + if (PG_ARGISNULL(3)) + cxt->max = -1; + else + cxt->max = GT_ARG_TO_INT4_DATUM(3); + cxt->stack = palloc0(sizeof(variable_edge_stack)); cxt->stack->array = palloc0(sizeof(graphid) * (1024)); cxt->stack->top = 0; @@ -361,8 +298,11 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) cxt->edge_stack->top = 0; cxt->edge_stack->capacity = 1024; + if (cxt->max == -1) + cxt->current_path = palloc(sizeof(graph_stack_count) * (cxt->min + 1)); + else + cxt->current_path = palloc(sizeof(graph_stack_count) * (cxt->max + 1)); - cxt->current_path = palloc(sizeof(graph_stack_count) * (cxt->min + 1)); cxt->current_path_length = 0; cxt->hashSet = createHashSet(1024); @@ -391,7 +331,7 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) insert(cxt->hashSet, edge_id); //ereport(WARNING, errmsg("cxt->hashSet->size %i, %lu", cxt->hashSet->size, id)); - if (cxt->hashSet->size == cxt->min) { + if (cxt->hashSet->size >= cxt->min) { Datum values[3]; bool nulls[3]; values[0] = NULL; @@ -406,10 +346,14 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) values[2] = HASHSET_P_GET_DATUM(hset); nulls[2] = false; - removeElement(cxt->hashSet, edge_id); + if (cxt->max == -1 || cxt->current_path_length < cxt->max) { + scan_and_push_neighbors(cxt, id); + } else { + removeElement(cxt->hashSet, edge_id); + } //ereport(WARNING, errmsg("sending %lu", id)); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); - } else { + } else if (cxt->max == -1 || cxt->current_path_length < cxt->max) { scan_and_push_neighbors(cxt, id); } From 9ca2b389fede9c7180105212eaa253bc9055ceca Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 20 Sep 2025 10:43:27 -0500 Subject: [PATCH 45/56] feat: support Cypher variable-length edge bounds and add more test cases - Add proper handling for variable-length edge bounds in Cypher MATCH clause parsing and SRF construction. - Update add_variable_edge_to_query to pass NULL for unbounded min/max in variable_edge_search. - Fix default values for min/max in variable_edge_search (min=0, max=1024 if not specified). - Add new regression test cases for all Cypher variable-length edge patterns: * MATCH ()-[*..2]->() RETURN 1; * MATCH ()-[*2..]->() RETURN 1; * MATCH ()-[*1..]->() RETURN 1; - Update expected output files for new test cases and improved edge/vertex creation. - Minor code cleanup and comment improvements. --- regress/expected/cypher_create.out | 1418 +++++++++++++++++++++++++--- regress/expected/new_cypher.out | 8 +- regress/sql/cypher_create.sql | 8 + src/backend/parser/cypher_clause.c | 37 +- src/backend/utils/edge_searching.c | 19 +- 5 files changed, 1356 insertions(+), 134 deletions(-) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 8c97ade8..9937234d 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 PostGraphDB + * Copyright (C) 2023-2025 PostGraphDB * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -20,6 +20,7 @@ LOAD 'postgraph'; CREATE GRAPH cypher_create; NOTICE: graph "cypher_create" has been created +WARNING: relcache reference leak: relation "_adj__adj__ag_label_vertex" not closed create_graph -------------- @@ -48,100 +49,67 @@ MATCH () RETURN 1; 1 (1 row) +SELECT '{}'::postgraph.gtype; + gtype +------- + {} +(1 row) + +CREATE ({msg: 'Hello World'}); +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +-- +(0 rows) + +MATCH (a) RETURN a; + a +---------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} +(2 rows) + +SELECT * FROM cypher_create._ag_label_vertex; + id | properties +--------+------------------------ + (1, 1) | + (1, 2) | {"msg": "Hello World"} +(2 rows) + CREATE VLABEL test; NOTICE: VLabel "test" has been created +WARNING: relcache reference leak: relation "_adj__adj_test" not closed create_vlabel --------------- (1 row) -SELECT * FROM postgraph.ag_label; - name | graph | id | kind | relation | vertex_adjlist | label_path -----------------------------+-------+----+------+------------------------------------------+-------------------------------------+------------------------------------------- - _adj__adj__ag_label_vertex | 18790 | 2 | e | 18807 | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18790 | 1 | v | 18793 | 18802 | _ag_label_vertex - _ag_label_edge | 18790 | 3 | e | 18817 | | _ag_label_edge - _adj__adj__ag_label_vertex | 18826 | 2 | e | 18843 | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18826 | 1 | v | 18829 | 18838 | _ag_label_vertex - _ag_label_edge | 18826 | 3 | e | 18853 | | _ag_label_edge - _adj__adj__ag_label_vertex | 18862 | 2 | e | cypher_create._adj__adj__ag_label_vertex | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18862 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex - _ag_label_edge | 18862 | 3 | e | cypher_create._ag_label_edge | | _ag_label_edge - _adj__adj_test | 18862 | 5 | e | cypher_create._adj__adj_test | | _ag_label_edge._adj__adj_test - test | 18862 | 4 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test -(11 rows) - CREATE (:test); WARNING: relcache reference leak: relation "test" not closed -- (0 rows) -SELECT * FROM postgraph.ag_label; - name | graph | id | kind | relation | vertex_adjlist | label_path -----------------------------+-------+----+------+------------------------------------------+-------------------------------------+------------------------------------------- - _adj__adj__ag_label_vertex | 18790 | 2 | e | 18807 | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18790 | 1 | v | 18793 | 18802 | _ag_label_vertex - _ag_label_edge | 18790 | 3 | e | 18817 | | _ag_label_edge - _adj__adj__ag_label_vertex | 18826 | 2 | e | 18843 | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18826 | 1 | v | 18829 | 18838 | _ag_label_vertex - _ag_label_edge | 18826 | 3 | e | 18853 | | _ag_label_edge - _adj__adj__ag_label_vertex | 18862 | 2 | e | cypher_create._adj__adj__ag_label_vertex | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18862 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex - _ag_label_edge | 18862 | 3 | e | cypher_create._ag_label_edge | | _ag_label_edge - _adj__adj_test | 18862 | 5 | e | cypher_create._adj__adj_test | | _ag_label_edge._adj__adj_test - test | 18862 | 4 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test -(11 rows) - -SELECT * FROM cypher_create.test; - id | properties ---------+------------ - (4, 1) | -(1 row) - CREATE (:test2); +WARNING: relcache reference leak: relation "_adj__adj_test2" not closed WARNING: relcache reference leak: relation "test2" not closed -- (0 rows) -SELECT * FROM postgraph.ag_label; - name | graph | id | kind | relation | vertex_adjlist | label_path -----------------------------+-------+----+------+------------------------------------------+-------------------------------------+------------------------------------------- - _adj__adj__ag_label_vertex | 18790 | 2 | e | 18807 | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18790 | 1 | v | 18793 | 18802 | _ag_label_vertex - _ag_label_edge | 18790 | 3 | e | 18817 | | _ag_label_edge - _adj__adj__ag_label_vertex | 18826 | 2 | e | 18843 | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18826 | 1 | v | 18829 | 18838 | _ag_label_vertex - _ag_label_edge | 18826 | 3 | e | 18853 | | _ag_label_edge - _adj__adj__ag_label_vertex | 18862 | 2 | e | cypher_create._adj__adj__ag_label_vertex | | _ag_label_edge._adj__adj__ag_label_vertex - _ag_label_vertex | 18862 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex - _ag_label_edge | 18862 | 3 | e | cypher_create._ag_label_edge | | _ag_label_edge - _adj__adj_test | 18862 | 5 | e | cypher_create._adj__adj_test | | _ag_label_edge._adj__adj_test - test | 18862 | 4 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test - _adj__adj_test2 | 18862 | 7 | e | cypher_create._adj__adj_test2 | | _ag_label_edge._adj__adj_test2 - test2 | 18862 | 6 | v | cypher_create.test2 | cypher_create._adj_test2 | _ag_label_vertex.test2 -(13 rows) - -SELECT * FROM cypher_create.test2; - id | properties ---------+------------ - (6, 1) | -(1 row) - MATCH () RETURN 1; ?column? ---------- 1 1 1 -(3 rows) + 1 +(4 rows) MATCH (a) RETURN a; - a --------------------------------------------------------------- + a +---------------------------------------------------------------------------- {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} -(3 rows) +(4 rows) MATCH (a:test) RETURN a; a @@ -156,71 +124,1227 @@ WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) -CREATE ()-[:elabel]->(); +CREATE (a)-[]->(); WARNING: relcache reference leak: relation "_ag_label_vertex" not closed WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "elabel" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) -EXPLAIN MATCH ()-[]->() RETURN 1; - QUERY PLAN ------------------------------------------------------------------------------------------------------ - Nested Loop (cost=12.50..50471.30 rows=2520000 width=32) - -> Append (cost=0.00..58.80 rows=2520 width=8) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) - -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) - -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=0) -(6 rows) - -EXPLAIN ANALYZE MATCH ()-[]->() RETURN 1; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------ - Nested Loop (cost=12.50..50471.30 rows=2520000 width=32) (actual time=0.057..0.302 rows=2 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.009..0.018 rows=7 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.008..0.010 rows=5 loops=1) - -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.003 rows=1 loops=1) - -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.003 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.039..0.039 rows=0 loops=7) - Planning Time: 0.126 ms - Execution Time: 0.332 ms +CREATE (a)-[]->() RETURN a; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed + a +-------------------------------------------------------- + {"id": 281474976710663, "label": "", "properties": {}} +(1 row) + +CREATE (a)-[]->(b) RETURN a; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed + a +-------------------------------------------------------- + {"id": 281474976710665, "label": "", "properties": {}} +(1 row) + +CREATE (a {msg: 'hello vertex out'}) RETURN a; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed + a +--------------------------------------------------------------------------------- + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} +(1 row) + +CREATE (a {msg: 'hello vertex out'}) RETURN a.msg; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed + msg +-------------------- + "hello vertex out" +(1 row) + +CREATE (a {msg: 'hello vertex out'})-[]->(b) RETURN a; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed + a +--------------------------------------------------------------------------------- + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} +(1 row) + +CREATE (a)-[e]->(b) RETURN e; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed + e +---------------------------------------------------------------------------------------------------------------- + {"id": 844424930131974, "start_id": 281474976710671, "end_id": 281474976710672, "label": "", "properties": {}} +(1 row) + +CREATE (a)-[e {msg: 'hello edge'}]->(b) RETURN e; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed + e +---------------------------------------------------------------------------------------------------------------- + {"id": 844424930131975, "start_id": 281474976710673, "end_id": 281474976710674, "label": "", "properties": {}} +(1 row) + +CREATE ()-[{msg: 'hello edge'}]->(); +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed +-- +(0 rows) + +SELECT * FROM cypher_create._adj__ag_label_vertex; + id | start_id | end_id | properties +--------+----------+---------+----------------------- + (3, 1) | (1, 3) | (1, 4) | + (3, 2) | (1, 5) | (1, 6) | + (3, 3) | (1, 7) | (1, 8) | + (3, 4) | (1, 9) | (1, 10) | + (3, 5) | (1, 13) | (1, 14) | + (3, 6) | (1, 15) | (1, 16) | + (3, 7) | (1, 17) | (1, 18) | + (3, 8) | (1, 19) | (1, 20) | {"msg": "hello edge"} (8 rows) +CREATE ()-[:elabel]->(); +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "elabel" not closed +-- +(0 rows) + MATCH ()-[]->() RETURN 1; ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(9 rows) + +MATCH ()<-[]-() RETURN 1; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(9 rows) + +MATCH ()<-[q]-() RETURN 1; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(9 rows) + +MATCH (a)-[]->() RETURN a; + a +--------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} +(9 rows) + +MATCH (a)<-[]-() RETURN a; + a +-------------------------------------------------------- + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} +(9 rows) + +MATCH ()-[]->(a) RETURN a; + a +-------------------------------------------------------- + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} +(9 rows) + +MATCH ()<-[]-(a) RETURN a; + a +--------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} +(9 rows) + +CREATE ()<-[{msg: 'Hello edge'}]-(); +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed +-- +(0 rows) + +CREATE ()-[{msg: 'Hello edge'}]->(); +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed +-- +(0 rows) + +MATCH ()<-[a]-() RETURN a; + a +----------------------------------------------------------------------------------------------------------------------------------- + {"id": 844424930131969, "start_id": 281474976710659, "end_id": 281474976710660, "label": "", "properties": {}} + {"id": 844424930131970, "start_id": 281474976710661, "end_id": 281474976710662, "label": "", "properties": {}} + {"id": 844424930131971, "start_id": 281474976710663, "end_id": 281474976710664, "label": "", "properties": {}} + {"id": 844424930131972, "start_id": 281474976710665, "end_id": 281474976710666, "label": "", "properties": {}} + {"id": 844424930131973, "start_id": 281474976710669, "end_id": 281474976710670, "label": "", "properties": {}} + {"id": 844424930131974, "start_id": 281474976710671, "end_id": 281474976710672, "label": "", "properties": {}} + {"id": 844424930131975, "start_id": 281474976710673, "end_id": 281474976710674, "label": "", "properties": {}} + {"id": 844424930131976, "start_id": 281474976710675, "end_id": 281474976710676, "label": "", "properties": {"msg": "hello edge"}} + {"id": 2251799813685249, "start_id": 281474976710677, "end_id": 281474976710678, "label": "elabel", "properties": {}} + {"id": 844424930131977, "start_id": 281474976710680, "end_id": 281474976710679, "label": "", "properties": {"msg": "Hello edge"}} + {"id": 844424930131978, "start_id": 281474976710681, "end_id": 281474976710682, "label": "", "properties": {"msg": "Hello edge"}} +(11 rows) + +CYPHER WITH 1 as a +CREATE (); +ERROR: CREATE doesn't work with previous clauses +CREATE () RETURN 1 as a; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed + a +--- + 1 +(1 row) + +CREATE ()-[]->()-[]->(); +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed +-- +(0 rows) + +CREATE ()-[]->()-[]->(); +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "_ag_label_edge" not closed +-- +(0 rows) + +MATCH ()<-[]-()<-[]-() RETURN 1; + ?column? ---------- 1 1 (2 rows) -SELECT * FROM cypher_create._ag_label_vertex; - id | properties ---------+------------ - (1, 1) | - (1, 2) | - (1, 3) | - (1, 4) | - (1, 5) | - (4, 1) | - (6, 1) | -(7 rows) - -SELECT * FROM cypher_create._adj__adj__ag_label_vertex; - id | start_id | end_id | properties ----------------+----------+--------+------------ - (0, 36327104) | (1, 2) | (1, 3) | - (0, 35832480) | (1, 4) | (1, 5) | +MATCH ()-[]->()-[]->() RETURN 1; + ?column? +---------- + 1 + 1 (2 rows) +EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.987..8.443 rows=0 loops=1) + -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.987..8.442 rows=0 loops=1) + Workers Planned: 2 + Workers Launched: 2 + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.122..0.122 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.006 rows=12 loops=3) + -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.009 rows=33 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.009..0.009 rows=0 loops=35) + Filter: (q.id OPERATOR(postgraph.=) endid) + Rows Removed by Filter: 0 + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..25.00 rows=995 width=8) (never executed) + Filter: (_age_default_alias_0.id OPERATOR(postgraph.<>) id) + Planning Time: 0.125 ms + Execution Time: 8.479 ms +(16 rows) + +MATCH (q)-[]->(q)-[]->() RETURN 1; + ?column? +---------- +(0 rows) + +EXPLAIN ANALYZE +MATCH (q) +MATCH (q)-[]->() +RETURN q; +WARNING: here + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.035..0.283 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.002..0.009 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.002..0.005 rows=33 loops=1) + -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) + -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=35) + Planning Time: 0.083 ms + Execution Time: 0.298 ms +(8 rows) + +MATCH (q) +MATCH (q)-[]->() +RETURN q; +WARNING: here + q +--------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} +(15 rows) + +EXPLAIN ANALYZE +MATCH (q) +MATCH ()-[]->(q) +RETURN q; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.348..0.368 rows=15 loops=1) + Merge Cond: (q.id OPERATOR(postgraph.=) _age_default_alias_1.endid) + -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.010..0.018 rows=34 loops=1) + Sort Key: q.id + -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.002..0.007 rows=33 loops=1) + -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.005..0.005 rows=1 loops=1) + -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) + -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.334..0.337 rows=15 loops=1) + -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.333..0.334 rows=15 loops=1) + Sort Key: _age_default_alias_1.endid + Sort Method: quicksort Memory: 25kB + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.045..0.327 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.012 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.008..0.009 rows=0 loops=35) + Planning Time: 0.183 ms + Execution Time: 0.412 ms +(19 rows) + +MATCH (q) +MATCH ()-[]->(q) +RETURN q; + q +-------------------------------------------------------- + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} +(15 rows) + +EXPLAIN ANALYZE +MATCH (q) +MATCH (b)-[]->() +RETURN q; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.036..0.436 rows=525 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.032..0.284 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.011 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) + -> Seq Scan on test b_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Seq Scan on test2 b_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=35) + -> Materialize (cost=0.00..71.40 rows=2520 width=40) (actual time=0.000..0.002 rows=35 loops=15) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.006 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.001..0.003 rows=33 loops=1) + -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) + -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) + Planning Time: 0.125 ms + Execution Time: 0.467 ms +(14 rows) + +MATCH (q) +MATCH (b)-[]->() +RETURN q; + q +--------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} +(525 rows) + +EXPLAIN MATCH (q)-[]->()<-[]-(q) RETURN 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Nested Loop (cost=144.66..723066.56 rows=12537000 width=32) + -> Append (cost=0.00..58.80 rows=2520 width=8) + -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=8) + -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=8) + -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=8) + -> Merge Join (cost=144.66..237.16 rows=4975 width=0) + Merge Cond: (_age_default_alias_0.endid OPERATOR(postgraph.=) _age_default_alias_1.endid) + Join Filter: (_age_default_alias_0.id OPERATOR(postgraph.<>) _age_default_alias_1.id) + -> Sort (cost=72.33..74.83 rows=1000 width=16) + Sort Key: _age_default_alias_0.endid + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=16) + -> Sort (cost=72.33..74.83 rows=1000 width=16) + Sort Key: _age_default_alias_1.endid + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) +(14 rows) + +MATCH (q)-[]->()<-[]-(q) RETURN 1; + ?column? +---------- +(0 rows) + +EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------- + Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.755..7.246 rows=0 loops=1) + Workers Planned: 2 + Workers Launched: 2 + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.093..0.093 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.004 rows=12 loops=3) + -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.000..0.001 rows=1 loops=1) + -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.007 rows=33 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.007..0.007 rows=0 loops=35) + Filter: (q.id OPERATOR(postgraph.=) endid) + Rows Removed by Filter: 0 + Planning Time: 0.093 ms + Execution Time: 7.267 ms +(13 rows) + +MATCH (q)<-[]-(q) RETURN 1; + ?column? +---------- +(0 rows) + +EXPLAIN MATCH ()-[]->()<-[]-() RETURN 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) + Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) + Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) + Sort Key: _age_default_alias_1.endid + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) + -> Append (cost=0.00..58.80 rows=2520 width=8) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) + -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) + -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) + Sort Key: _age_default_alias_3.endid + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) + -> Append (cost=0.00..58.80 rows=2520 width=8) + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) + -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) + -> Seq Scan on test2 _age_default_alias_2_3 (cost=0.00..22.00 rows=1200 width=8) + -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) +(20 rows) + +EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.594..0.595 rows=0 loops=1) + Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) + Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) + Rows Removed by Join Filter: 15 + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.288..0.289 rows=15 loops=1) + Sort Key: _age_default_alias_1.endid + Sort Method: quicksort Memory: 25kB + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.038..0.284 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=33 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=35) + -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.296..0.299 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.295..0.296 rows=15 loops=1) + Sort Key: _age_default_alias_3.endid + Sort Method: quicksort Memory: 25kB + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.031..0.289 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.010 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.006 rows=33 loops=1) + -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Seq Scan on test2 _age_default_alias_2_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=35) + Planning Time: 0.141 ms + Execution Time: 0.632 ms +(25 rows) + +MATCH ()-[]->()<-[]-() RETURN 1; + ?column? +---------- +(0 rows) + +EXPLAIN MATCH (:test)-[]->()<-[]-(:test) RETURN 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------- + Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) + Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) + Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) + Sort Key: _age_default_alias_1.endid + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) + -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) + -> Materialize (cost=165712.12..171712.12 rows=1200000 width=16) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) + Sort Key: _age_default_alias_3.endid + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) + -> Seq Scan on test _age_default_alias_2 (cost=0.00..22.00 rows=1200 width=8) + -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) +(14 rows) + +EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------- + Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.032..0.032 rows=0 loops=1) + Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) + Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.032..0.032 rows=0 loops=1) + Sort Key: _age_default_alias_1.endid + Sort Method: quicksort Memory: 25kB + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.030..0.031 rows=0 loops=1) + -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.017..0.017 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.012..0.012 rows=0 loops=1) + -> Materialize (cost=165712.12..171712.12 rows=1200000 width=16) (never executed) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (never executed) + Sort Key: _age_default_alias_3.endid + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (never executed) + -> Seq Scan on test _age_default_alias_2 (cost=0.00..22.00 rows=1200 width=8) (never executed) + -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (never executed) + Planning Time: 0.094 ms + Execution Time: 0.055 ms +(17 rows) + +MATCH (:test)-[]->()<-[]-(:test) RETURN 1; + ?column? +---------- +(0 rows) + +MATCH ()-[*1]->()-[]->() RETURN 1; + ?column? +---------- + 1 + 1 +(2 rows) + +EXPLAIN ANALYZE MATCH ()-[*1]->()-[]->() RETURN 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.353..0.449 rows=2 loops=1) + -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.035..0.336 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.011 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.009..0.009 rows=0 loops=35) + -> Function Scan on edge_search _age_default_alias_2 (cost=12.50..25.00 rows=500 width=8) (actual time=0.007..0.007 rows=0 loops=15) + Filter: (_age_default_alias_1.hashset OPERATOR(postgraph.@>) id) + Planning Time: 0.081 ms + Execution Time: 0.472 ms +(11 rows) + SELECT * FROM cypher_create._adj__ag_label_vertex; - id | start_id | end_id | properties ----------------+----------+--------+------------ - (0, 36327104) | (1, 2) | (1, 3) | - (0, 35832480) | (1, 4) | (1, 5) | + id | start_id | end_id | properties +---------+----------+---------+----------------------- + (3, 1) | (1, 3) | (1, 4) | + (3, 2) | (1, 5) | (1, 6) | + (3, 3) | (1, 7) | (1, 8) | + (3, 4) | (1, 9) | (1, 10) | + (3, 5) | (1, 13) | (1, 14) | + (3, 6) | (1, 15) | (1, 16) | + (3, 7) | (1, 17) | (1, 18) | + (3, 8) | (1, 19) | (1, 20) | {"msg": "hello edge"} + (8, 1) | (1, 21) | (1, 22) | + (3, 9) | (1, 24) | (1, 23) | {"msg": "Hello edge"} + (3, 10) | (1, 25) | (1, 26) | {"msg": "Hello edge"} + (3, 11) | (1, 28) | (1, 29) | + (3, 12) | (1, 29) | (1, 30) | + (3, 13) | (1, 31) | (1, 32) | + (3, 14) | (1, 32) | (1, 33) | +(15 rows) + +MATCH (a) RETURN a; + a +--------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"msg": "Hello World"}} + {"id": 281474976710659, "label": "", "properties": {}} + {"id": 281474976710660, "label": "", "properties": {}} + {"id": 281474976710661, "label": "", "properties": {}} + {"id": 281474976710662, "label": "", "properties": {}} + {"id": 281474976710663, "label": "", "properties": {}} + {"id": 281474976710664, "label": "", "properties": {}} + {"id": 281474976710665, "label": "", "properties": {}} + {"id": 281474976710666, "label": "", "properties": {}} + {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710668, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} + {"id": 281474976710670, "label": "", "properties": {}} + {"id": 281474976710671, "label": "", "properties": {}} + {"id": 281474976710672, "label": "", "properties": {}} + {"id": 281474976710673, "label": "", "properties": {}} + {"id": 281474976710674, "label": "", "properties": {}} + {"id": 281474976710675, "label": "", "properties": {}} + {"id": 281474976710676, "label": "", "properties": {}} + {"id": 281474976710677, "label": "", "properties": {}} + {"id": 281474976710678, "label": "", "properties": {}} + {"id": 281474976710679, "label": "", "properties": {}} + {"id": 281474976710680, "label": "", "properties": {}} + {"id": 281474976710681, "label": "", "properties": {}} + {"id": 281474976710682, "label": "", "properties": {}} + {"id": 281474976710683, "label": "", "properties": {}} + {"id": 281474976710684, "label": "", "properties": {}} + {"id": 281474976710685, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} + {"id": 1125899906842625, "label": "test", "properties": {}} + {"id": 1688849860263937, "label": "test2", "properties": {}} +(35 rows) + +--SELECT * FROM cypher_create.idx_hash__adj__ag_label_vertex; +MATCH ()-[*1]->() RETURN 1; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(15 rows) + +MATCH ()-[]->() RETURN 1; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(15 rows) + +MATCH ()-[*2]->() RETURN 1; +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x4048bfb0, chunk 0x4048c248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x4048bfb0, chunk 0x4048c248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x4048bfb0, chunk 0x4048c248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x4048bfb0, chunk 0x4048c248 + ?column? +---------- + 1 + 1 (2 rows) -MATCH () RETURN 1; +MATCH ()-[*1..2]->() RETURN 1; ?column? ---------- 1 @@ -230,13 +1354,69 @@ MATCH () RETURN 1; 1 1 1 -(7 rows) + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(17 rows) + +MATCH ()-[*..2]->() RETURN 1; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(17 rows) + +MATCH ()-[*2..]->() RETURN 1; + ?column? +---------- + 1 + 1 +(2 rows) + +MATCH ()-[*1..]->() RETURN 1; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(17 rows) -CYPHER WITH 1 as a -CREATE (); -ERROR: CREATE doesn't work with previous clauses -CREATE () RETURN 1 as a; -ERROR: CREATE doesn't work with next clauses -- -- Clean up -- diff --git a/regress/expected/new_cypher.out b/regress/expected/new_cypher.out index 05ddf924..8377e273 100644 --- a/regress/expected/new_cypher.out +++ b/regress/expected/new_cypher.out @@ -28,6 +28,7 @@ CREATE EXTENSION IF NOT EXISTS pg_trgm VERSION '1.3'; -- Basic Graph creation CREATE GRAPH new_cypher; NOTICE: graph "new_cypher" has been created +WARNING: relcache reference leak: relation "_adj__adj__ag_label_vertex" not closed create_graph -------------- @@ -52,6 +53,7 @@ USE GRAPH new_cypher; CREATE GRAPH new_cypher_2; NOTICE: graph "new_cypher_2" has been created +WARNING: relcache reference leak: relation "_adj__adj__ag_label_vertex" not closed create_graph -------------- @@ -72,8 +74,8 @@ USE GRAPH new_cypher; SELECT * FROM postgraph.ag_label WHERE name = '_ag_label_vertex'; name | graph | id | kind | relation | vertex_adjlist | label_path ------------------+-------+----+------+-------------------------------+------------------------------------+------------------ - _ag_label_vertex | 18790 | 1 | v | new_cypher._ag_label_vertex | new_cypher._adj__ag_label_vertex | _ag_label_vertex - _ag_label_vertex | 18826 | 1 | v | new_cypher_2._ag_label_vertex | new_cypher_2._adj__ag_label_vertex | _ag_label_vertex + _ag_label_vertex | 18815 | 1 | v | new_cypher._ag_label_vertex | new_cypher._adj__ag_label_vertex | _ag_label_vertex + _ag_label_vertex | 18853 | 1 | v | new_cypher_2._ag_label_vertex | new_cypher_2._adj__ag_label_vertex | _ag_label_vertex (2 rows) \d+ new_cypher._adj__ag_label_vertex @@ -84,6 +86,8 @@ SELECT * FROM postgraph.ag_label WHERE name = '_ag_label_vertex'; start_id | graphid | | not null | | plain | | end_id | graphid | | not null | | plain | | properties | gtype | | | gtype_build_map() | extended | | +Indexes: + "idx_hash__adj__ag_label_vertex" btree (start_id) SELECT c.relname AS table_name,am.amname AS access_method FROM pg_class c diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index a4db5e56..490eaec8 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -162,6 +162,14 @@ MATCH ()-[*2]->() RETURN 1; MATCH ()-[*1..2]->() RETURN 1; +MATCH ()-[*..2]->() RETURN 1; + + + +MATCH ()-[*2..]->() RETURN 1; + + +MATCH ()-[*1..]->() RETURN 1; -- -- Clean up -- diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 37c48e01..ff043b7c 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1226,6 +1226,37 @@ add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cyphe list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); } +/* + * add_variable_edge_to_query + * + * Adds a variable-length edge traversal to the query for Cypher MATCH clauses. + * This function constructs a set-returning function (SRF) call to the + * 'variable_edge_search' function, which finds all paths between vertices + * with a variable number of edges (e.g., for patterns like (a)-[*1..3]->(b)). + * + * Parameters: + * cpstate - Cypher parser state containing context for parsing. + * query - The Query node being constructed. + * edge - The cypher_relationship structure representing the edge. + * vertex_pnsi- The ParseNamespaceItem for the starting vertex. + * + * Behavior: + * - Ensures the edge does not have a variable name or label (not supported). + * - Assigns a default alias and label if necessary. + * - Extracts the variable-length bounds from the edge's varlen field. + * - Constructs a FuncCall node for 'variable_edge_search', passing: + * * graph_oid (the graph's OID) + * * vertex id (from the starting vertex) + * * lower bound (lidx) or NULL + * * upper bound (uidx) or NULL + * - Adds the SRF to the query with the edge's alias and expected output columns: + * * "edges" - the list of traversed edge IDs + * * "endid" - the ID of the ending vertex + * * "hashset" - a set of visited edge IDs for cycle prevention + * + * Returns: + * The ParseNamespaceItem for the SRF, which can be used to reference its output columns. + */ static ParseNamespaceItem * add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi) { edge->has_variable = false; @@ -1243,6 +1274,8 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela errmsg("MATCH labels are not supported"))); else edge->label = AG_DEFAULT_LABEL_EDGE; + + A_Indices *varlen = (A_Indices *)edge->varlen; return add_srf_to_query( cpstate, @@ -1251,8 +1284,8 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela list_make4( make_int_const(cpstate->graph_oid, -1), scanNSItemForColumn(cpstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1), - ((A_Indices *)edge->varlen)->lidx, - ((A_Indices *)edge->varlen)->uidx), + varlen->lidx ? varlen->lidx : make_null_const(-1), + varlen->uidx ? varlen->uidx : make_null_const(-1)), COERCE_EXPLICIT_CALL, -1), edge->name, list_make3(makeString("edges"), makeString("endid"), makeString("hashset"))); diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 35d5c372..e932b4e1 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -245,10 +245,7 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) RelationGetDescr(cxt->scan_desc->rs_rd), &isnull)); - //ereport(WARNING, errmsg("trying to push %lu with edge id %lu", new_id, edge_id)); if (!contains(cxt->hashSet, edge_id)) { - - //ereport(WARNING, errmsg("pushing %lu", new_id)); cxt->current_path[cxt->current_path_length].count++; variable_edge_stack_push(cxt->stack, new_id); variable_edge_stack_push(cxt->edge_stack, edge_id); @@ -262,7 +259,6 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) PG_FUNCTION_INFO_V1(variable_edge_search); Datum variable_edge_search(PG_FUNCTION_ARGS) { - //ereport(WARNING, (errmsg("variable_edge_search called with graphid %lu and min %d", AG_GETARG_GRAPHID(1), GT_ARG_TO_INT4_DATUM(2)))); FuncCallContext *funcctx; if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -281,10 +277,14 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) variable_edge_search_cxt *cxt= palloc(sizeof(variable_edge_search_cxt)); cxt->graph_oid = GT_ARG_TO_INT4_DATUM(0); - cxt->min = GT_ARG_TO_INT4_DATUM(2); - + + if (PG_ARGISNULL(2)) + cxt->min = 0; + else + cxt->min = GT_ARG_TO_INT4_DATUM(2); + if (PG_ARGISNULL(3)) - cxt->max = -1; + cxt->max = 1024; else cxt->max = GT_ARG_TO_INT4_DATUM(3); @@ -306,7 +306,6 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) cxt->current_path_length = 0; cxt->hashSet = createHashSet(1024); - //insert(cxt->hashSet, GRAPHID_GET_DATUM(id)); scan_and_push_neighbors(cxt, id); funcctx->user_fctx = cxt; @@ -317,7 +316,6 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) variable_edge_search_cxt *cxt = (variable_edge_search_cxt *) funcctx->user_fctx; - //while (cxt->hashSet->size > 0) { while (cxt->stack->top > 0) { graphid id; graphid edge_id; @@ -330,7 +328,6 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) insert(cxt->hashSet, edge_id); - //ereport(WARNING, errmsg("cxt->hashSet->size %i, %lu", cxt->hashSet->size, id)); if (cxt->hashSet->size >= cxt->min) { Datum values[3]; bool nulls[3]; @@ -351,7 +348,7 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) } else { removeElement(cxt->hashSet, edge_id); } - //ereport(WARNING, errmsg("sending %lu", id)); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); } else if (cxt->max == -1 || cxt->current_path_length < cxt->max) { scan_and_push_neighbors(cxt, id); From 9e1ad8cd4f3f4b48cc36fcca0e7bafba3f29c1cc Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 20 Sep 2025 12:10:01 -0500 Subject: [PATCH 46/56] feat: add edge label filtering to edge_search and Cypher clause - Implement edge label filtering in edge_search by accepting a label id argument and filtering edges by label OID during scan. - Update Cypher clause parser to resolve label names to label ids using the label cache and pass them as gtype integer to edge_search. - Support label filtering in add_edge_to_query and add_edge_to_query_with_prev_edge. - Update regression tests to include edge label filter queries. --- postgraph--0.1.0.sql | 2 +- regress/expected/cypher_create.out | 122 +++++++++++++++-------------- regress/sql/cypher_create.sql | 5 +- src/backend/parser/cypher_clause.c | 46 ++++++++--- src/backend/utils/edge_searching.c | 56 ++++++++++--- 5 files changed, 148 insertions(+), 83 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 32a233ba..78e821b0 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -6892,7 +6892,7 @@ CREATE OPERATOR @> ( --JOIN = neqjoinsel ); -CREATE FUNCTION variable_edge_search(graph_oid gtype, id graphid, min gtype, max gtype) +CREATE FUNCTION variable_edge_search(graph_oid gtype, id graphid, min gtype, max gtype, label_filter text[] DEFAULT NULL) RETURNS TABLE (edges variable_edge, endid graphid, hset hashset) CALLED ON NULL INPUT STABLE diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 9937234d..6cad51f4 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -390,12 +390,12 @@ MATCH ()-[]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.987..8.443 rows=0 loops=1) - -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.987..8.442 rows=0 loops=1) + Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.972..8.561 rows=0 loops=1) + -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.971..8.560 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.122..0.122 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.006 rows=12 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.119..0.120 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.002..0.006 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.009 rows=33 loops=1) @@ -404,8 +404,8 @@ EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; Rows Removed by Filter: 0 -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..25.00 rows=995 width=8) (never executed) Filter: (_age_default_alias_0.id OPERATOR(postgraph.<>) id) - Planning Time: 0.125 ms - Execution Time: 8.479 ms + Planning Time: 0.123 ms + Execution Time: 8.602 ms (16 rows) MATCH (q)-[]->(q)-[]->() RETURN 1; @@ -420,14 +420,14 @@ RETURN q; WARNING: here QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.035..0.283 rows=15 loops=1) + Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.035..0.284 rows=15 loops=1) -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.002..0.009 rows=35 loops=1) -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.002..0.005 rows=33 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=35) - Planning Time: 0.083 ms - Execution Time: 0.298 ms + Planning Time: 0.086 ms + Execution Time: 0.300 ms (8 rows) MATCH (q) @@ -459,25 +459,25 @@ MATCH ()-[]->(q) RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.348..0.368 rows=15 loops=1) + Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.307..0.326 rows=15 loops=1) Merge Cond: (q.id OPERATOR(postgraph.=) _age_default_alias_1.endid) -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.010..0.018 rows=34 loops=1) Sort Key: q.id - -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.002..0.007 rows=33 loops=1) + -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.003..0.008 rows=33 loops=1) -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.005..0.005 rows=1 loops=1) -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.334..0.337 rows=15 loops=1) - -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.333..0.334 rows=15 loops=1) + -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.293..0.296 rows=15 loops=1) + -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.292..0.293 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.045..0.327 rows=15 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.041..0.287 rows=15 loops=1) -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.012 rows=35 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.008..0.009 rows=0 loops=35) - Planning Time: 0.183 ms - Execution Time: 0.412 ms + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.007..0.007 rows=0 loops=35) + Planning Time: 0.188 ms + Execution Time: 0.370 ms (19 rows) MATCH (q) @@ -508,20 +508,20 @@ MATCH (b)-[]->() RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.036..0.436 rows=525 loops=1) - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.032..0.284 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.011 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) + Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.039..0.482 rows=525 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.035..0.330 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.007 rows=33 loops=1) -> Seq Scan on test b_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 b_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=35) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.009..0.009 rows=0 loops=35) -> Materialize (cost=0.00..71.40 rows=2520 width=40) (actual time=0.000..0.002 rows=35 loops=15) -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.006 rows=35 loops=1) -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.001..0.003 rows=33 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - Planning Time: 0.125 ms - Execution Time: 0.467 ms + Planning Time: 0.120 ms + Execution Time: 0.516 ms (14 rows) MATCH (q) @@ -1083,19 +1083,19 @@ MATCH (q)-[]->()<-[]-(q) RETURN 1; EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- - Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.755..7.246 rows=0 loops=1) + Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.269..6.781 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.093..0.093 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.004 rows=12 loops=3) - -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.000..0.001 rows=1 loops=1) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.097..0.098 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.005 rows=12 loops=3) + -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.007 rows=33 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.007..0.007 rows=0 loops=35) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=33 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.008..0.008 rows=0 loops=35) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 - Planning Time: 0.093 ms - Execution Time: 7.267 ms + Planning Time: 0.090 ms + Execution Time: 6.802 ms (13 rows) MATCH (q)<-[]-(q) RETURN 1; @@ -1135,27 +1135,27 @@ EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) Rows Removed by Join Filter: 15 - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.288..0.289 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.291..0.292 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.038..0.284 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=35 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.038..0.287 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=35 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=33 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=35) - -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.296..0.299 rows=15 loops=1) - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.295..0.296 rows=15 loops=1) + -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.293..0.296 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.292..0.293 rows=15 loops=1) Sort Key: _age_default_alias_3.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.031..0.289 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.010 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.006 rows=33 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.043..0.286 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.009..0.018 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.009..0.013 rows=33 loops=1) -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_2_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=35) - Planning Time: 0.141 ms - Execution Time: 0.632 ms + Planning Time: 0.146 ms + Execution Time: 0.630 ms (25 rows) MATCH ()-[]->()<-[]-() RETURN 1; @@ -1185,23 +1185,23 @@ EXPLAIN MATCH (:test)-[]->()<-[]-(:test) RETURN 1; EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- - Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.032..0.032 rows=0 loops=1) + Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.031..0.032 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) - -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.032..0.032 rows=0 loops=1) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.031..0.031 rows=0 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.030..0.031 rows=0 loops=1) - -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.017..0.017 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.012..0.012 rows=0 loops=1) + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.029..0.029 rows=0 loops=1) + -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.003 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.026..0.026 rows=0 loops=1) -> Materialize (cost=165712.12..171712.12 rows=1200000 width=16) (never executed) -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (never executed) Sort Key: _age_default_alias_3.endid -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (never executed) -> Seq Scan on test _age_default_alias_2 (cost=0.00..22.00 rows=1200 width=8) (never executed) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (never executed) - Planning Time: 0.094 ms - Execution Time: 0.055 ms + Planning Time: 0.117 ms + Execution Time: 0.056 ms (17 rows) MATCH (:test)-[]->()<-[]-(:test) RETURN 1; @@ -1219,17 +1219,17 @@ MATCH ()-[*1]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[*1]->()-[]->() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.353..0.449 rows=2 loops=1) - -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.035..0.336 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.011 rows=35 loops=1) + Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.356..0.453 rows=2 loops=1) + -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.036..0.334 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.012 rows=35 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.009..0.009 rows=0 loops=35) -> Function Scan on edge_search _age_default_alias_2 (cost=12.50..25.00 rows=500 width=8) (actual time=0.007..0.007 rows=0 loops=15) Filter: (_age_default_alias_1.hashset OPERATOR(postgraph.@>) id) - Planning Time: 0.081 ms - Execution Time: 0.472 ms + Planning Time: 0.089 ms + Execution Time: 0.477 ms (11 rows) SELECT * FROM cypher_create._adj__ag_label_vertex; @@ -1334,10 +1334,10 @@ MATCH ()-[]->() RETURN 1; (15 rows) MATCH ()-[*2]->() RETURN 1; -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x4048bfb0, chunk 0x4048c248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x4048bfb0, chunk 0x4048c248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x4048bfb0, chunk 0x4048c248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x4048bfb0, chunk 0x4048c248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x30033fb0, chunk 0x30034248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x30033fb0, chunk 0x30034248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x30033fb0, chunk 0x30034248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x30033fb0, chunk 0x30034248 ?column? ---------- 1 @@ -1417,6 +1417,12 @@ MATCH ()-[*1..]->() RETURN 1; 1 (17 rows) +MATCH ()-[:elabel]->() RETURN 1; + ?column? +---------- + 1 +(1 row) + -- -- Clean up -- diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 490eaec8..06120e8d 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -164,12 +164,13 @@ MATCH ()-[*1..2]->() RETURN 1; MATCH ()-[*..2]->() RETURN 1; - - MATCH ()-[*2..]->() RETURN 1; MATCH ()-[*1..]->() RETURN 1; + +MATCH ()-[:elabel]->() RETURN 1; + -- -- Clean up -- diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index ff043b7c..18ce3516 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1168,12 +1168,19 @@ add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship edge->name = get_next_default_alias(cpstate); bool is_default_label = true; - if (edge->label) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH labels are not supported"))); - else + Node *label_filter = NULL; + if (edge->label) { + is_default_label = false; + label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); + if (!lcd) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("Edge label \"%s\" does not exist", edge->label))); + + label_filter = make_int_const(lcd->id, -1); + }else { edge->label = AG_DEFAULT_LABEL_EDGE; + } return add_srf_to_query( cpstate, @@ -1184,8 +1191,9 @@ add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship vertex->declared_in_previous_clause ? colNameToVar(cpstate, make_id_alias(vertex->name), false, -1) : scanNSItemForColumn(cpstate, vertex->pnsi, 0, AG_VERTEX_COLNAME_ID, -1), - make_null_const(-1), - make_null_const(-1)), + !is_default_label ? (Node *)label_filter : make_null_const(-1) , + make_null_const(-1) + ), COERCE_EXPLICIT_CALL, -1), edge->name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); @@ -1205,12 +1213,19 @@ add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cyphe edge->name = get_next_default_alias(cpstate); bool is_default_label = true; - if (edge->label) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH labels are not supported"))); - else + Node *label_filter = NULL; + if (edge->label) { + is_default_label = false; + label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); + if (!lcd) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("Edge label \"%s\" does not exist", edge->label))); + + label_filter = make_int_const(lcd->id, -1); + }else { edge->label = AG_DEFAULT_LABEL_EDGE; + } if(edge->dir == CYPHER_REL_DIR_NONE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -1220,7 +1235,12 @@ add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cyphe cpstate, makeFuncCall( list_make2(makeString("postgraph"), makeString("edge_search")), - list_make4(make_int_const(cpstate->graph_oid, -1), scanNSItemForColumn(cpstate, prev_pnsi, 0, "endid", -1), make_null_const(-1), make_null_const(-1)), + list_make4( + make_int_const(cpstate->graph_oid, -1), + scanNSItemForColumn(cpstate, prev_pnsi, 0, "endid", -1), + !is_default_label ? (Node *)label_filter : make_null_const(-1), + make_null_const(-1) + ), COERCE_EXPLICIT_CALL, -1), edge->name, list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index e932b4e1..716f1bc1 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -90,6 +90,7 @@ typedef struct edge_search_cxt TableScanDesc scan_desc; Relation rel; TupleTableSlot *slot; + List *label_filter; } edge_search_cxt; PG_FUNCTION_INFO_V1(edge_search); @@ -101,9 +102,13 @@ Datum edge_search(PG_FUNCTION_ARGS) TupleDesc tupdesc; graphid id = AG_GETARG_GRAPHID(1); + + funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(4); TupleDescInitEntry(tupdesc, 1, "id", GRAPHIDOID, -1, 0); @@ -126,6 +131,11 @@ Datum edge_search(PG_FUNCTION_ARGS) List *empty = NIL; cxt->slot = table_slot_create(cxt->rel, &empty); + cxt->label_filter = NIL; + if (!PG_ARGISNULL(2)) { + cxt->label_filter = lappend_oid(cxt->label_filter, GT_ARG_TO_INT4_DATUM(2)); + } + funcctx->user_fctx = cxt; MemoryContextSwitchTo(oldcontext); @@ -134,17 +144,45 @@ Datum edge_search(PG_FUNCTION_ARGS) funcctx = SRF_PERCALL_SETUP(); edge_search_cxt *cxt = (edge_search_cxt *) funcctx->user_fctx; + while (table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { + cxt->slot->tts_ops->materialize(cxt->slot); + HeapTuple heap_tuple = cxt->slot->tts_ops->get_heap_tuple(cxt->slot); + Relation rel = cxt->scan_desc->rs_rd; + + + if (cxt->label_filter != NIL) { + bool isnull; + Oid edge_label_oid = (DATUM_GET_GRAPHID(heap_getattr(heap_tuple, 1, RelationGetDescr(rel), &isnull)) >> ENTRY_ID_BITS); + + bool found = false; + ListCell *lc; + foreach(lc, cxt->label_filter) { + if (edge_label_oid == lfirst_oid(lc)) { + found = true; + break; + } + } + if (!found) + continue; + } + + Datum values[4]; + bool nulls[4]; + values[0] = heap_getattr(heap_tuple, 1, RelationGetDescr(rel), &nulls[0]); + values[1] = heap_getattr(heap_tuple, 2, RelationGetDescr(rel), &nulls[1]); + values[2] = heap_getattr(heap_tuple, 3, RelationGetDescr(rel), &nulls[2]); + values[3] = heap_getattr(heap_tuple, 4, RelationGetDescr(rel), &nulls[3]); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); + } + + ReleaseTupleDesc(RelationGetDescr(cxt->rel)); + table_endscan(cxt->scan_desc); + table_close(cxt->rel, ShareLock); + SRF_RETURN_DONE(funcctx); +/* if (!table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { ReleaseTupleDesc(RelationGetDescr(cxt->rel)); - table_endscan(cxt->scan_desc); - /* - List *indexoidlist = RelationGetIndexList(cxt->rel); - if(list_length(indexoidlist) == 1) { - Oid idx = linitial_oid(indexoidlist); - index_close(idx, RowExclusiveLock); - }*/ - table_close(cxt->rel, ShareLock); SRF_RETURN_DONE(funcctx); } @@ -160,7 +198,7 @@ Datum edge_search(PG_FUNCTION_ARGS) values[2] = heap_getattr(heap_tuple, 3, RelationGetDescr(rel), &nulls[2]); values[3] = heap_getattr(heap_tuple, 4, RelationGetDescr(rel), &nulls[3]); //ereport(WARNING, (errmsg("edge_search sending graphid %lu", DATUM_GET_GRAPHID(values[2])))); - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls)));*/ } typedef struct variable_edge_stack From d202fb3cd056088edbd852219726956c271522e6 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 20 Sep 2025 12:19:52 -0500 Subject: [PATCH 47/56] feat: Support label filtering in vle match search --- postgraph--0.1.0.sql | 2 +- regress/sql/cypher_create.sql | 10 ++++++++++ src/backend/parser/cypher_clause.c | 23 ++++++++++++++++------- src/backend/utils/edge_searching.c | 30 ++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 78e821b0..07297530 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -6892,7 +6892,7 @@ CREATE OPERATOR @> ( --JOIN = neqjoinsel ); -CREATE FUNCTION variable_edge_search(graph_oid gtype, id graphid, min gtype, max gtype, label_filter text[] DEFAULT NULL) +CREATE FUNCTION variable_edge_search(graph_oid gtype, id graphid, min gtype, max gtype, label gtype) RETURNS TABLE (edges variable_edge, endid graphid, hset hashset) CALLED ON NULL INPUT STABLE diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 06120e8d..6907ffec 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -171,6 +171,16 @@ MATCH ()-[*1..]->() RETURN 1; MATCH ()-[:elabel]->() RETURN 1; +MATCH ()-[:elabel*1]->() RETURN 1; + +MATCH ()-[:elabel*1..]->() RETURN 1; + +MATCH ()-[:elabel*1..2]->() RETURN 1; + +MATCH ()-[:elabel*2]->() RETURN 1; + +MATCH ()-[:elabel*..2]->() RETURN 1; + -- -- Clean up -- diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 18ce3516..bc21ec41 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1288,12 +1288,19 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela edge->name = get_next_default_alias(cpstate); bool is_default_label = true; - if (edge->label) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MATCH labels are not supported"))); - else + Node *label_filter = NULL; + if (edge->label) { + is_default_label = false; + label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); + if (!lcd) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("Edge label \"%s\" does not exist", edge->label))); + + label_filter = make_int_const(lcd->id, -1); + }else { edge->label = AG_DEFAULT_LABEL_EDGE; + } A_Indices *varlen = (A_Indices *)edge->varlen; @@ -1301,11 +1308,13 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela cpstate, makeFuncCall( list_make2(makeString("postgraph"), makeString("variable_edge_search")), - list_make4( + list_make5( make_int_const(cpstate->graph_oid, -1), scanNSItemForColumn(cpstate, vertex_pnsi, 0, AG_VERTEX_COLNAME_ID, -1), varlen->lidx ? varlen->lidx : make_null_const(-1), - varlen->uidx ? varlen->uidx : make_null_const(-1)), + varlen->uidx ? varlen->uidx : make_null_const(-1), + !is_default_label ? (Node *)label_filter : make_null_const(-1) + ), COERCE_EXPLICIT_CALL, -1), edge->name, list_make3(makeString("edges"), makeString("endid"), makeString("hashset"))); diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 716f1bc1..9378fd6b 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -227,6 +227,7 @@ typedef struct variable_edge_search_cxt graph_stack_count *current_path; variable_edge_stack *edge_stack; int current_path_length; + List *label_filter; } variable_edge_search_cxt; static void @@ -269,6 +270,7 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) while (table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { cxt->slot->tts_ops->materialize(cxt->slot); + bool isnull; graphid edge_id = DATUM_GET_GRAPHID( heap_getattr( @@ -276,6 +278,24 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) 1, RelationGetDescr(cxt->scan_desc->rs_rd), &isnull)); + + if (cxt->label_filter != NIL) { + bool isnull; + Oid edge_label_oid = (edge_id >> ENTRY_ID_BITS); + + bool found = false; + ListCell *lc; + foreach(lc, cxt->label_filter) { + if (edge_label_oid == lfirst_oid(lc)) { + found = true; + break; + } + } + if (!found) + continue; + } + + graphid new_id = DATUM_GET_GRAPHID( heap_getattr( cxt->slot->tts_ops->get_heap_tuple(cxt->slot), @@ -344,7 +364,17 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) cxt->current_path_length = 0; cxt->hashSet = createHashSet(1024); + + + cxt->label_filter = NIL; + if (!PG_ARGISNULL(4)) { + cxt->label_filter = lappend_oid(cxt->label_filter, GT_ARG_TO_INT4_DATUM(4)); + } + + scan_and_push_neighbors(cxt, id); + + funcctx->user_fctx = cxt; MemoryContextSwitchTo(oldcontext); From 7eb6f0c252ab7094fd8bd5cb47ac517295fba317 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 20 Sep 2025 13:23:11 -0500 Subject: [PATCH 48/56] Have retrieve_vertex use BTrees if it exists --- regress/expected/cypher_create.out | 155 +++++++++++++++++------------ src/backend/utils/edge_searching.c | 95 ++++++++---------- 2 files changed, 133 insertions(+), 117 deletions(-) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 6cad51f4..fbd993a3 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -390,22 +390,22 @@ MATCH ()-[]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.972..8.561 rows=0 loops=1) - -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.971..8.560 rows=0 loops=1) + Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.411..8.053 rows=0 loops=1) + -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.410..8.052 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.119..0.120 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.002..0.006 rows=12 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.115..0.116 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.005 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.009 rows=33 loops=1) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=33 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.009..0.009 rows=0 loops=35) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..25.00 rows=995 width=8) (never executed) Filter: (_age_default_alias_0.id OPERATOR(postgraph.<>) id) - Planning Time: 0.123 ms - Execution Time: 8.602 ms + Planning Time: 0.129 ms + Execution Time: 8.090 ms (16 rows) MATCH (q)-[]->(q)-[]->() RETURN 1; @@ -420,14 +420,14 @@ RETURN q; WARNING: here QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.035..0.284 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.002..0.009 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.002..0.005 rows=33 loops=1) + Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.039..0.285 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.003..0.010 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.003..0.005 rows=33 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=35) - Planning Time: 0.086 ms - Execution Time: 0.300 ms + Planning Time: 0.097 ms + Execution Time: 0.301 ms (8 rows) MATCH (q) @@ -459,25 +459,25 @@ MATCH ()-[]->(q) RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.307..0.326 rows=15 loops=1) + Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.321..0.340 rows=15 loops=1) Merge Cond: (q.id OPERATOR(postgraph.=) _age_default_alias_1.endid) - -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.010..0.018 rows=34 loops=1) + -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.010..0.019 rows=34 loops=1) Sort Key: q.id - -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.003..0.008 rows=33 loops=1) - -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.005..0.005 rows=1 loops=1) + -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.002..0.007 rows=33 loops=1) + -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.006..0.006 rows=1 loops=1) -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.293..0.296 rows=15 loops=1) - -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.292..0.293 rows=15 loops=1) + -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.306..0.309 rows=15 loops=1) + -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.305..0.306 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.041..0.287 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.012 rows=35 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.047..0.299 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=35 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.007..0.007 rows=0 loops=35) - Planning Time: 0.188 ms - Execution Time: 0.370 ms + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.008..0.008 rows=0 loops=35) + Planning Time: 0.203 ms + Execution Time: 0.387 ms (19 rows) MATCH (q) @@ -508,20 +508,20 @@ MATCH (b)-[]->() RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.039..0.482 rows=525 loops=1) - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.035..0.330 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.007 rows=33 loops=1) + Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.037..0.456 rows=525 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.033..0.306 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) -> Seq Scan on test b_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 b_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.009..0.009 rows=0 loops=35) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.008..0.008 rows=0 loops=35) -> Materialize (cost=0.00..71.40 rows=2520 width=40) (actual time=0.000..0.002 rows=35 loops=15) -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.006 rows=35 loops=1) -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.001..0.003 rows=33 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - Planning Time: 0.120 ms - Execution Time: 0.516 ms + Planning Time: 0.111 ms + Execution Time: 0.487 ms (14 rows) MATCH (q) @@ -1083,19 +1083,19 @@ MATCH (q)-[]->()<-[]-(q) RETURN 1; EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- - Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.269..6.781 rows=0 loops=1) + Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.949..7.525 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.097..0.098 rows=0 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.098..0.099 rows=0 loops=3) -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.005 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=33 loops=1) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.007 rows=33 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.008..0.008 rows=0 loops=35) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 - Planning Time: 0.090 ms - Execution Time: 6.802 ms + Planning Time: 0.089 ms + Execution Time: 7.549 ms (13 rows) MATCH (q)<-[]-(q) RETURN 1; @@ -1131,31 +1131,31 @@ EXPLAIN MATCH ()-[]->()<-[]-() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.594..0.595 rows=0 loops=1) + Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.589..0.590 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) Rows Removed by Join Filter: 15 - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.291..0.292 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.298..0.299 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.038..0.287 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=35 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.039..0.294 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.013 rows=35 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=33 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=35) - -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.293..0.296 rows=15 loops=1) - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.292..0.293 rows=15 loops=1) + -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.281..0.283 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.280..0.281 rows=15 loops=1) Sort Key: _age_default_alias_3.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.043..0.286 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.009..0.018 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.009..0.013 rows=33 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.031..0.274 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.010 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.006 rows=33 loops=1) -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_2_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=35) - Planning Time: 0.146 ms - Execution Time: 0.630 ms + Planning Time: 0.150 ms + Execution Time: 0.628 ms (25 rows) MATCH ()-[]->()<-[]-() RETURN 1; @@ -1185,23 +1185,23 @@ EXPLAIN MATCH (:test)-[]->()<-[]-(:test) RETURN 1; EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- - Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.031..0.032 rows=0 loops=1) + Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.017..0.018 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) - -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.031..0.031 rows=0 loops=1) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.017..0.017 rows=0 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.029..0.029 rows=0 loops=1) - -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.003 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.026..0.026 rows=0 loops=1) + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.016 rows=0 loops=1) + -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.002 rows=1 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.013..0.013 rows=0 loops=1) -> Materialize (cost=165712.12..171712.12 rows=1200000 width=16) (never executed) -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (never executed) Sort Key: _age_default_alias_3.endid -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (never executed) -> Seq Scan on test _age_default_alias_2 (cost=0.00..22.00 rows=1200 width=8) (never executed) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (never executed) - Planning Time: 0.117 ms - Execution Time: 0.056 ms + Planning Time: 0.099 ms + Execution Time: 0.041 ms (17 rows) MATCH (:test)-[]->()<-[]-(:test) RETURN 1; @@ -1219,17 +1219,17 @@ MATCH ()-[*1]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[*1]->()-[]->() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.356..0.453 rows=2 loops=1) - -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.036..0.334 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.012 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) + Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.362..0.455 rows=2 loops=1) + -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.040..0.341 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=35 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=33 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.009..0.009 rows=0 loops=35) -> Function Scan on edge_search _age_default_alias_2 (cost=12.50..25.00 rows=500 width=8) (actual time=0.007..0.007 rows=0 loops=15) Filter: (_age_default_alias_1.hashset OPERATOR(postgraph.@>) id) - Planning Time: 0.089 ms - Execution Time: 0.477 ms + Planning Time: 0.090 ms + Execution Time: 0.476 ms (11 rows) SELECT * FROM cypher_create._adj__ag_label_vertex; @@ -1334,10 +1334,10 @@ MATCH ()-[]->() RETURN 1; (15 rows) MATCH ()-[*2]->() RETURN 1; -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x30033fb0, chunk 0x30034248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x30033fb0, chunk 0x30034248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x30033fb0, chunk 0x30034248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x30033fb0, chunk 0x30034248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x33c7dfb0, chunk 0x33c7e248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x33c7dfb0, chunk 0x33c7e248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x33c7dfb0, chunk 0x33c7e248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x33c7dfb0, chunk 0x33c7e248 ?column? ---------- 1 @@ -1423,6 +1423,35 @@ MATCH ()-[:elabel]->() RETURN 1; 1 (1 row) +MATCH ()-[:elabel*1]->() RETURN 1; + ?column? +---------- + 1 +(1 row) + +MATCH ()-[:elabel*1..]->() RETURN 1; + ?column? +---------- + 1 +(1 row) + +MATCH ()-[:elabel*1..2]->() RETURN 1; + ?column? +---------- + 1 +(1 row) + +MATCH ()-[:elabel*2]->() RETURN 1; + ?column? +---------- +(0 rows) + +MATCH ()-[:elabel*..2]->() RETURN 1; + ?column? +---------- + 1 +(1 row) + -- -- Clean up -- diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 9378fd6b..d1a8ddb7 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -59,29 +59,54 @@ Datum retrieve_vertex(PG_FUNCTION_ARGS) { int64 graphid = AG_GETARG_GRAPHID(1); bool isnull; - label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, (graphid >> ENTRY_ID_BITS)); - ScanKeyData scan_keys[1]; ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_OIDEQ, Int64GetDatum(graphid)); + label_cache_data *lcd = search_label_graph_oid_cache(graph_oid, (graphid >> ENTRY_ID_BITS)); Relation rel = table_open(lcd->relation, ShareLock); + List *indexoidlist = RelationGetIndexList(rel); - TableScanDesc scan_desc = table_beginscan(rel, GetActiveSnapshot(), 1, scan_keys); + if(list_length(indexoidlist) == 1) { + Oid idx = linitial_oid(indexoidlist); + Relation idxrel = index_open(idx, ShareLock); + IndexScanDesc *desc = index_beginscan(rel, idxrel, GetActiveSnapshot(), 1, scan_keys); + TupleTableSlot *slot; + HeapTuple tuple; + if (!index_getnext_slot(desc, ForwardScanDirection, slot)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); - HeapTuple tuple; - if (!HeapTupleIsValid(tuple = heap_getnext(scan_desc, ForwardScanDirection))) - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); + ereport(WARNING, (errmsg("here"))); + tuple = ExecFetchSlotHeapTuple(slot, false, NULL); - Datum properties = heap_getattr(tuple, 2, RelationGetDescr(rel), &isnull); + Datum properties = heap_getattr(tuple, 2, RelationGetDescr(rel), &isnull); - table_endscan(scan_desc); - table_close(rel, ShareLock); + index_endscan(desc); + index_close(idxrel, ShareLock); + table_close(rel, ShareLock); - if (isnull) - PG_RETURN_NULL(); - - AG_RETURN_GTYPE_P(properties); + if (isnull) + PG_RETURN_NULL(); + + AG_RETURN_GTYPE_P(properties); + + + } else { + TableScanDesc scan_desc = table_beginscan(rel, GetActiveSnapshot(), 1, scan_keys); + + HeapTuple tuple; + if (!HeapTupleIsValid(tuple = heap_getnext(scan_desc, ForwardScanDirection))) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); + Datum properties = heap_getattr(tuple, 2, RelationGetDescr(rel), &isnull); + + table_endscan(scan_desc); + table_close(rel, ShareLock); + + if (isnull) + PG_RETURN_NULL(); + + AG_RETURN_GTYPE_P(properties); + } } typedef struct edge_search_cxt @@ -102,13 +127,9 @@ Datum edge_search(PG_FUNCTION_ARGS) TupleDesc tupdesc; graphid id = AG_GETARG_GRAPHID(1); - - funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - tupdesc = CreateTemplateTupleDesc(4); TupleDescInitEntry(tupdesc, 1, "id", GRAPHIDOID, -1, 0); @@ -179,26 +200,6 @@ Datum edge_search(PG_FUNCTION_ARGS) table_endscan(cxt->scan_desc); table_close(cxt->rel, ShareLock); SRF_RETURN_DONE(funcctx); -/* - if (!table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { - ReleaseTupleDesc(RelationGetDescr(cxt->rel)); - - - SRF_RETURN_DONE(funcctx); - } - - Datum values[4]; - bool nulls[4]; - VertexHeapScanDesc vertex_desc = cxt->scan_desc; - cxt->slot->tts_ops->materialize(cxt->slot); - HeapTuple heap_tuple = cxt->slot->tts_ops->get_heap_tuple(cxt->slot); - Relation rel = vertex_desc->desc[0]->rs_rd; - values[0] = heap_getattr(heap_tuple, 1, RelationGetDescr(rel), &nulls[0]); - values[1] = heap_getattr(heap_tuple, 2, RelationGetDescr(rel), &nulls[1]); - values[2] = heap_getattr(heap_tuple, 3, RelationGetDescr(rel), &nulls[2]); - values[3] = heap_getattr(heap_tuple, 4, RelationGetDescr(rel), &nulls[3]); - //ereport(WARNING, (errmsg("edge_search sending graphid %lu", DATUM_GET_GRAPHID(values[2])))); - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls)));*/ } typedef struct variable_edge_stack @@ -233,21 +234,17 @@ typedef struct variable_edge_search_cxt static void variable_edge_stack_push(variable_edge_stack *stack, graphid value) { - - if (stack->top >= stack->capacity) - { + if (stack->top >= stack->capacity) { int new_capacity = stack->capacity * 2; stack->array = repalloc(stack->array, sizeof(graphid) * (new_capacity + 1)); stack->capacity = new_capacity; } stack->array[stack->top++] = value; - // ereport(WARNING, (errmsg("pushing to stack %i, %i, %lu, %lu", stack->top, stack->capacity, value, stack->array[stack->top-1]))); } static bool variable_edge_stack_pop(variable_edge_stack *stack, graphid *result) { - // ereport(WARNING, (errmsg("popping from stack %i, %i ", stack->top, stack->capacity))); if (stack->top == 0) return false; *result = stack->array[--stack->top]; @@ -258,8 +255,7 @@ static void scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) { label_cache_data *lcd = search_label_graph_oid_cache(cxt->graph_oid, (id >> ENTRY_ID_BITS)); - //ereport(WARNING, (errmsg("here"))); - cxt->scanKey = palloc(sizeof(ScanKeyData)); + cxt->scanKey = palloc(sizeof(ScanKeyData)); ScanKeyInit(cxt->scanKey, 1, BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id)); cxt->rel = table_open(lcd->vertex_adjlist, ShareLock); @@ -269,8 +265,6 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) while (table_scan_getnextslot(cxt->scan_desc, ForwardScanDirection, cxt->slot)) { cxt->slot->tts_ops->materialize(cxt->slot); - - bool isnull; graphid edge_id = DATUM_GET_GRAPHID( heap_getattr( @@ -280,9 +274,7 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) &isnull)); if (cxt->label_filter != NIL) { - bool isnull; Oid edge_label_oid = (edge_id >> ENTRY_ID_BITS); - bool found = false; ListCell *lc; foreach(lc, cxt->label_filter) { @@ -295,7 +287,6 @@ scan_and_push_neighbors(variable_edge_search_cxt *cxt, graphid id) continue; } - graphid new_id = DATUM_GET_GRAPHID( heap_getattr( cxt->slot->tts_ops->get_heap_tuple(cxt->slot), @@ -360,21 +351,17 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) cxt->current_path = palloc(sizeof(graph_stack_count) * (cxt->min + 1)); else cxt->current_path = palloc(sizeof(graph_stack_count) * (cxt->max + 1)); - cxt->current_path_length = 0; - cxt->hashSet = createHashSet(1024); - + cxt->hashSet = createHashSet(1024); cxt->label_filter = NIL; if (!PG_ARGISNULL(4)) { cxt->label_filter = lappend_oid(cxt->label_filter, GT_ARG_TO_INT4_DATUM(4)); } - scan_and_push_neighbors(cxt, id); - funcctx->user_fctx = cxt; MemoryContextSwitchTo(oldcontext); From 6b4244b9f968bf7b39f7d279f173b0fe1a02c1b5 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 20 Sep 2025 14:57:30 -0500 Subject: [PATCH 49/56] Allow CREATE to reference previous vertices --- regress/expected/cypher_create.out | 350 ++++++++++++++++++++------- regress/sql/cypher_create.sql | 15 ++ src/backend/executor/cypher_create.c | 166 +++++++------ src/backend/parser/cypher_clause.c | 93 +++++-- src/backend/utils/edge_searching.c | 12 +- 5 files changed, 456 insertions(+), 180 deletions(-) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index fbd993a3..1b6469b8 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -347,7 +347,10 @@ MATCH ()<-[a]-() RETURN a; CYPHER WITH 1 as a CREATE (); -ERROR: CREATE doesn't work with previous clauses +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +-- +(0 rows) + CREATE () RETURN 1 as a; WARNING: relcache reference leak: relation "_ag_label_vertex" not closed a @@ -390,22 +393,22 @@ MATCH ()-[]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.411..8.053 rows=0 loops=1) - -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.410..8.052 rows=0 loops=1) + Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=8.673..9.278 rows=0 loops=1) + -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=8.672..9.277 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.115..0.116 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.005 rows=12 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.122..0.122 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.006 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=33 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.009..0.009 rows=0 loops=35) + -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.002 rows=1 loops=1) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.009 rows=34 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.008..0.008 rows=0 loops=36) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..25.00 rows=995 width=8) (never executed) Filter: (_age_default_alias_0.id OPERATOR(postgraph.<>) id) - Planning Time: 0.129 ms - Execution Time: 8.090 ms + Planning Time: 0.122 ms + Execution Time: 9.315 ms (16 rows) MATCH (q)-[]->(q)-[]->() RETURN 1; @@ -420,14 +423,14 @@ RETURN q; WARNING: here QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.039..0.285 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.003..0.010 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.003..0.005 rows=33 loops=1) + Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.037..0.280 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.003..0.010 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.002..0.005 rows=34 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=35) - Planning Time: 0.097 ms - Execution Time: 0.301 ms + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=36) + Planning Time: 0.089 ms + Execution Time: 0.295 ms (8 rows) MATCH (q) @@ -447,10 +450,10 @@ WARNING: here {"id": 281474976710677, "label": "", "properties": {}} {"id": 281474976710680, "label": "", "properties": {}} {"id": 281474976710681, "label": "", "properties": {}} - {"id": 281474976710684, "label": "", "properties": {}} {"id": 281474976710685, "label": "", "properties": {}} - {"id": 281474976710687, "label": "", "properties": {}} + {"id": 281474976710686, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710689, "label": "", "properties": {}} (15 rows) EXPLAIN ANALYZE @@ -459,25 +462,25 @@ MATCH ()-[]->(q) RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.321..0.340 rows=15 loops=1) + Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.305..0.325 rows=15 loops=1) Merge Cond: (q.id OPERATOR(postgraph.=) _age_default_alias_1.endid) - -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.010..0.019 rows=34 loops=1) + -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.009..0.018 rows=35 loops=1) Sort Key: q.id - -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.002..0.007 rows=33 loops=1) - -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.006..0.006 rows=1 loops=1) + -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.003..0.007 rows=34 loops=1) + -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.005..0.005 rows=1 loops=1) -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.306..0.309 rows=15 loops=1) - -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.305..0.306 rows=15 loops=1) + -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.292..0.295 rows=15 loops=1) + -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.291..0.292 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.047..0.299 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.043..0.286 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.011 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.008..0.008 rows=0 loops=35) - Planning Time: 0.203 ms - Execution Time: 0.387 ms + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.007..0.007 rows=0 loops=36) + Planning Time: 0.175 ms + Execution Time: 0.366 ms (19 rows) MATCH (q) @@ -496,10 +499,10 @@ RETURN q; {"id": 281474976710678, "label": "", "properties": {}} {"id": 281474976710679, "label": "", "properties": {}} {"id": 281474976710682, "label": "", "properties": {}} - {"id": 281474976710685, "label": "", "properties": {}} {"id": 281474976710686, "label": "", "properties": {}} - {"id": 281474976710688, "label": "", "properties": {}} + {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} (15 rows) EXPLAIN ANALYZE @@ -508,20 +511,20 @@ MATCH (b)-[]->() RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.037..0.456 rows=525 loops=1) - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.033..0.306 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=33 loops=1) + Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.044..0.497 rows=540 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.040..0.317 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.020 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.004..0.014 rows=34 loops=1) -> Seq Scan on test b_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 b_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.008..0.008 rows=0 loops=35) - -> Materialize (cost=0.00..71.40 rows=2520 width=40) (actual time=0.000..0.002 rows=35 loops=15) - -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.006 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.001..0.003 rows=33 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.008..0.008 rows=0 loops=36) + -> Materialize (cost=0.00..71.40 rows=2520 width=40) (actual time=0.000..0.002 rows=36 loops=15) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.007 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.001..0.003 rows=34 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - Planning Time: 0.111 ms - Execution Time: 0.487 ms + Planning Time: 0.225 ms + Execution Time: 0.533 ms (14 rows) MATCH (q) @@ -562,6 +565,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -597,6 +601,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -632,6 +637,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -667,6 +673,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -702,6 +709,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -737,6 +745,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -772,6 +781,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -807,6 +817,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -842,6 +853,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -877,6 +889,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -912,6 +925,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -947,6 +961,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -982,6 +997,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -1017,6 +1033,7 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} {"id": 281474976710657, "label": "", "properties": {}} @@ -1052,9 +1069,10 @@ RETURN q; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} -(525 rows) +(540 rows) EXPLAIN MATCH (q)-[]->()<-[]-(q) RETURN 1; QUERY PLAN @@ -1083,19 +1101,19 @@ MATCH (q)-[]->()<-[]-(q) RETURN 1; EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- - Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.949..7.525 rows=0 loops=1) + Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=7.017..7.575 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.098..0.099 rows=0 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.095..0.096 rows=0 loops=3) -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.005 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.007 rows=33 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.008..0.008 rows=0 loops=35) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.007 rows=34 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.007..0.007 rows=0 loops=36) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 - Planning Time: 0.089 ms - Execution Time: 7.549 ms + Planning Time: 0.083 ms + Execution Time: 7.597 ms (13 rows) MATCH (q)<-[]-(q) RETURN 1; @@ -1131,31 +1149,31 @@ EXPLAIN MATCH ()-[]->()<-[]-() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.589..0.590 rows=0 loops=1) + Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.600..0.601 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) Rows Removed by Join Filter: 15 - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.298..0.299 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.301..0.302 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.039..0.294 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.013 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=33 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.041..0.297 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.014 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.004..0.008 rows=34 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=35) - -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.281..0.283 rows=15 loops=1) - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.280..0.281 rows=15 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=36) + -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.289..0.292 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.288..0.289 rows=15 loops=1) Sort Key: _age_default_alias_3.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.031..0.274 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.010 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.006 rows=33 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.031..0.282 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.010 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.006 rows=34 loops=1) -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_2_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=35) - Planning Time: 0.150 ms - Execution Time: 0.628 ms + -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=36) + Planning Time: 0.153 ms + Execution Time: 0.640 ms (25 rows) MATCH ()-[]->()<-[]-() RETURN 1; @@ -1185,13 +1203,13 @@ EXPLAIN MATCH (:test)-[]->()<-[]-(:test) RETURN 1; EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- - Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.017..0.018 rows=0 loops=1) + Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.018..0.018 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) - -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.017..0.017 rows=0 loops=1) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.018..0.018 rows=0 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.016 rows=0 loops=1) + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.017 rows=0 loops=1) -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.002 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.013..0.013 rows=0 loops=1) -> Materialize (cost=165712.12..171712.12 rows=1200000 width=16) (never executed) @@ -1200,8 +1218,8 @@ EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (never executed) -> Seq Scan on test _age_default_alias_2 (cost=0.00..22.00 rows=1200 width=8) (never executed) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (never executed) - Planning Time: 0.099 ms - Execution Time: 0.041 ms + Planning Time: 0.094 ms + Execution Time: 0.042 ms (17 rows) MATCH (:test)-[]->()<-[]-(:test) RETURN 1; @@ -1219,17 +1237,17 @@ MATCH ()-[*1]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[*1]->()-[]->() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.362..0.455 rows=2 loops=1) - -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.040..0.341 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=35 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=33 loops=1) + Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.370..0.463 rows=2 loops=1) + -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.042..0.349 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=34 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.009..0.009 rows=0 loops=35) + -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.009..0.009 rows=0 loops=36) -> Function Scan on edge_search _age_default_alias_2 (cost=12.50..25.00 rows=500 width=8) (actual time=0.007..0.007 rows=0 loops=15) Filter: (_age_default_alias_1.hashset OPERATOR(postgraph.@>) id) - Planning Time: 0.090 ms - Execution Time: 0.476 ms + Planning Time: 0.088 ms + Execution Time: 0.485 ms (11 rows) SELECT * FROM cypher_create._adj__ag_label_vertex; @@ -1246,10 +1264,10 @@ SELECT * FROM cypher_create._adj__ag_label_vertex; (8, 1) | (1, 21) | (1, 22) | (3, 9) | (1, 24) | (1, 23) | {"msg": "Hello edge"} (3, 10) | (1, 25) | (1, 26) | {"msg": "Hello edge"} - (3, 11) | (1, 28) | (1, 29) | - (3, 12) | (1, 29) | (1, 30) | - (3, 13) | (1, 31) | (1, 32) | - (3, 14) | (1, 32) | (1, 33) | + (3, 11) | (1, 29) | (1, 30) | + (3, 12) | (1, 30) | (1, 31) | + (3, 13) | (1, 32) | (1, 33) | + (3, 14) | (1, 33) | (1, 34) | (15 rows) MATCH (a) RETURN a; @@ -1288,9 +1306,10 @@ MATCH (a) RETURN a; {"id": 281474976710687, "label": "", "properties": {}} {"id": 281474976710688, "label": "", "properties": {}} {"id": 281474976710689, "label": "", "properties": {}} + {"id": 281474976710690, "label": "", "properties": {}} {"id": 1125899906842625, "label": "test", "properties": {}} {"id": 1688849860263937, "label": "test2", "properties": {}} -(35 rows) +(36 rows) --SELECT * FROM cypher_create.idx_hash__adj__ag_label_vertex; MATCH ()-[*1]->() RETURN 1; @@ -1334,10 +1353,10 @@ MATCH ()-[]->() RETURN 1; (15 rows) MATCH ()-[*2]->() RETURN 1; -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x33c7dfb0, chunk 0x33c7e248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x33c7dfb0, chunk 0x33c7e248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x33c7dfb0, chunk 0x33c7e248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x33c7dfb0, chunk 0x33c7e248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x2de36fb0, chunk 0x2de37248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x2de36fb0, chunk 0x2de37248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x2de36fb0, chunk 0x2de37248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x2de36fb0, chunk 0x2de37248 ?column? ---------- 1 @@ -1452,11 +1471,168 @@ MATCH ()-[:elabel*..2]->() RETURN 1; 1 (1 row) +MATCH (n:test) +CREATE ()-[:elabel]->(:test2) +RETURN 1; +WARNING: relcache reference leak: relation "_ag_label_vertex" not closed +WARNING: relcache reference leak: relation "test2" not closed +WARNING: relcache reference leak: relation "elabel" not closed + ?column? +---------- + 1 +(1 row) + +MATCH (n:test) +CREATE (n)-[:elabel]->(:test2) +RETURN 1; +WARNING: relcache reference leak: relation "elabel" not closed +WARNING: relcache reference leak: relation "test2" not closed + ?column? +---------- + 1 +(1 row) + +MATCH (n) +CREATE (n)-[:elabel2]->(:test3) +RETURN 1; +WARNING: relcache reference leak: relation "elabel2" not closed +WARNING: relcache reference leak: relation "test3" not closed +WARNING: relcache reference leak: relation "_adj__adj_test3" not closed + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(39 rows) + +MATCH (n) +CREATE (n)<-[:elabel2]-(:test3) +RETURN 1; +WARNING: relcache reference leak: relation "elabel2" not closed +WARNING: relcache reference leak: relation "test3" not closed + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(78 rows) + -- -- Clean up -- DROP GRAPH cypher_create CASCADE; -NOTICE: drop cascades to 11 other objects +NOTICE: drop cascades to 15 other objects DETAIL: drop cascades to table cypher_create._ag_label_vertex drop cascades to table cypher_create._adj__ag_label_vertex drop cascades to table cypher_create._adj__adj__ag_label_vertex @@ -1468,6 +1644,10 @@ drop cascades to table cypher_create.test2 drop cascades to table cypher_create._adj_test2 drop cascades to table cypher_create._adj__adj_test2 drop cascades to table cypher_create.elabel +drop cascades to table cypher_create.elabel2 +drop cascades to table cypher_create.test3 +drop cascades to table cypher_create._adj_test3 +drop cascades to table cypher_create._adj__adj_test3 NOTICE: graph "cypher_create" has been dropped drop_graph ------------ diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index 6907ffec..c450d60d 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -181,6 +181,21 @@ MATCH ()-[:elabel*2]->() RETURN 1; MATCH ()-[:elabel*..2]->() RETURN 1; +MATCH (n:test) +CREATE ()-[:elabel]->(:test2) +RETURN 1; + +MATCH (n:test) +CREATE (n)-[:elabel]->(:test2) +RETURN 1; +MATCH (n) +CREATE (n)-[:elabel2]->(:test3) +RETURN 1; + +MATCH (n) +CREATE (n)<-[:elabel2]-(:test3) +RETURN 1; + -- -- Clean up -- diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 7359a05f..5b17a1bd 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -159,7 +159,11 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag foreach (lc1, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc1); - Assert(cypher_node->id_expr); + if (cypher_node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) { + i++; + continue; + } + cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); if (i % 2 == 1) { @@ -227,11 +231,13 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) foreach(lc, path->target_nodes) { cypher_target_node *node = (cypher_target_node *)lfirst(lc); bool isNull; - if(i % 2 == 1) - css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - else { + if(i % 2 == 1) { + if(node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) + css->vertex_ids[0][i/2] = DATUM_GET_GRAPHID(slot->tts_values[node->id_attr_num - 1]); + else + css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + } else { css->edge_ids[0][(i-1)/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - } i++; } @@ -248,83 +254,105 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) old_estate_es_result_relations_info = estate->es_result_relations; estate->es_result_relations = &resultRelInfo; - - ExecClearTuple(elemTupleSlot); - - // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; - elemTupleSlot->tts_isnull[0] = false; - if (node->id_attr_num != InvalidAttrNumber) { - slot->tts_values[node->id_attr_num - 1] = GRAPHID_GET_DATUM(css->vertex_ids[0][i]); - slot->tts_isnull[node->id_attr_num - 1] = false; - } - - // get the properties for this vertex - if (node->prop_attr_num == InvalidAttrNumber) { - elemTupleSlot->tts_values[1] = NULL; - elemTupleSlot->tts_isnull[1] = true; - } else { - elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; - elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->prop_attr_num - 1]; - } - - if (node->tuple_position != InvalidAttrNumber) { - slot->tts_values[node->tuple_position - 1] = VERTEX_GET_DATUM(create_vertex( - slot->tts_values[node->id_attr_num - 1], - css->graph_oid, - elemTupleSlot->tts_isnull[1] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[1]))); - slot->tts_isnull[node->tuple_position - 1] = false; - } - - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - - - if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { - - - resultRelInfo = node->adj_resultRelInfo; - elemTupleSlot = node->adj_elemTupleSlot; - - estate->es_result_relations = &resultRelInfo; - + if(!(node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE)){ ExecClearTuple(elemTupleSlot); - cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, (i * 2) + 1); + // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; + elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; elemTupleSlot->tts_isnull[0] = false; - - elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; - elemTupleSlot->tts_isnull[1] = false; - - elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; - elemTupleSlot->tts_isnull[2] = false; - + if (node->id_attr_num != InvalidAttrNumber) { + slot->tts_values[node->id_attr_num - 1] = GRAPHID_GET_DATUM(css->vertex_ids[0][i]); + slot->tts_isnull[node->id_attr_num - 1] = false; + } + // get the properties for this vertex if (node->prop_attr_num == InvalidAttrNumber) { - elemTupleSlot->tts_values[3] = NULL; - elemTupleSlot->tts_isnull[3] = true; + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; } else { - //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; - elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; + elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->prop_attr_num - 1]; } - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - - if (node->tuple_position != InvalidAttrNumber) { - slot->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(create_edge( - css->edge_ids[0][i], - css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1], - css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i], + slot->tts_values[node->tuple_position - 1] = VERTEX_GET_DATUM(create_vertex( + slot->tts_values[node->id_attr_num - 1], css->graph_oid, - elemTupleSlot->tts_isnull[3] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[3]))); + elemTupleSlot->tts_isnull[1] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[1]))); slot->tts_isnull[node->tuple_position - 1] = false; } + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); } + + + if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { + cypher_target_node *start_vertex; + if (node->dir == CYPHER_REL_DIR_RIGHT) { + start_vertex = list_nth(path->target_nodes, i * 2); + } else { + start_vertex = list_nth(path->target_nodes, (i * 2) + 2); + } + + if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ + label_cache_data *lcd = search_label_name_graph_cache(DATUM_GET_GRAPHID(slot->tts_values[node->id_attr_num - 1])>> ENTRY_ID_BITS, css->graph_oid); + start_vertex->adj_relid = lcd->relation; + Relation adj_rel = table_open(node->adj_relid, RowExclusiveLock); + start_vertex->adj_resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(node->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); + ExecOpenIndices(node->adj_resultRelInfo, false); + start_vertex->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); + } + + resultRelInfo = start_vertex->adj_resultRelInfo; + elemTupleSlot = start_vertex->adj_elemTupleSlot; + + estate->es_result_relations = &resultRelInfo; + + ExecClearTuple(elemTupleSlot); + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, (i * 2) + 1); + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; + + elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; + elemTupleSlot->tts_isnull[1] = false; + + elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; + elemTupleSlot->tts_isnull[2] = false; + + + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[3] = NULL; + elemTupleSlot->tts_isnull[3] = true; + } else { + //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; + elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; + } + + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + + + if (node->tuple_position != InvalidAttrNumber) { + slot->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(create_edge( + css->edge_ids[0][i], + css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1], + css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i], + css->graph_oid, + elemTupleSlot->tts_isnull[3] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[3]))); + slot->tts_isnull[node->tuple_position - 1] = false; + } + + if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ + ExecCloseIndices(start_vertex->adj_resultRelInfo); + table_close(start_vertex->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); + } + } + /* restore the old result relation info */ estate->es_result_relations = old_estate_es_result_relations_info; } @@ -357,7 +385,7 @@ static void end_cypher_create(CustomScanState *node) foreach (lc2, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc2); - if (!cypher_node->resultRelInfo) + if (!cypher_node->resultRelInfo || node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) continue; ExecCloseIndices(cypher_node->resultRelInfo); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index bc21ec41..793bcada 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -51,6 +51,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, \ prev_clause, NULL, add_rte_to_query) static ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, Alias *alias, bool add_rte_to_query); +static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, bool first_rte); static Query *analyze_cypher_clause(transform_method transform, cypher_clause *clause, cypher_parsestate *parent_cpstate); static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); @@ -306,6 +307,21 @@ static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *n } +static int get_target_entry_resno(cypher_parsestate *cpstate, List *target_list, char *name) { + ListCell *lc; + + foreach (lc, target_list) { + TargetEntry *te = (TargetEntry *)lfirst(lc); + + if (!strcmp(te->resname, name)) { + return te->resno; + } + } + + return -1; +} + + static void process_create_vertex( cypher_parsestate *cpstate, @@ -316,17 +332,31 @@ process_create_vertex( ParseState *pstate = (ParseState *)cpstate; cypher_target_node *target = make_ag_node(cypher_target_node); - if (node->label) - validate_or_create_vlabel(cpstate, node); - else - node->label = AG_DEFAULT_LABEL_VERTEX; + if (node->name) { // /ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); + Var *var; + if (var = colNameToVar(cpstate, node->name, false, -1)) { + + if (var->vartype != VERTEXOID) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CREATE vertex variable %s already exists", node->name))); + + if (node->props) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("previously declared nodes in a create clause cannot have properties"))); + if (node->label) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("previously declared variables cannot have a label"))); + + target->flags |= EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE; + + target->id_attr_num = get_target_entry_resno(cpstate, query->targetList, make_id_alias(node->name)); + ccp->target_nodes = lappend(ccp->target_nodes, target); + return ; + } - if (colNameToVar(cpstate, node->name, false, -1)) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("CREATE vertex variable %s already exists", node->name))); target->variable_name = node->name; query->targetList = lappend(query->targetList, @@ -378,6 +408,11 @@ process_create_vertex( } } + + if (node->label) + validate_or_create_vlabel(cpstate, node); + else + node->label = AG_DEFAULT_LABEL_VERTEX; label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); @@ -405,8 +440,6 @@ process_create_edge( if (edge->name) { - // /ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); - if (colNameToVar(cpstate, edge->name, false, -1)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("CREATE edge variable %s already exists", edge->name))); @@ -481,20 +514,23 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause query->commandType = CMD_SELECT; query->targetList = NIL; - if (clause->prev != NULL) - ereport(ERROR, (errmsg_internal("CREATE doesn't work with previous clauses"))); - //if (clause->next) - // ereport(ERROR, (errmsg_internal("CREATE doesn't work with next clauses"))); + cypher_create_target_nodes *target_nodes; + target_nodes = make_ag_node(cypher_create_target_nodes); + target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE; + target_nodes->graph_oid = cpstate->graph_oid; + + + if (clause->prev) { + handle_prev_clause(cpstate, query, clause->prev, true); + + target_nodes->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE; + } if (list_length(self->pattern) != 1) ereport(ERROR, (errmsg_internal("CREATE doesn't work with patterns"))); - cypher_create_target_nodes *target_nodes; - target_nodes = make_ag_node(cypher_create_target_nodes); - target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE; - target_nodes->graph_oid = cpstate->graph_oid; if (!clause->next) target_nodes->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; @@ -1863,4 +1899,25 @@ Query *cypher_parse_sub_analyze(Node *parseTree, cypher_parsestate *cpstate, Com free_cypher_parsestate(pstate); return query; -} \ No newline at end of file +} + +/* + * Utility function that helps a clause add the information needed to + * the query from the previous clause. + */ +static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, bool first_rte) { + ParseState *pstate = (ParseState *) cpstate; + int rtindex; + ParseNamespaceItem *pnsi; + + pnsi = transform_prev_cypher_clause(cpstate, clause, true); + + rtindex = list_length(pstate->p_rtable); + + // rte is the first RangeTblEntry in pstate + if (first_rte) + Assert(rtindex == 1); + + // add all the rte's attributes to the current queries targetlist + query->targetList = list_concat(query->targetList, expandNSItemAttrs(pstate, pnsi, 0, -1)); +} diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index d1a8ddb7..9bdd5ec7 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -75,7 +75,6 @@ Datum retrieve_vertex(PG_FUNCTION_ARGS) { if (!index_getnext_slot(desc, ForwardScanDirection, slot)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("id %lu does not exist", graphid))); - ereport(WARNING, (errmsg("here"))); tuple = ExecFetchSlotHeapTuple(slot, false, NULL); Datum properties = heap_getattr(tuple, 2, RelationGetDescr(rel), &isnull); @@ -88,8 +87,6 @@ Datum retrieve_vertex(PG_FUNCTION_ARGS) { PG_RETURN_NULL(); AG_RETURN_GTYPE_P(properties); - - } else { TableScanDesc scan_desc = table_beginscan(rel, GetActiveSnapshot(), 1, scan_keys); @@ -398,12 +395,11 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) values[2] = HASHSET_P_GET_DATUM(hset); nulls[2] = false; - if (cxt->max == -1 || cxt->current_path_length < cxt->max) { + if (cxt->max == -1 || cxt->current_path_length < cxt->max) scan_and_push_neighbors(cxt, id); - } else { + else removeElement(cxt->hashSet, edge_id); - } - + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); } else if (cxt->max == -1 || cxt->current_path_length < cxt->max) { scan_and_push_neighbors(cxt, id); @@ -415,6 +411,6 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) removeElement(cxt->hashSet, edge_id); } } - //destroyHashSetValue(cxt->hashSet); + SRF_RETURN_DONE(funcctx); } From 8a4e79779ef4ca892bedac2390dd8eed8e36fff0 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Sat, 20 Sep 2025 15:39:08 -0500 Subject: [PATCH 50/56] Update some test cases --- regress/expected/cypher_create.out | 289 ++++++++++------------------- regress/sql/cypher_create.sql | 11 +- src/backend/utils/edge_searching.c | 2 +- 3 files changed, 109 insertions(+), 193 deletions(-) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 1b6469b8..15cd56f7 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -393,22 +393,22 @@ MATCH ()-[]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=8.673..9.278 rows=0 loops=1) - -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=8.672..9.277 rows=0 loops=1) + Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=8.057..8.888 rows=0 loops=1) + -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=8.056..8.887 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.122..0.122 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.006 rows=12 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.104..0.105 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.002..0.005 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.002 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.009 rows=34 loops=1) + -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=34 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.008..0.008 rows=0 loops=36) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..25.00 rows=995 width=8) (never executed) Filter: (_age_default_alias_0.id OPERATOR(postgraph.<>) id) - Planning Time: 0.122 ms - Execution Time: 9.315 ms + Planning Time: 0.118 ms + Execution Time: 8.929 ms (16 rows) MATCH (q)-[]->(q)-[]->() RETURN 1; @@ -423,14 +423,14 @@ RETURN q; WARNING: here QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.037..0.280 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.003..0.010 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.002..0.005 rows=34 loops=1) + Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.043..0.303 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.003..0.011 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.003..0.005 rows=34 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=36) - Planning Time: 0.089 ms - Execution Time: 0.295 ms + Planning Time: 0.107 ms + Execution Time: 0.319 ms (8 rows) MATCH (q) @@ -462,25 +462,25 @@ MATCH ()-[]->(q) RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.305..0.325 rows=15 loops=1) + Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.531..0.553 rows=15 loops=1) Merge Cond: (q.id OPERATOR(postgraph.=) _age_default_alias_1.endid) - -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.009..0.018 rows=35 loops=1) + -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.011..0.021 rows=35 loops=1) Sort Key: q.id - -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.003..0.007 rows=34 loops=1) - -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.005..0.005 rows=1 loops=1) - -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.292..0.295 rows=15 loops=1) - -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.291..0.292 rows=15 loops=1) + -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.003..0.008 rows=34 loops=1) + -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.006..0.006 rows=1 loops=1) + -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.002..0.002 rows=1 loops=1) + -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.514..0.517 rows=15 loops=1) + -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.512..0.514 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.043..0.286 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.011 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) - -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.094..0.505 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.017 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.008 rows=34 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.003..0.003 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.007..0.007 rows=0 loops=36) - Planning Time: 0.175 ms - Execution Time: 0.366 ms + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.013..0.013 rows=0 loops=36) + Planning Time: 0.219 ms + Execution Time: 0.604 ms (19 rows) MATCH (q) @@ -511,20 +511,20 @@ MATCH (b)-[]->() RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.044..0.497 rows=540 loops=1) - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.040..0.317 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.020 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.004..0.014 rows=34 loops=1) + Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.036..0.421 rows=540 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.032..0.274 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.011 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) -> Seq Scan on test b_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 b_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.008..0.008 rows=0 loops=36) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=36) -> Materialize (cost=0.00..71.40 rows=2520 width=40) (actual time=0.000..0.002 rows=36 loops=15) - -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.007 rows=36 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.006 rows=36 loops=1) -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.001..0.003 rows=34 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - Planning Time: 0.225 ms - Execution Time: 0.533 ms + Planning Time: 0.114 ms + Execution Time: 0.452 ms (14 rows) MATCH (q) @@ -1101,19 +1101,19 @@ MATCH (q)-[]->()<-[]-(q) RETURN 1; EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- - Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=7.017..7.575 rows=0 loops=1) + Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=7.476..8.091 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.095..0.096 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.005 rows=12 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.100..0.101 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.006 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.007 rows=34 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.007..0.007 rows=0 loops=36) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=34 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.008..0.008 rows=0 loops=36) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 - Planning Time: 0.083 ms - Execution Time: 7.597 ms + Planning Time: 0.089 ms + Execution Time: 8.123 ms (13 rows) MATCH (q)<-[]-(q) RETURN 1; @@ -1149,31 +1149,31 @@ EXPLAIN MATCH ()-[]->()<-[]-() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.600..0.601 rows=0 loops=1) + Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.696..0.697 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) Rows Removed by Join Filter: 15 - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.301..0.302 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.316..0.318 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.041..0.297 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.014 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.004..0.008 rows=34 loops=1) - -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.040..0.312 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.015 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=34 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.002 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=36) - -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.289..0.292 rows=15 loops=1) - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.288..0.289 rows=15 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.008..0.008 rows=0 loops=36) + -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.369..0.372 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.368..0.369 rows=15 loops=1) Sort Key: _age_default_alias_3.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.031..0.282 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.010 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.006 rows=34 loops=1) - -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.032..0.356 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.015 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.007 rows=34 loops=1) + -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.003..0.003 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_2_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=36) - Planning Time: 0.153 ms - Execution Time: 0.640 ms + -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.009..0.009 rows=0 loops=36) + Planning Time: 0.151 ms + Execution Time: 0.741 ms (25 rows) MATCH ()-[]->()<-[]-() RETURN 1; @@ -1206,10 +1206,10 @@ EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.018..0.018 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) - -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.018..0.018 rows=0 loops=1) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.017..0.018 rows=0 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.017 rows=0 loops=1) + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.016 rows=0 loops=1) -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.002 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.013..0.013 rows=0 loops=1) -> Materialize (cost=165712.12..171712.12 rows=1200000 width=16) (never executed) @@ -1218,8 +1218,8 @@ EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (never executed) -> Seq Scan on test _age_default_alias_2 (cost=0.00..22.00 rows=1200 width=8) (never executed) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (never executed) - Planning Time: 0.094 ms - Execution Time: 0.042 ms + Planning Time: 0.095 ms + Execution Time: 0.041 ms (17 rows) MATCH (:test)-[]->()<-[]-(:test) RETURN 1; @@ -1237,17 +1237,17 @@ MATCH ()-[*1]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[*1]->()-[]->() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.370..0.463 rows=2 loops=1) - -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.042..0.349 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=34 loops=1) + Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.415..0.507 rows=2 loops=1) + -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.083..0.392 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.012 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.009..0.009 rows=0 loops=36) + -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.010..0.010 rows=0 loops=36) -> Function Scan on edge_search _age_default_alias_2 (cost=12.50..25.00 rows=500 width=8) (actual time=0.007..0.007 rows=0 loops=15) Filter: (_age_default_alias_1.hashset OPERATOR(postgraph.@>) id) - Planning Time: 0.088 ms - Execution Time: 0.485 ms + Planning Time: 0.085 ms + Execution Time: 0.526 ms (11 rows) SELECT * FROM cypher_create._adj__ag_label_vertex; @@ -1353,10 +1353,10 @@ MATCH ()-[]->() RETURN 1; (15 rows) MATCH ()-[*2]->() RETURN 1; -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x2de36fb0, chunk 0x2de37248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x2de36fb0, chunk 0x2de37248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x2de36fb0, chunk 0x2de37248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x2de36fb0, chunk 0x2de37248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x1f6fffb0, chunk 0x1f700248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x1f6fffb0, chunk 0x1f700248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x1f6fffb0, chunk 0x1f700248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x1f6fffb0, chunk 0x1f700248 ?column? ---------- 1 @@ -1492,7 +1492,7 @@ WARNING: relcache reference leak: relation "test2" not closed 1 (1 row) -MATCH (n) +MATCH (n:test) CREATE (n)-[:elabel2]->(:test3) RETURN 1; WARNING: relcache reference leak: relation "elabel2" not closed @@ -1501,47 +1501,9 @@ WARNING: relcache reference leak: relation "_adj__adj_test3" not closed ?column? ---------- 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 -(39 rows) +(1 row) -MATCH (n) +MATCH (n:test) CREATE (n)<-[:elabel2]-(:test3) RETURN 1; WARNING: relcache reference leak: relation "elabel2" not closed @@ -1549,84 +1511,31 @@ WARNING: relcache reference leak: relation "test3" not closed ?column? ---------- 1 +(1 row) + +MATCH (n:test) +CREATE (n)<-[:elabel2]-(:test3)-[:elabel2]->(:test3) +RETURN 1; +WARNING: relcache reference leak: relation "elabel2" not closed +WARNING: relcache reference leak: relation "test3" not closed +WARNING: relcache reference leak: relation "elabel2" not closed +WARNING: relcache reference leak: relation "test3" not closed + ?column? +---------- 1 +(1 row) + +MATCH (n:test) +CREATE (n)-[:elabel2]->(:test3)<-[:elabel2]-(:test3) +RETURN 1; +WARNING: relcache reference leak: relation "elabel2" not closed +WARNING: relcache reference leak: relation "test3" not closed +WARNING: relcache reference leak: relation "elabel2" not closed +WARNING: relcache reference leak: relation "test3" not closed + ?column? +---------- 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 -(78 rows) +(1 row) -- -- Clean up diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index c450d60d..cafea74b 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -188,14 +188,21 @@ RETURN 1; MATCH (n:test) CREATE (n)-[:elabel]->(:test2) RETURN 1; -MATCH (n) +MATCH (n:test) CREATE (n)-[:elabel2]->(:test3) RETURN 1; -MATCH (n) +MATCH (n:test) CREATE (n)<-[:elabel2]-(:test3) RETURN 1; +MATCH (n:test) +CREATE (n)<-[:elabel2]-(:test3)-[:elabel2]->(:test3) +RETURN 1; + +MATCH (n:test) +CREATE (n)-[:elabel2]->(:test3)<-[:elabel2]-(:test3) +RETURN 1; -- -- Clean up -- diff --git a/src/backend/utils/edge_searching.c b/src/backend/utils/edge_searching.c index 9bdd5ec7..87abb45e 100644 --- a/src/backend/utils/edge_searching.c +++ b/src/backend/utils/edge_searching.c @@ -376,7 +376,7 @@ Datum variable_edge_search(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); cxt->current_path[++cxt->current_path_length].count = 0; - cxt->current_path[cxt->current_path_length].count = id; + cxt->current_path[cxt->current_path_length].id = id; insert(cxt->hashSet, edge_id); From de43f3e37e90435158ecb1e0e2b5ce30eefce992 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Tue, 23 Sep 2025 14:24:23 -0500 Subject: [PATCH 51/56] Fix Locking Error Message for CREATE Clause RelationIdGetRelation increments reference counts and needs to get closed after use. --- regress/expected/cypher_create.out | 360 ++++++++++++++++----------- regress/sql/cypher_create.sql | 66 +++++ src/backend/executor/cypher_create.c | 140 +++++------ src/backend/parser/cypher_clause.c | 37 ++- src/include/nodes/cypher_nodes.h | 3 + 5 files changed, 369 insertions(+), 237 deletions(-) diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 15cd56f7..3c7ecc2a 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -33,7 +33,6 @@ USE GRAPH cypher_create; (1 row) CREATE (); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -- (0 rows) @@ -56,7 +55,6 @@ SELECT '{}'::postgraph.gtype; (1 row) CREATE ({msg: 'Hello World'}); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -- (0 rows) @@ -83,13 +81,11 @@ WARNING: relcache reference leak: relation "_adj__adj_test" not closed (1 row) CREATE (:test); -WARNING: relcache reference leak: relation "test" not closed -- (0 rows) CREATE (:test2); WARNING: relcache reference leak: relation "_adj__adj_test2" not closed -WARNING: relcache reference leak: relation "test2" not closed -- (0 rows) @@ -118,82 +114,56 @@ MATCH (a:test) RETURN a; (1 row) CREATE ()-[]->(); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) CREATE (a)-[]->(); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) CREATE (a)-[]->() RETURN a; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed a -------------------------------------------------------- {"id": 281474976710663, "label": "", "properties": {}} (1 row) CREATE (a)-[]->(b) RETURN a; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed a -------------------------------------------------------- {"id": 281474976710665, "label": "", "properties": {}} (1 row) CREATE (a {msg: 'hello vertex out'}) RETURN a; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed a --------------------------------------------------------------------------------- {"id": 281474976710667, "label": "", "properties": {"msg": "hello vertex out"}} (1 row) CREATE (a {msg: 'hello vertex out'}) RETURN a.msg; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed msg -------------------- "hello vertex out" (1 row) CREATE (a {msg: 'hello vertex out'})-[]->(b) RETURN a; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed a --------------------------------------------------------------------------------- {"id": 281474976710669, "label": "", "properties": {"msg": "hello vertex out"}} (1 row) CREATE (a)-[e]->(b) RETURN e; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed e ---------------------------------------------------------------------------------------------------------------- {"id": 844424930131974, "start_id": 281474976710671, "end_id": 281474976710672, "label": "", "properties": {}} (1 row) CREATE (a)-[e {msg: 'hello edge'}]->(b) RETURN e; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed - e ----------------------------------------------------------------------------------------------------------------- - {"id": 844424930131975, "start_id": 281474976710673, "end_id": 281474976710674, "label": "", "properties": {}} + e +----------------------------------------------------------------------------------------------------------------------------------- + {"id": 844424930131975, "start_id": 281474976710673, "end_id": 281474976710674, "label": "", "properties": {"msg": "hello edge"}} (1 row) CREATE ()-[{msg: 'hello edge'}]->(); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) @@ -206,14 +176,11 @@ SELECT * FROM cypher_create._adj__ag_label_vertex; (3, 4) | (1, 9) | (1, 10) | (3, 5) | (1, 13) | (1, 14) | (3, 6) | (1, 15) | (1, 16) | - (3, 7) | (1, 17) | (1, 18) | + (3, 7) | (1, 17) | (1, 18) | {"msg": "hello edge"} (3, 8) | (1, 19) | (1, 20) | {"msg": "hello edge"} (8 rows) CREATE ()-[:elabel]->(); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "elabel" not closed -- (0 rows) @@ -316,16 +283,10 @@ MATCH ()<-[]-(a) RETURN a; (9 rows) CREATE ()<-[{msg: 'Hello edge'}]-(); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) CREATE ()-[{msg: 'Hello edge'}]->(); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) @@ -338,7 +299,7 @@ MATCH ()<-[a]-() RETURN a; {"id": 844424930131972, "start_id": 281474976710665, "end_id": 281474976710666, "label": "", "properties": {}} {"id": 844424930131973, "start_id": 281474976710669, "end_id": 281474976710670, "label": "", "properties": {}} {"id": 844424930131974, "start_id": 281474976710671, "end_id": 281474976710672, "label": "", "properties": {}} - {"id": 844424930131975, "start_id": 281474976710673, "end_id": 281474976710674, "label": "", "properties": {}} + {"id": 844424930131975, "start_id": 281474976710673, "end_id": 281474976710674, "label": "", "properties": {"msg": "hello edge"}} {"id": 844424930131976, "start_id": 281474976710675, "end_id": 281474976710676, "label": "", "properties": {"msg": "hello edge"}} {"id": 2251799813685249, "start_id": 281474976710677, "end_id": 281474976710678, "label": "elabel", "properties": {}} {"id": 844424930131977, "start_id": 281474976710680, "end_id": 281474976710679, "label": "", "properties": {"msg": "Hello edge"}} @@ -347,32 +308,20 @@ MATCH ()<-[a]-() RETURN a; CYPHER WITH 1 as a CREATE (); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -- (0 rows) CREATE () RETURN 1 as a; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed a --- 1 (1 row) CREATE ()-[]->()-[]->(); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) CREATE ()-[]->()-[]->(); -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "_ag_label_edge" not closed -- (0 rows) @@ -393,22 +342,22 @@ MATCH ()-[]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=8.057..8.888 rows=0 loops=1) - -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=8.056..8.887 rows=0 loops=1) + Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.908..8.465 rows=0 loops=1) + -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.908..8.464 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.104..0.105 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.002..0.005 rows=12 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.173..0.174 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.002..0.008 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=34 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.008..0.008 rows=0 loops=36) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.005..0.014 rows=34 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.013..0.013 rows=0 loops=36) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..25.00 rows=995 width=8) (never executed) Filter: (_age_default_alias_0.id OPERATOR(postgraph.<>) id) - Planning Time: 0.118 ms - Execution Time: 8.929 ms + Planning Time: 0.130 ms + Execution Time: 8.502 ms (16 rows) MATCH (q)-[]->(q)-[]->() RETURN 1; @@ -423,14 +372,14 @@ RETURN q; WARNING: here QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.043..0.303 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.003..0.011 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.003..0.005 rows=34 loops=1) + Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.038..0.301 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.003..0.010 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.002..0.005 rows=34 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=36) - Planning Time: 0.107 ms - Execution Time: 0.319 ms + Planning Time: 0.092 ms + Execution Time: 0.317 ms (8 rows) MATCH (q) @@ -462,25 +411,25 @@ MATCH ()-[]->(q) RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.531..0.553 rows=15 loops=1) + Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.329..0.349 rows=15 loops=1) Merge Cond: (q.id OPERATOR(postgraph.=) _age_default_alias_1.endid) - -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.011..0.021 rows=35 loops=1) + -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.010..0.019 rows=35 loops=1) Sort Key: q.id - -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.003..0.008 rows=34 loops=1) - -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.006..0.006 rows=1 loops=1) - -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.002..0.002 rows=1 loops=1) - -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.514..0.517 rows=15 loops=1) - -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.512..0.514 rows=15 loops=1) + -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.002..0.008 rows=34 loops=1) + -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.005..0.005 rows=1 loops=1) + -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) + -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.315..0.318 rows=15 loops=1) + -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.314..0.315 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.094..0.505 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.017 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.008 rows=34 loops=1) - -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.003..0.003 rows=1 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.050..0.309 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.013..0.013 rows=0 loops=36) - Planning Time: 0.219 ms - Execution Time: 0.604 ms + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.008..0.008 rows=0 loops=36) + Planning Time: 0.215 ms + Execution Time: 0.400 ms (19 rows) MATCH (q) @@ -511,20 +460,20 @@ MATCH (b)-[]->() RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.036..0.421 rows=540 loops=1) - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.032..0.274 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.011 rows=36 loops=1) + Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.039..0.478 rows=540 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.031..0.293 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=36 loops=1) -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) -> Seq Scan on test b_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 b_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=36) -> Materialize (cost=0.00..71.40 rows=2520 width=40) (actual time=0.000..0.002 rows=36 loops=15) - -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.006 rows=36 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.007 rows=36 loops=1) -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.001..0.003 rows=34 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - Planning Time: 0.114 ms - Execution Time: 0.452 ms + Planning Time: 0.115 ms + Execution Time: 0.511 ms (14 rows) MATCH (q) @@ -1101,19 +1050,19 @@ MATCH (q)-[]->()<-[]-(q) RETURN 1; EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- - Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=7.476..8.091 rows=0 loops=1) + Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.408..6.934 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.100..0.101 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.006 rows=12 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.100..0.100 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.005 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=34 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.008..0.008 rows=0 loops=36) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 - Planning Time: 0.089 ms - Execution Time: 8.123 ms + Planning Time: 0.086 ms + Execution Time: 6.955 ms (13 rows) MATCH (q)<-[]-(q) RETURN 1; @@ -1149,31 +1098,31 @@ EXPLAIN MATCH ()-[]->()<-[]-() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.696..0.697 rows=0 loops=1) + Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.604..0.605 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) Rows Removed by Join Filter: 15 - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.316..0.318 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.304..0.305 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.040..0.312 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.015 rows=36 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.042..0.299 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.014 rows=36 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=34 loops=1) - -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.002 rows=1 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.008..0.008 rows=0 loops=36) - -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.369..0.372 rows=15 loops=1) - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.368..0.369 rows=15 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=36) + -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.291..0.294 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.290..0.291 rows=15 loops=1) Sort Key: _age_default_alias_3.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.032..0.356 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.015 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.007 rows=34 loops=1) - -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.003..0.003 rows=1 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.032..0.283 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.010 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.006 rows=34 loops=1) + -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_2_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.009..0.009 rows=0 loops=36) - Planning Time: 0.151 ms - Execution Time: 0.741 ms + -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=36) + Planning Time: 0.160 ms + Execution Time: 0.642 ms (25 rows) MATCH ()-[]->()<-[]-() RETURN 1; @@ -1203,14 +1152,14 @@ EXPLAIN MATCH (:test)-[]->()<-[]-(:test) RETURN 1; EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- - Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.018..0.018 rows=0 loops=1) + Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.018..0.019 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) - -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.017..0.018 rows=0 loops=1) + -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.018..0.018 rows=0 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.016 rows=0 loops=1) - -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.002 rows=1 loops=1) + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.017 rows=0 loops=1) + -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.003 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.013..0.013 rows=0 loops=1) -> Materialize (cost=165712.12..171712.12 rows=1200000 width=16) (never executed) -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (never executed) @@ -1218,8 +1167,8 @@ EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (never executed) -> Seq Scan on test _age_default_alias_2 (cost=0.00..22.00 rows=1200 width=8) (never executed) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (never executed) - Planning Time: 0.095 ms - Execution Time: 0.041 ms + Planning Time: 0.128 ms + Execution Time: 0.040 ms (17 rows) MATCH (:test)-[]->()<-[]-(:test) RETURN 1; @@ -1237,17 +1186,17 @@ MATCH ()-[*1]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[*1]->()-[]->() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.415..0.507 rows=2 loops=1) - -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.083..0.392 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.012 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) + Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.390..0.485 rows=2 loops=1) + -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.043..0.367 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=34 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.010..0.010 rows=0 loops=36) + -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.009..0.009 rows=0 loops=36) -> Function Scan on edge_search _age_default_alias_2 (cost=12.50..25.00 rows=500 width=8) (actual time=0.007..0.007 rows=0 loops=15) Filter: (_age_default_alias_1.hashset OPERATOR(postgraph.@>) id) - Planning Time: 0.085 ms - Execution Time: 0.526 ms + Planning Time: 0.096 ms + Execution Time: 0.507 ms (11 rows) SELECT * FROM cypher_create._adj__ag_label_vertex; @@ -1259,7 +1208,7 @@ SELECT * FROM cypher_create._adj__ag_label_vertex; (3, 4) | (1, 9) | (1, 10) | (3, 5) | (1, 13) | (1, 14) | (3, 6) | (1, 15) | (1, 16) | - (3, 7) | (1, 17) | (1, 18) | + (3, 7) | (1, 17) | (1, 18) | {"msg": "hello edge"} (3, 8) | (1, 19) | (1, 20) | {"msg": "hello edge"} (8, 1) | (1, 21) | (1, 22) | (3, 9) | (1, 24) | (1, 23) | {"msg": "Hello edge"} @@ -1353,10 +1302,10 @@ MATCH ()-[]->() RETURN 1; (15 rows) MATCH ()-[*2]->() RETURN 1; -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x1f6fffb0, chunk 0x1f700248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x1f6fffb0, chunk 0x1f700248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x1f6fffb0, chunk 0x1f700248 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x1f6fffb0, chunk 0x1f700248 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x11f47fe0, chunk 0x11f48278 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x11f47fe0, chunk 0x11f48278 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x11f47fe0, chunk 0x11f48278 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x11f47fe0, chunk 0x11f48278 ?column? ---------- 1 @@ -1474,19 +1423,33 @@ MATCH ()-[:elabel*..2]->() RETURN 1; MATCH (n:test) CREATE ()-[:elabel]->(:test2) RETURN 1; -WARNING: relcache reference leak: relation "_ag_label_vertex" not closed -WARNING: relcache reference leak: relation "test2" not closed -WARNING: relcache reference leak: relation "elabel" not closed ?column? ---------- 1 (1 row) +EXPLAIN +MATCH (n:test) +CREATE (n)-[:elabel]->(:test2) +RETURN n; + QUERY PLAN +------------------------------------------------------------------------- + Custom Scan (Cypher Create) (cost=0.00..0.00 rows=0 width=32) + -> Subquery Scan on _ (cost=0.00..187.00 rows=1200 width=32) + -> Seq Scan on test n (cost=0.00..175.00 rows=1200 width=104) +(3 rows) + +MATCH (n:test) +CREATE (n)-[:elabel]->(:test2) +RETURN n; + n +------------------------------------------------------------- + {"id": 1125899906842625, "label": "test", "properties": {}} +(1 row) + MATCH (n:test) CREATE (n)-[:elabel]->(:test2) RETURN 1; -WARNING: relcache reference leak: relation "elabel" not closed -WARNING: relcache reference leak: relation "test2" not closed ?column? ---------- 1 @@ -1495,8 +1458,6 @@ WARNING: relcache reference leak: relation "test2" not closed MATCH (n:test) CREATE (n)-[:elabel2]->(:test3) RETURN 1; -WARNING: relcache reference leak: relation "elabel2" not closed -WARNING: relcache reference leak: relation "test3" not closed WARNING: relcache reference leak: relation "_adj__adj_test3" not closed ?column? ---------- @@ -1506,8 +1467,6 @@ WARNING: relcache reference leak: relation "_adj__adj_test3" not closed MATCH (n:test) CREATE (n)<-[:elabel2]-(:test3) RETURN 1; -WARNING: relcache reference leak: relation "elabel2" not closed -WARNING: relcache reference leak: relation "test3" not closed ?column? ---------- 1 @@ -1516,10 +1475,6 @@ WARNING: relcache reference leak: relation "test3" not closed MATCH (n:test) CREATE (n)<-[:elabel2]-(:test3)-[:elabel2]->(:test3) RETURN 1; -WARNING: relcache reference leak: relation "elabel2" not closed -WARNING: relcache reference leak: relation "test3" not closed -WARNING: relcache reference leak: relation "elabel2" not closed -WARNING: relcache reference leak: relation "test3" not closed ?column? ---------- 1 @@ -1528,15 +1483,132 @@ WARNING: relcache reference leak: relation "test3" not closed MATCH (n:test) CREATE (n)-[:elabel2]->(:test3)<-[:elabel2]-(:test3) RETURN 1; -WARNING: relcache reference leak: relation "elabel2" not closed -WARNING: relcache reference leak: relation "test3" not closed -WARNING: relcache reference leak: relation "elabel2" not closed -WARNING: relcache reference leak: relation "test3" not closed ?column? ---------- 1 (1 row) +--CREATE (n:vlabel_loop)-[:elabel_loop]->(n) RETURN 1; +MATCH (n)-[:elabel_loop]->(n) RETURN n; +ERROR: Edge label "elabel_loop" does not exist +MATCH (n:test) +CREATE (n)<-[:elabel2]-(:test3)-[:elabel2]->(:test3) +RETURN n; + n +------------------------------------------------------------- + {"id": 1125899906842625, "label": "test", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)-[:elabel2]->(:test3)<-[:elabel2]-(:test3) +RETURN n; + n +------------------------------------------------------------- + {"id": 1125899906842625, "label": "test", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)<-[:elabel2]-(m:test3)-[:elabel2]->(:test3) +RETURN m; + m +-------------------------------------------------------------- + {"id": 2814749767106571, "label": "test3", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)-[:elabel2]->(m:test3)<-[:elabel2]-(:test3) +RETURN m; + m +-------------------------------------------------------------- + {"id": 2814749767106573, "label": "test3", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)<-[:elabel2]-(:test3)-[:elabel2]->(m:test3) +RETURN m; + m +-------------------------------------------------------------- + {"id": 2814749767106576, "label": "test3", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)-[:elabel2]->(:test3)<-[:elabel2]-(m:test3) +RETURN m; + m +-------------------------------------------------------------- + {"id": 2814749767106578, "label": "test3", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)<-[m:elabel2]-(:test3)-[:elabel2]->(:test3) +RETURN m; + m +-------------------------------------------------------------------------------------------------------------------------- + {"id": 2533274790395923, "start_id": 2814749767106579, "end_id": 1125899906842625, "label": "elabel2", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)-[m:elabel2]->(:test3)<-[:elabel2]-(:test3) +RETURN m; + m +-------------------------------------------------------------------------------------------------------------------------- + {"id": 2533274790395925, "start_id": 1125899906842625, "end_id": 2814749767106581, "label": "elabel2", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)<-[:elabel2]-(:test3)-[m:elabel2]->(:test3) +RETURN m; + m +-------------------------------------------------------------------------------------------------------------------------- + {"id": 2533274790395928, "start_id": 2814749767106583, "end_id": 2814749767106584, "label": "elabel2", "properties": {}} +(1 row) + +MATCH (n:test) +CREATE (n)-[:elabel2]->(:test3)<-[m:elabel2]-(:test3) +RETURN m; + m +-------------------------------------------------------------------------------------------------------------------------- + {"id": 2533274790395930, "start_id": 2814749767106586, "end_id": 2814749767106585, "label": "elabel2", "properties": {}} +(1 row) + +SELECT * FROM postgraph.ag_label; + name | graph | id | kind | relation | vertex_adjlist | label_path +----------------------------+-------+----+------+------------------------------------------+-------------------------------------+------------------------------------------- + _adj__adj__ag_label_vertex | 18815 | 2 | e | 18832 | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18815 | 1 | v | 18818 | 18827 | _ag_label_vertex + _ag_label_edge | 18815 | 3 | e | 18844 | | _ag_label_edge + _adj__adj__ag_label_vertex | 18853 | 2 | e | 18870 | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18853 | 1 | v | 18856 | 18865 | _ag_label_vertex + _ag_label_edge | 18853 | 3 | e | 18882 | | _ag_label_edge + _adj__adj__ag_label_vertex | 18891 | 2 | e | cypher_create._adj__adj__ag_label_vertex | | _ag_label_edge._adj__adj__ag_label_vertex + _ag_label_vertex | 18891 | 1 | v | cypher_create._ag_label_vertex | cypher_create._adj__ag_label_vertex | _ag_label_vertex + _ag_label_edge | 18891 | 3 | e | cypher_create._ag_label_edge | | _ag_label_edge + _adj__adj_test | 18891 | 5 | e | cypher_create._adj__adj_test | | _ag_label_edge._adj__adj_test + test | 18891 | 4 | v | cypher_create.test | cypher_create._adj_test | _ag_label_vertex.test + _adj__adj_test2 | 18891 | 7 | e | cypher_create._adj__adj_test2 | | _ag_label_edge._adj__adj_test2 + test2 | 18891 | 6 | v | cypher_create.test2 | cypher_create._adj_test2 | _ag_label_vertex.test2 + elabel | 18891 | 8 | e | cypher_create.elabel | | _ag_label_edge.elabel + elabel2 | 18891 | 9 | e | cypher_create.elabel2 | | _ag_label_edge.elabel2 + _adj__adj_test3 | 18891 | 11 | e | cypher_create._adj__adj_test3 | | _ag_label_edge._adj__adj_test3 + test3 | 18891 | 10 | v | cypher_create.test3 | cypher_create._adj_test3 | _ag_label_vertex.test3 +(17 rows) + +SELECT * FROM cypher_create._adj_vlabel_loop; +ERROR: relation "cypher_create._adj_vlabel_loop" does not exist +LINE 1: SELECT * FROM cypher_create._adj_vlabel_loop; + ^ +SELECT * FROM cypher_create.vlabel_loop; +ERROR: relation "cypher_create.vlabel_loop" does not exist +LINE 1: SELECT * FROM cypher_create.vlabel_loop; + ^ +SELECT * FROM cypher_create._adj_elabel_loop; +ERROR: relation "cypher_create._adj_elabel_loop" does not exist +LINE 1: SELECT * FROM cypher_create._adj_elabel_loop; + ^ +SELECT * FROM cypher_create.elabel_loop; +ERROR: relation "cypher_create.elabel_loop" does not exist +LINE 1: SELECT * FROM cypher_create.elabel_loop; + ^ -- -- Clean up -- diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql index cafea74b..bb7bdce2 100644 --- a/regress/sql/cypher_create.sql +++ b/regress/sql/cypher_create.sql @@ -185,9 +185,19 @@ MATCH (n:test) CREATE ()-[:elabel]->(:test2) RETURN 1; +EXPLAIN +MATCH (n:test) +CREATE (n)-[:elabel]->(:test2) +RETURN n; + +MATCH (n:test) +CREATE (n)-[:elabel]->(:test2) +RETURN n; + MATCH (n:test) CREATE (n)-[:elabel]->(:test2) RETURN 1; + MATCH (n:test) CREATE (n)-[:elabel2]->(:test3) RETURN 1; @@ -203,6 +213,62 @@ RETURN 1; MATCH (n:test) CREATE (n)-[:elabel2]->(:test3)<-[:elabel2]-(:test3) RETURN 1; + +--CREATE (n:vlabel_loop)-[:elabel_loop]->(n) RETURN 1; + +MATCH (n)-[:elabel_loop]->(n) RETURN n; + + +MATCH (n:test) +CREATE (n)<-[:elabel2]-(:test3)-[:elabel2]->(:test3) +RETURN n; + +MATCH (n:test) +CREATE (n)-[:elabel2]->(:test3)<-[:elabel2]-(:test3) +RETURN n; + +MATCH (n:test) +CREATE (n)<-[:elabel2]-(m:test3)-[:elabel2]->(:test3) +RETURN m; + +MATCH (n:test) +CREATE (n)-[:elabel2]->(m:test3)<-[:elabel2]-(:test3) +RETURN m; + + +MATCH (n:test) +CREATE (n)<-[:elabel2]-(:test3)-[:elabel2]->(m:test3) +RETURN m; + +MATCH (n:test) +CREATE (n)-[:elabel2]->(:test3)<-[:elabel2]-(m:test3) +RETURN m; + +MATCH (n:test) +CREATE (n)<-[m:elabel2]-(:test3)-[:elabel2]->(:test3) +RETURN m; + +MATCH (n:test) +CREATE (n)-[m:elabel2]->(:test3)<-[:elabel2]-(:test3) +RETURN m; + + +MATCH (n:test) +CREATE (n)<-[:elabel2]-(:test3)-[m:elabel2]->(:test3) +RETURN m; + +MATCH (n:test) +CREATE (n)-[:elabel2]->(:test3)<-[m:elabel2]-(:test3) +RETURN m; + +SELECT * FROM postgraph.ag_label; + +SELECT * FROM cypher_create._adj_vlabel_loop; +SELECT * FROM cypher_create.vlabel_loop; + +SELECT * FROM cypher_create._adj_elabel_loop; +SELECT * FROM cypher_create.elabel_loop; + -- -- Clean up -- diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 5b17a1bd..b1edf99c 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -171,24 +171,24 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, int eflag continue; } - Relation rel = table_open(cypher_node->relid, RowExclusiveLock); + cypher_node->rel = table_open(cypher_node->relid, RowExclusiveLock); cypher_node->resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + InitResultRelInfo(cypher_node->resultRelInfo, cypher_node->rel, list_length(estate->es_range_table), NULL, estate->es_instrument); // Open all indexes for the relation ExecOpenIndices(cypher_node->resultRelInfo, false); // Setup the relation's tuple slot - cypher_node->elemTupleSlot = table_slot_create(rel, &estate->es_tupleTable); + cypher_node->elemTupleSlot = table_slot_create(cypher_node->rel, &estate->es_tupleTable); - Relation adj_rel = table_open(cypher_node->adj_relid, RowExclusiveLock); + cypher_node->adj_rel = table_open(cypher_node->adj_relid, RowExclusiveLock); cypher_node->adj_resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + InitResultRelInfo(cypher_node->adj_resultRelInfo, cypher_node->adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); ExecOpenIndices(cypher_node->adj_resultRelInfo, false); // Setup the relation's tuple slot - cypher_node->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); + cypher_node->adj_elemTupleSlot = table_slot_create(cypher_node->adj_rel, &estate->es_tupleTable); i++; @@ -214,12 +214,10 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) if (TupIsNull(return_slot)) return return_slot; - // if ((return_slot->tts_flags = return_slot & TTS_FLAG_EMPTY)) - // return NULL; + econtext->ecxt_scantuple = csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - TupleTableSlot *slot = econtext->ecxt_scantuple;//csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - //slot->tts_ops->materialize(slot); + TupleTableSlot *slot = econtext->ecxt_scantuple; if (list_length(css->pattern) != 1) ereport(ERROR, (errmsg_internal("executor create found a multi pattern"))); @@ -278,7 +276,7 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) slot->tts_values[node->tuple_position - 1] = VERTEX_GET_DATUM(create_vertex( slot->tts_values[node->id_attr_num - 1], css->graph_oid, - elemTupleSlot->tts_isnull[1] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[1]))); + elemTupleSlot->tts_isnull[1] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[1]))); slot->tts_isnull[node->tuple_position - 1] = false; } @@ -287,82 +285,81 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *csnode) } - if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { - cypher_target_node *start_vertex; - if (node->dir == CYPHER_REL_DIR_RIGHT) { - start_vertex = list_nth(path->target_nodes, i * 2); - } else { - start_vertex = list_nth(path->target_nodes, (i * 2) + 2); - } - - if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ - label_cache_data *lcd = search_label_name_graph_cache(DATUM_GET_GRAPHID(slot->tts_values[node->id_attr_num - 1])>> ENTRY_ID_BITS, css->graph_oid); - start_vertex->adj_relid = lcd->relation; - Relation adj_rel = table_open(node->adj_relid, RowExclusiveLock); - start_vertex->adj_resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(node->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); - cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); - ExecOpenIndices(node->adj_resultRelInfo, false); - start_vertex->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); - } - - resultRelInfo = start_vertex->adj_resultRelInfo; - elemTupleSlot = start_vertex->adj_elemTupleSlot; - - estate->es_result_relations = &resultRelInfo; - - ExecClearTuple(elemTupleSlot); - cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, (i * 2) + 1); - // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; - elemTupleSlot->tts_isnull[0] = false; + if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { + cypher_target_node *start_vertex; + if (node->dir == CYPHER_REL_DIR_RIGHT) { + start_vertex = list_nth(path->target_nodes, i * 2); + } else { + start_vertex = list_nth(path->target_nodes, (i * 2) + 2); + } + + if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ + label_cache_data *lcd = search_label_graph_oid_cache( + css->graph_oid, + DATUM_GET_GRAPHID(slot->tts_values[start_vertex->id_attr_num - 1]) >> ENTRY_ID_BITS); + start_vertex->adj_relid = lcd->relation; + + Relation adj_rel = table_open(start_vertex->adj_relid, RowExclusiveLock); + + start_vertex->adj_resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(start_vertex->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + ExecOpenIndices(start_vertex->adj_resultRelInfo, false); + + start_vertex->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); + } + + resultRelInfo = start_vertex->adj_resultRelInfo; + elemTupleSlot = start_vertex->adj_elemTupleSlot; + + estate->es_result_relations = &resultRelInfo; + + ExecClearTuple(elemTupleSlot); + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, (i * 2) + 1); + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; - elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; - elemTupleSlot->tts_isnull[1] = false; + elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; + elemTupleSlot->tts_isnull[1] = false; - elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; - elemTupleSlot->tts_isnull[2] = false; - + elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; + elemTupleSlot->tts_isnull[2] = false; - if (node->prop_attr_num == InvalidAttrNumber) { - elemTupleSlot->tts_values[3] = NULL; - elemTupleSlot->tts_isnull[3] = true; - } else { - //TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; - elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; - elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; - } - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[3] = NULL; + elemTupleSlot->tts_isnull[3] = true; + } else { + elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; + } + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - if (node->tuple_position != InvalidAttrNumber) { - slot->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(create_edge( + if (node->tuple_position != InvalidAttrNumber) { + slot->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(create_edge( css->edge_ids[0][i], css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1], css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i], css->graph_oid, elemTupleSlot->tts_isnull[3] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[3]))); - slot->tts_isnull[node->tuple_position - 1] = false; - } + slot->tts_isnull[node->tuple_position - 1] = false; + } - if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ - ExecCloseIndices(start_vertex->adj_resultRelInfo); - table_close(start_vertex->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); - } + if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ + ExecCloseIndices(start_vertex->adj_resultRelInfo); + table_close(start_vertex->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); } + } - /* restore the old result relation info */ + // restore the old result relation info estate->es_result_relations = old_estate_es_result_relations_info; } - // if(CYPHER_CLAUSE_IS_TERMINAL(css->flags)) - // return NULL; if(!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) { econtext->ecxt_scantuple = ExecProject(csnode->ss.ps.lefttree->ps_ProjInfo); return ExecProject(csnode->ss.ps.ps_ProjInfo); - // return return_slot; } } @@ -389,12 +386,11 @@ static void end_cypher_create(CustomScanState *node) continue; ExecCloseIndices(cypher_node->resultRelInfo); - table_close(cypher_node->resultRelInfo->ri_RelationDesc, RowExclusiveLock); + table_close(cypher_node->rel, RowExclusiveLock); ExecCloseIndices(cypher_node->adj_resultRelInfo); - table_close(cypher_node->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); - - - + table_close(cypher_node->adj_rel, RowExclusiveLock); + //RelationDecrementReferenceCount(cypher_node->relation); + //RelationDecrementReferenceCount(lcd->relation); } } } diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 793bcada..a608b7a9 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -314,6 +314,7 @@ static int get_target_entry_resno(cypher_parsestate *cpstate, List *target_list, TargetEntry *te = (TargetEntry *)lfirst(lc); if (!strcmp(te->resname, name)) { + //te->expr = add_volatile_wrapper(te->expr); return te->resno; } } @@ -321,7 +322,6 @@ static int get_target_entry_resno(cypher_parsestate *cpstate, List *target_list, return -1; } - static void process_create_vertex( cypher_parsestate *cpstate, @@ -333,13 +333,13 @@ process_create_vertex( cypher_target_node *target = make_ag_node(cypher_target_node); - if (node->name) { // /ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); - Var *var; - if (var = colNameToVar(cpstate, node->name, false, -1)) { + Var *var = NULL; + if ((var = colNameToVar(cpstate, node->name, false, -1)) || + (target->id_attr_num = get_target_entry_resno(cpstate, query->targetList, make_id_alias(node->name))) != -1) { - if (var->vartype != VERTEXOID) + if (var && var->vartype != VERTEXOID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("CREATE vertex variable %s already exists", node->name))); @@ -353,8 +353,9 @@ process_create_vertex( target->flags |= EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE; target->id_attr_num = get_target_entry_resno(cpstate, query->targetList, make_id_alias(node->name)); + ccp->target_nodes = lappend(ccp->target_nodes, target); - return ; + return; } @@ -375,12 +376,6 @@ process_create_vertex( false)); } else { target->prop_attr_num = InvalidAttrNumber; - /*query->targetList = lappend(query->targetList, - makeTargetEntry( - make_gtype_placeholder(cpstate), - target->prop_attr_num = pstate->p_next_resno++, - make_property_alias(node->name), - false));*/ } query->targetList = lappend(query->targetList, @@ -415,9 +410,11 @@ process_create_vertex( node->label = AG_DEFAULT_LABEL_VERTEX; label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); - - target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(lcd->relation), 1); + Relation *rel = RelationIdGetRelation(lcd->relation); + target->id_expr = (Expr *)build_column_default(rel, 1); target->relid = lcd->relation; + table_close(rel, NoLock); + target->adj_relid = lcd->vertex_adjlist; ccp->target_nodes = lappend(ccp->target_nodes, target); @@ -461,12 +458,6 @@ process_create_edge( false)); } else { target->prop_attr_num = InvalidAttrNumber; - /*query->targetList = lappend(query->targetList, - makeTargetEntry( - make_gtype_placeholder(cpstate), - target->prop_attr_num = pstate->p_next_resno++, - make_property_alias(edge->name), - false));*/ } query->targetList = lappend(query->targetList, @@ -500,8 +491,12 @@ process_create_edge( label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); target->relid = lcd->relation; + + Relation *rel = RelationIdGetRelation(lcd->relation); + target->id_expr = (Expr *)build_column_default(rel, 1); + + table_close(rel, NoLock); - target->id_expr = (Expr *)build_column_default(RelationIdGetRelation(target->relid), 1); ccp->target_nodes = lappend(ccp->target_nodes, target); } diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index 0a97ebf3..620b269a 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -369,6 +369,9 @@ typedef struct cypher_target_node char *variable_name; AttrNumber tuple_position; +Relation rel; +Relation adj_rel; + } cypher_target_node; #define CYPHER_TARGET_NODE_FLAG_NONE 0x0000 From 7338257e86d87c6e76bdda9234b4ce2505bd7b15 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Fri, 10 Oct 2025 19:54:44 -0500 Subject: [PATCH 52/56] Start Reimplementing MERGE Clause --- Makefile | 4 +- src/backend/optimizer/cypher_createplan.c | 64 +- src/backend/optimizer/cypher_pathnode.c | 46 +- src/backend/optimizer/cypher_paths.c | 33 +- src/backend/parser/cypher_clause.c | 913 +++++++++++++++++++++- src/include/executor/cypher_executor.h | 3 +- src/include/optimizer/cypher_createplan.h | 3 + src/include/optimizer/cypher_pathnode.h | 5 +- 8 files changed, 1039 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 911719f0..05426960 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ OBJS = src/backend/postgraph.o \ src/backend/commands/graph_commands.o \ src/backend/commands/label_commands.o \ src/backend/executor/cypher_create.o \ + src/backend/executor/cypher_merge.o \ src/backend/nodes/ag_nodes.o \ src/backend/nodes/cypher_copyfuncs.o \ src/backend/nodes/cypher_outfuncs.o \ @@ -68,7 +69,8 @@ EXTENSION = postgraph DATA = postgraph--0.1.0.sql REGRESS = new_cypher \ - cypher_create + cypher_create \ + cypher_merge srcdir=`pwd` POSTGIS_DIR ?= postgis_dir diff --git a/src/backend/optimizer/cypher_createplan.c b/src/backend/optimizer/cypher_createplan.c index 32486464..ad417233 100644 --- a/src/backend/optimizer/cypher_createplan.c +++ b/src/backend/optimizer/cypher_createplan.c @@ -29,7 +29,8 @@ const CustomScanMethods cypher_create_plan_methods = { "Cypher Create", create_cypher_create_plan_state}; - +const CustomScanMethods cypher_merge_plan_methods = { + "Cypher Merge", create_cypher_merge_plan_state}; Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, @@ -72,3 +73,64 @@ Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, return (Plan *)cs; } + +/* + * Coverts the Scan node representing the delete clause + * to the merge Plan node + */ +Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, + CustomPath *best_path, List *tlist, + List *clauses, List *custom_plans) +{ + CustomScan *cs; + Plan *subplan = linitial(custom_plans); + + cs = makeNode(CustomScan); + + cs->scan.plan.startup_cost = best_path->path.startup_cost; + cs->scan.plan.total_cost = best_path->path.total_cost; + + cs->scan.plan.plan_rows = best_path->path.rows; + cs->scan.plan.plan_width = 0; + + cs->scan.plan.parallel_aware = best_path->path.parallel_aware; + cs->scan.plan.parallel_safe = best_path->path.parallel_safe; + + cs->scan.plan.plan_node_id = 0; // Set later in set_plan_refs + /* + * the scan list of the delete node, used for its ScanTupleSlot used + * by its parent in the execution phase. + */ + cs->scan.plan.targetlist = tlist; + cs->scan.plan.qual = NIL; + cs->scan.plan.lefttree = NULL; + cs->scan.plan.righttree = NULL; + cs->scan.plan.initPlan = NIL; + + cs->scan.plan.extParam = NULL; + cs->scan.plan.allParam = NULL; + + /* + * We do not want Postgres to assume we are scanning a table, postgres' + * optimizer will make assumptions about our targetlist that are false + */ + cs->scan.scanrelid = 0; + + cs->flags = best_path->flags; + + // child plan nodes are here, Postgres processed them for us. + cs->custom_plans = custom_plans; + cs->custom_exprs = NIL; + // transfer delete metadata needed by the delete clause. + cs->custom_private = best_path->custom_private; + /* + * the scan list of the merge node's children, used for ScanTupleSlot + * in execution. + */ + cs->custom_scan_tlist = subplan->targetlist; + + cs->custom_relids = NULL; + cs->methods = &cypher_merge_plan_methods; + + return (Plan *)cs; +} \ No newline at end of file diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index 3bab7a6e..e25769e6 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -28,7 +28,8 @@ const CustomPathMethods cypher_create_path_methods = { CREATE_PATH_NAME, plan_cypher_create_path, NULL}; - +const CustomPathMethods cypher_merge_path_methods = { + MERGE_PATH_NAME, plan_cypher_merge_path, NULL}; CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) @@ -65,3 +66,46 @@ CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, return cp; } + +/* + * Creates a Delete Path. Makes the original path a child of the new + * path. We leave it to the caller to replace the pathlist of the rel. + */ +CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, + List *custom_private) +{ + CustomPath *cp; + + cp = makeNode(CustomPath); + + cp->path.pathtype = T_CustomScan; + + cp->path.parent = rel; + cp->path.pathtarget = rel->reltarget; + + cp->path.param_info = NULL; + + // Do not allow parallel methods + cp->path.parallel_aware = false; + cp->path.parallel_safe = false; + cp->path.parallel_workers = 0; + + cp->path.rows = 0; + cp->path.startup_cost = 0; + cp->path.total_cost = 0; + + // No output ordering for basic SET + cp->path.pathkeys = NULL; + + // Disable all custom flags for now + cp->flags = 0; + + // Make the original paths the children of the new path + cp->custom_paths = rel->pathlist; + // Store the metadata Delete will need in the execution phase. + cp->custom_private = custom_private; + // Tells Postgres how to turn this path to the correct CustomScan + cp->methods = &cypher_merge_path_methods; + + return cp; +} \ No newline at end of file diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index a71c6ccf..e5e11c6a 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -44,7 +44,8 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte); static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); - +static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); void set_rel_pathlist_init(void) { prev_set_rel_pathlist_hook = set_rel_pathlist_hook; @@ -59,6 +60,7 @@ void set_rel_pathlist_fini(void) static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + if (prev_set_rel_pathlist_hook) prev_set_rel_pathlist_hook(root, rel, rti, rte); @@ -67,6 +69,9 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, case CYPHER_CLAUSE_CREATE: handle_cypher_create_clause(root, rel, rti, rte); break; + case CYPHER_CLAUSE_MERGE: + handle_cypher_merge_clause(root, rel, rti, rte); + break; case CYPHER_CLAUSE_NONE: break; default: @@ -103,6 +108,8 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte) if (is_oid_ag_func(fe->funcid, CREATE_CLAUSE_FUNCTION_NAME)) return CYPHER_CLAUSE_CREATE; + else if (is_oid_ag_func(fe->funcid, MERGE_CLAUSE_FUNCTION_NAME)) + return CYPHER_CLAUSE_MERGE; else return CYPHER_CLAUSE_NONE; } @@ -130,3 +137,27 @@ static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, // Add the new path to the rel. add_path(rel, (Path *)cp); } + +// replace all possible paths with our CustomPath +static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + TargetEntry *te; + FuncExpr *fe; + List *custom_private; + CustomPath *cp; + + // Add the pattern to the CustomPath + te = (TargetEntry *)llast(rte->subquery->targetList); + fe = (FuncExpr *)te->expr; + // pass the const that holds the data structure to the path. + custom_private = fe->args; + + cp = create_cypher_merge_path(root, rel, custom_private); + + // Discard any pre-existing paths + rel->pathlist = NIL; + rel->partial_pathlist = NIL; + + add_path(rel, (Path *)cp); +} \ No newline at end of file diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index a608b7a9..2a73d2e8 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -22,6 +22,7 @@ #include "utils/ag_func.h" #include "utils/gtype.h" #include "utils/graphid.h" +#include "utils/variable_edge.h" #include "utils/vertex.h" #include "utils/edge.h" @@ -42,7 +43,18 @@ static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, ParseNa static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, List **namespace) ; // create clause static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause); - +// merge +static Query *transform_cypher_merge(cypher_parsestate *cpstate, cypher_clause *clause); +static cypher_create_path *transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, cypher_clause *isolated_merge_clause); +static cypher_create_path *transform_cypher_merge_path(cypher_parsestate *cpstate, Query *query, cypher_path *path); +static cypher_target_node *transform_merge_cypher_node(cypher_parsestate *cpstate, Query *query, cypher_node *node); +static cypher_target_node *transform_merge_cypher_edge(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge); +static Node *transform_clause_for_join(cypher_parsestate *cpstate, cypher_clause *clause, RangeTblEntry **rte, ParseNamespaceItem **nsitem, Alias* alias); +static cypher_clause *convert_merge_to_match(cypher_merge *merge); +static void transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate, List *target_list, cypher_create_path *path); +static TargetEntry *placeholder_vertex(cypher_parsestate *cpstate, char *name); +static TargetEntry *placeholder_edge(cypher_parsestate *cpstate, char *name); +static TargetEntry *placeholder_traversal(cypher_parsestate *cpstate, char *name); // transform #define PREV_CYPHER_CLAUSE_ALIAS "_" @@ -51,11 +63,12 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, \ prev_clause, NULL, add_rte_to_query) static ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, Alias *alias, bool add_rte_to_query); -static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, bool first_rte); +static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause); static Query *analyze_cypher_clause(transform_method transform, cypher_clause *clause, cypher_parsestate *parent_cpstate); static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); - +static void +transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query); static char *make_id_alias(char *var_name); static char *make_property_alias(char *var_name); static char *make_startid_alias(char *var_name); @@ -99,6 +112,8 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause result = transform_cypher_match(cpstate, clause); } else if (is_ag_node(self, cypher_create)) { result = transform_cypher_create(cpstate, clause); + } else if (is_ag_node(self, cypher_merge)) { + result = transform_cypher_merge(cpstate, clause); } else { ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); } @@ -280,7 +295,48 @@ transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query) static Expr *add_volatile_wrapper(Expr *node) { return (Expr *)makeFuncExpr(get_ag_func_oid("gtype_volatile_wrapper", 1, GTYPEOID), GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } +static Expr *add_volatile_edge_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, EDGEOID); + + return (Expr *)makeFuncExpr(oid, EDGEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} + +static Expr *add_volatile_vle_edge_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, VARIABLEEDGEOID); + + return (Expr *)makeFuncExpr(oid, VARIABLEEDGEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} + +static Expr *add_volatile_vertex_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, VERTEXOID); + + return (Expr *)makeFuncExpr(oid, VERTEXOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} +/* +static Expr *add_volatile_traversal_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, TRAVERSALOID); + + return (Expr *)makeFuncExpr(oid, TRAVERSALOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +}*/ +/* + * validate_or_create_elabel + * + * This function ensures that an edge label exists and is valid. If the label + * does not exist, it is created automatically. + * + * Parameters: + * cpstate - The current Cypher parser state. + * edge - The `cypher_relationship` whose label is being validated. + * + * Behavior: + * 1. It searches the graph's label cache for the label name specified in the edge. + * 2. If the label is found, it verifies that it is an edge label. If it is + * a vertex label, it throws an error. + * 3. If the label is not found, it calls `create_label` to create a new + * edge label with the specified name. The new label inherits from the + * default edge label. + */ static void validate_or_create_elabel(cypher_parsestate *cpstate, cypher_relationship *edge) { label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); @@ -295,6 +351,24 @@ static void validate_or_create_elabel(cypher_parsestate *cpstate, cypher_relatio } +/* + * validate_or_create_vlabel + * + * This function ensures that a vertex label exists and is valid. If the label + * does not exist, it is created automatically. + * + * Parameters: + * cpstate - The current Cypher parser state. + * node - The `cypher_node` whose label is being validated. + * + * Behavior: + * 1. It searches the graph's label cache for the label name specified in the node. + * 2. If the label is found, it verifies that it is a vertex label. If it is + * an edge label, it throws an error. + * 3. If the label is not found, it calls `create_label` to create a new + * vertex label with the specified name. The new label inherits from the + * default vertex label. + */ static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *node) { label_cache_data *lcd = search_label_name_graph_cache(node->label, cpstate->graph_oid); @@ -306,7 +380,20 @@ static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *n create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, list_make1(get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_VERTEX)), NULL); } +static TargetEntry * get_target_entry(cypher_parsestate *cpstate, List *target_list, char *name) { + ListCell *lc; + + foreach (lc, target_list) { + TargetEntry *te = (TargetEntry *)lfirst(lc); + + if (!strcmp(te->resname, name)) { + //te->expr = add_volatile_wrapper(te->expr); + return te; + } + } + return NULL; +} static int get_target_entry_resno(cypher_parsestate *cpstate, List *target_list, char *name) { ListCell *lc; @@ -322,6 +409,49 @@ static int get_target_entry_resno(cypher_parsestate *cpstate, List *target_list, return -1; } +/* + * process_create_vertex + * + * This function transforms a `cypher_node` from a CREATE clause into the + * necessary structures for execution. It handles both creating new vertices and + * referencing existing ones within the same query. + * + * Parameters: + * cpstate - The current Cypher parser state. + * query - The Query node being constructed. + * node - The `cypher_node` from the CREATE pattern to be processed. + * ccp - The `cypher_create_path` struct that will hold the transformation's output. + * + * Behavior: + * 1. Handles Named vs. Anonymous Nodes: + * - If the node has a variable name, it checks if the variable has been + * previously defined in the query (e.g., in a MATCH clause). + * - If it's an anonymous node, a default internal name is generated. + * + * 2. Handles Existing Variables: + * - If a named variable already exists, it flags the node as a reference + * to an existing entity. + * - It validates that one cannot add new properties or labels to an + * already-existing vertex within a CREATE clause. + * + * 3. Handles New Vertices: + * - For new vertices (named or anonymous), it adds placeholder target + * entries to the query's target list for the new vertex's ID, properties, + * and the full vertex object itself. This ensures the created vertex's + * data is available to subsequent clauses. + * - It transforms any provided property maps. + * + * 4. Handles Labels and Storage: + * - It validates the vertex's label, creating it if it doesn't exist. + * - It looks up the label's metadata to determine the underlying storage + * relation (`relid`) and its corresponding adjacency list relation + * (`adj_relid`). + * + * 5. Output: + * - It populates a `cypher_target_node` with all the collected information + * (relation OIDs, attribute numbers in the target list, flags, etc.) and + * appends it to the `cypher_create_path`. + */ static void process_create_vertex( cypher_parsestate *cpstate, @@ -420,6 +550,42 @@ process_create_vertex( ccp->target_nodes = lappend(ccp->target_nodes, target); } +/* + * process_create_edge + * + * This function transforms a `cypher_relationship` from a CREATE clause into + * the necessary structures for execution. It is a helper function for + * `transform_cypher_create`. + * + * Parameters: + * cpstate - The current Cypher parser state. + * query - The Query node being constructed, which this function modifies. + * edge - The `cypher_relationship` from the CREATE pattern to be processed. + * ccp - The `cypher_create_path` struct that will hold the transformation's output. + * + * Behavior: + * 1. Handles Named vs. Anonymous Edges: + * - If the edge has a variable name, it validates that the name is not + * already in use. + * - If it's an anonymous edge, a default internal name is generated. + * + * 2. Adds Placeholders to Target List: + * - For new edges, it adds placeholder target entries to the query's + * target list for the new edge's ID, properties, and the full edge + * object itself. This ensures the created edge's data is available to + * subsequent clauses. + * - It transforms any provided property maps. + * + * 3. Handles Labels and Storage: + * - It validates the edge's label, creating it if it doesn't exist. + * - It looks up the label's metadata to determine the underlying storage + * relation (`relid`). + * + * 4. Output: + * - It populates a `cypher_target_node` with all the collected information + * (relation OID, attribute numbers in the target list, direction, etc.) + * and appends it to the `cypher_create_path`. + */ static void process_create_edge( cypher_parsestate *cpstate, @@ -491,16 +657,48 @@ process_create_edge( label_cache_data *lcd = search_label_name_graph_cache(edge->label, cpstate->graph_oid); target->relid = lcd->relation; - + Relation *rel = RelationIdGetRelation(lcd->relation); target->id_expr = (Expr *)build_column_default(rel, 1); table_close(rel, NoLock); - ccp->target_nodes = lappend(ccp->target_nodes, target); } +/* + * transform_cypher_create + * + * This function transforms a Cypher `CREATE` clause into a `Query` node. It + * orchestrates the processing of vertices and edges to be created and packages + * the creation logic into a placeholder function call for later execution. + * + * Parameters: + * cpstate - The current Cypher parser state. + * clause - The `cypher_clause` containing the `cypher_create` node. + * + * Behavior: + * 1. Initializes a new `SELECT` query. + * 2. If the `CREATE` follows another clause (e.g., `MATCH`), it transforms + * the preceding clause into a subquery and adds its output columns to the + * current query's target list. This makes variables from the `MATCH` + * available to the `CREATE`. + * 3. It iterates through each path pattern in the `CREATE` clause. For each + * path, it calls `process_create_vertex` for each node and + * `process_create_edge` for each relationship. These helpers add + * placeholder target entries for the new entities and gather metadata. + * 4. It aggregates all the metadata from the helpers into a + * `cypher_create_target_nodes` structure. + * 5. It serializes this structure and embeds it in a placeholder function + * call (e.g., `_cypher_create_clause`), which is added as the final + * entry in the query's target list. The actual creation logic is deferred + * to the execution stage. + * 6. If `CREATE` is the final clause in the statement, it wraps the generated + * query in an outer `SELECT` query to produce a clean final result set. + * + * Returns: + * A `Query` node representing the transformed `CREATE` clause. + */ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause) { ParseState *pstate = (ParseState *)cpstate; cypher_create *self = (cypher_create *)clause->self; @@ -509,7 +707,6 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause query->commandType = CMD_SELECT; query->targetList = NIL; - cypher_create_target_nodes *target_nodes; target_nodes = make_ag_node(cypher_create_target_nodes); target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE; @@ -517,7 +714,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause if (clause->prev) { - handle_prev_clause(cpstate, query, clause->prev, true); + handle_prev_clause(cpstate, query, clause->prev); target_nodes->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE; } @@ -1082,8 +1279,6 @@ add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, if (node->name) { node->has_variable = true; if (colNameToVar(cpstate, node->name, false, -1)) { - ereport(WARNING,(errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("here"))); node->declared_in_previous_clause = true; return refnameNamespaceItem(cpstate, NULL, PREV_CYPHER_CLAUSE_ALIAS, -1, NULL); } @@ -1121,6 +1316,38 @@ add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, return pnsi; } +/* + * add_vertex_retrieval_to_query + * + * This function handles adding the end vertex of a relationship to a MATCH + * query. It determines how to retrieve or reference the vertex based on whether + * it's a new variable, an existing variable from a previous clause, or a + * variable already present in the current join tree. + * + * Parameters: + * cpstate - The current Cypher parser state. + * query - The Query node being constructed. + * node - The cypher_node representing the vertex to be added. + * edge_pnsi - The ParseNamespaceItem for the incoming edge, used to get the + * end vertex's ID. + * quals - A list of qualifications (WHERE clause conditions) to be updated. + * + * Behavior: + * 1. If the vertex variable already exists (from a previous clause or earlier + * in the current path), it adds a qualification to join the edge's 'endid' + * with the existing vertex's 'id'. It then returns the namespace item for + * the existing variable. + * 2. If the vertex is new, it adds a call to the `retrieve_vertex` + * set-returning function (SRF) to the query's FROM clause. This function + * fetches the vertex's properties using the 'endid' from the edge. + * 3. If the vertex is anonymous (no variable name), it does nothing and + * returns NULL, as there's no need to retrieve or reference it. + * + * Returns: + * The ParseNamespaceItem for the vertex, which can be either a new SRF entry + * or a reference to an existing entry in the query's namespace. Returns NULL + * for anonymous vertices. + */ static ParseNamespaceItem * add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, ParseNamespaceItem *edge_pnsi, List **quals) { @@ -1128,6 +1355,11 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n if (node->name) { node->has_variable = true; + /* + * If the vertex variable has already been defined, create a join + * qualification to link the incoming edge's end ID to the existing + * vertex's ID. + */ Var *var; if (var = colNameToVar(cpstate, make_id_alias(node->name), false, -1)) { node->declared_in_previous_clause = true; @@ -1151,9 +1383,7 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n return pnsi; } - } - - else { + } else { node->in_join_tree = false; return NULL; } @@ -1184,6 +1414,36 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n return pnsi; } + +/* + * add_edge_to_query + * + * This function adds a fixed-length edge to a MATCH query. It does this by + * constructing a call to the `edge_search` set-returning function (SRF) and + * adding it to the query's FROM clause as a lateral join. + * + * Parameters: + * cpstate - The current Cypher parser state. + * query - The Query node being constructed. + * edge - The cypher_relationship representing the edge to be added. + * vertex - The cypher_node representing the start vertex for the edge search. + * + * Behavior: + * 1. Validates that if the edge has a variable name, it has not been + * previously defined in the current scope. Assigns a default alias if + * unnamed. + * 2. Processes the edge's label. If a label is specified, it looks up the + * label's ID and creates a filter constant. If no label is given, it + * defaults to the standard edge label. + * 3. Constructs a `FuncCall` to the `edge_search` SRF. The arguments include + * the graph OID, the ID of the start `vertex`, and the label filter. + * 4. Adds the SRF to the query's join tree using `add_srf_to_query`. The + * SRF is defined to return `id`, `startid`, `endid`, and `properties`. + * + * Returns: + * The ParseNamespaceItem for the newly added SRF, allowing its output + * columns to be referenced in subsequent transformations. + */ static ParseNamespaceItem * add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, cypher_node *vertex) { @@ -1351,6 +1611,22 @@ add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_rela list_make3(makeString("edges"), makeString("endid"), makeString("hashset"))); } +/* + * add_all_fields_to_target_list + * + * This function is responsible for adding all the necessary fields from a + * three-element path (left_vertex, edge, right_vertex) to the query's + * target list. This is typically called for the first segment of a path in a + * MATCH clause, ensuring that all variables and their properties are available + * for subsequent clauses. + * + * For each element (vertex or edge) that has a variable, it adds entries for + * its id, properties, and the complete entity expression (vertex or edge object) + * to the target list. + * + * Special handling is included for relationship direction and variable length + * edges. + */ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *query, cypher_node *left_vertex, cypher_relationship *edge, cypher_node *right_vertex) { ParseState *pstate = (ParseState *)cpstate; @@ -1467,6 +1743,21 @@ static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *que } } +/* + * add_new_fields_to_target_list + * + * This function is responsible for adding fields from a path segment to the + * query's target list. It is intended for use on subsequent segments of a + * multi-hop path (e.g., for the `(b)-[r2]->(c)` part of `MATCH (a)-[r1]->(b)-[r2]->(c)`). + * + * Unlike `add_all_fields_to_target_list`, this function assumes the left_vertex + * of the segment has already been processed and its fields are present in the + * target list. It only adds the fields for the new edge and the new right_vertex. + * + * For the edge, it adds its id, start/end ids, and properties. For the right + * vertex, it adds its id and properties. If either has a variable, the full + * entity expression is also added. + */ static void add_new_fields_to_target_list(cypher_parsestate *cpstate, Query *query, cypher_node *left_vertex, cypher_relationship *edge, cypher_node *right_vertex) { ParseState *pstate = (ParseState *)cpstate; @@ -1552,6 +1843,27 @@ static void add_new_fields_to_target_list(cypher_parsestate *cpstate, Query *que false)); } } + +/* + * process_three_element_path + * + * This function handles the transformation of a simple Cypher path of length three, + * such as (a)-[r]->(b), into its corresponding SQL query components within a + * MATCH clause. + * + * It performs the following steps: + * 1. Determines the start and end vertices based on the relationship's direction. + * 2. Adds the start vertex to the query's FROM clause, either as a new table + * scan or by referencing an existing variable. + * 3. Adds the edge to the query as a set-returning function (SRF) + * call (e.g., edge_search or variable_edge_search), which is joined to the + * start vertex. + * 4. Adds the end vertex by either retrieving it through a function call or by + * adding qualifications to match an existing variable. + * 5. Populates the query's target list with all necessary fields (id, properties, + * etc.) from all three path elements, making them available for subsequent + * clauses. + */ static void process_three_element_path(cypher_parsestate *cpstate, Query *query, cypher_path *path, List **quals) { @@ -1588,6 +1900,28 @@ typedef struct path_parts { cypher_rel_dir dir; } path_parts; +/* + * find_path_parts + * + * This function analyzes a Cypher MATCH path and breaks it down into + * continuous, single-direction segments. A complex path with changing + * directions, such as (a)-[]->(b)<-[]-(c), is not processed directly. Instead, + * this function identifies the points where the direction changes and divides + * the path into "parts," where each part has a consistent direction (either + * all right-directed `->` or all left-directed `<-`). + * + * It iterates through the relationships in the path, creating a new `path_parts` + * segment each time the direction of a relationship differs from the previous one. + * + * It also validates that no relationship is directionless (`-[]-`), as this is + * not supported in a MATCH clause, yet. + * + * Returns: + * A List of `path_parts` structs. Each struct defines a segment with a + * `start` index, an `end` index (within the original path list), and a `dir` + * (direction). This allows the parser to process complex paths one simple, + * unidirectional segment at a time. + */ static List *find_path_parts(cypher_path *path) { cypher_relationship *start = list_nth(path->path,1); @@ -1624,6 +1958,38 @@ static List *find_path_parts(cypher_path *path) { return parts; } +/* + * process_path_part + * + * This function transforms a continuous, single-direction segment of a Cypher + * MATCH path into its corresponding SQL query components. A complex path with + * changing directions (e.g., (a)-->(b)<--(c)) is broken down into "parts" of + * consistent direction, and this function processes one such part at a time. + * + * It iterates through the vertices and edges of the path segment, building up + * the query by: + * + * 1. Adding Vertices and Edges: + * - For the starting vertex of the segment, it adds a table scan. + * - For each edge, it adds a set-returning function call (e.g., `edge_search`) + * to the FROM clause, chaining it to the previous element in the path. + * - For each subsequent vertex, it adds a function call to retrieve it or + * a join condition if the vertex variable already exists. + * + * 2. Adding Qualifications: + * - It generates join conditions to link the end of one path segment to the + * start of another (e.g., to join `(a)->(b)` and `(c)->(b)` on `b`). + * - It adds cycle-prevention qualifications to ensure that no edge is + * traversed more than once within the same MATCH clause. + * + * 3. Populating the Target List: + * - It calls `add_all_fields_to_target_list` or `add_new_fields_to_target_list` + * to project all necessary columns (id, properties, etc.) for the new + * elements, making them available to subsequent query clauses. + * + * The function handles both right-directed (`->`) and left-directed (`<-`) + * path segments by adjusting its iteration order accordingly. + */ static void process_path_part( cypher_parsestate *cpstate, @@ -1732,6 +2098,8 @@ process_path_part( } } } + + static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc; @@ -1897,22 +2265,521 @@ Query *cypher_parse_sub_analyze(Node *parseTree, cypher_parsestate *cpstate, Com } /* - * Utility function that helps a clause add the information needed to - * the query from the previous clause. + * handle_prev_clause + * + * Utility function to bring the output of a previous Cypher clause (such as MATCH or WITH) + * into the current query context. This is used to chain clauses together so that variables + * and expressions projected by the previous clause are available in the current clause. + * + * Parameters: + * cpstate - The current Cypher parser state. + * query - The Query node being constructed (modified in-place). + * clause - The previous cypher_clause whose output should be made available. + + * Behavior: + * 1. Transforms the previous clause as a subquery and adds it as a RangeTblEntry. + * 2. Expands all attributes from the subquery and appends them to the current query's target list, + * making all projected variables/columns available for use in the current clause. */ -static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, bool first_rte) { +static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause) { ParseState *pstate = (ParseState *) cpstate; - int rtindex; - ParseNamespaceItem *pnsi; - - pnsi = transform_prev_cypher_clause(cpstate, clause, true); - rtindex = list_length(pstate->p_rtable); + ParseNamespaceItem *pnsi = transform_prev_cypher_clause(cpstate, clause, true); - // rte is the first RangeTblEntry in pstate - if (first_rte) - Assert(rtindex == 1); + int rtindex = list_length(pstate->p_rtable); // add all the rte's attributes to the current queries targetlist query->targetList = list_concat(query->targetList, expandNSItemAttrs(pstate, pnsi, 0, -1)); } + + + +/* + * + * There are two cases for the form Query that is returned from here will + * take: + * + * 1. If there is no previous clause, the query will have a subquery that + * represents the path as a select staement, similar to match with a targetList + * that is all declared variables and the FuncExpr that represents the MERGE + * clause with its needed metadata information, that will be caught in the + * planner phase and converted into a path. + * + * 2. If there is a previous clause then the query will have two subqueries. + * The first query will be for the previous clause that we recursively handle. + * The second query will be for the path that this MERGE clause defines. The + * two subqueries will be joined together using a LATERAL LEFT JOIN with the + * previous query on the left and the MERGE path subquery on the right. Like + * case 1 the targetList will have all the decalred variables and a FuncExpr + * that represents the MERGE clause with its needed metadata information, that + * will be caught in the planner phase and converted into a path. + * + * This will allow us to be capable of handling the 2 cases that exist with a + * MERGE clause correctly. + * + * Case 1: the path already exists. In this case we do not need to create + * the path and MERGE will simply pass the tuple information up the execution + * tree. + * + * Case 2: the path does not exist. In this case the LEFT part of the join + * will not prevent the tuples from the previous clause from being emitted. We + * can catch when this happens in the execution phase and create the missing + * data, before passing up the execution tree. + * + * It should be noted that both cases can happen in the same query. If the + * MERGE clause references a variable from a previous clause, it could be that + * for one tuple the path exists (or there is multiple paths that exist and all + * paths must be emitted) and for another the path does not exist. This is + * similar to OPTIONAL MATCH, however with the added feature of creating the + * path if not there, rather than just emiting NULL. + */ +static Query *transform_cypher_merge(cypher_parsestate *cpstate, cypher_clause *clause) { + ParseState *pstate = (ParseState *) cpstate; + cypher_clause *merge_clause_as_match; + cypher_create_path *merge_path; + cypher_merge *self = (cypher_merge *)clause->self; + cypher_merge_information *merge_information; + Query *query; + FuncExpr *func_expr; + TargetEntry *tle; + + Assert(is_ag_node(self->path, cypher_path)); + + merge_information = make_ag_node(cypher_merge_information); + + query = makeNode(Query); + query->commandType = CMD_SELECT; + query->targetList = NIL; + + Const *null_const = makeNullConst(GTYPEOID, -1, InvalidOid); + tle = makeTargetEntry((Expr *)null_const, pstate->p_next_resno++, "_null", false); + query->targetList = lappend(query->targetList, tle); + + merge_information->flags = CYPHER_CLAUSE_FLAG_NONE; + + // make the merge node into a match node + merge_clause_as_match = convert_merge_to_match(self); + + /* + * If there is a previous clause we need to turn this query into a lateral + * join. See the function transform_merge_make_lateral_join for details. + */ + if (clause->prev != NULL) { + merge_path = transform_merge_make_lateral_join(cpstate, query, clause, merge_clause_as_match); + + merge_information->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE; + } else { + // make the merge node into a match node + //cypher_clause *merge_clause_as_match = convert_merge_to_match(self); + + // Create the metadata needed for creating missing paths. + merge_path = transform_cypher_merge_path(cpstate, query, (cypher_path *)self->path); + + /* + * If there is not a previous clause, then treat the MERGE's path + * itself as the previous clause. We need to do this because if the + * pattern exists, then we need to path all paths that match the + * query patterns in the execution phase. WE way to do that by + * converting the merge to a match and have the match logic create the + * query. the merge execution phase will just pass the results up the + * execution tree if the path exists. + */ + handle_prev_clause(cpstate, query, merge_clause_as_match); + + /* + * For the metadata need to create paths, find the tuple position that + * will represent the entity in the execution phase. + */ + transform_cypher_merge_mark_tuple_position(cpstate, query->targetList, merge_path); + } + + merge_information->graph_oid = cpstate->graph_oid; + merge_information->path = merge_path; + + if (!clause->next) + merge_information->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; + + // Creates the function expression that the planner will find and convert to a MERGE path. + query->targetList = lappend(query->targetList, + makeTargetEntry( + (Expr *)make_write_clause_function_placeholder(MERGE_CLAUSE_FUNCTION_NAME, merge_information), + merge_information->merge_function_attr = pstate->p_next_resno++, + "_merge_clause", + false)); + + + markTargetListOrigins(pstate, query->targetList); + + query->rtable = pstate->p_rtable; + query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + + query->hasSubLinks = pstate->p_hasSubLinks; + + assign_query_collations(pstate, query); + + if (clause->next) + return query; + + { + Query *topquery; + cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); + topquery = makeNode(Query); + topquery->commandType = CMD_SELECT; + topquery->targetList = NIL; + + ParseState *pstate = (ParseState *) cpstate; + int rtindex; + + transform_cypher_clause_as_subquery_2(new_cpstate, query); + topquery->rtable = new_cpstate->pstate.p_rtable; + topquery->jointree = makeFromExpr(new_cpstate->pstate.p_joinlist, NULL); + + return topquery; + } +} + +/* + * This function does the heavy lifting of transforming a MERGE clause that has + * a clause before it in the query of turning that into a lateral left join. + * The previous clause will still be able to emit tuples if the path defined in + * MERGE clause is not found. In that case variable assinged in the MERGE + * clause will be emitted as NULL (same as OPTIONAL MATCH). + */ +static cypher_create_path * +transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, + cypher_clause *isolated_merge_clause) { + cypher_create_path *merge_path; + ParseState *pstate = (ParseState *) cpstate; + int i; + Alias *l_alias; + Alias *r_alias; + RangeTblEntry *l_rte, *r_rte; + ParseNamespaceItem *l_nsitem, *r_nsitem; + JoinExpr *j = makeNode(JoinExpr); + List *res_colnames = NIL, *res_colvars = NIL; + ParseNamespaceItem *jnsitem; + ParseExprKind tmp; + cypher_merge *self = (cypher_merge *)clause->self; + cypher_path *path; + + Assert(is_ag_node(self->path, cypher_path)); + + path = (cypher_path *)self->path; + + r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL); + l_alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); + + j->jointype = JOIN_LEFT; + + /* + * transform the previous clause + */ + j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, &l_nsitem, l_alias); + pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); + + /* + * Get the merge path now. This is the only moment where it is simple + * to know if a variable was declared in the MERGE clause or a previous + * clause. Unlike create, we do not add these missing variables to the + * targetList, we just create all the metadata necessary to make the + * potentially missing parts of the path. + */ + merge_path = transform_cypher_merge_path(cpstate, query, path); + + /* + * Transform this MERGE clause as a match clause, mark the parsestate + * with the flag that a lateral join is active + */ + pstate->p_lateral_active = true; + tmp = pstate->p_expr_kind; + pstate->p_expr_kind = EXPR_KIND_OTHER; + + // transform MERGE + j->rarg = transform_clause_for_join(cpstate, isolated_merge_clause, &r_rte, &r_nsitem, r_alias); + + // deactivate the lateral flag + pstate->p_lateral_active = false; + + pstate->p_namespace = NIL; + + /* + * Resolve the column names and variables between the two subqueries, + * in most cases, we can expect there to be overlap + */ + get_res_cols(pstate, l_nsitem, r_nsitem, &res_colnames, &res_colvars); + + // make the RTE for the join + jnsitem = addRangeTableEntryForJoin(pstate, res_colnames, NULL, j->jointype, 0, res_colvars, NIL, + NIL, j->alias, NULL, true); + + j->rtindex = jnsitem->p_rtindex; + + /* + * The index of a node in the p_joinexpr list is expected to match the + * rtindex the join expression is for. Add NULLs for all the previous + * rtindexes and add the JoinExpr. + */ + for (i = list_length(pstate->p_joinexprs) + 1; i < j->rtindex; i++) + pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL); + + pstate->p_joinexprs = lappend(pstate->p_joinexprs, j); + + Assert(list_length(pstate->p_joinexprs) == j->rtindex); + + pstate->p_joinlist = lappend(pstate->p_joinlist, j); + + pstate->p_expr_kind = tmp; + + // add jnsitem to column namespace only + addNSItemToQuery(pstate, jnsitem, false, true, true); + + /* + * Create the targetList from the joined subqueries, add everything. + */ + query->targetList = list_concat(query->targetList, make_target_list_from_join(pstate, jnsitem->p_rte)); + + /* + * For the metadata need to create paths, find the tuple position that + * will represent the entity in the execution phase. + */ + transform_cypher_merge_mark_tuple_position(cpstate, query->targetList, merge_path); + + return merge_path; +} + +/* + * Iterate through the path and find the TargetEntry in the target_list + * that each cypher_target_node is referencing. Add the volatile wrapper + * function to keep the optimizer from removing the TargetEntry. + */ +static void transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate, List *target_list, cypher_create_path *path) { + /*if (path->var_name) { + TargetEntry *te = findTarget(target_list, path->var_name); + te->expr = add_volatile_traversal_wrapper(te->expr); + path->path_attr_num = te->resno; + }*/ + + ListCell *lc; + foreach (lc, path->target_nodes) { + cypher_target_node *node = lfirst(lc); + + TargetEntry *te = get_target_entry(cpstate, target_list, node->variable_name); + if (!IsA(te->expr, Var)) + continue; + /* + * Add the volatile wrapper function around the expression, this + * ensures the optimizer will not remove the expression, if nothing + * other than a private data structure needs it. + */ + if (((Var *)te->expr)->vartype == VERTEXOID) + te->expr = add_volatile_vertex_wrapper(te->expr); + else if (((Var *)te->expr)->vartype == EDGEOID) + te->expr = add_volatile_edge_wrapper(te->expr); + else if (((Var *)te->expr)->vartype == VARIABLEEDGEOID) + te->expr = add_volatile_vle_edge_wrapper(te->expr); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("internal error: unsupported type for MERGE clause"))); + + // Mark the tuple position the target_node is for. + node->tuple_position = te->resno; + } +} + +/* + * Creates the target nodes for a merge path. If MERGE has a path that doesn't + * exist then in the MERGE clause we act like a CREATE clause. This function + * sets up the metadata needed for that process. + */ +static cypher_create_path *transform_cypher_merge_path(cypher_parsestate *cpstate, Query *query, cypher_path *path) { + cypher_create_path *ccp = make_ag_node(cypher_create_path); + + ccp->target_nodes = NIL; + ListCell *lc; + foreach (lc, path->path) { + if (is_ag_node(lfirst(lc), cypher_node)) + ccp->target_nodes = lappend(ccp->target_nodes, + transform_merge_cypher_node(cpstate, query, lfirst(lc))); + else + ccp->target_nodes = lappend(ccp->target_nodes, + transform_merge_cypher_edge(cpstate, query, lfirst(lc))); + } + + ccp->path_attr_num = InvalidAttrNumber; + + return ccp; +} + +/* + * Transforms the parse cypher_relationship to a target_entry for merge. + * All edges that have variables assigned in a merge must be declared in + * the merge. Throw an error otherwise. + */ +static cypher_target_node *transform_merge_cypher_edge(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge) { + ParseState *pstate = (ParseState *)cpstate; + cypher_target_node *target_node = make_ag_node(cypher_target_node); + + if (edge->name != NULL) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MERGE does not support variables"), + parser_errposition(get_parse_state(cpstate), edge->location))); + else + edge->name = get_next_default_alias(cpstate); + + target_node->type = LABEL_KIND_EDGE; + target_node->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; + target_node->label_name = edge->label; + target_node->variable_name = edge->name; + target_node->resultRelInfo = NULL; + target_node->dir = edge->dir; + + if (edge->label) { + validate_or_create_elabel(cpstate, edge); + target_node->label_name = edge->label; + } else { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("edges declared in a MERGE clause must have a label"), + parser_errposition(get_parse_state(cpstate), edge->location))); + } + + // id + label_cache_data *lcd = search_label_name_graph_cache(edge->label ? edge->label : AG_DEFAULT_LABEL_EDGE , cpstate->graph_oid); + Relation rel = RelationIdGetRelation(lcd->relation); + target_node->id_expr = (Expr *)build_column_default(rel, 1); + target_node->relid = lcd->relation; + table_close(rel, NoLock); + + // props + if (edge->props) { + /* query->targetList = lappend(query->targetList, + makeTargetEntry(//BlackPink + add_volatile_wrapper(transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), + target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, + make_property_alias(edge->name), + false)); +*/ + query->targetList = lappend(query->targetList, + makeTargetEntry( + add_volatile_wrapper(scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1)), + target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, + make_property_alias(edge->name), + false)); + } else { + target_node->prop_attr_num = InvalidAttrNumber; + } + + return target_node; +} + + +static cypher_target_node *transform_merge_cypher_node(cypher_parsestate *cpstate, Query *query, cypher_node *node) { + cypher_target_node *target_node = make_ag_node(cypher_target_node); + + target_node->type = LABEL_KIND_VERTEX; + + + if (node->name) { + /* ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MERGE does not support variables"), + parser_errposition(get_parse_state(cpstate), node->location))); +*/ + // /ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); + Var *var = NULL; + if ((var = colNameToVar(cpstate, node->name, false, -1)) || + (target_node->id_attr_num = get_target_entry_resno(cpstate, query->targetList, make_id_alias(node->name))) != -1) { + + if (var && var->vartype != VERTEXOID) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CREATE vertex variable %s already exists", node->name))); + + if (node->props) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("previously declared nodes in a create clause cannot have properties"))); + if (node->label) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("previously declared variables cannot have a label"))); + + target_node->flags |= EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE; + + target_node->id_attr_num = get_target_entry_resno(cpstate, query->targetList, make_id_alias(node->name)); + + return target_node; + } + } else { + target_node->tuple_position = InvalidAttrNumber; + node->name = get_next_default_alias(cpstate); + } + + target_node->variable_name = node->name; + + if (node->label) { + validate_or_create_vlabel(cpstate, node); + target_node->label_name = node->label; + } else { + target_node->label_name = ""; + //node->label = AG_DEFAULT_LABEL_VERTEX; + } + + target_node->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; + + // id + label_cache_data *lcd = search_label_name_graph_cache(node->label ? node->label : AG_DEFAULT_LABEL_VERTEX, cpstate->graph_oid); + Relation rel = RelationIdGetRelation(lcd->relation); + target_node->id_expr = (Expr *)build_column_default(rel, 1); + target_node->relid = lcd->relation; + table_close(rel, NoLock); + target_node->adj_relid = lcd->vertex_adjlist; + // props + if (node->props) { + /* query->targetList = lappend(query->targetList, + makeTargetEntry( + add_volatile_wrapper(transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), + target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, + make_property_alias(node->name), + false));*/ + query->targetList = lappend(query->targetList, + makeTargetEntry( + colNameToVar(cpstate, make_property_alias(node->name), false, -1), + target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, + make_property_alias(node->name), + false)); + /* + query->targetList = lappend(query->targetList, + makeTargetEntry( + add_volatile_wrapper(scanNSItemForColumn(get_parse_state(cpstate), refnameNamespaceItem(cpstate, NULL, node->name, -1, NULL), 0, AG_VERTEX_COLNAME_PROPERTIES, -1)), + target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, + make_property_alias(node->name), + false)); + +colNameToVar(cpstate, node->name, false, -1)) +*/ + + + } else { + target_node->prop_attr_num = InvalidAttrNumber; + } + + return target_node; +} + +// Takes a MERGE parse node and converts it to a MATCH parse node +static cypher_clause *convert_merge_to_match(cypher_merge *merge) { + cypher_match *match = make_ag_node(cypher_match); + cypher_clause *clause = palloc(sizeof(cypher_clause)); + + // match supports multiple paths, whereas merge only supports one. + match->pattern = list_make1(merge->path); + // MERGE does not support where + match->where = NULL; + + /* + * We do not want the transform logic to transform the previous clauses + * with this, just handle this one clause. + */ + clause->prev = NULL; + clause->self = (Node *)match; + clause->next = NULL; + + return clause; +} diff --git a/src/include/executor/cypher_executor.h b/src/include/executor/cypher_executor.h index 4343156f..7252d3eb 100644 --- a/src/include/executor/cypher_executor.h +++ b/src/include/executor/cypher_executor.h @@ -29,5 +29,6 @@ Node *create_cypher_create_plan_state(CustomScan *cscan); extern const CustomExecMethods cypher_create_exec_methods; - +Node *create_cypher_merge_plan_state(CustomScan *cscan); +extern const CustomExecMethods cypher_merge_exec_methods; #endif diff --git a/src/include/optimizer/cypher_createplan.h b/src/include/optimizer/cypher_createplan.h index f27dd498..00388d3d 100644 --- a/src/include/optimizer/cypher_createplan.h +++ b/src/include/optimizer/cypher_createplan.h @@ -27,6 +27,9 @@ Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans); +Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, + CustomPath *best_path, List *tlist, + List *clauses, List *custom_plans); #endif diff --git a/src/include/optimizer/cypher_pathnode.h b/src/include/optimizer/cypher_pathnode.h index 22404f25..24b36240 100644 --- a/src/include/optimizer/cypher_pathnode.h +++ b/src/include/optimizer/cypher_pathnode.h @@ -29,10 +29,7 @@ CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private); -CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, - List *custom_private); -CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, - List *custom_private); + CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private); From 50b178534b4055891317de9bb46d562540527d03 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Tue, 21 Oct 2025 21:26:25 -0500 Subject: [PATCH 53/56] Add Executor/Cypher_Merge.c uWu I forgot --- src/backend/executor/cypher_merge.c | 783 ++++++++++++++++++++++++++++ src/backend/parser/cypher_clause.c | 520 +++++++++--------- 2 files changed, 1057 insertions(+), 246 deletions(-) create mode 100644 src/backend/executor/cypher_merge.c diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c new file mode 100644 index 00000000..b7ac5102 --- /dev/null +++ b/src/backend/executor/cypher_merge.c @@ -0,0 +1,783 @@ +/* + * 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. + */ + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/xact.h" +#include "executor/tuptable.h" +#include "nodes/execnodes.h" +#include "nodes/extensible.h" +#include "nodes/nodes.h" +#include "nodes/plannodes.h" +#include "parser/parse_relation.h" +#include "rewrite/rewriteHandler.h" +#include "utils/rel.h" + + +#include "catalog/ag_label.h" +#include "executor/cypher_executor.h" +#include "nodes/cypher_nodes.h" +#include "utils/gtype.h" +#include "utils/graphid.h" +#include "utils/vertex.h" +#include "utils/edge.h" +#include "utils/ag_cache.h" +#include "utils/traversal.h" +#define AdvanceCmdId(estate) \ + estate->es_output_cid++; \ + estate->es_snapshot->curcid++; + +#define RollbackCmdId(estate) \ + estate->es_output_cid--; \ + estate->es_snapshot->curcid--; +typedef struct cypher_merge_custom_scan_state +{ + CustomScanState css; + CustomScan *cs; + cypher_merge_information *merge_information; + int flags; + cypher_create_path *path; + List *path_values; + Oid graph_oid; + AttrNumber merge_function_attr; + bool created_new_path; + bool found_a_path; + graphid **vertex_ids; + graphid **edge_ids; +} cypher_merge_custom_scan_state; + +static void begin_cypher_merge(CustomScanState *node, EState *estate, + int eflags); +static TupleTableSlot *exec_cypher_merge(CustomScanState *node); +static void end_cypher_merge(CustomScanState *node); +static void rescan_cypher_merge(CustomScanState *node); +static Datum merge_vertex(cypher_merge_custom_scan_state *css, + cypher_target_node *node, ListCell *next); +static void merge_edge(cypher_merge_custom_scan_state *css, + cypher_target_node *node, Datum prev_vertex_id, + ListCell *next); +static void process_simple_merge(CustomScanState *node); +static bool check_path(cypher_merge_custom_scan_state *css, + TupleTableSlot *slot); +static void process_path(cypher_merge_custom_scan_state *css); +static void mark_tts_isnull(TupleTableSlot *slot); + +const CustomExecMethods cypher_merge_exec_methods = {"Merge Scan State", + begin_cypher_merge, + exec_cypher_merge, + end_cypher_merge, + rescan_cypher_merge, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}; + + + +/* + * Insert the edge/vertex tuple into the table and indices. Check that the + * table's constraints have not been violated. + */ +HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, + TupleTableSlot *elemTupleSlot, + EState *estate) +{ + HeapTuple tuple = NULL; + + ExecStoreVirtualTuple(elemTupleSlot); + //tuple = ExecFetchSlotHeapTuple(elemTupleSlot, true, NULL); + + /* Check the constraints of the tuple */ + //tuple->t_tableOid = resultRelInfo->ri_RelationDesc->rd_id; + /* if (resultRelInfo->ri_RelationDesc->rd_att->constr != NULL) + { + ExecConstraints(resultRelInfo, elemTupleSlot, estate); + }*/ + + // Insert the tuple normally + table_tuple_insert(resultRelInfo->ri_RelationDesc, elemTupleSlot, estate->es_output_cid, 0, NULL); + + // Insert index entries for the tuple + if (resultRelInfo->ri_NumIndices > 0) + { + Oid oid = elemTupleSlot->tts_tableOid; + elemTupleSlot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);// RelationGetRelid(heapRelation) + + ExecInsertIndexTuples(resultRelInfo, elemTupleSlot, estate, false, false, NULL, NIL); + elemTupleSlot->tts_tableOid = oid; + } + + return NULL; +} + + +/* + * Initializes the MERGE Execution Node at the begginning of the execution + * phase. + */ +static void begin_cypher_merge(CustomScanState *node, EState *estate, + int eflags) +{ + cypher_merge_custom_scan_state *css = + (cypher_merge_custom_scan_state *)node; + ListCell *lc; + Plan *subplan; +css->found_a_path = false; + Assert(list_length(css->cs->custom_plans) == 1); + + // initialize the subplan + subplan = linitial(css->cs->custom_plans); + node->ss.ps.lefttree = ExecInitNode(subplan, estate, eflags); + + ExecAssignExprContext(estate, &node->ss.ps); + + ExecInitScanTupleSlot(estate, &node->ss, + ExecGetResultType(node->ss.ps.lefttree), + &TTSOpsHeapTuple); + + /* + * When MERGE is not the last clause in a cypher query. Setup projection + * information to pass to the parent execution node. + */ + if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) + { + TupleDesc tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + + ExecAssignProjectionInfo(&node->ss.ps, tupdesc); + } + + cypher_create_path *path = css->path; + + css->vertex_ids = palloc(sizeof(graphid *) * 1); + css->vertex_ids[0] = palloc(sizeof(graphid) * ((list_length(path->target_nodes)/ 2) + 1) ); + + if (path->target_nodes) { + css->edge_ids = palloc(sizeof(graphid *) * 1); + css->edge_ids[0] = palloc(sizeof(graphid) * ((list_length(path->target_nodes) / 2) ) ); + } + + ListCell *lc1; + int i = 0; + foreach (lc1, path->target_nodes) { + cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc1); + + if (cypher_node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) { + i++; + continue; + } + + cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); + + if (i % 2 == 1) { + i++; + continue; + } + + cypher_node->rel = table_open(cypher_node->relid, RowExclusiveLock); + cypher_node->resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(cypher_node->resultRelInfo, cypher_node->rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + + // Open all indexes for the relation + ExecOpenIndices(cypher_node->resultRelInfo, false); + + // Setup the relation's tuple slot + cypher_node->elemTupleSlot = table_slot_create(cypher_node->rel, &estate->es_tupleTable); + + cypher_node->adj_rel = table_open(cypher_node->adj_relid, RowExclusiveLock); + cypher_node->adj_resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(cypher_node->adj_resultRelInfo, cypher_node->adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + + ExecOpenIndices(cypher_node->adj_resultRelInfo, false); + + // Setup the relation's tuple slot + cypher_node->adj_elemTupleSlot = table_slot_create(cypher_node->adj_rel, &estate->es_tupleTable); + + i++; + + } + + if (estate->es_output_cid == 0) + estate->es_output_cid = estate->es_snapshot->curcid; + + AdvanceCmdId(estate); +} + + + +/* + * Checks the subtree to see if the lateral join representing the MERGE path + * found results. Returns true if the path does not exist and must be created, + * false otherwise. + */ +static bool check_path(cypher_merge_custom_scan_state *css, + TupleTableSlot *slot) +{ + cypher_create_path *path = css->path; + ListCell *lc; + + foreach(lc, path->target_nodes) + { + cypher_target_node *node = lfirst(lc); + + /* + * If target_node as a valid attribute number and is a node not + * declared in a previous clause, check the tuple position in the + * slot. If the slot is null, the path was not found. The rules + * state that if one part of the path does not exists, the whold + * path must be created. + */ + if (node->tuple_position != InvalidAttrNumber || + ((node->flags & CYPHER_TARGET_NODE_MERGE_EXISTS) == 0)) + { + /* + * Attribute number is 1 indexed and tts_values is 0 indexed, + * offset by 1. + */ + if (slot->tts_isnull[node->tuple_position - 1]) + { + return true; + } + } + + } + + return false; +} +static void process_path(cypher_merge_custom_scan_state *css) +{ + cypher_create_path *path = css->path; + EState *estate = css->css.ss.ps.state; + ExprContext *econtext = css->css.ss.ps.ps_ExprContext; + TupleTableSlot *slot = econtext->ecxt_scantuple; + + ListCell *lc; + int i = 1; + foreach(lc, path->target_nodes) { + cypher_target_node *node = (cypher_target_node *)lfirst(lc); + bool isNull; + if(i % 2 == 1) { + if(node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) + css->vertex_ids[0][i/2] = DATUM_GET_GRAPHID(slot->tts_values[node->id_attr_num - 1]); + else + css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + } else { + css->edge_ids[0][(i-1)/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + } + i++; + } + + for (int i = 0; i < list_length(path->target_nodes)/2 + 1; i++) { + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); + + ResultRelInfo *resultRelInfo = node->resultRelInfo; + TupleTableSlot *elemTupleSlot = node->elemTupleSlot; + + ResultRelInfo **old_estate_es_result_relations_info = NULL; + + /* save the old result relation info */ + old_estate_es_result_relations_info = estate->es_result_relations; + + estate->es_result_relations = &resultRelInfo; + if(!(node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE)){ + ExecClearTuple(elemTupleSlot); + + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; + if (node->id_attr_num != InvalidAttrNumber) { + slot->tts_values[node->id_attr_num - 1] = GRAPHID_GET_DATUM(css->vertex_ids[0][i]); + slot->tts_isnull[node->id_attr_num - 1] = false; + } + + // get the properties for this vertex + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + } else { + elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->prop_attr_num - 1]; + } + + if (node->tuple_position != InvalidAttrNumber) { + slot->tts_values[node->tuple_position - 1] = VERTEX_GET_DATUM(create_vertex( + slot->tts_values[node->id_attr_num - 1], + css->graph_oid, + elemTupleSlot->tts_isnull[1] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[1]))); + slot->tts_isnull[node->tuple_position - 1] = false; + } + + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + } + + + if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { + cypher_target_node *start_vertex; + if (node->dir == CYPHER_REL_DIR_RIGHT) { + start_vertex = list_nth(path->target_nodes, i * 2); + } else { + start_vertex = list_nth(path->target_nodes, (i * 2) + 2); + } + + if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ + label_cache_data *lcd = search_label_graph_oid_cache( + css->graph_oid, + DATUM_GET_GRAPHID(slot->tts_values[start_vertex->id_attr_num - 1]) >> ENTRY_ID_BITS); + start_vertex->adj_relid = lcd->relation; + + Relation adj_rel = table_open(start_vertex->adj_relid, RowExclusiveLock); + + start_vertex->adj_resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(start_vertex->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + ExecOpenIndices(start_vertex->adj_resultRelInfo, false); + + start_vertex->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); + } + + resultRelInfo = start_vertex->adj_resultRelInfo; + elemTupleSlot = start_vertex->adj_elemTupleSlot; + + estate->es_result_relations = &resultRelInfo; + + ExecClearTuple(elemTupleSlot); + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, (i * 2) + 1); + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; + + elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; + elemTupleSlot->tts_isnull[1] = false; + + elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; + elemTupleSlot->tts_isnull[2] = false; + + + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[3] = NULL; + elemTupleSlot->tts_isnull[3] = true; + } else { + elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; + } + + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + + if (node->tuple_position != InvalidAttrNumber) { + slot->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(create_edge( + css->edge_ids[0][i], + css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1], + css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i], + css->graph_oid, + elemTupleSlot->tts_isnull[3] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[3]))); + slot->tts_isnull[node->tuple_position - 1] = false; + } + + if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ + ExecCloseIndices(start_vertex->adj_resultRelInfo); + table_close(start_vertex->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); + } + } + + // restore the old result relation info + estate->es_result_relations = old_estate_es_result_relations_info; + } + +} + +/* + * Function that handles the case where MERGE is the only clause in the query. + */ +static void process_simple_merge(CustomScanState *node) +{ + cypher_merge_custom_scan_state *css = + (cypher_merge_custom_scan_state *)node; + EState *estate = css->css.ss.ps.state; + TupleTableSlot *slot; + + /*Process the subtree first */ + RollbackCmdId(estate); + slot = ExecProcNode(node->ss.ps.lefttree); + AdvanceCmdId(estate); + + if (TupIsNull(slot)) + { + ExprContext *econtext = node->ss.ps.ps_ExprContext; + + /* setup the scantuple that the process_path needs */ + econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ResultTupleSlot; + //econtext->ecxt_scantuple->tts_isempty = false; + + process_path(css); + } +} + +/* + * Iterate through the TupleTableSlot's tts_values and marks the isnull field + * with true. + */ +static void mark_tts_isnull(TupleTableSlot *slot) +{ + int numberOfAttributes = slot->tts_tupleDescriptor->natts; + int i; + + for (i = 0; i < numberOfAttributes; i++) + { + Datum val; + + val = slot->tts_values[i]; + + if (val == (Datum)NULL) + { + slot->tts_isnull[i] = true; + } + } +} + +/* + * Function that is called mid-execution. This function will call + * its subtree in the execution tree, and depending on the results + * create the new path, and depending on the the context of the MERGE + * within the query pass data to the parent execution node. + * + * Returns a TupleTableSlot with the next tuple to it parent or + * Returns NULL when MERGE has no more tuples to emit. + */ +static TupleTableSlot *exec_cypher_merge(CustomScanState *node) +{ + cypher_merge_custom_scan_state *css = + (cypher_merge_custom_scan_state *)node; + EState *estate = css->css.ss.ps.state; + ExprContext *econtext = css->css.ss.ps.ps_ExprContext; + TupleTableSlot *slot; + bool terminal = CYPHER_CLAUSE_IS_TERMINAL(css->flags); + + /* + * There are three cases that dictate the flow of the execution logic. + * + * 1. MERGE is not the first clause in the cypher query. + * 2. MERGE is the first clause and there are no following clauses. + * 3. MERGE is the first clause and there are following clauses. + * CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE + */ + if (CYPHER_CLAUSE_HAS_PREVIOUS_CLAUSE(css->flags)) + { + /* + * Case 1: MERGE is not the first clause in the cypher query. + * + * For this case, we need to process all tuples give to us by the + * previous clause. When we receive a tuple from the previous clause: + * check to see if the left lateral join found the pattern already. If + * it did, we don't need to create the pattern. If the lateral join did + * not find the whole path, create the whole path. + * + * If this is a terminal clause, process all tuples. If not, pass the + * tuple to up the execution tree. + */ + do + { + /*Process the subtree first */ + RollbackCmdId(estate); + slot = ExecProcNode(node->ss.ps.lefttree); + AdvanceCmdId(estate); + + /* + * We are done processing the subtree, mark as terminal + * so the function returns NULL. + */ + if (TupIsNull(slot)) + { + terminal = true; + break; + } + + /* setup the scantuple that the process_path needs */ + econtext->ecxt_scantuple = + node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; + + if (check_path(css, econtext->ecxt_scantuple)) + { + process_path(css); + } + + } while (terminal); + + /* if this was a terminal MERGE just return NULL */ + if (terminal) + return NULL; + + econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); + return ExecProject(node->ss.ps.ps_ProjInfo); + } + else if (terminal) + { + /* + * Case 2: MERGE is the first clause and there are no following clauses + * + * For case 2, check to see if we found the pattern, if not create it. + * Return NULL in either cases, because no rows are expected. + */ + process_simple_merge(node); + + /* + * Case 2 always returns NULL the first time exec_cypher_merge is + * called. + */ + return NULL; + } + else + { + /* + * Case 3: MERGE is the first clause and there are following clauses. + * + * Case three has two subcases: + * + * 1. The already path exists. + * 2. The path does not exist. + */ + + /* + * Part of Case 2. + * + * If created_new_path is marked as true. The path did not exist and + * MERGE created it. We have already passed that information up the + * execution tree, and now we tell MERGE's parents in the execution + * tree there is no more tuples to pass. + */ + if (css->created_new_path) + { + /* + * If the created_new_path is set to true. Then MERGE should not + * have found a path, because this should only be set to true if + * merge found sub-case 1 + */ + Assert(css->found_a_path == false); + + return NULL; + } + + /* + * Process the subtree. The subtree will only consist of the MERGE + * path. + */ + RollbackCmdId(estate); + slot = ExecProcNode(node->ss.ps.lefttree); + AdvanceCmdId(estate); + + if (!TupIsNull(slot)) + { + /* + * Part of Sub-Case 1. + * + * If we found a path, mark the found_a_path flag to true and + * pass the tuple to the next execution tree. When the path + * exists, we don't need to create/modify anything. + */ + css->found_a_path = true; + + return node->ss.ps.lefttree->ps_ResultTupleSlot; + } + else if (TupIsNull(slot) && css->found_a_path) + { + /* + * Part of Sub-Case 2. + * + * MERGE found the path(s) that already exists and we are done passing + * all the found path(s) up the execution tree. + */ + return NULL; + } + else + { + /* + * Part of Sub-Case 1. + * + * MERGE could not find the path in memory and the sub-node in the + * execution tree returned NULL. We need to create the path and + * pass the tuple to the next execution node. The subtrees will + * begin its cleanup process when there are no more tuples found. + * So we will need to create a TupleTableSlot and populate with the + * information from the newly created path that the query needs. + */ + ExprContext *econtext = node->ss.ps.ps_ExprContext; + SubqueryScanState *sss = (SubqueryScanState *)node->ss.ps.lefttree; + HeapTuple heap_tuple; + + /* + * Our child execution node is always a subquery. If not there + * is an issue. + */ + Assert(IsA(sss, SubqueryScanState)); + + /* + * found_a_path should only be set to true if MERGE is following + * sub-case 2. + */ + Assert(css->found_a_path == false); + + /* + * This block of sub-case 1 should only be exectuted once. To + * create the single path if the path does not exist. If we find + * ourselves here again, the internal state of the MERGE execution + * node was incorrectly altered. + */ + Assert(css->created_new_path == false); + + /* + * Postgres cleared the child tuple table slot, we need to remake + * it. + */ + ExecInitScanTupleSlot(estate, &sss->ss, + ExecGetResultType(sss->subplan),&TTSOpsHeapTuple); + + + /* setup the scantuple that the process_path needs */ + econtext->ecxt_scantuple = sss->ss.ss_ScanTupleSlot; + + // create the path + process_path(css); + + // mark the create_new_path flag to true. + css->created_new_path = true; + + /* + * find the tts_values that process_path did not populate and + * mark as null. + */ + mark_tts_isnull(econtext->ecxt_scantuple); + + // create the physical heap tuple + /*heap_tuple = heap_form_tuple( + econtext->ecxt_scantuple->tts_tupleDescriptor, + econtext->ecxt_scantuple->tts_values, + econtext->ecxt_scantuple->tts_isnull); +*/ + + // store the heap tuble + ExecStoreVirtualTuple(econtext->ecxt_scantuple); + + /* + * make the subquery's projection scan slot be the tuple table we + * created and run the projection logic. + */ + sss->ss.ps.ps_ProjInfo->pi_exprContext->ecxt_scantuple = + econtext->ecxt_scantuple; + + // assign this to be our scantuple + econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); + + /* + * run the merge's projection logic and pass to its parent + * execution node + */ + return ExecProject(node->ss.ps.ps_ProjInfo); + } + } +} + + +/* + * Function called at the end of the execution phase to cleanup + * MERGE. + */ +static void end_cypher_merge(CustomScanState *node) +{ + cypher_merge_custom_scan_state *css = + (cypher_merge_custom_scan_state *)node; + cypher_create_path *path = css->path; + + // increment the command counter + CommandCounterIncrement(); + + ExecEndNode(node->ss.ps.lefttree); + + ListCell *lc; + //foreach (lc, css->pattern) { + ListCell *lc2; + + foreach (lc2, path->target_nodes) { + cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc2); + + if (!cypher_node->resultRelInfo || node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) + continue; + + ExecCloseIndices(cypher_node->resultRelInfo); + table_close(cypher_node->rel, RowExclusiveLock); + ExecCloseIndices(cypher_node->adj_resultRelInfo); + table_close(cypher_node->adj_rel, RowExclusiveLock); + //RelationDecrementReferenceCount(cypher_node->relation); + //RelationDecrementReferenceCount(lcd->relation); + } + //} +} + +/* + * Rescan is mostly used by join execution nodes, and several others. + * Since we are creating data here its not safe to rescan the node. Throw + * an error and try to help the uer understand what went wrong. + */ +static void rescan_cypher_merge(CustomScanState *node) +{ + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cypher merge clause cannot be rescaned"), + errhint("its unsafe to use joins in a query with a Cypher MERGE clause"))); +} + +/* + * Extracts the metadata information that MERGE needs from the + * merge_custom_scan node and creates the cypher_merge_custom_scan_state + * for the execution phase. + */ +Node *create_cypher_merge_plan_state(CustomScan *cscan) +{ + cypher_merge_custom_scan_state *cypher_css = + palloc0(sizeof(cypher_merge_custom_scan_state)); + cypher_merge_information *merge_information; + char *serialized_data; + Const *c; + + cypher_css->cs = cscan; + + // get the serialized data structure from the Const and deserialize it. + c = linitial(cscan->custom_private); + serialized_data = (char *)c->constvalue; + merge_information = stringToNode(serialized_data); + + Assert(is_ag_node(merge_information, cypher_merge_information)); + + cypher_css->merge_information = merge_information; + cypher_css->flags = merge_information->flags; + cypher_css->merge_function_attr = merge_information->merge_function_attr; + cypher_css->path = merge_information->path; + cypher_css->created_new_path = false; + cypher_css->found_a_path = false; + cypher_css->graph_oid = merge_information->graph_oid; + + cypher_css->css.ss.ps.type = T_CustomScanState; + cypher_css->css.methods = &cypher_merge_exec_methods; + + return (Node *)cypher_css; +} + diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 2a73d2e8..d68cc1e9 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -32,17 +32,42 @@ typedef Query *(*transform_method)(cypher_parsestate *cpstate, cypher_clause *cl static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, ParseExprKind expr_kind); static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, ParseExprKind expr_kind, const char *construct_name); static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause); +Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause); + // match clause static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause); static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where); static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause); +// match from clause functions +static void process_three_element_path(cypher_parsestate *cpstate, Query *query, cypher_path *path, List **quals); +static List *find_path_parts(cypher_path *path); +static void process_path_part(cypher_parsestate *cpstate, Query *query, cypher_path *path, path_parts *pp, List **quals, cypher_relationship **prev_edge); +static ParseNamespaceItem *add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, List **quals); +static ParseNamespaceItem *add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, ParseNamespaceItem *edge_pnsi, List **quals); +static ParseNamespaceItem *add_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, cypher_node *vertex); +static ParseNamespaceItem *add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *prev_pnsi, char *prev_name); +static ParseNamespaceItem *add_variable_edge_to_query(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, ParseNamespaceItem *vertex_pnsi); +// match select clause functions +static void add_all_fields_to_target_list(cypher_parsestate *cpstate, Query *query, cypher_node *left_vertex, cypher_relationship *edge, cypher_node *right_vertex); +static void add_new_fields_to_target_list(cypher_parsestate *cpstate, Query *query, cypher_node *left_vertex, cypher_relationship *edge, cypher_node *right_vertex); +// match expression logic +static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, List **namespace); +static A_Expr *makeSimpleCypherA_Expr(A_Expr_Kind kind, char *name, Node *lexpr, Node *rexpr, int location); +static ParseNamespaceItem *add_srf_to_query(cypher_parsestate *cpstate, Node *n, char *var_name, List *colnames); +static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); +static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, List **namespace); +static ParseNamespaceItem *transformRangeFunction(cypher_parsestate *cpstate, RangeFunction *r); +RangeFunction *make_range_function(FuncCall *func, Alias *alias, bool is_lateral, bool ordinality, bool is_rows_from); +static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, ParseNamespaceItem *r_pnsi, List **res_colnames, List **res_colvars); static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi); static Node *make_vertex_expr_with_edge(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi, ParseNamespaceItem *edge_pnsi); -static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, ParseNamespaceItem *r_pnsi, List **res_colnames, List **res_colvars); -static Node *transform_srf_function(cypher_parsestate *cpstate, Node *n, List **namespace) ; + // create clause static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause *clause); +static void process_create_vertex(cypher_parsestate *cpstate, Query *query, cypher_node *node, cypher_create_path *ccp); +static void process_create_edge(cypher_parsestate *cpstate, Query *query, cypher_relationship *edge, cypher_create_path *ccp); + // merge static Query *transform_cypher_merge(cypher_parsestate *cpstate, cypher_clause *clause); static cypher_create_path *transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, cypher_clause *isolated_merge_clause); @@ -52,44 +77,46 @@ static cypher_target_node *transform_merge_cypher_edge(cypher_parsestate *cpstat static Node *transform_clause_for_join(cypher_parsestate *cpstate, cypher_clause *clause, RangeTblEntry **rte, ParseNamespaceItem **nsitem, Alias* alias); static cypher_clause *convert_merge_to_match(cypher_merge *merge); static void transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate, List *target_list, cypher_create_path *path); -static TargetEntry *placeholder_vertex(cypher_parsestate *cpstate, char *name); -static TargetEntry *placeholder_edge(cypher_parsestate *cpstate, char *name); -static TargetEntry *placeholder_traversal(cypher_parsestate *cpstate, char *name); // transform #define PREV_CYPHER_CLAUSE_ALIAS "_" #define CYPHER_OPT_RIGHT_ALIAS "_R" #define transform_prev_cypher_clause(cpstate, prev_clause, add_rte_to_query) \ - transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, \ - prev_clause, NULL, add_rte_to_query) + transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, prev_clause, NULL, add_rte_to_query) static ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, Alias *alias, bool add_rte_to_query); -static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause); static Query *analyze_cypher_clause(transform_method transform, cypher_clause *clause, cypher_parsestate *parent_cpstate); static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); static void setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok); -static void -transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query); +static void transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query); + +// utility functions +// label validation +static void validate_or_create_elabel(cypher_parsestate *cpstate, cypher_relationship *edge); +static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *node); +// write clause function placeholder +static FuncExpr *make_write_clause_function_placeholder(char *function_name, Node *clause_information); +//empty placeholders +static Node *make_graphid_placeholder(cypher_parsestate *cpstate); +static Node *make_gtype_placeholder(cypher_parsestate *cpstate); +static Node *make_edge_placeholder(cypher_parsestate *cpstate); +static Node *make_vertex_placeholder(cypher_parsestate *cpstate); +// volatile wrappers +static Expr *add_volatile_wrapper(Expr *node); +static Expr *add_volatile_edge_wrapper(Expr *node); +static Expr *add_volatile_vle_edge_wrapper(Expr *node); +static Expr *add_volatile_vertex_wrapper(Expr *node); +// aliasing static char *make_id_alias(char *var_name); static char *make_property_alias(char *var_name); static char *make_startid_alias(char *var_name); static char *make_endid_alias(char *var_name); +static char *make_vertex_adjlist_alias(char *var_name); +Alias *make_alias(char *name, List *colnames); -static char *make_vertex_adjlist_alias(char *var_name) { - char *str = palloc(strlen(var_name) + 8); +static Node *make_int_const(int i, int location); +static Node *make_null_const(int location); - str[0] = '_'; - str[1] = 'a'; - str[2] = 'd'; - str[3] = 'j'; - str[4] = '_'; - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 5] = var_name[i]; - str[i + 5] = '\0'; - - return str; -} /* * transform a cypher_clause @@ -124,156 +151,6 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause return result; } -static FuncExpr *make_write_clause_function_placeholder(char *function_name, Node *clause_information) { - StringInfo str = makeStringInfo(); - outNode(str, clause_information); - - return makeFuncExpr( - get_ag_func_oid(function_name, 1, INTERNALOID), - GTYPEOID, - list_make1(makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false)), - InvalidOid, - InvalidOid, - COERCE_EXPLICIT_CALL); -} - -static char *make_id_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); - - str[0] = '_'; - str[1] = 'i'; - str[2] = 'd'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\0'; - - return str; -} - -static char *make_startid_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); - - str[0] = '_'; - str[1] = 's'; - str[2] = 't'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\0'; - - return str; -} - -static char *make_endid_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); - - str[0] = '_'; - str[1] = 'e'; - str[2] = 'n'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\0'; - - return str; -} - -static char *make_property_alias(char *var_name) { - char *str = palloc0(strlen(var_name) + 8); - - str[0] = '_'; - str[1] = 'p'; - str[2] = 'r'; - str[3] = '_'; - - int i = 0; - for (; i < strlen(var_name); i++) - str[i + 4] = var_name[i]; - - str[i + 5] = '_'; - str[i + 6] = '_'; - str[i + 7] = '\0'; - - return str; -} - -static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { - return makeFuncExpr( - get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID), - VERTEXOID, - list_make3( - scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1), - makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), - scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1)), - InvalidOid, - InvalidOid, - COERCE_EXPLICIT_CALL); - -} - -static Node *make_vertex_expr_with_edge(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi, ParseNamespaceItem *edge_pnsi) { - return makeFuncExpr( - get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID), - VERTEXOID, - list_make3( - scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1), - makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), - scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1)), - InvalidOid, - InvalidOid, - COERCE_EXPLICIT_CALL); -} - -static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { - return makeFuncExpr( - get_ag_func_oid("build_edge", 5, GRAPHIDOID, GRAPHIDOID, GRAPHIDOID, OIDOID, GTYPEOID), - EDGEOID, - list_make5( - scanNSItemForColumn(cpstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1), - scanNSItemForColumn(cpstate, pnsi, 0, "startid", -1), - scanNSItemForColumn(cpstate, pnsi, 0, "endid", -1), - makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), - scanNSItemForColumn(cpstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1)), - InvalidOid, - InvalidOid, - COERCE_EXPLICIT_CALL); -} - -static Node * -make_graphid_placeholder(cypher_parsestate *cpstate) { - return makeConst(GRAPHIDOID, -1, InvalidOid, sizeof(graphid), GRAPHID_GET_DATUM(0), false, true); -} - -static Node * -make_gtype_placeholder(cypher_parsestate *cpstate) { - return makeConst(GTYPEOID, -1, InvalidOid, -1, integer_to_gtype(0), false, false); -} - -static Node * -make_edge_placeholder(cypher_parsestate *cpstate) { - return makeConst(EDGEOID, -1, InvalidOid, -1, create_edge(0, 0, 0, cpstate->graph_oid, NULL), false, false); -} - -static Node * -make_vertex_placeholder(cypher_parsestate *cpstate) { - return makeConst(VERTEXOID, -1, InvalidOid, -1, create_vertex(0, cpstate->graph_oid, NULL), false, false); -} /* * This function is similar to transformFromClause() that is called with a @@ -292,32 +169,6 @@ transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query) //query->targetList = list_concat(query->targetList, expandNSItemAttrs(pstate, pnsi, 0, -1)); } -static Expr *add_volatile_wrapper(Expr *node) { - return (Expr *)makeFuncExpr(get_ag_func_oid("gtype_volatile_wrapper", 1, GTYPEOID), GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} -static Expr *add_volatile_edge_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, EDGEOID); - - return (Expr *)makeFuncExpr(oid, EDGEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} - -static Expr *add_volatile_vle_edge_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, VARIABLEEDGEOID); - - return (Expr *)makeFuncExpr(oid, VARIABLEEDGEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} - -static Expr *add_volatile_vertex_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, VERTEXOID); - - return (Expr *)makeFuncExpr(oid, VERTEXOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -} -/* -static Expr *add_volatile_traversal_wrapper(Expr *node) { - Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, TRAVERSALOID); - - return (Expr *)makeFuncExpr(oid, TRAVERSALOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); -}*/ /* * validate_or_create_elabel @@ -345,10 +196,11 @@ static void validate_or_create_elabel(cypher_parsestate *cpstate, cypher_relatio errmsg("label %s is for vertices, not edges", edge->label), parser_errposition(cpstate, edge->location))); else if (!lcd) - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, + create_label(cpstate->graph_name, + edge->label, + LABEL_TYPE_EDGE, list_make1(get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_EDGE)), NULL); - } /* @@ -376,8 +228,12 @@ static void validate_or_create_vlabel(cypher_parsestate *cpstate, cypher_node *n ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("label %s is for edges, not vertices", node->label), parser_errposition(cpstate, node->location))); - else if (!lcd) - create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, list_make1(get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_VERTEX)), NULL); + else if (!lcd) + create_label(cpstate->graph_name, + node->label, + LABEL_TYPE_VERTEX, + list_make1(get_label_range_var(cpstate->graph_name, cpstate->graph_oid, AG_DEFAULT_LABEL_VERTEX)), + NULL); } static TargetEntry * get_target_entry(cypher_parsestate *cpstate, List *target_list, char *name) { @@ -459,12 +315,10 @@ process_create_vertex( cypher_node *node, cypher_create_path *ccp) { - ParseState *pstate = (ParseState *)cpstate; cypher_target_node *target = make_ag_node(cypher_target_node); if (node->name) { - // /ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); Var *var = NULL; if ((var = colNameToVar(cpstate, node->name, false, -1)) || (target->id_attr_num = get_target_entry_resno(cpstate, query->targetList, make_id_alias(node->name))) != -1) { @@ -488,13 +342,11 @@ process_create_vertex( return; } - - target->variable_name = node->name; query->targetList = lappend(query->targetList, makeTargetEntry( make_graphid_placeholder(cpstate), target->id_attr_num = pstate->p_next_resno++, - make_id_alias(node->name), + make_id_alias(target->variable_name = node->name), false)); if (node->props) { @@ -516,7 +368,6 @@ process_create_vertex( false)); } else { - node->name = get_next_default_alias(cpstate); target->id_attr_num = InvalidAttrNumber; target->tuple_position = InvalidAttrNumber; @@ -526,7 +377,7 @@ process_create_vertex( (Expr *)add_volatile_wrapper( transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), target->prop_attr_num = pstate->p_next_resno++, - make_property_alias(node->name), + make_property_alias(node->name = get_next_default_alias(cpstate)), false)); } else { target->prop_attr_num = InvalidAttrNumber; @@ -714,7 +565,11 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, cypher_clause if (clause->prev) { - handle_prev_clause(cpstate, query, clause->prev); + query->targetList = list_concat(query->targetList, + expandNSItemAttrs(get_parse_state(cpstate), + transform_prev_cypher_clause(cpstate, clause->prev, true), + 0, + -1)); target_nodes->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE; } @@ -1114,6 +969,7 @@ makeSimpleCypherA_Expr(A_Expr_Kind kind, char *name, return a; } + Alias *make_alias(char *name, List *colnames) { Alias *alias = makeNode(Alias); alias->aliasname = name; @@ -1414,7 +1270,6 @@ add_vertex_retrieval_to_query(cypher_parsestate *cpstate, Query *query, cypher_n return pnsi; } - /* * add_edge_to_query * @@ -1537,6 +1392,8 @@ add_edge_to_query_with_prev_edge(cypher_parsestate *cpstate, Query *query, cyphe list_make4(makeString("id"), makeString("startid"), makeString("endid"), makeString("properties"))); } + + /* * add_variable_edge_to_query * @@ -2099,7 +1956,6 @@ process_path_part( } } - static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, List *pattern, Node *where) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc; @@ -2264,36 +2120,6 @@ Query *cypher_parse_sub_analyze(Node *parseTree, cypher_parsestate *cpstate, Com return query; } -/* - * handle_prev_clause - * - * Utility function to bring the output of a previous Cypher clause (such as MATCH or WITH) - * into the current query context. This is used to chain clauses together so that variables - * and expressions projected by the previous clause are available in the current clause. - * - * Parameters: - * cpstate - The current Cypher parser state. - * query - The Query node being constructed (modified in-place). - * clause - The previous cypher_clause whose output should be made available. - - * Behavior: - * 1. Transforms the previous clause as a subquery and adds it as a RangeTblEntry. - * 2. Expands all attributes from the subquery and appends them to the current query's target list, - * making all projected variables/columns available for use in the current clause. - */ -static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause) { - ParseState *pstate = (ParseState *) cpstate; - - ParseNamespaceItem *pnsi = transform_prev_cypher_clause(cpstate, clause, true); - - int rtindex = list_length(pstate->p_rtable); - - // add all the rte's attributes to the current queries targetlist - query->targetList = list_concat(query->targetList, expandNSItemAttrs(pstate, pnsi, 0, -1)); -} - - - /* * * There are two cases for the form Query that is returned from here will @@ -2384,8 +2210,11 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, cypher_clause * * query. the merge execution phase will just pass the results up the * execution tree if the path exists. */ - handle_prev_clause(cpstate, query, merge_clause_as_match); - + query->targetList = list_concat(query->targetList, + expandNSItemAttrs(get_parse_state(cpstate), + transform_prev_cypher_clause(cpstate, merge_clause_as_match, true), + 0, + -1)); /* * For the metadata need to create paths, find the tuple position that * will represent the entity in the execution phase. @@ -2783,3 +2612,202 @@ static cypher_clause *convert_merge_to_match(cypher_merge *merge) { return clause; } + + +static FuncExpr *make_write_clause_function_placeholder(char *function_name, Node *clause_information) { + StringInfo str = makeStringInfo(); + outNode(str, clause_information); + + return makeFuncExpr( + get_ag_func_oid(function_name, 1, INTERNALOID), + GTYPEOID, + list_make1(makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { + return makeFuncExpr( + get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID), + VERTEXOID, + list_make3( + scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1), + makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), + scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); + +} + +static Node *make_vertex_expr_with_edge(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi, ParseNamespaceItem *edge_pnsi) { + return makeFuncExpr( + get_ag_func_oid("build_vertex", 3, GRAPHIDOID, OIDOID, GTYPEOID), + VERTEXOID, + list_make3( + scanNSItemForColumn(cpstate, edge_pnsi, 0, "endid", -1), + makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), + scanNSItemForColumn(cpstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *make_edge_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { + return makeFuncExpr( + get_ag_func_oid("build_edge", 5, GRAPHIDOID, GRAPHIDOID, GRAPHIDOID, OIDOID, GTYPEOID), + EDGEOID, + list_make5( + scanNSItemForColumn(cpstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1), + scanNSItemForColumn(cpstate, pnsi, 0, "startid", -1), + scanNSItemForColumn(cpstate, pnsi, 0, "endid", -1), + makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, true), + scanNSItemForColumn(cpstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node * +make_graphid_placeholder(cypher_parsestate *cpstate) { + return makeConst(GRAPHIDOID, -1, InvalidOid, sizeof(graphid), GRAPHID_GET_DATUM(0), false, true); +} + +static Node * +make_gtype_placeholder(cypher_parsestate *cpstate) { + return makeConst(GTYPEOID, -1, InvalidOid, -1, integer_to_gtype(0), false, false); +} + +static Node * +make_edge_placeholder(cypher_parsestate *cpstate) { + return makeConst(EDGEOID, -1, InvalidOid, -1, create_edge(0, 0, 0, cpstate->graph_oid, NULL), false, false); +} + +static Node * +make_vertex_placeholder(cypher_parsestate *cpstate) { + return makeConst(VERTEXOID, -1, InvalidOid, -1, create_vertex(0, cpstate->graph_oid, NULL), false, false); +} + + +static char *make_vertex_adjlist_alias(char *var_name) { + char *str = palloc(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'a'; + str[2] = 'd'; + str[3] = 'j'; + str[4] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 5] = var_name[i]; + str[i + 5] = '\0'; + + return str; +} + +static char *make_id_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'i'; + str[2] = 'd'; + str[3] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; + + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\0'; + + return str; +} + +static char *make_startid_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 's'; + str[2] = 't'; + str[3] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; + + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\0'; + + return str; +} + +static char *make_endid_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'e'; + str[2] = 'n'; + str[3] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; + + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\0'; + + return str; +} + +static char *make_property_alias(char *var_name) { + char *str = palloc0(strlen(var_name) + 8); + + str[0] = '_'; + str[1] = 'p'; + str[2] = 'r'; + str[3] = '_'; + + int i = 0; + for (; i < strlen(var_name); i++) + str[i + 4] = var_name[i]; + + str[i + 5] = '_'; + str[i + 6] = '_'; + str[i + 7] = '\0'; + + return str; +} + + + +static Expr *add_volatile_wrapper(Expr *node) { + return (Expr *)makeFuncExpr(get_ag_func_oid("gtype_volatile_wrapper", 1, GTYPEOID), GTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} +static Expr *add_volatile_edge_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, EDGEOID); + + return (Expr *)makeFuncExpr(oid, EDGEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} + +static Expr *add_volatile_vle_edge_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, VARIABLEEDGEOID); + + return (Expr *)makeFuncExpr(oid, VARIABLEEDGEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} + +static Expr *add_volatile_vertex_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, VERTEXOID); + + return (Expr *)makeFuncExpr(oid, VERTEXOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +} +/* +static Expr *add_volatile_traversal_wrapper(Expr *node) { + Oid oid = get_ag_func_oid("gtype_volatile_wrapper", 1, TRAVERSALOID); + + return (Expr *)makeFuncExpr(oid, TRAVERSALOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); +}*/ \ No newline at end of file From 6d67dfcfba2a3597dc7bdcd918b8b3cc79ea9b2b Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Mon, 27 Oct 2025 17:14:18 -0500 Subject: [PATCH 54/56] :Get the MERGE Clause Basic functionality working for one row Very basic functionality, but it is good progress --- regress/expected/cypher_create.out | 120 +++++++++++++------------ regress/expected/cypher_merge.out | 132 ++++++++++++++++++++++++++++ regress/sql/cypher_merge.sql | 55 ++++++++++++ src/backend/executor/cypher_merge.c | 15 ++-- src/backend/parser/cypher_clause.c | 92 +++++++++++++++---- 5 files changed, 332 insertions(+), 82 deletions(-) create mode 100644 regress/expected/cypher_merge.out create mode 100644 regress/sql/cypher_merge.sql diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out index 3c7ecc2a..4b744406 100644 --- a/regress/expected/cypher_create.out +++ b/regress/expected/cypher_create.out @@ -342,22 +342,22 @@ MATCH ()-[]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH (q)-[]->(q)-[]->() RETURN 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.908..8.465 rows=0 loops=1) - -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.908..8.464 rows=0 loops=1) + Nested Loop (cost=1025.00..298373.57 rows=12537000 width=32) (actual time=7.344..8.064 rows=0 loops=1) + -> Gather (cost=1012.50..15491.07 rows=12600 width=16) (actual time=7.344..8.062 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.173..0.174 rows=0 loops=3) - -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.002..0.008 rows=12 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=16) (actual time=0.105..0.105 rows=0 loops=3) + -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.002..0.005 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.005..0.014 rows=34 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.013..0.013 rows=0 loops=36) + -> Parallel Seq Scan on _ag_label_vertex q_1 (cost=0.00..1.71 rows=71 width=8) (actual time=0.003..0.008 rows=34 loops=1) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=16) (actual time=0.008..0.008 rows=0 loops=36) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..25.00 rows=995 width=8) (never executed) Filter: (_age_default_alias_0.id OPERATOR(postgraph.<>) id) - Planning Time: 0.130 ms - Execution Time: 8.502 ms + Planning Time: 0.123 ms + Execution Time: 8.106 ms (16 rows) MATCH (q)-[]->(q)-[]->() RETURN 1; @@ -369,23 +369,21 @@ EXPLAIN ANALYZE MATCH (q) MATCH (q)-[]->() RETURN q; -WARNING: here QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.038..0.301 rows=15 loops=1) + Nested Loop (cost=12.50..365471.30 rows=2520000 width=32) (actual time=0.038..0.298 rows=15 loops=1) -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.003..0.010 rows=36 loops=1) -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.002..0.005 rows=34 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=36) Planning Time: 0.092 ms - Execution Time: 0.317 ms + Execution Time: 0.314 ms (8 rows) MATCH (q) MATCH (q)-[]->() RETURN q; -WARNING: here q --------------------------------------------------------------------------------- {"id": 281474976710659, "label": "", "properties": {}} @@ -411,25 +409,25 @@ MATCH ()-[]->(q) RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.329..0.349 rows=15 loops=1) + Merge Join (cost=387318.67..4839083.34 rows=31752000 width=32) (actual time=0.321..0.341 rows=15 loops=1) Merge Cond: (q.id OPERATOR(postgraph.=) _age_default_alias_1.endid) - -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.010..0.019 rows=35 loops=1) + -> Merge Append (cost=0.47..178.84 rows=2520 width=40) (actual time=0.010..0.018 rows=35 loops=1) Sort Key: q.id - -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.002..0.008 rows=34 loops=1) + -> Index Scan using idx_hash__ag_label_vertex on _ag_label_vertex q_1 (cost=0.14..13.94 rows=120 width=40) (actual time=0.002..0.007 rows=34 loops=1) -> Index Scan using idx_hash_test on test q_2 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.005..0.005 rows=1 loops=1) -> Index Scan using idx_hash_test2 on test2 q_3 (cost=0.15..66.15 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.315..0.318 rows=15 loops=1) - -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.314..0.315 rows=15 loops=1) + -> Materialize (cost=387318.20..399918.20 rows=2520000 width=8) (actual time=0.307..0.311 rows=15 loops=1) + -> Sort (cost=387318.20..393618.20 rows=2520000 width=8) (actual time=0.306..0.307 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.050..0.309 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=36 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=8) (actual time=0.044..0.301 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.012 rows=36 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.008..0.008 rows=0 loops=36) - Planning Time: 0.215 ms - Execution Time: 0.400 ms + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=8) (actual time=0.007..0.008 rows=0 loops=36) + Planning Time: 0.183 ms + Execution Time: 0.386 ms (19 rows) MATCH (q) @@ -460,20 +458,20 @@ MATCH (b)-[]->() RETURN q; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.039..0.478 rows=540 loops=1) - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.031..0.293 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) + Nested Loop (cost=12.50..873230536.40 rows=6350400000 width=32) (actual time=0.039..0.528 rows=540 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=0) (actual time=0.035..0.361 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex b_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=34 loops=1) -> Seq Scan on test b_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 b_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.007..0.007 rows=0 loops=36) + -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..22.50 rows=1000 width=0) (actual time=0.008..0.008 rows=0 loops=36) -> Materialize (cost=0.00..71.40 rows=2520 width=40) (actual time=0.000..0.002 rows=36 loops=15) -> Append (cost=0.00..58.80 rows=2520 width=40) (actual time=0.001..0.007 rows=36 loops=1) -> Seq Scan on _ag_label_vertex q_1 (cost=0.00..2.20 rows=120 width=40) (actual time=0.001..0.003 rows=34 loops=1) -> Seq Scan on test q_2 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 q_3 (cost=0.00..22.00 rows=1200 width=40) (actual time=0.001..0.001 rows=1 loops=1) - Planning Time: 0.115 ms - Execution Time: 0.511 ms + Planning Time: 0.128 ms + Execution Time: 0.562 ms (14 rows) MATCH (q) @@ -1050,10 +1048,10 @@ MATCH (q)-[]->()<-[]-(q) RETURN 1; EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- - Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.408..6.934 rows=0 loops=1) + Gather (cost=1012.50..15491.07 rows=12600 width=32) (actual time=6.575..7.097 rows=0 loops=1) Workers Planned: 2 Workers Launched: 2 - -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.100..0.100 rows=0 loops=3) + -> Nested Loop (cost=12.50..13231.07 rows=5250 width=32) (actual time=0.104..0.104 rows=0 loops=3) -> Parallel Append (cost=0.00..41.07 rows=1050 width=8) (actual time=0.001..0.005 rows=12 loops=3) -> Parallel Seq Scan on test q_2 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Parallel Seq Scan on test2 q_3 (cost=0.00..17.06 rows=706 width=8) (actual time=0.001..0.001 rows=1 loops=1) @@ -1061,8 +1059,8 @@ EXPLAIN ANALYZE MATCH (q)<-[]-(q) RETURN 1; -> Function Scan on edge_search _age_default_alias_0 (cost=12.50..25.00 rows=5 width=8) (actual time=0.008..0.008 rows=0 loops=36) Filter: (q.id OPERATOR(postgraph.=) endid) Rows Removed by Filter: 0 - Planning Time: 0.086 ms - Execution Time: 6.955 ms + Planning Time: 0.098 ms + Execution Time: 7.121 ms (13 rows) MATCH (q)<-[]-(q) RETURN 1; @@ -1098,31 +1096,31 @@ EXPLAIN MATCH ()-[]->()<-[]-() RETURN 1; EXPLAIN ANALYZE MATCH ()-[]->()<-[]-() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.604..0.605 rows=0 loops=1) + Merge Join (cost=809090.41..556487990.41 rows=31593240000 width=32) (actual time=0.663..0.664 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) Rows Removed by Join Filter: 15 - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.304..0.305 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.349..0.350 rows=15 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.042..0.299 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.004..0.014 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=34 loops=1) - -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.039..0.343 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.018 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.009 rows=34 loops=1) + -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.003..0.003 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) - -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=36) - -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.291..0.294 rows=15 loops=1) - -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.290..0.291 rows=15 loops=1) + -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.008..0.008 rows=0 loops=36) + -> Materialize (cost=404545.20..417145.20 rows=2520000 width=16) (actual time=0.303..0.306 rows=15 loops=1) + -> Sort (cost=404545.20..410845.20 rows=2520000 width=16) (actual time=0.303..0.304 rows=15 loops=1) Sort Key: _age_default_alias_3.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.032..0.283 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.001..0.010 rows=36 loops=1) + -> Nested Loop (cost=12.50..50471.30 rows=2520000 width=16) (actual time=0.033..0.295 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.002..0.011 rows=36 loops=1) -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.001..0.006 rows=34 loops=1) -> Seq Scan on test _age_default_alias_2_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_2_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.007..0.007 rows=0 loops=36) - Planning Time: 0.160 ms - Execution Time: 0.642 ms + Planning Time: 0.144 ms + Execution Time: 0.703 ms (25 rows) MATCH ()-[]->()<-[]-() RETURN 1; @@ -1152,14 +1150,14 @@ EXPLAIN MATCH (:test)-[]->()<-[]-(:test) RETURN 1; EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- - Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.018..0.019 rows=0 loops=1) + Merge Join (cost=331424.24..126340424.24 rows=7164000000 width=32) (actual time=0.018..0.018 rows=0 loops=1) Merge Cond: (_age_default_alias_1.endid OPERATOR(postgraph.=) _age_default_alias_3.endid) Join Filter: (_age_default_alias_1.id OPERATOR(postgraph.<>) _age_default_alias_3.id) -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (actual time=0.018..0.018 rows=0 loops=1) Sort Key: _age_default_alias_1.endid Sort Method: quicksort Memory: 25kB - -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.017 rows=0 loops=1) - -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.003 rows=1 loops=1) + -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (actual time=0.016..0.016 rows=0 loops=1) + -> Seq Scan on test _age_default_alias_0 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.002..0.002 rows=1 loops=1) -> Function Scan on edge_search _age_default_alias_1 (cost=12.50..22.50 rows=1000 width=16) (actual time=0.013..0.013 rows=0 loops=1) -> Materialize (cost=165712.12..171712.12 rows=1200000 width=16) (never executed) -> Sort (cost=165712.12..168712.12 rows=1200000 width=16) (never executed) @@ -1167,8 +1165,8 @@ EXPLAIN ANALYZE MATCH (:test)-[]->()<-[]-(:test) RETURN 1; -> Nested Loop (cost=12.50..24034.50 rows=1200000 width=16) (never executed) -> Seq Scan on test _age_default_alias_2 (cost=0.00..22.00 rows=1200 width=8) (never executed) -> Function Scan on edge_search _age_default_alias_3 (cost=12.50..22.50 rows=1000 width=16) (never executed) - Planning Time: 0.128 ms - Execution Time: 0.040 ms + Planning Time: 0.098 ms + Execution Time: 0.042 ms (17 rows) MATCH (:test)-[]->()<-[]-(:test) RETURN 1; @@ -1186,17 +1184,17 @@ MATCH ()-[*1]->()-[]->() RETURN 1; EXPLAIN ANALYZE MATCH ()-[*1]->()-[]->() RETURN 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.390..0.485 rows=2 loops=1) - -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.043..0.367 rows=15 loops=1) - -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.013 rows=36 loops=1) - -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.003..0.008 rows=34 loops=1) + Nested Loop (cost=137.50..44150596.30 rows=1260000000 width=32) (actual time=0.369..0.485 rows=2 loops=1) + -> Nested Loop (cost=125.00..50583.80 rows=2520000 width=40) (actual time=0.038..0.370 rows=15 loops=1) + -> Append (cost=0.00..58.80 rows=2520 width=8) (actual time=0.003..0.012 rows=36 loops=1) + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 (cost=0.00..2.20 rows=120 width=8) (actual time=0.002..0.007 rows=34 loops=1) -> Seq Scan on test _age_default_alias_0_2 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Seq Scan on test2 _age_default_alias_0_3 (cost=0.00..22.00 rows=1200 width=8) (actual time=0.001..0.001 rows=1 loops=1) -> Function Scan on variable_edge_search _age_default_alias_1 (cost=125.00..135.00 rows=1000 width=40) (actual time=0.009..0.009 rows=0 loops=36) -> Function Scan on edge_search _age_default_alias_2 (cost=12.50..25.00 rows=500 width=8) (actual time=0.007..0.007 rows=0 loops=15) Filter: (_age_default_alias_1.hashset OPERATOR(postgraph.@>) id) - Planning Time: 0.096 ms - Execution Time: 0.507 ms + Planning Time: 0.135 ms + Execution Time: 0.505 ms (11 rows) SELECT * FROM cypher_create._adj__ag_label_vertex; @@ -1302,10 +1300,10 @@ MATCH ()-[]->() RETURN 1; (15 rows) MATCH ()-[*2]->() RETURN 1; -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x11f47fe0, chunk 0x11f48278 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x11f47fe0, chunk 0x11f48278 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x11f47fe0, chunk 0x11f48278 -WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x11f47fe0, chunk 0x11f48278 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x371a5df0, chunk 0x371a6088 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x371a5df0, chunk 0x371a6088 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x371a5df0, chunk 0x371a6088 +WARNING: problem in alloc set SRF multi-call context: detected write past chunk end in block 0x371a5df0, chunk 0x371a6088 ?column? ---------- 1 diff --git a/regress/expected/cypher_merge.out b/regress/expected/cypher_merge.out new file mode 100644 index 00000000..29b2d3a1 --- /dev/null +++ b/regress/expected/cypher_merge.out @@ -0,0 +1,132 @@ + +/* + * 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. + */ + +LOAD 'postgraph'; +SET search_path TO postgraph; + +CREATE GRAPH cypher_merge; +NOTICE: graph "cypher_merge" has been created +WARNING: relcache reference leak: relation "_adj__adj__ag_label_vertex" not closed + create_graph +-------------- + +(1 row) + +USE GRAPH cypher_merge; + use_graph +----------- + +(1 row) + + +/* + * Section 1: MERGE with single vertex + */ +/* + * test 1: Single MERGE Clause, path doesn't exist + */ +--test query +MERGE (); +-- +(0 rows) + +MATCH (n) RETURN n; + n +-------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {}} +(1 row) + + +EXPLAIN MERGE ({i: 1}); + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Custom Scan (Cypher Merge) (cost=0.00..0.00 rows=0 width=0) + -> Subquery Scan on _ (cost=0.00..25.64 rows=1 width=0) + -> Seq Scan on _ag_label_vertex _age_default_alias_0 (cost=0.00..25.63 rows=1 width=168) + Filter: (properties @> '{"i": 1}'::gtype) +(4 rows) + + +MERGE ({i: 1}); +-- +(0 rows) + +MATCH (n) RETURN n; + n +-------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"i": 1}} +(2 rows) + + +MATCH (n {i: 1}) RETURN n; + n +-------------------------------------------------------------- + {"id": 281474976710658, "label": "", "properties": {"i": 1}} +(1 row) + + +MERGE ({i: 1}); +-- +(0 rows) + +MATCH (n) RETURN n; + n +-------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"i": 1}} +(2 rows) + + +MERGE (n {i: 'Hello Merge'}); +-- +(0 rows) + +MATCH (n) RETURN n; + n +-------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {}} + {"id": 281474976710658, "label": "", "properties": {"i": 1}} + {"id": 281474976710659, "label": "", "properties": {"i": "Hello Merge"}} +(3 rows) + + +MERGE (n {i: 'Hello Merge'}) RETURN n; + n +-------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {"i": "Hello Merge"}} +(1 row) + + +/* + * Clean up graph + */ +DROP GRAPH cypher_merge CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table cypher_merge._ag_label_vertex +drop cascades to table cypher_merge._adj__ag_label_vertex +drop cascades to table cypher_merge._adj__adj__ag_label_vertex +drop cascades to table cypher_merge._ag_label_edge +NOTICE: graph "cypher_merge" has been dropped + drop_graph +------------ + +(1 row) + diff --git a/regress/sql/cypher_merge.sql b/regress/sql/cypher_merge.sql new file mode 100644 index 00000000..d8752f07 --- /dev/null +++ b/regress/sql/cypher_merge.sql @@ -0,0 +1,55 @@ + +/* + * 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. + */ + +LOAD 'postgraph'; +SET search_path TO postgraph; + +CREATE GRAPH cypher_merge; +USE GRAPH cypher_merge; + +/* + * Section 1: MERGE with single vertex + */ +/* + * test 1: Single MERGE Clause, path doesn't exist + */ +--test query +MERGE (); +MATCH (n) RETURN n; + +EXPLAIN MERGE ({i: 1}); + +MERGE ({i: 1}); +MATCH (n) RETURN n; + +MATCH (n {i: 1}) RETURN n; + +MERGE ({i: 1}); +MATCH (n) RETURN n; + +MERGE (n {i: 'Hello Merge'}); +MATCH (n) RETURN n; + +MERGE (n {i: 'Hello Merge'}) RETURN n; + +/* + * Clean up graph + */ +DROP GRAPH cypher_merge CASCADE; diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index b7ac5102..9e02354b 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -183,6 +183,9 @@ css->found_a_path = false; foreach (lc1, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc1); + if (cypher_node->prop_expr != NULL) + cypher_node->prop_expr_state = ExecInitExpr(cypher_node->prop_expr, (PlanState *)node); + if (cypher_node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) { i++; continue; @@ -316,8 +319,9 @@ static void process_path(cypher_merge_custom_scan_state *css) elemTupleSlot->tts_values[1] = NULL; elemTupleSlot->tts_isnull[1] = true; } else { - elemTupleSlot->tts_values[1] = slot->tts_values[node->prop_attr_num - 1]; - elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->prop_attr_num - 1]; + bool isNull; + elemTupleSlot->tts_values[1] = slot->tts_values[node->id_attr_num - 1] = ExecEvalExpr(node->prop_expr_state, econtext, &isNull); + elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->id_attr_num - 1] = isNull; } if (node->tuple_position != InvalidAttrNumber) { @@ -415,11 +419,9 @@ static void process_simple_merge(CustomScanState *node) cypher_merge_custom_scan_state *css = (cypher_merge_custom_scan_state *)node; EState *estate = css->css.ss.ps.state; - TupleTableSlot *slot; - /*Process the subtree first */ RollbackCmdId(estate); - slot = ExecProcNode(node->ss.ps.lefttree); + TupleTableSlot *slot = ExecProcNode(node->ss.ps.lefttree); AdvanceCmdId(estate); if (TupIsNull(slot)) @@ -427,7 +429,8 @@ static void process_simple_merge(CustomScanState *node) ExprContext *econtext = node->ss.ps.ps_ExprContext; /* setup the scantuple that the process_path needs */ - econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ResultTupleSlot; + econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ProjInfo->pi_state.resultslot; + //csnode->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple;// node->ss.ps.lefttree->ps_ResultTupleSlot; //econtext->ecxt_scantuple->tts_isempty = false; process_path(css); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index d68cc1e9..52afc691 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -28,6 +28,12 @@ typedef Query *(*transform_method)(cypher_parsestate *cpstate, cypher_clause *clause); +typedef struct path_parts { + int start; + int end; + cypher_rel_dir dir; +} path_parts; + // projection static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, Node *node, List **target_list, ParseExprKind expr_kind); static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, ParseExprKind expr_kind, const char *construct_name); @@ -116,8 +122,6 @@ Alias *make_alias(char *name, List *colnames); static Node *make_int_const(int i, int location); static Node *make_null_const(int location); - - /* * transform a cypher_clause */ @@ -345,7 +349,7 @@ process_create_vertex( query->targetList = lappend(query->targetList, makeTargetEntry( make_graphid_placeholder(cpstate), - target->id_attr_num = pstate->p_next_resno++, + target->id_attr_num = get_parse_state(cpstate)->p_next_resno++, make_id_alias(target->variable_name = node->name), false)); @@ -353,7 +357,7 @@ process_create_vertex( query->targetList = lappend(query->targetList, makeTargetEntry( add_volatile_wrapper(transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), - target->prop_attr_num = pstate->p_next_resno++, + target->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, make_property_alias(node->name), false)); } else { @@ -363,7 +367,7 @@ process_create_vertex( query->targetList = lappend(query->targetList, makeTargetEntry( make_vertex_placeholder(cpstate), - target->tuple_position = pstate->p_next_resno++, + target->tuple_position = get_parse_state(cpstate)->p_next_resno++, node->name, false)); @@ -376,7 +380,7 @@ process_create_vertex( makeTargetEntry( (Expr *)add_volatile_wrapper( transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), - target->prop_attr_num = pstate->p_next_resno++, + target->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, make_property_alias(node->name = get_next_default_alias(cpstate)), false)); } else { @@ -1126,6 +1130,51 @@ char *get_vertex_relation_name(cypher_parsestate *cpstate, char *label, bool is_ return rel_name; } +ParseNamespaceItem *find_pnsi(cypher_parsestate *cpstate, char *varname) { + ParseState *pstate = (ParseState *) cpstate; + ListCell *lc; + + foreach (lc, pstate->p_namespace) { + ParseNamespaceItem *pnsi = (ParseNamespaceItem *)lfirst(lc); + Alias *alias = pnsi->p_rte->alias; + if (!alias) + continue; + + if (!strcmp(alias->aliasname, varname)) + return pnsi; + } + + return NULL; +} +/* + * Creates the Contains operator to process property contraints for a vertex/ + * edge in a MATCH clause. creates the gtype @> with the enitity's properties + * on the right and the contraints in the MATCH clause on the left. + */ +static Node *create_property_constraints(cypher_parsestate *cpstate, char *entity_name, Node *property_constraints) { + ParseState *pstate = (ParseState *)cpstate; + ColumnRef *cr; + Node *prop_expr, *const_expr; + Node *last_srf = pstate->p_last_srf; + ParseNamespaceItem *pnsi; + + cr = makeNode(ColumnRef); + + + cr->fields = list_make2(makeString(entity_name), makeString("properties")); + + // use Postgres to get the properties' transform node + if ((pnsi = find_pnsi(cpstate, entity_name))) + prop_expr = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); + else + prop_expr = transform_cypher_expr(pstate, (Node *)cr, EXPR_KIND_WHERE); + + // use cypher to get the constraints' transform node + const_expr = transform_cypher_expr(cpstate, property_constraints, EXPR_KIND_WHERE); + + return (Node *)make_op(pstate, list_make2(makeString("postgraph"), makeString("@>")), prop_expr, const_expr, last_srf, -1); +} + static ParseNamespaceItem * add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, List **quals) @@ -1158,6 +1207,8 @@ add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, node->in_join_tree = true; + + ParseNamespaceItem *pnsi = addRangeTableEntry(cpstate, makeRangeVar(get_graph_namespace_name(cpstate->graph_name), get_vertex_relation_name(cpstate, node->label, node->is_default_label), @@ -1169,6 +1220,13 @@ add_vertex_to_query(cypher_parsestate *cpstate, Query *query, cypher_node *node, addNSItemToQuery(cpstate, pnsi, true, true, true); + // transform properties if they exist + if (node->props) + cpstate->property_constraint_quals = + lappend(cpstate->property_constraint_quals, + create_property_constraints(cpstate, node->name, node->props)); + + return pnsi; } @@ -1751,11 +1809,7 @@ process_three_element_path(cypher_parsestate *cpstate, Query *query, cypher_path add_all_fields_to_target_list(cpstate, query, linitial(path->path), edge, lthird(path->path)); } -typedef struct path_parts { - int start; - int end; - cypher_rel_dir dir; -} path_parts; + /* * find_path_parts @@ -2019,6 +2073,13 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, Li if (quals != NIL) expr = (Expr *)transformExpr(cpstate, (Node *)makeBoolExpr(AND_EXPR, quals, -1), EXPR_KIND_WHERE); + + if (cpstate->property_constraint_quals != NIL) { + Expr *prop_qual = makeBoolExpr(AND_EXPR, cpstate->property_constraint_quals, -1); + + expr = expr == NULL ? prop_qual : + makeBoolExpr(AND_EXPR, list_make2(expr, prop_qual), -1); + } // WHERE Clause /* if (where != NULL) { @@ -2561,18 +2622,19 @@ static cypher_target_node *transform_merge_cypher_node(cypher_parsestate *cpstat target_node->adj_relid = lcd->vertex_adjlist; // props if (node->props) { - /* query->targetList = lappend(query->targetList, + target_node->prop_expr = transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET); + query->targetList = lappend(query->targetList, makeTargetEntry( add_volatile_wrapper(transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, make_property_alias(node->name), - false));*/ - query->targetList = lappend(query->targetList, + false)); + /*query->targetList = lappend(query->targetList, makeTargetEntry( colNameToVar(cpstate, make_property_alias(node->name), false, -1), target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, make_property_alias(node->name), - false)); + false));*/ /* query->targetList = lappend(query->targetList, makeTargetEntry( From e33c44b7978a4b5c9ed492930c8ae5079c2f6079 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Tue, 28 Oct 2025 00:51:17 -0500 Subject: [PATCH 55/56] Add Barebones of LOAD CSV Command That Old AGE csv system was always broken. Start replacing it with something that works well. Not John well. Actually well. --- Makefile | 4 +- postgraph--0.1.0.sql | 9 + regress/expected/csv_load.out | 0 regress/sql/csv_load.sql | 19 +++ src/backend/parser/cypher_clause.c | 123 +++++++++----- src/backend/parser/cypher_gram.y | 5 +- src/backend/utils/csv/csv.c | 261 +++++++++++++++++++++++++++++ src/include/nodes/cypher_nodes.h | 1 + 8 files changed, 377 insertions(+), 45 deletions(-) create mode 100644 regress/expected/csv_load.out create mode 100644 regress/sql/csv_load.sql create mode 100644 src/backend/utils/csv/csv.c diff --git a/Makefile b/Makefile index 05426960..5bfb0ace 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,7 @@ OBJS = src/backend/postgraph.o \ src/backend/utils/ag_func.o \ src/backend/utils/edge_searching.o \ src/backend/utils/cache/ag_cache.o \ + src/backend/utils/csv/csv.o EXTENSION = postgraph @@ -70,7 +71,8 @@ DATA = postgraph--0.1.0.sql REGRESS = new_cypher \ cypher_create \ - cypher_merge + cypher_merge \ + csv_load srcdir=`pwd` POSTGIS_DIR ?= postgis_dir diff --git a/postgraph--0.1.0.sql b/postgraph--0.1.0.sql index 07297530..3471ed37 100755 --- a/postgraph--0.1.0.sql +++ b/postgraph--0.1.0.sql @@ -6901,3 +6901,12 @@ LANGUAGE C COST 50000 AS 'MODULE_PATHNAME'; +CREATE FUNCTION load_csv(gtype) +RETURNS SETOF gtype +CALLED ON NULL INPUT +STABLE +PARALLEL SAFE +LANGUAGE C +COST 50000 +AS 'MODULE_PATHNAME'; + diff --git a/regress/expected/csv_load.out b/regress/expected/csv_load.out new file mode 100644 index 00000000..e69de29b diff --git a/regress/sql/csv_load.sql b/regress/sql/csv_load.sql new file mode 100644 index 00000000..9a356938 --- /dev/null +++ b/regress/sql/csv_load.sql @@ -0,0 +1,19 @@ + +\! cp regress/test.csv regress/instance/data/ + +LOAD 'postgraph'; + +CREATE GRAPH cypher_create; +USE GRAPH cypher_create; + +CYPHER LOAD CSV './test.csv' AS n +RETURN n; + +-- +-- Clean up +-- +DROP GRAPH cypher_create CASCADE; + +-- +-- End +-- diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 52afc691..9717cc2a 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -84,6 +84,9 @@ static Node *transform_clause_for_join(cypher_parsestate *cpstate, cypher_clause static cypher_clause *convert_merge_to_match(cypher_merge *merge); static void transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate, List *target_list, cypher_create_path *path); +// load csv +static Query *transform_cypher_load_csv(cypher_parsestate *cpstate, cypher_clause *clause); + // transform #define PREV_CYPHER_CLAUSE_ALIAS "_" #define CYPHER_OPT_RIGHT_ALIAS "_R" @@ -145,6 +148,8 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause result = transform_cypher_create(cpstate, clause); } else if (is_ag_node(self, cypher_merge)) { result = transform_cypher_merge(cpstate, clause); + } else if (is_ag_node(self, cypher_load_csv)) { + result = transform_cypher_load_csv(cpstate, clause); } else { ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); } @@ -156,6 +161,74 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, cypher_clause *clause } +static FuncExpr *make_load_csv_clause_function(char *file_name) { + + return makeFuncExpr( + get_ag_func_oid("load_csv", 1, TEXTOID), + GTYPEOID, + list_make1(makeConst(TEXTOID, -1, InvalidOid, strlen(file_name), PointerGetDatum(file_name), false, false)), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Query *transform_cypher_load_csv(cypher_parsestate *cpstate, cypher_clause *clause) { + ParseState *pstate = (ParseState *)cpstate; + cypher_load_csv *self = (cypher_create *)clause->self; + + Query *query = makeNode(Query); + query->commandType = CMD_SELECT; + query->targetList = NIL; + + if (clause->prev) { + query->targetList = list_concat(query->targetList, + expandNSItemAttrs(get_parse_state(cpstate), + transform_prev_cypher_clause(cpstate, clause->prev, true), + 0, + -1)); + } + + + + ParseNamespaceItem *pnsi = add_srf_to_query( + cpstate, + makeFuncCall( + list_make2(makeString("postgraph"), makeString("load_csv")), + list_make1(makeString(self->file)), + COERCE_EXPLICIT_CALL, -1), + self->alias, + list_make1(makeString("val"))); + + query->targetList = lappend(query->targetList, + makeTargetEntry( + scanNSItemForColumn(pstate, pnsi, 0, "val", -1), + pstate->p_next_resno++, + self->alias, + false)); + + query->rtable = pstate->p_rtable; + query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + + if (clause->next) + return query; + + + { + cypher_parsestate *new_cpstate = make_cypher_parsestate(cpstate); + Query *topquery = makeNode(Query); + topquery->commandType = CMD_SELECT; + topquery->targetList = NIL; + + transform_cypher_clause_as_subquery_2(new_cpstate, query); + + topquery->rtable = new_cpstate->pstate.p_rtable; + topquery->jointree = makeFromExpr(new_cpstate->pstate.p_joinlist, NULL); + + return topquery; + } +} + + /* * This function is similar to transformFromClause() that is called with a * single RangeSubselect. @@ -2541,23 +2614,15 @@ static cypher_target_node *transform_merge_cypher_edge(cypher_parsestate *cpstat table_close(rel, NoLock); // props - if (edge->props) { - /* query->targetList = lappend(query->targetList, - makeTargetEntry(//BlackPink - add_volatile_wrapper(transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), + if (edge->props) + query->targetList = lappend(query->targetList, + makeTargetEntry( + add_volatile_wrapper(target_node->prop_expr = transform_cypher_expr(cpstate, edge->props, EXPR_KIND_INSERT_TARGET)), target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, make_property_alias(edge->name), false)); -*/ - query->targetList = lappend(query->targetList, - makeTargetEntry( - add_volatile_wrapper(scanNSItemForColumn(pstate, edge->pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1)), - target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, - make_property_alias(edge->name), - false)); - } else { + else target_node->prop_attr_num = InvalidAttrNumber; - } return target_node; } @@ -2570,11 +2635,6 @@ static cypher_target_node *transform_merge_cypher_node(cypher_parsestate *cpstat if (node->name) { - /* ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MERGE does not support variables"), - parser_errposition(get_parse_state(cpstate), node->location))); -*/ - // /ereport(ERROR, (errmsg_internal("nodes in CREATE cannot be a variable"))); Var *var = NULL; if ((var = colNameToVar(cpstate, node->name, false, -1)) || (target_node->id_attr_num = get_target_entry_resno(cpstate, query->targetList, make_id_alias(node->name))) != -1) { @@ -2608,7 +2668,6 @@ static cypher_target_node *transform_merge_cypher_node(cypher_parsestate *cpstat target_node->label_name = node->label; } else { target_node->label_name = ""; - //node->label = AG_DEFAULT_LABEL_VERTEX; } target_node->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; @@ -2621,35 +2680,15 @@ static cypher_target_node *transform_merge_cypher_node(cypher_parsestate *cpstat table_close(rel, NoLock); target_node->adj_relid = lcd->vertex_adjlist; // props - if (node->props) { - target_node->prop_expr = transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET); + if (node->props) query->targetList = lappend(query->targetList, makeTargetEntry( - add_volatile_wrapper(transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), + add_volatile_wrapper(target_node->prop_expr = transform_cypher_expr(cpstate, node->props, EXPR_KIND_INSERT_TARGET)), target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, make_property_alias(node->name), false)); - /*query->targetList = lappend(query->targetList, - makeTargetEntry( - colNameToVar(cpstate, make_property_alias(node->name), false, -1), - target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, - make_property_alias(node->name), - false));*/ - /* - query->targetList = lappend(query->targetList, - makeTargetEntry( - add_volatile_wrapper(scanNSItemForColumn(get_parse_state(cpstate), refnameNamespaceItem(cpstate, NULL, node->name, -1, NULL), 0, AG_VERTEX_COLNAME_PROPERTIES, -1)), - target_node->prop_attr_num = get_parse_state(cpstate)->p_next_resno++, - make_property_alias(node->name), - false)); - -colNameToVar(cpstate, node->name, false, -1)) -*/ - - - } else { + else target_node->prop_attr_num = InvalidAttrNumber; - } return target_node; } diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index ad79d9eb..ab6322f6 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -11849,11 +11849,12 @@ clause: load_stmt: - LOAD CSV Sconst + LOAD CSV Sconst AS ColId { cypher_load_csv *n = make_ag_node(cypher_load_csv); n->file = $3; - + n->alias = $5; + $$ = n; }; diff --git a/src/backend/utils/csv/csv.c b/src/backend/utils/csv/csv.c new file mode 100644 index 00000000..29e5334c --- /dev/null +++ b/src/backend/utils/csv/csv.c @@ -0,0 +1,261 @@ +#include "postgres.h" +#include "fmgr.h" +#include "funcapi.h" +#include "access/xact.h" +#include "executor/spi.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "mb/pg_wchar.h" + +#include "access/relscan.h" + +#include "utils/gtype.h" +#include "utils/gtype_typecasting.h" +#define MAX_CSV_COLS 128 + +/* + * A simple, state-machine based CSV line parser. + * It handles double-quoted fields and escaped quotes (""). + * - line: The input string to parse. + * - fields: An output array of strings (char*). + * - max_fields: The allocated size of the fields array. + * Returns the number of fields parsed. + * Memory for the fields is allocated in the current memory context. + */ +static int +parse_csv_line(char *line, char **fields, int max_fields) +{ + char *ptr = line; + char *start_field; + int field_idx = 0; + bool in_quotes = false; + + // Remove trailing newline/carriage return + int len = strlen(line); + if (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r')) { + line[len-1] = '\0'; + if (len > 1 && (line[len-2] == '\r' || line[len-2] == '\n')) { + line[len-2] = '\0'; + } + } + + start_field = ptr; + + while (*ptr && field_idx < max_fields) { + if (in_quotes) { + if (*ptr == '"') { + if (*(ptr + 1) == '"') { // Escaped quote + // Overwrite the second quote and move on + memmove(ptr, ptr + 1, strlen(ptr)); + } else { + in_quotes = false; + } + } + } else { + if (*ptr == '"') { + in_quotes = true; + if (ptr == start_field) { + start_field++; // Field starts after the quote + } + } else if (*ptr == ',') { + *ptr = '\0'; // Terminate the field string + fields[field_idx++] = start_field; + start_field = ptr + 1; + } + } + ptr++; + } + + // Add the last field + if (field_idx < max_fields) { + // Remove trailing quote if it exists + len = strlen(start_field); + if (len > 0 && start_field[len - 1] == '"') { + start_field[len - 1] = '\0'; + } + fields[field_idx++] = start_field; + } + + return field_idx; +} + +typedef struct load_csv_cxt +{ + FILE *file; + TableScanDesc scan_desc; + char *line_buf; + char **fields; + TupleTableSlot *slot; +} load_csv_cxt; + +/* + * SQL function: load_csv_manual(file_path TEXT) + * + * Description: Manually parses a CSV and inserts rows one-by-one. + * Assumes the first line of the CSV is a header and skips it. + */ +PG_FUNCTION_INFO_V1(load_csv); +Datum load_csv(PG_FUNCTION_ARGS) { + + int nrows = 0; + FuncCallContext *funcctx; + if (SRF_IS_FIRSTCALL()) { + char *file_path = GT_ARG_TO_STRING_DATUM(0); + + MemoryContext oldcontext; + + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + load_csv_cxt *cxt= palloc(sizeof(load_csv_cxt)); + + cxt->line_buf = palloc(sizeof(char) * 16384); // 16KB buffer for a single line + + if ((cxt->file = fopen(file_path, "r")) == NULL) + ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" for reading: %m", file_path))); + + cxt->fields = palloc(sizeof(char *) * MAX_CSV_COLS); + for (int i = 0; i < MAX_CSV_COLS; i++) { + cxt->fields[i] = palloc(sizeof(char) * 1024); // Allocate 1KB per field + } + + TupleDesc tupdesc = CreateTemplateTupleDesc(1); + + TupleDescInitEntry(tupdesc, 1, "value", GTYPEOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + + funcctx->user_fctx = cxt; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + load_csv_cxt *cxt = (load_csv_cxt *) funcctx->user_fctx; + // 2. Read header line and discard it + /*if (fgets(line_buf, sizeof(line_buf), file) == NULL) { + fclose(file); + ereport(ERROR, (errmsg("could not read header line from file \"%s\"", file_path))); + }*/ + + // 4. Loop through the file, parsing and inserting each row + while (fgets(cxt->line_buf, sizeof(cxt->line_buf), cxt->file) != NULL) + { + int nfields = parse_csv_line(cxt->line_buf, cxt->fields, MAX_CSV_COLS); + + /* only care when headers are supplied + if (nfields != num_columns) { + SPI_finish(); + fclose(file); + ereport(WARNING, (errmsg("skipping malformed CSV line with %d fields (expected %d)", nfields, num_columns))); + continue; + }*/ + gtype_in_state result; + + memset(&result, 0, sizeof(gtype_in_state)); + + result.res = push_gtype_value(&result.parse_state, WGT_BEGIN_ARRAY, NULL); + + for (int i = 0; i < nfields; i++) + add_gtype(string_to_gtype(cxt->fields[i]), false, &result, GTYPEOID, false); + + result.res = push_gtype_value(&result.parse_state, WGT_END_ARRAY, NULL); + + Datum values[1]; + bool nulls[1]; + values[0] = GTYPE_P_GET_DATUM(gtype_value_to_gtype(result.res)); + nulls[0] = false; + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); + } + + SRF_RETURN_DONE(funcctx); +} +/* + * SQL function: load_csv_manual(table_name TEXT, file_path TEXT) + * + * Description: Manually parses a CSV and inserts rows one-by-one. + * Assumes the first line of the CSV is a header and skips it. + */ +/* +Datum +load_csv_manual(PG_FUNCTION_ARGS) +{ + char *table_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *file_path = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + FILE *file; + char line_buf[16384]; // 16KB buffer for a single line + char *fields[MAX_CSV_COLS]; + int nfields; + int nrows = 0; + + SPIPlanPtr prep_plan; + Oid argtypes[MAX_CSV_COLS]; + Datum values[MAX_CSV_COLS]; + char nulls[MAX_CSV_COLS]; + + // 1. Open the CSV file from the server's filesystem + if ((file = fopen(file_path, "r")) == NULL) + ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" for reading: %m", file_path))); + + // 2. Read header line and discard it + if (fgets(line_buf, sizeof(line_buf), file) == NULL) { + fclose(file); + ereport(ERROR, (errmsg("could not read header line from file \"%s\"", file_path))); + } + + // 3. Connect to SPI and prepare the INSERT statement + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + // NOTE: This is a simplified example assuming a specific table structure. + // A more robust function would query pg_attribute and pg_type to get + // the actual column types and build the INSERT statement dynamically. + int num_columns = 3; // Hardcoded for 'users' table (id INT, name TEXT, city TEXT) + const char *insert_sql = "INSERT INTO users (id, name, city) VALUES ($1, $2, $3)"; + + argtypes[0] = INT4OID; // OID for INTEGER + argtypes[1] = TEXTOID; // OID for TEXT + argtypes[2] = TEXTOID; // OID for TEXT + + prep_plan = SPI_prepare(insert_sql, num_columns, argtypes); + if (prep_plan == NULL) + elog(ERROR, "SPI_prepare failed for command: %s", insert_sql); + + // 4. Loop through the file, parsing and inserting each row + while (fgets(line_buf, sizeof(line_buf), file) != NULL) + { + nfields = parse_csv_line(line_buf, fields, MAX_CSV_COLS); + + if (nfields != num_columns) { + SPI_finish(); + fclose(file); + ereport(WARNING, (errmsg("skipping malformed CSV line with %d fields (expected %d)", nfields, num_columns))); + continue; + } + + // Prepare values for insertion by calling the type's input function + values[0] = DirectFunctionCall1(int4in, CStringGetDatum(fields[0])); + values[1] = CStringGetTextDatum(fields[1]); + values[2] = CStringGetTextDatum(fields[2]); + + // Assume no nulls for this example (' ' means not null) + memset(nulls, ' ', num_columns); + + if (SPI_execute_plan(prep_plan, values, nulls, false, 0) != SPI_OK_INSERT) { + elog(ERROR, "SPI_execute_plan failed"); + } + + nrows++; + } + + // 5. Clean up + SPI_finish(); + fclose(file); + pfree(table_name); + pfree(file_path); + + PG_RETURN_INT32(nrows); +}*/ \ No newline at end of file diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index 620b269a..6f48b908 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -169,6 +169,7 @@ typedef struct cypher_load_csv { ExtensibleNode extensible; char *file; + char *alias } cypher_load_csv; From 60548b29210720b9d08cfef2f38cdd163ad17919 Mon Sep 17 00:00:00 2001 From: Josh Innis Date: Tue, 28 Oct 2025 23:37:17 -0500 Subject: [PATCH 56/56] First Version of LOAD CSV --- regress/{ => load/data}/test.csv | 0 regress/sql/csv_load.sql | 87 ++++-- src/backend/executor/cypher_merge.c | 467 ++++++++++------------------ src/backend/parser/cypher_clause.c | 17 +- src/backend/utils/csv/csv.c | 276 ++++++---------- 5 files changed, 334 insertions(+), 513 deletions(-) rename regress/{ => load/data}/test.csv (100%) diff --git a/regress/test.csv b/regress/load/data/test.csv similarity index 100% rename from regress/test.csv rename to regress/load/data/test.csv diff --git a/regress/sql/csv_load.sql b/regress/sql/csv_load.sql index 9a356938..2cea5a2d 100644 --- a/regress/sql/csv_load.sql +++ b/regress/sql/csv_load.sql @@ -1,19 +1,68 @@ - -\! cp regress/test.csv regress/instance/data/ - -LOAD 'postgraph'; - -CREATE GRAPH cypher_create; -USE GRAPH cypher_create; - -CYPHER LOAD CSV './test.csv' AS n -RETURN n; - --- --- Clean up --- -DROP GRAPH cypher_create CASCADE; - --- --- End --- +/* + * Copyright (C) 2025 PostGraphDB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +\! cp -r regress/load/data regress/instance/data/load + +LOAD 'postgraph'; + +CREATE GRAPH load_csv; +USE GRAPH load_csv; + +CYPHER LOAD CSV 'load/test.csv' AS n +RETURN n; + +CYPHER LOAD CSV 'load/test.csv' AS n +RETURN n[0]; + +CYPHER WITH '["booker12", "9012", "Rachel", "Booker"]' AS n +RETURN n[2]; + +CYPHER LOAD CSV 'load/test.csv' AS n; + +CYPHER LOAD CSV 'load/test.csv' AS n +CREATE (a:Person {name: n}); + +MATCH (a:Person) RETURN a; + +MATCH (a:Person) +LOAD CSV 'load/test.csv' AS n +CREATE (a)-[:elabel]->(b:Person {name: n}) +RETURN a, b; + + + +/* Crashes +MATCH (a:Person) +LOAD CSV 'load/test.csv' AS n +MERGE (b:Person {name: n}) +CREATE (a)-[:elabel2]->(b); + + +MATCH (a:Person) +LOAD CSV 'load/test.csv' AS n +MERGE (b:Person {name: n}) +CREATE (a)-[:elabel2]->(b) +RETURN a, b;*/ + +-- +-- Clean up +-- +DROP GRAPH load_csv CASCADE; + +-- +-- End +-- diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 9e02354b..c8d22535 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -1,21 +1,22 @@ + /* - * 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 + * Copyright (C) 2023-2025 PostGraphDB * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. * - * 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. - */ + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * Portions Copyright (c) 2020-2023, Apache Software Foundation + */ #include "postgres.h" @@ -69,16 +70,10 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, static TupleTableSlot *exec_cypher_merge(CustomScanState *node); static void end_cypher_merge(CustomScanState *node); static void rescan_cypher_merge(CustomScanState *node); -static Datum merge_vertex(cypher_merge_custom_scan_state *css, - cypher_target_node *node, ListCell *next); -static void merge_edge(cypher_merge_custom_scan_state *css, - cypher_target_node *node, Datum prev_vertex_id, - ListCell *next); static void process_simple_merge(CustomScanState *node); static bool check_path(cypher_merge_custom_scan_state *css, TupleTableSlot *slot); static void process_path(cypher_merge_custom_scan_state *css); -static void mark_tts_isnull(TupleTableSlot *slot); const CustomExecMethods cypher_merge_exec_methods = {"Merge Scan State", begin_cypher_merge, @@ -100,10 +95,7 @@ const CustomExecMethods cypher_merge_exec_methods = {"Merge Scan State", * Insert the edge/vertex tuple into the table and indices. Check that the * table's constraints have not been violated. */ -HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, - TupleTableSlot *elemTupleSlot, - EState *estate) -{ +HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate) { HeapTuple tuple = NULL; ExecStoreVirtualTuple(elemTupleSlot); @@ -120,8 +112,7 @@ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, table_tuple_insert(resultRelInfo->ri_RelationDesc, elemTupleSlot, estate->es_output_cid, 0, NULL); // Insert index entries for the tuple - if (resultRelInfo->ri_NumIndices > 0) - { + if (resultRelInfo->ri_NumIndices > 0) { Oid oid = elemTupleSlot->tts_tableOid; elemTupleSlot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);// RelationGetRelid(heapRelation) @@ -131,42 +122,25 @@ HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, return NULL; } - -/* - * Initializes the MERGE Execution Node at the begginning of the execution - * phase. - */ -static void begin_cypher_merge(CustomScanState *node, EState *estate, - int eflags) -{ - cypher_merge_custom_scan_state *css = - (cypher_merge_custom_scan_state *)node; - ListCell *lc; - Plan *subplan; -css->found_a_path = false; +static void begin_cypher_merge(CustomScanState *node, EState *estate, int eflags) { + cypher_merge_custom_scan_state *css = (cypher_merge_custom_scan_state *)node; + Assert(list_length(css->cs->custom_plans) == 1); // initialize the subplan - subplan = linitial(css->cs->custom_plans); + Plan *subplan = linitial(css->cs->custom_plans); node->ss.ps.lefttree = ExecInitNode(subplan, estate, eflags); ExecAssignExprContext(estate, &node->ss.ps); - - ExecInitScanTupleSlot(estate, &node->ss, - ExecGetResultType(node->ss.ps.lefttree), - &TTSOpsHeapTuple); + ExecInitScanTupleSlot(estate, &node->ss, ExecGetResultType(node->ss.ps.lefttree), &TTSOpsHeapTuple); /* * When MERGE is not the last clause in a cypher query. Setup projection * information to pass to the parent execution node. */ - if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) - { - TupleDesc tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; - - ExecAssignProjectionInfo(&node->ss.ps, tupdesc); - } + if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags)) + ExecAssignProjectionInfo(&node->ss.ps, node->ss.ss_ScanTupleSlot->tts_tupleDescriptor); cypher_create_path *path = css->path; @@ -207,10 +181,12 @@ css->found_a_path = false; // Setup the relation's tuple slot cypher_node->elemTupleSlot = table_slot_create(cypher_node->rel, &estate->es_tupleTable); - - cypher_node->adj_rel = table_open(cypher_node->adj_relid, RowExclusiveLock); - cypher_node->adj_resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->adj_resultRelInfo, cypher_node->adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + InitResultRelInfo( + cypher_node->adj_resultRelInfo = makeNode(ResultRelInfo), + cypher_node->adj_rel = table_open(cypher_node->adj_relid, RowExclusiveLock), + list_length(estate->es_range_table), + NULL, + estate->es_instrument); ExecOpenIndices(cypher_node->adj_resultRelInfo, false); @@ -218,7 +194,6 @@ css->found_a_path = false; cypher_node->adj_elemTupleSlot = table_slot_create(cypher_node->adj_rel, &estate->es_tupleTable); i++; - } if (estate->es_output_cid == 0) @@ -227,47 +202,31 @@ css->found_a_path = false; AdvanceCmdId(estate); } - - /* * Checks the subtree to see if the lateral join representing the MERGE path * found results. Returns true if the path does not exist and must be created, * false otherwise. + * + * If target_node as a valid attribute number and is a node not + * declared in a previous clause, check the tuple position in the + * slot. If the slot is null, the path was not found. The rules + * state that if one part of the path does not exists, the whold + * path must be created. */ -static bool check_path(cypher_merge_custom_scan_state *css, - TupleTableSlot *slot) -{ - cypher_create_path *path = css->path; +static bool check_path(cypher_merge_custom_scan_state *css, TupleTableSlot *slot) { ListCell *lc; - - foreach(lc, path->target_nodes) - { + foreach(lc, css->path->target_nodes) { cypher_target_node *node = lfirst(lc); - /* - * If target_node as a valid attribute number and is a node not - * declared in a previous clause, check the tuple position in the - * slot. If the slot is null, the path was not found. The rules - * state that if one part of the path does not exists, the whold - * path must be created. - */ - if (node->tuple_position != InvalidAttrNumber || - ((node->flags & CYPHER_TARGET_NODE_MERGE_EXISTS) == 0)) - { - /* - * Attribute number is 1 indexed and tts_values is 0 indexed, - * offset by 1. - */ + if (node->tuple_position != InvalidAttrNumber || ((node->flags & CYPHER_TARGET_NODE_MERGE_EXISTS) == 0)) { if (slot->tts_isnull[node->tuple_position - 1]) - { return true; - } } - } return false; } + static void process_path(cypher_merge_custom_scan_state *css) { cypher_create_path *path = css->path; @@ -278,136 +237,136 @@ static void process_path(cypher_merge_custom_scan_state *css) ListCell *lc; int i = 1; foreach(lc, path->target_nodes) { - cypher_target_node *node = (cypher_target_node *)lfirst(lc); - bool isNull; - if(i % 2 == 1) { - if(node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) - css->vertex_ids[0][i/2] = DATUM_GET_GRAPHID(slot->tts_values[node->id_attr_num - 1]); - else - css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - } else { - css->edge_ids[0][(i-1)/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); - } - i++; + cypher_target_node *node = (cypher_target_node *)lfirst(lc); + bool isNull; + if(i % 2 == 1) { + if(node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) + css->vertex_ids[0][i/2] = DATUM_GET_GRAPHID(slot->tts_values[node->id_attr_num - 1]); + else + css->vertex_ids[0][i/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + } else { + css->edge_ids[0][(i-1)/2] = ExecEvalExpr(node->id_expr_state, econtext, &isNull); + } + i++; } for (int i = 0; i < list_length(path->target_nodes)/2 + 1; i++) { - cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, i * 2); - ResultRelInfo *resultRelInfo = node->resultRelInfo; - TupleTableSlot *elemTupleSlot = node->elemTupleSlot; + ResultRelInfo *resultRelInfo = node->resultRelInfo; + TupleTableSlot *elemTupleSlot = node->elemTupleSlot; - ResultRelInfo **old_estate_es_result_relations_info = NULL; + ResultRelInfo **old_estate_es_result_relations_info = NULL; - /* save the old result relation info */ - old_estate_es_result_relations_info = estate->es_result_relations; + /* save the old result relation info */ + old_estate_es_result_relations_info = estate->es_result_relations; - estate->es_result_relations = &resultRelInfo; - if(!(node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE)){ - ExecClearTuple(elemTupleSlot); - - // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; - elemTupleSlot->tts_isnull[0] = false; - if (node->id_attr_num != InvalidAttrNumber) { - slot->tts_values[node->id_attr_num - 1] = GRAPHID_GET_DATUM(css->vertex_ids[0][i]); - slot->tts_isnull[node->id_attr_num - 1] = false; - } - - // get the properties for this vertex - if (node->prop_attr_num == InvalidAttrNumber) { - elemTupleSlot->tts_values[1] = NULL; - elemTupleSlot->tts_isnull[1] = true; - } else { - bool isNull; - elemTupleSlot->tts_values[1] = slot->tts_values[node->id_attr_num - 1] = ExecEvalExpr(node->prop_expr_state, econtext, &isNull); - elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->id_attr_num - 1] = isNull; - } - - if (node->tuple_position != InvalidAttrNumber) { - slot->tts_values[node->tuple_position - 1] = VERTEX_GET_DATUM(create_vertex( - slot->tts_values[node->id_attr_num - 1], - css->graph_oid, - elemTupleSlot->tts_isnull[1] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[1]))); - slot->tts_isnull[node->tuple_position - 1] = false; - } - - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + estate->es_result_relations = &resultRelInfo; + if(!(node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE)){ + ExecClearTuple(elemTupleSlot); + + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->vertex_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; + if (node->id_attr_num != InvalidAttrNumber) { + slot->tts_values[node->id_attr_num - 1] = GRAPHID_GET_DATUM(css->vertex_ids[0][i]); + slot->tts_isnull[node->id_attr_num - 1] = false; + } + + // get the properties for this vertex + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[1] = NULL; + elemTupleSlot->tts_isnull[1] = true; + } else { + bool isNull; + elemTupleSlot->tts_values[1] = slot->tts_values[node->id_attr_num - 1] = ExecEvalExpr(node->prop_expr_state, econtext, &isNull); + elemTupleSlot->tts_isnull[1] = slot->tts_isnull[node->id_attr_num - 1] = isNull; + } + + if (node->tuple_position != InvalidAttrNumber) { + slot->tts_values[node->tuple_position - 1] = VERTEX_GET_DATUM(create_vertex( + slot->tts_values[node->id_attr_num - 1], + css->graph_oid, + elemTupleSlot->tts_isnull[1] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[1]))); + slot->tts_isnull[node->tuple_position - 1] = false; } + + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + } - if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { - cypher_target_node *start_vertex; - if (node->dir == CYPHER_REL_DIR_RIGHT) { - start_vertex = list_nth(path->target_nodes, i * 2); - } else { - start_vertex = list_nth(path->target_nodes, (i * 2) + 2); - } + if (list_length(path->target_nodes) > 1 && i < (list_length(path->target_nodes)/2)) { + cypher_target_node *start_vertex; + if (node->dir == CYPHER_REL_DIR_RIGHT) { + start_vertex = list_nth(path->target_nodes, i * 2); + } else { + start_vertex = list_nth(path->target_nodes, (i * 2) + 2); + } - if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ - label_cache_data *lcd = search_label_graph_oid_cache( - css->graph_oid, - DATUM_GET_GRAPHID(slot->tts_values[start_vertex->id_attr_num - 1]) >> ENTRY_ID_BITS); - start_vertex->adj_relid = lcd->relation; + if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ + label_cache_data *lcd = search_label_graph_oid_cache( + css->graph_oid, + DATUM_GET_GRAPHID(slot->tts_values[start_vertex->id_attr_num - 1]) >> ENTRY_ID_BITS); + start_vertex->adj_relid = lcd->relation; - Relation adj_rel = table_open(start_vertex->adj_relid, RowExclusiveLock); + Relation adj_rel = table_open(start_vertex->adj_relid, RowExclusiveLock); - start_vertex->adj_resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(start_vertex->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); - ExecOpenIndices(start_vertex->adj_resultRelInfo, false); + start_vertex->adj_resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(start_vertex->adj_resultRelInfo, adj_rel, list_length(estate->es_range_table), NULL, estate->es_instrument); + ExecOpenIndices(start_vertex->adj_resultRelInfo, false); - start_vertex->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); - } + start_vertex->adj_elemTupleSlot = table_slot_create(adj_rel, &estate->es_tupleTable); + } - resultRelInfo = start_vertex->adj_resultRelInfo; - elemTupleSlot = start_vertex->adj_elemTupleSlot; + resultRelInfo = start_vertex->adj_resultRelInfo; + elemTupleSlot = start_vertex->adj_elemTupleSlot; - estate->es_result_relations = &resultRelInfo; + estate->es_result_relations = &resultRelInfo; - ExecClearTuple(elemTupleSlot); - cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, (i * 2) + 1); - // get the next graphid for this vertex. - elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; - elemTupleSlot->tts_isnull[0] = false; + ExecClearTuple(elemTupleSlot); + cypher_target_node *node = (cypher_target_node *)list_nth(path->target_nodes, (i * 2) + 1); + // get the next graphid for this vertex. + elemTupleSlot->tts_values[0] = css->edge_ids[0][i]; + elemTupleSlot->tts_isnull[0] = false; - elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; - elemTupleSlot->tts_isnull[1] = false; + elemTupleSlot->tts_values[1] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1]; + elemTupleSlot->tts_isnull[1] = false; - elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; - elemTupleSlot->tts_isnull[2] = false; + elemTupleSlot->tts_values[2] = css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i]; + elemTupleSlot->tts_isnull[2] = false; - if (node->prop_attr_num == InvalidAttrNumber) { - elemTupleSlot->tts_values[3] = NULL; - elemTupleSlot->tts_isnull[3] = true; - } else { - elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; - elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; - } + if (node->prop_attr_num == InvalidAttrNumber) { + elemTupleSlot->tts_values[3] = NULL; + elemTupleSlot->tts_isnull[3] = true; + } else { + elemTupleSlot->tts_values[3] = slot->tts_values[node->prop_attr_num - 1]; + elemTupleSlot->tts_isnull[3] = slot->tts_isnull[node->prop_attr_num - 1]; + } - // Insert the new vertex - insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + // Insert the new vertex + insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); - if (node->tuple_position != InvalidAttrNumber) { - slot->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(create_edge( + if (node->tuple_position != InvalidAttrNumber) { + slot->tts_values[node->tuple_position - 1] = EDGE_GET_DATUM(create_edge( css->edge_ids[0][i], css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i : i + 1], css->vertex_ids[0][node->dir == CYPHER_REL_DIR_RIGHT ? i + 1 : i], css->graph_oid, elemTupleSlot->tts_isnull[3] ? NULL : DATUM_GET_GTYPE_P(elemTupleSlot->tts_values[3]))); - slot->tts_isnull[node->tuple_position - 1] = false; - } + slot->tts_isnull[node->tuple_position - 1] = false; + } - if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ - ExecCloseIndices(start_vertex->adj_resultRelInfo); - table_close(start_vertex->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); - } + if(start_vertex->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE){ + ExecCloseIndices(start_vertex->adj_resultRelInfo); + table_close(start_vertex->adj_resultRelInfo->ri_RelationDesc, RowExclusiveLock); } - - // restore the old result relation info - estate->es_result_relations = old_estate_es_result_relations_info; } + + // restore the old result relation info + estate->es_result_relations = old_estate_es_result_relations_info; + } } @@ -437,28 +396,6 @@ static void process_simple_merge(CustomScanState *node) } } -/* - * Iterate through the TupleTableSlot's tts_values and marks the isnull field - * with true. - */ -static void mark_tts_isnull(TupleTableSlot *slot) -{ - int numberOfAttributes = slot->tts_tupleDescriptor->natts; - int i; - - for (i = 0; i < numberOfAttributes; i++) - { - Datum val; - - val = slot->tts_values[i]; - - if (val == (Datum)NULL) - { - slot->tts_isnull[i] = true; - } - } -} - /* * Function that is called mid-execution. This function will call * its subtree in the execution tree, and depending on the results @@ -485,8 +422,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) * 3. MERGE is the first clause and there are following clauses. * CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE */ - if (CYPHER_CLAUSE_HAS_PREVIOUS_CLAUSE(css->flags)) - { + if (CYPHER_CLAUSE_HAS_PREVIOUS_CLAUSE(css->flags)) { /* * Case 1: MERGE is not the first clause in the cypher query. * @@ -506,12 +442,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) slot = ExecProcNode(node->ss.ps.lefttree); AdvanceCmdId(estate); - /* - * We are done processing the subtree, mark as terminal - * so the function returns NULL. - */ - if (TupIsNull(slot)) - { + if (TupIsNull(slot)) { terminal = true; break; } @@ -521,9 +452,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; if (check_path(css, econtext->ecxt_scantuple)) - { process_path(css); - } } while (terminal); @@ -533,9 +462,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); return ExecProject(node->ss.ps.ps_ProjInfo); - } - else if (terminal) - { + } else if (terminal) { /* * Case 2: MERGE is the first clause and there are no following clauses * @@ -543,15 +470,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) * Return NULL in either cases, because no rows are expected. */ process_simple_merge(node); - - /* - * Case 2 always returns NULL the first time exec_cypher_merge is - * called. - */ return NULL; - } - else - { + } else { /* * Case 3: MERGE is the first clause and there are following clauses. * @@ -569,15 +489,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) * execution tree, and now we tell MERGE's parents in the execution * tree there is no more tuples to pass. */ - if (css->created_new_path) - { - /* - * If the created_new_path is set to true. Then MERGE should not - * have found a path, because this should only be set to true if - * merge found sub-case 1 - */ + if (css->created_new_path) { Assert(css->found_a_path == false); - return NULL; } @@ -589,8 +502,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) slot = ExecProcNode(node->ss.ps.lefttree); AdvanceCmdId(estate); - if (!TupIsNull(slot)) - { + if (!TupIsNull(slot)) { /* * Part of Sub-Case 1. * @@ -601,9 +513,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) css->found_a_path = true; return node->ss.ps.lefttree->ps_ResultTupleSlot; - } - else if (TupIsNull(slot) && css->found_a_path) - { + } else if (TupIsNull(slot) && css->found_a_path) { /* * Part of Sub-Case 2. * @@ -611,9 +521,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) * all the found path(s) up the execution tree. */ return NULL; - } - else - { + } else { /* * Part of Sub-Case 1. * @@ -628,55 +536,22 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) SubqueryScanState *sss = (SubqueryScanState *)node->ss.ps.lefttree; HeapTuple heap_tuple; - /* - * Our child execution node is always a subquery. If not there - * is an issue. - */ Assert(IsA(sss, SubqueryScanState)); - - /* - * found_a_path should only be set to true if MERGE is following - * sub-case 2. - */ Assert(css->found_a_path == false); - - /* - * This block of sub-case 1 should only be exectuted once. To - * create the single path if the path does not exist. If we find - * ourselves here again, the internal state of the MERGE execution - * node was incorrectly altered. - */ Assert(css->created_new_path == false); - /* - * Postgres cleared the child tuple table slot, we need to remake - * it. - */ ExecInitScanTupleSlot(estate, &sss->ss, ExecGetResultType(sss->subplan),&TTSOpsHeapTuple); - - /* setup the scantuple that the process_path needs */ econtext->ecxt_scantuple = sss->ss.ss_ScanTupleSlot; - // create the path process_path(css); - // mark the create_new_path flag to true. css->created_new_path = true; - /* - * find the tts_values that process_path did not populate and - * mark as null. - */ - mark_tts_isnull(econtext->ecxt_scantuple); - - // create the physical heap tuple - /*heap_tuple = heap_form_tuple( - econtext->ecxt_scantuple->tts_tupleDescriptor, - econtext->ecxt_scantuple->tts_values, - econtext->ecxt_scantuple->tts_isnull); -*/ + for (int i = 0; i < slot->tts_tupleDescriptor->natts; i++) { + slot->tts_isnull[i] = slot->tts_values[i] == NULL; + } // store the heap tuble ExecStoreVirtualTuple(econtext->ecxt_scantuple); @@ -690,11 +565,6 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) // assign this to be our scantuple econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); - - /* - * run the merge's projection logic and pass to its parent - * execution node - */ return ExecProject(node->ss.ps.ps_ProjInfo); } } @@ -717,23 +587,20 @@ static void end_cypher_merge(CustomScanState *node) ExecEndNode(node->ss.ps.lefttree); ListCell *lc; - //foreach (lc, css->pattern) { - ListCell *lc2; - foreach (lc2, path->target_nodes) { - cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc2); + foreach (lc, path->target_nodes) { + cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc); - if (!cypher_node->resultRelInfo || node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) - continue; + if (!cypher_node->resultRelInfo || node->flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE) + continue; - ExecCloseIndices(cypher_node->resultRelInfo); - table_close(cypher_node->rel, RowExclusiveLock); - ExecCloseIndices(cypher_node->adj_resultRelInfo); - table_close(cypher_node->adj_rel, RowExclusiveLock); - //RelationDecrementReferenceCount(cypher_node->relation); - //RelationDecrementReferenceCount(lcd->relation); - } - //} + ExecCloseIndices(cypher_node->resultRelInfo); + table_close(cypher_node->rel, RowExclusiveLock); + ExecCloseIndices(cypher_node->adj_resultRelInfo); + table_close(cypher_node->adj_rel, RowExclusiveLock); + //RelationDecrementReferenceCount(cypher_node->relation); + //RelationDecrementReferenceCount(lcd->relation); + } } /* @@ -757,16 +624,13 @@ Node *create_cypher_merge_plan_state(CustomScan *cscan) { cypher_merge_custom_scan_state *cypher_css = palloc0(sizeof(cypher_merge_custom_scan_state)); - cypher_merge_information *merge_information; - char *serialized_data; - Const *c; cypher_css->cs = cscan; // get the serialized data structure from the Const and deserialize it. - c = linitial(cscan->custom_private); - serialized_data = (char *)c->constvalue; - merge_information = stringToNode(serialized_data); + Const *c = linitial(cscan->custom_private); + char *serialized_data = (char *)c->constvalue; + cypher_merge_information *merge_information = stringToNode(serialized_data); Assert(is_ag_node(merge_information, cypher_merge_information)); @@ -783,4 +647,3 @@ Node *create_cypher_merge_plan_state(CustomScan *cscan) return (Node *)cypher_css; } - diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 9717cc2a..d377d78f 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -7,6 +7,7 @@ #include "nodes/parsenodes.h" #include "nodes/pg_list.h" #include "parser/parsetree.h" +#include "parser/parse_relation.h" #include "parser/cypher_analyze.h" #include "catalog/ag_graph.h" @@ -188,23 +189,21 @@ static Query *transform_cypher_load_csv(cypher_parsestate *cpstate, cypher_claus -1)); } - - ParseNamespaceItem *pnsi = add_srf_to_query( cpstate, makeFuncCall( list_make2(makeString("postgraph"), makeString("load_csv")), list_make1(makeString(self->file)), COERCE_EXPLICIT_CALL, -1), - self->alias, - list_make1(makeString("val"))); + "val", + NIL); query->targetList = lappend(query->targetList, makeTargetEntry( scanNSItemForColumn(pstate, pnsi, 0, "val", -1), pstate->p_next_resno++, self->alias, - false)); + false)); query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); @@ -235,13 +234,9 @@ static Query *transform_cypher_load_csv(cypher_parsestate *cpstate, cypher_claus */ static void transform_cypher_clause_as_subquery_2(cypher_parsestate *cpstate, Query *query) { - ParseState *pstate = (ParseState *)cpstate; - ParseExprKind old_expr_kind = pstate->p_expr_kind; - bool lateral = pstate->p_lateral_active; - - ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(pstate, query, makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL), lateral, true); + ParseNamespaceItem *pnsi = addRangeTableEntryForSubquery(cpstate, query, makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL), get_parse_state(cpstate)->p_lateral_active, true); - addNSItemToQuery(pstate, pnsi, true, false, true); + addNSItemToQuery(cpstate, pnsi, true, false, true); //query->targetList = list_concat(query->targetList, expandNSItemAttrs(pstate, pnsi, 0, -1)); } diff --git a/src/backend/utils/csv/csv.c b/src/backend/utils/csv/csv.c index 29e5334c..ed0f2ca4 100644 --- a/src/backend/utils/csv/csv.c +++ b/src/backend/utils/csv/csv.c @@ -7,11 +7,106 @@ #include "utils/memutils.h" #include "mb/pg_wchar.h" +#include "miscadmin.h" + +#include "storage/fd.h" + #include "access/relscan.h" #include "utils/gtype.h" #include "utils/gtype_typecasting.h" #define MAX_CSV_COLS 128 +typedef struct load_csv_cxt +{ + FILE *file; + TableScanDesc scan_desc; + char *line_buf; + char **fields; + TupleTableSlot *slot; +} load_csv_cxt; + +static int parse_csv_line(char *line, char **fields, int max_fields); + +PG_FUNCTION_INFO_V1(load_csv); +Datum load_csv(PG_FUNCTION_ARGS) { + char *file_path = GT_ARG_TO_STRING_DATUM(0); + + load_csv_cxt *cxt = palloc(sizeof(load_csv_cxt)); + cxt->line_buf = palloc(sizeof(char) * 16384); + + //if (!(cxt->file = AllocateFile(file_path, "r"))) + if (!(cxt->file = fopen(file_path, "r"))) + ereport(ERROR, (errcode_for_file_access(), + errmsg("could not open file \"%s\" in: %m", file_path))); + + cxt->fields = palloc(sizeof(char *) * MAX_CSV_COLS); + for (int i = 0; i < MAX_CSV_COLS; i++) { + cxt->fields[i] = palloc(sizeof(char) * 1024); + } + + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + rsi->returnMode = SFRM_Materialize; + TupleDesc tupdesc = rsi->expectedDesc; + + MemoryContext old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); + + TupleDesc ret_tdesc = CreateTupleDescCopy(tupdesc); + BlessTupleDesc(ret_tdesc); + + Tuplestorestate *tuple_store = tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, false, work_mem); + + MemoryContextSwitchTo(old_cxt); + + MemoryContext tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, "LOAD CSV temporary cxt", ALLOCSET_DEFAULT_SIZES); + + /*if (fgets(line_buf, sizeof(line_buf), file) == NULL) { + fclose(file); + ereport(ERROR, (errmsg("could not read header line from file \"%s\"", file_path))); + }*/ + + while (fgets(cxt->line_buf, 16384, cxt->file) != NULL) + { + int nfields = parse_csv_line(cxt->line_buf, cxt->fields, MAX_CSV_COLS); + + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + /* only care when headers are supplied + if (nfields != num_columns) { + SPI_finish(); + fclose(file); + ereport(WARNING, (errmsg("skipping malformed CSV line with %d fields (expected %d)", nfields, num_columns))); + continue; + }*/ + gtype_in_state result; + memset(&result, 0, sizeof(gtype_in_state)); + + result.res = push_gtype_value(&result.parse_state, WGT_BEGIN_ARRAY, NULL); + for (int i = 0; i < nfields; i++) { + add_gtype(string_to_gtype(cxt->fields[i]), false, &result, GTYPEOID, false); + } + result.res = push_gtype_value(&result.parse_state, WGT_END_ARRAY, NULL); + + Datum values[1]; + bool nulls[1] = {false}; + values[0] = GTYPE_P_GET_DATUM(gtype_value_to_gtype(result.res)); + + HeapTuple tuple = heap_form_tuple(ret_tdesc, values, nulls); + + tuplestore_puttuple(tuple_store, tuple); + + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(tmp_cxt); + + } + + MemoryContextDelete(tmp_cxt); + + rsi->setResult = tuple_store; + rsi->setDesc = ret_tdesc; + + PG_RETURN_NULL(); + +} /* * A simple, state-machine based CSV line parser. @@ -78,184 +173,3 @@ parse_csv_line(char *line, char **fields, int max_fields) return field_idx; } - -typedef struct load_csv_cxt -{ - FILE *file; - TableScanDesc scan_desc; - char *line_buf; - char **fields; - TupleTableSlot *slot; -} load_csv_cxt; - -/* - * SQL function: load_csv_manual(file_path TEXT) - * - * Description: Manually parses a CSV and inserts rows one-by-one. - * Assumes the first line of the CSV is a header and skips it. - */ -PG_FUNCTION_INFO_V1(load_csv); -Datum load_csv(PG_FUNCTION_ARGS) { - - int nrows = 0; - FuncCallContext *funcctx; - if (SRF_IS_FIRSTCALL()) { - char *file_path = GT_ARG_TO_STRING_DATUM(0); - - MemoryContext oldcontext; - - - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - load_csv_cxt *cxt= palloc(sizeof(load_csv_cxt)); - - cxt->line_buf = palloc(sizeof(char) * 16384); // 16KB buffer for a single line - - if ((cxt->file = fopen(file_path, "r")) == NULL) - ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" for reading: %m", file_path))); - - cxt->fields = palloc(sizeof(char *) * MAX_CSV_COLS); - for (int i = 0; i < MAX_CSV_COLS; i++) { - cxt->fields[i] = palloc(sizeof(char) * 1024); // Allocate 1KB per field - } - - TupleDesc tupdesc = CreateTemplateTupleDesc(1); - - TupleDescInitEntry(tupdesc, 1, "value", GTYPEOID, -1, 0); - - funcctx->tuple_desc = BlessTupleDesc(tupdesc); - - - funcctx->user_fctx = cxt; - - MemoryContextSwitchTo(oldcontext); - } - - funcctx = SRF_PERCALL_SETUP(); - load_csv_cxt *cxt = (load_csv_cxt *) funcctx->user_fctx; - // 2. Read header line and discard it - /*if (fgets(line_buf, sizeof(line_buf), file) == NULL) { - fclose(file); - ereport(ERROR, (errmsg("could not read header line from file \"%s\"", file_path))); - }*/ - - // 4. Loop through the file, parsing and inserting each row - while (fgets(cxt->line_buf, sizeof(cxt->line_buf), cxt->file) != NULL) - { - int nfields = parse_csv_line(cxt->line_buf, cxt->fields, MAX_CSV_COLS); - - /* only care when headers are supplied - if (nfields != num_columns) { - SPI_finish(); - fclose(file); - ereport(WARNING, (errmsg("skipping malformed CSV line with %d fields (expected %d)", nfields, num_columns))); - continue; - }*/ - gtype_in_state result; - - memset(&result, 0, sizeof(gtype_in_state)); - - result.res = push_gtype_value(&result.parse_state, WGT_BEGIN_ARRAY, NULL); - - for (int i = 0; i < nfields; i++) - add_gtype(string_to_gtype(cxt->fields[i]), false, &result, GTYPEOID, false); - - result.res = push_gtype_value(&result.parse_state, WGT_END_ARRAY, NULL); - - Datum values[1]; - bool nulls[1]; - values[0] = GTYPE_P_GET_DATUM(gtype_value_to_gtype(result.res)); - nulls[0] = false; - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc, values, nulls))); - } - - SRF_RETURN_DONE(funcctx); -} -/* - * SQL function: load_csv_manual(table_name TEXT, file_path TEXT) - * - * Description: Manually parses a CSV and inserts rows one-by-one. - * Assumes the first line of the CSV is a header and skips it. - */ -/* -Datum -load_csv_manual(PG_FUNCTION_ARGS) -{ - char *table_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); - char *file_path = text_to_cstring(PG_GETARG_TEXT_PP(1)); - - FILE *file; - char line_buf[16384]; // 16KB buffer for a single line - char *fields[MAX_CSV_COLS]; - int nfields; - int nrows = 0; - - SPIPlanPtr prep_plan; - Oid argtypes[MAX_CSV_COLS]; - Datum values[MAX_CSV_COLS]; - char nulls[MAX_CSV_COLS]; - - // 1. Open the CSV file from the server's filesystem - if ((file = fopen(file_path, "r")) == NULL) - ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" for reading: %m", file_path))); - - // 2. Read header line and discard it - if (fgets(line_buf, sizeof(line_buf), file) == NULL) { - fclose(file); - ereport(ERROR, (errmsg("could not read header line from file \"%s\"", file_path))); - } - - // 3. Connect to SPI and prepare the INSERT statement - if (SPI_connect() != SPI_OK_CONNECT) - elog(ERROR, "SPI_connect failed"); - - // NOTE: This is a simplified example assuming a specific table structure. - // A more robust function would query pg_attribute and pg_type to get - // the actual column types and build the INSERT statement dynamically. - int num_columns = 3; // Hardcoded for 'users' table (id INT, name TEXT, city TEXT) - const char *insert_sql = "INSERT INTO users (id, name, city) VALUES ($1, $2, $3)"; - - argtypes[0] = INT4OID; // OID for INTEGER - argtypes[1] = TEXTOID; // OID for TEXT - argtypes[2] = TEXTOID; // OID for TEXT - - prep_plan = SPI_prepare(insert_sql, num_columns, argtypes); - if (prep_plan == NULL) - elog(ERROR, "SPI_prepare failed for command: %s", insert_sql); - - // 4. Loop through the file, parsing and inserting each row - while (fgets(line_buf, sizeof(line_buf), file) != NULL) - { - nfields = parse_csv_line(line_buf, fields, MAX_CSV_COLS); - - if (nfields != num_columns) { - SPI_finish(); - fclose(file); - ereport(WARNING, (errmsg("skipping malformed CSV line with %d fields (expected %d)", nfields, num_columns))); - continue; - } - - // Prepare values for insertion by calling the type's input function - values[0] = DirectFunctionCall1(int4in, CStringGetDatum(fields[0])); - values[1] = CStringGetTextDatum(fields[1]); - values[2] = CStringGetTextDatum(fields[2]); - - // Assume no nulls for this example (' ' means not null) - memset(nulls, ' ', num_columns); - - if (SPI_execute_plan(prep_plan, values, nulls, false, 0) != SPI_OK_INSERT) { - elog(ERROR, "SPI_execute_plan failed"); - } - - nrows++; - } - - // 5. Clean up - SPI_finish(); - fclose(file); - pfree(table_name); - pfree(file_path); - - PG_RETURN_INT32(nrows); -}*/ \ No newline at end of file