├── .clang-format ├── .clang-tidy ├── .editorconfig ├── index.html ├── src ├── include │ ├── duckpgq │ │ ├── common.hpp │ │ └── core │ │ │ ├── module.hpp │ │ │ ├── operator │ │ │ ├── duckpgq_operator.hpp │ │ │ └── duckpgq_bind.hpp │ │ │ ├── utils │ │ │ ├── duckpgq_bitmap.hpp │ │ │ ├── duckpgq_utils.hpp │ │ │ └── compressed_sparse_row.hpp │ │ │ ├── pragma │ │ │ └── duckpgq_pragma.hpp │ │ │ ├── functions │ │ │ ├── function_data │ │ │ │ ├── iterative_length_function_data.hpp │ │ │ │ ├── cheapest_path_length_function_data.hpp │ │ │ │ ├── local_clustering_coefficient_function_data.hpp │ │ │ │ ├── weakly_connected_component_function_data.hpp │ │ │ │ └── pagerank_function_data.hpp │ │ │ ├── table.hpp │ │ │ ├── table │ │ │ │ ├── drop_property_graph.hpp │ │ │ │ ├── pagerank.hpp │ │ │ │ ├── describe_property_graph.hpp │ │ │ │ ├── local_clustering_coefficient.hpp │ │ │ │ ├── weakly_connected_component.hpp │ │ │ │ ├── summarize_property_graph.hpp │ │ │ │ └── create_property_graph.hpp │ │ │ └── scalar.hpp │ │ │ └── parser │ │ │ └── duckpgq_parser.hpp │ ├── duckpgq_extension.hpp │ ├── duckpgq_extension_callback.hpp │ └── duckpgq_state.hpp ├── core │ ├── operator │ │ ├── CMakeLists.txt │ │ └── duckpgq_bind.cpp │ ├── parser │ │ └── CMakeLists.txt │ ├── functions │ │ ├── CMakeLists.txt │ │ ├── function_data │ │ │ ├── CMakeLists.txt │ │ │ ├── iterative_length_function_data.cpp │ │ │ ├── local_clustering_coefficient_function_data.cpp │ │ │ ├── weakly_connected_component_function_data.cpp │ │ │ ├── cheapest_path_length_function_data.cpp │ │ │ └── pagerank_function_data.cpp │ │ ├── table │ │ │ ├── CMakeLists.txt │ │ │ ├── pagerank.cpp │ │ │ ├── weakly_connected_component.cpp │ │ │ ├── local_clustering_coefficient.cpp │ │ │ └── drop_property_graph.cpp │ │ └── scalar │ │ │ ├── CMakeLists.txt │ │ │ ├── csr_deletion.cpp │ │ │ ├── csr_get_w_type.cpp │ │ │ ├── local_clustering_coefficient.cpp │ │ │ └── weakly_connected_component.cpp │ ├── pragma │ │ ├── CMakeLists.txt │ │ ├── show_property_graphs.cpp │ │ └── create_vertex_table.cpp │ ├── utils │ │ ├── CMakeLists.txt │ │ └── duckpgq_bitmap.cpp │ ├── CMakeLists.txt │ └── module.cpp ├── CMakeLists.txt └── duckpgq_extension.cpp ├── .gitignore ├── .gitmodules ├── vcpkg.json ├── CITATION.cff ├── Makefile ├── extension_config.cmake ├── test ├── sql │ ├── duckdb_columns.test │ ├── create_pg │ │ ├── optional_edge_table_clause.test │ │ ├── create_pg_on_view.test │ │ ├── 209_property_undefined.test │ │ ├── create_or_replace_pg.test │ │ ├── except_properties.test │ │ ├── no_properties.test │ │ ├── create_if_not_exists.test │ │ ├── create_pg_multiple_connections.test │ │ ├── all_properties.test │ │ └── drop_property_graph.test │ ├── path_finding │ │ ├── edgeless_graph.test │ │ ├── top_k.test │ │ ├── path-finding-cte.test │ │ ├── non-unique-vertices.test │ │ ├── kleene_star.test │ │ ├── undirected_paths.test │ │ ├── parser_arrow_kleene.test │ │ ├── subpath_match.test │ │ └── shortest_path.test │ ├── wcc_segfault.test │ ├── non_existing_table.test │ ├── altering_table.test │ ├── pgq_keywords.test │ ├── pragma │ │ ├── create_vertex_table.test │ │ └── show_property_graphs.test │ ├── scalar │ │ ├── pagerank.test │ │ └── delete_csr.test │ ├── unnamed_subquery.test │ ├── csr_segfault.test │ ├── pattern_matching │ │ ├── undirected_edges.test │ │ └── path_modes.test │ ├── get_csr_ptr.test │ ├── source_keyword.test │ ├── optional_columns.test │ ├── with_statement_duckpgq.test │ ├── summarize_property_graph.test │ ├── snb │ │ └── bi.test │ ├── nested_subquery.test │ ├── explain_duckpgq.test │ ├── copy_to_duckpgq.test │ ├── multiple_graph_table.test │ └── label_optional.test ├── README.md ├── python │ └── duckpgq_test.py └── nodejs │ └── duckpgq_test.js ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml └── workflows │ └── MainDistributionPipeline.yml ├── scripts ├── python_helpers.py ├── set_tag.sh ├── test_header_generation.py ├── copy_tests.py ├── s3_availability.py ├── set_extension_name.py └── extension-upload.sh ├── LICENSE ├── CMakeLists.txt └── docs └── UPDATING.md /.clang-format: -------------------------------------------------------------------------------- 1 | duckdb/.clang-format -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | duckdb/.clang-tidy -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | duckdb/.editorconfig -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | This is the SQL/PGQ extension for DuckPGQ 2 | -------------------------------------------------------------------------------- /src/include/duckpgq/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "duckdb/common/helper.hpp" 5 | // TODO doc util 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .idea 3 | cmake-build-debug 4 | duckdb_unittest_tempdir/ 5 | .DS_Store 6 | testext 7 | test/python/__pycache__/ 8 | .Rhistory 9 | .venv -------------------------------------------------------------------------------- /src/core/operator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXTENSION_SOURCES 2 | ${CMAKE_CURRENT_SOURCE_DIR}/duckpgq_bind.cpp ${EXTENSION_SOURCES} 3 | PARENT_SCOPE) 4 | -------------------------------------------------------------------------------- /src/core/parser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXTENSION_SOURCES 2 | ${CMAKE_CURRENT_SOURCE_DIR}/duckpgq_parser.cpp ${EXTENSION_SOURCES} 3 | PARENT_SCOPE) 4 | -------------------------------------------------------------------------------- /src/core/functions/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(function_data) 2 | add_subdirectory(scalar) 3 | add_subdirectory(table) 4 | 5 | set(EXTENSION_SOURCES 6 | ${EXTENSION_SOURCES} 7 | PARENT_SCOPE) 8 | -------------------------------------------------------------------------------- /src/core/pragma/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXTENSION_SOURCES 2 | ${CMAKE_CURRENT_SOURCE_DIR}/create_vertex_table.cpp 3 | ${CMAKE_CURRENT_SOURCE_DIR}/show_property_graphs.cpp ${EXTENSION_SOURCES} 4 | PARENT_SCOPE) 5 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(core) 2 | 3 | set(EXTENSION_SOURCES 4 | ${CMAKE_CURRENT_SOURCE_DIR}/duckpgq_extension.cpp 5 | ${CMAKE_CURRENT_SOURCE_DIR}/duckpgq_state.cpp ${EXTENSION_SOURCES} 6 | PARENT_SCOPE) 7 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/module.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "duckpgq/common.hpp" 3 | 4 | namespace duckdb { 5 | 6 | struct CoreModule { 7 | public: 8 | static void Register(ExtensionLoader &loader); 9 | }; 10 | 11 | } // namespace duckdb 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "duckdb"] 2 | path = duckdb 3 | url = git@github.com:cwida/duckdb-pgq.git 4 | branch = main 5 | [submodule "extension-ci-tools"] 6 | path = extension-ci-tools 7 | url = git@github.com:duckdb/extension-ci-tools.git 8 | branch = main -------------------------------------------------------------------------------- /src/core/utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXTENSION_SOURCES 2 | ${EXTENSION_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/compressed_sparse_row.cpp 3 | ${CMAKE_CURRENT_SOURCE_DIR}/duckpgq_bitmap.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/duckpgq_utils.cpp 5 | PARENT_SCOPE) 6 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "openssl" 4 | ], 5 | "vcpkg-configuration": { 6 | "overlay-ports": [ 7 | "./extension-ci-tools/vcpkg_ports" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "ten Wolde" 5 | given-names: "Daniel" 6 | orcid: "https://orcid.org/0009-0008-8502-1148" 7 | title: "DuckPGQ" 8 | url: "https://github.com/cwida/duckpgq-extension" 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | # Configuration of extension 4 | EXT_NAME=duckpgq 5 | EXT_CONFIG=${PROJ_DIR}extension_config.cmake 6 | 7 | # Include the Makefile from extension-ci-tools 8 | include extension-ci-tools/makefiles/duckdb_extension.Makefile 9 | -------------------------------------------------------------------------------- /src/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(functions) 2 | add_subdirectory(operator) 3 | add_subdirectory(parser) 4 | add_subdirectory(pragma) 5 | add_subdirectory(utils) 6 | 7 | set(EXTENSION_SOURCES 8 | ${CMAKE_CURRENT_SOURCE_DIR}/module.cpp ${EXTENSION_SOURCES} 9 | PARENT_SCOPE) 10 | -------------------------------------------------------------------------------- /src/include/duckpgq_extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckpgq/common.hpp" 4 | 5 | namespace duckdb { 6 | 7 | class DuckpgqExtension : public Extension { 8 | public: 9 | void Load(ExtensionLoader &db) override; 10 | std::string Name() override; 11 | }; 12 | 13 | } // namespace duckdb 14 | -------------------------------------------------------------------------------- /extension_config.cmake: -------------------------------------------------------------------------------- 1 | # This file is included by DuckDB's build system. It specifies which extension to load 2 | 3 | # Extension from this repo 4 | duckdb_extension_load(duckpgq 5 | LOAD_TESTS 6 | SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR} 7 | ) 8 | 9 | # Any extra extensions that should be built 10 | # e.g.: duckdb_extension_load(json) 11 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/operator/duckpgq_operator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckpgq/common.hpp" 4 | 5 | namespace duckdb { 6 | 7 | struct CorePGQOperator { 8 | static void Register(ExtensionLoader &loader) { 9 | RegisterPGQBindOperator(loader); 10 | } 11 | 12 | private: 13 | static void RegisterPGQBindOperator(ExtensionLoader &loader); 14 | }; 15 | 16 | } // namespace duckdb 17 | -------------------------------------------------------------------------------- /test/sql/duckdb_columns.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/duckdb_columns.test 2 | # description: Testing that normal duckdb queries do not interfere 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | from duckdb_columns; 9 | 10 | statement ok 11 | from duckdb_constraints(); 12 | 13 | statement ok 14 | select * from information_schema.columns; 15 | 16 | statement ok 17 | select * from information_schema.tables; 18 | -------------------------------------------------------------------------------- /src/core/functions/function_data/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXTENSION_SOURCES 2 | ${EXTENSION_SOURCES} 3 | ${CMAKE_CURRENT_SOURCE_DIR}/cheapest_path_length_function_data.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/iterative_length_function_data.cpp 5 | ${CMAKE_CURRENT_SOURCE_DIR}/local_clustering_coefficient_function_data.cpp 6 | ${CMAKE_CURRENT_SOURCE_DIR}/pagerank_function_data.cpp 7 | ${CMAKE_CURRENT_SOURCE_DIR}/weakly_connected_component_function_data.cpp 8 | PARENT_SCOPE) 9 | -------------------------------------------------------------------------------- /src/include/duckpgq_extension_callback.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckpgq/common.hpp" 4 | #include "duckdb/planner/extension_callback.hpp" 5 | #include 6 | 7 | namespace duckdb { 8 | // class DuckpgqExtensionCallback : public ExtensionCallback { 9 | // void OnConnectionOpened(ClientContext &context) override { 10 | // context.registered_state->Insert( 11 | // "duckpgq", make_shared_ptr(context.shared_from_this())); 12 | // } 13 | // }; 14 | } // namespace duckdb 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Request 4 | # manual template until discussion templates are GA 5 | url: https://github.com/cwida/duckpgq-extension/discussions/new?category=ideas&title=Feature%20Request:%20...&labels=feature&body=Why%20do%20you%20want%20this%20feature%3F 6 | about: Submit feature requests here 7 | - name: Discussions 8 | url: https://github.com/cwida/duckpgq-extension/discussions 9 | about: Please ask and answer general questions here. -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Testing this extension 2 | This directory contains all the tests for this extension. The `sql` directory holds tests that are written as [SQLLogicTests](https://duckdb.org/dev/sqllogictest/intro.html). DuckDB aims to have most its tests in this format as SQL statements, so for the quack extension, this should probably be the goal too. 3 | 4 | The root makefile contains targets to build and run all of these tests. To run the SQLLogicTests: 5 | ```bash 6 | make test 7 | ``` 8 | or 9 | ```bash 10 | make test_debug 11 | ``` -------------------------------------------------------------------------------- /src/core/utils/duckpgq_bitmap.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/utils/duckpgq_bitmap.hpp" 2 | 3 | namespace duckdb { 4 | 5 | DuckPGQBitmap::DuckPGQBitmap(size_t size) { 6 | bitmap.resize((size + 63) / 64, 0); 7 | } 8 | 9 | void DuckPGQBitmap::set(size_t index) { 10 | bitmap[index / 64] |= (1ULL << (index % 64)); 11 | } 12 | 13 | bool DuckPGQBitmap::test(size_t index) const { 14 | return (bitmap[index / 64] & (1ULL << (index % 64))) != 0; 15 | } 16 | 17 | void DuckPGQBitmap::reset() { 18 | fill(bitmap.begin(), bitmap.end(), 0); 19 | } 20 | 21 | } // namespace duckdb 22 | -------------------------------------------------------------------------------- /src/core/functions/table/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXTENSION_SOURCES 2 | ${CMAKE_CURRENT_SOURCE_DIR}/create_property_graph.cpp 3 | ${CMAKE_CURRENT_SOURCE_DIR}/describe_property_graph.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/drop_property_graph.cpp 5 | ${CMAKE_CURRENT_SOURCE_DIR}/local_clustering_coefficient.cpp 6 | ${CMAKE_CURRENT_SOURCE_DIR}/match.cpp 7 | ${CMAKE_CURRENT_SOURCE_DIR}/pagerank.cpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/pgq_scan.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/summarize_property_graph.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/weakly_connected_component.cpp 11 | ${EXTENSION_SOURCES} 12 | PARENT_SCOPE) 13 | -------------------------------------------------------------------------------- /scripts/python_helpers.py: -------------------------------------------------------------------------------- 1 | def open_utf8(fpath, flags): 2 | import sys 3 | if sys.version_info[0] < 3: 4 | return open(fpath, flags) 5 | else: 6 | return open(fpath, flags, encoding="utf8") 7 | 8 | def normalize_path(path): 9 | import os 10 | 11 | def normalize(p): 12 | return os.path.sep.join(p.split('/')) 13 | 14 | if isinstance(path, list): 15 | normed = map(lambda p: normalize(p), path) 16 | return list(normed) 17 | 18 | if (isinstance, str): 19 | return normalize(path) 20 | 21 | raise Exception("Can only be called with a str or list argument") 22 | 23 | -------------------------------------------------------------------------------- /src/core/module.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "duckpgq/core/module.hpp" 3 | #include "duckpgq/common.hpp" 4 | #include "duckpgq/core/functions/scalar.hpp" 5 | #include "duckpgq/core/functions/table.hpp" 6 | #include "duckpgq/core/operator/duckpgq_operator.hpp" 7 | #include "duckpgq/core/parser/duckpgq_parser.hpp" 8 | #include "duckpgq/core/pragma/duckpgq_pragma.hpp" 9 | 10 | namespace duckdb { 11 | 12 | void CoreModule::Register(ExtensionLoader &loader) { 13 | CoreTableFunctions::Register(loader); 14 | CoreScalarFunctions::Register(loader); 15 | CorePGQParser::Register(loader); 16 | CorePGQPragma::Register(loader); 17 | CorePGQOperator::Register(loader); 18 | } 19 | 20 | } // namespace duckdb 21 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/utils/duckpgq_bitmap.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/utils/duckpgq_bitmap.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckpgq/common.hpp" 11 | #include "duckdb/common/vector.hpp" 12 | 13 | namespace duckdb { 14 | 15 | class DuckPGQBitmap { 16 | public: 17 | explicit DuckPGQBitmap(size_t size); 18 | void set(size_t index); 19 | bool test(size_t index) const; 20 | void reset(); 21 | 22 | private: 23 | vector bitmap; 24 | }; 25 | 26 | } // namespace duckdb 27 | -------------------------------------------------------------------------------- /test/sql/create_pg/optional_edge_table_clause.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/optional_edge_table_clause.test 2 | # description: Testing the optional edge table property graphs 3 | # group: [create_pg] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | import database 'duckdb/data/SNB0.003'; 9 | 10 | statement ok 11 | -CREATE PROPERTY GRAPH snb 12 | VERTEX TABLES (Message, person); 13 | 14 | 15 | statement ok 16 | -FROM GRAPH_TABLE (snb 17 | MATCH (m:Message) 18 | COLUMNS (*) 19 | ) tmp 20 | 21 | statement error 22 | -FROM GRAPH_TABLE (snb 23 | MATCH (p:Person)-[k:Knows]->(p2:Person) 24 | COLUMNS (*) 25 | ) tmp 26 | ---- 27 | Binder Error: The label knows is not registered in property graph snb 28 | -------------------------------------------------------------------------------- /src/duckpgq_extension.cpp: -------------------------------------------------------------------------------- 1 | #define DUCKDB_EXTENSION_MAIN 2 | 3 | #include "duckpgq_extension.hpp" 4 | #include "duckpgq/common.hpp" 5 | #include "duckpgq/core/module.hpp" 6 | #include 7 | #include "duckdb/main/connection_manager.hpp" 8 | 9 | namespace duckdb { 10 | 11 | static void LoadInternal(ExtensionLoader &loader) { 12 | CoreModule::Register(loader); 13 | } 14 | 15 | void DuckpgqExtension::Load(ExtensionLoader &loader) { 16 | LoadInternal(loader); 17 | } 18 | 19 | std::string DuckpgqExtension::Name() { 20 | return "duckpgq"; 21 | } 22 | 23 | } // namespace duckdb 24 | 25 | extern "C" { 26 | 27 | DUCKDB_CPP_EXTENSION_ENTRY(duckpgq, loader) { 28 | duckdb::LoadInternal(loader); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/operator/duckpgq_bind.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckpgq/common.hpp" 4 | 5 | namespace duckdb { 6 | 7 | BoundStatement duckpgq_bind(ClientContext &context, Binder &binder, OperatorExtensionInfo *info, 8 | SQLStatement &statement); 9 | 10 | struct DuckPGQOperatorExtension : public OperatorExtension { 11 | DuckPGQOperatorExtension() : OperatorExtension() { 12 | Bind = duckpgq_bind; 13 | } 14 | 15 | std::string GetName() override { 16 | return "duckpgq_bind"; 17 | } 18 | 19 | unique_ptr Deserialize(Deserializer &deserializer) override { 20 | throw InternalException("DuckPGQ operator should not be serialized"); 21 | } 22 | }; 23 | 24 | } // namespace duckdb 25 | -------------------------------------------------------------------------------- /src/core/functions/scalar/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXTENSION_SOURCES 2 | ${EXTENSION_SOURCES} 3 | ${CMAKE_CURRENT_SOURCE_DIR}/cheapest_path_length.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/csr_creation.cpp 5 | ${CMAKE_CURRENT_SOURCE_DIR}/csr_deletion.cpp 6 | ${CMAKE_CURRENT_SOURCE_DIR}/csr_get_w_type.cpp 7 | ${CMAKE_CURRENT_SOURCE_DIR}/iterativelength.cpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/iterativelength2.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/iterativelength_bidirectional.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/pagerank.cpp 11 | ${CMAKE_CURRENT_SOURCE_DIR}/reachability.cpp 12 | ${CMAKE_CURRENT_SOURCE_DIR}/shortest_path.cpp 13 | ${CMAKE_CURRENT_SOURCE_DIR}/csr_creation.cpp 14 | ${CMAKE_CURRENT_SOURCE_DIR}/local_clustering_coefficient.cpp 15 | ${CMAKE_CURRENT_SOURCE_DIR}/weakly_connected_component.cpp 16 | PARENT_SCOPE) 17 | -------------------------------------------------------------------------------- /test/sql/path_finding/edgeless_graph.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/edgeless_graph.test 2 | # group: [path_finding] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | CREATE TABLE nodes (id INTEGER); 8 | 9 | statement ok 10 | CREATE TABLE edges (src INTEGER, dst INTEGER); 11 | 12 | statement ok 13 | INSERT INTO nodes VALUES (1), (2), (3); 14 | 15 | statement ok 16 | -CREATE PROPERTY GRAPH testgraph 17 | VERTEX TABLES ( 18 | nodes LABEL N 19 | ) 20 | EDGE TABLES ( 21 | edges SOURCE KEY (src) REFERENCES nodes (id) 22 | DESTINATION KEY (dst) REFERENCES nodes (id) 23 | LABEL E 24 | ); 25 | 26 | query IIIII 27 | -FROM GRAPH_TABLE(testgraph 28 | MATCH p = ANY SHORTEST (n1:N)-[e:E]-> * (n2:N) 29 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 30 | ); 31 | ---- 32 | 1 1 [0] [] 0 33 | 2 2 [1] [] 0 34 | 3 3 [2] [] 0 -------------------------------------------------------------------------------- /src/include/duckpgq/core/pragma/duckpgq_pragma.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // duckpgq/include/core/pragma/show_property_graphs.hpp 5 | // 6 | //===----------------------------------------------------------------------===// 7 | 8 | #pragma once 9 | #include "duckpgq/common.hpp" 10 | 11 | namespace duckdb { 12 | 13 | //! Class to register the PRAGMA create_inbox function 14 | class CorePGQPragma { 15 | public: 16 | //! Register the PRAGMA function 17 | static void Register(ExtensionLoader &loader) { 18 | RegisterShowPropertyGraphs(loader); 19 | RegisterCreateVertexTable(loader); 20 | } 21 | 22 | private: 23 | static void RegisterShowPropertyGraphs(ExtensionLoader &loader); 24 | static void RegisterCreateVertexTable(ExtensionLoader &loader); 25 | }; 26 | 27 | } // namespace duckdb 28 | -------------------------------------------------------------------------------- /src/core/pragma/show_property_graphs.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb/function/pragma_function.hpp" 2 | #include 3 | 4 | namespace duckdb { 5 | 6 | static string PragmaShowPropertyGraphs(ClientContext &context, const FunctionParameters ¶meters) { 7 | return "SELECT DISTINCT property_graph from __duckpgq_internal"; 8 | } 9 | 10 | void CorePGQPragma::RegisterShowPropertyGraphs(ExtensionLoader &loader) { 11 | // Define the pragma function 12 | auto pragma_func = PragmaFunction::PragmaCall("show_property_graphs", // Name of the pragma 13 | PragmaShowPropertyGraphs, // Query substitution function 14 | {} // Parameter types (mail_limit is an integer) 15 | ); 16 | 17 | // Register the pragma function 18 | loader.RegisterFunction(pragma_func); 19 | } 20 | 21 | } // namespace duckdb 22 | -------------------------------------------------------------------------------- /scripts/set_tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | cd "$( cd "$( dirname "${BASH_SOURCE[0]:-${(%):-%x}}" )" >/dev/null 2>&1 && pwd )" 7 | cd .. 8 | 9 | cd duckdb 10 | 11 | TAG_NAME="$1" 12 | REMOTE_NAME="origin" # Explicitly define the target remote 13 | 14 | echo "--- Updating tag '$TAG_NAME' on remote '$REMOTE_NAME' ---" 15 | 16 | # 1. Delete the remote tag on your fork, ignoring errors if it doesn't exist. 17 | git push --delete "$REMOTE_NAME" "$TAG_NAME" || true 18 | 19 | # 2. Force-delete the local tag to ensure it can be recreated. 20 | git tag -d "$TAG_NAME" || true 21 | 22 | # 3. Create the new annotated tag locally on your current commit. 23 | # The '-f' flag will force replacement if it somehow still exists. 24 | git tag -fa "$TAG_NAME" -m "DuckPGQ custom tag '$TAG_NAME'" 25 | 26 | # 4. Force-push the new tag to your fork, overwriting the remote one if it exists. 27 | git push "$REMOTE_NAME" --tags --force 28 | 29 | echo "--- Successfully updated tag '$TAG_NAME' on remote '$REMOTE_NAME' ---" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018-2025 Stichting DuckDB Foundation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/sql/wcc_segfault.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/wcc_segfault.test 2 | # group: [sql] 3 | 4 | # require duckpgq 5 | # 6 | # 7 | # statement ok 8 | # create or replace table data_table as select * from read_csv("/Users/dljtw/git/duckpgq/test/python/links.tsv"); 9 | # 10 | # statement ok 11 | # CREATE or replace TABLE devices AS SELECT a AS device FROM data_table UNION SELECT b AS device FROM data_table order by device; 12 | # 13 | # statement ok 14 | # CREATE or replace TABLE edges AS SELECT a.rowid as a, b.rowid as b FROM data_table e join devices a on a.device = e.a join devices b on b.device = e.b; 15 | # 16 | # statement ok 17 | # CREATE or replace TABLE nodes AS SELECT rowid as id from devices; 18 | # 19 | # statement ok 20 | # -CREATE OR REPLACE PROPERTY GRAPH graph 21 | # VERTEX TABLES ( 22 | # nodes 23 | # ) 24 | # EDGE TABLES ( 25 | # edges 26 | # SOURCE KEY (a) REFERENCES nodes(id) 27 | # DESTINATION KEY (b) REFERENCES nodes(id) 28 | # LABEL connects 29 | # ); 30 | # 31 | # statement ok 32 | # -FROM weakly_connected_component(graph, nodes, connects); 33 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/function_data/iterative_length_function_data.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/functions/function_data/iterative_length_function_data.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckdb/main/client_context.hpp" 11 | #include "duckpgq/common.hpp" 12 | 13 | namespace duckdb { 14 | 15 | struct IterativeLengthFunctionData final : FunctionData { 16 | ClientContext &context; 17 | int32_t csr_id; 18 | 19 | IterativeLengthFunctionData(ClientContext &context, int32_t csr_id) : context(context), csr_id(csr_id) { 20 | } 21 | static unique_ptr IterativeLengthBind(ClientContext &context, ScalarFunction &bound_function, 22 | vector> &arguments); 23 | 24 | unique_ptr Copy() const override; 25 | bool Equals(const FunctionData &other_p) const override; 26 | }; 27 | 28 | } // namespace duckdb 29 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/function_data/cheapest_path_length_function_data.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/functions/function_data/cheapest_path_length_function_data.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckpgq/common.hpp" 11 | #include "duckdb/main/client_context.hpp" 12 | 13 | namespace duckdb { 14 | 15 | struct CheapestPathLengthFunctionData final : FunctionData { 16 | ClientContext &context; 17 | int32_t csr_id; 18 | 19 | CheapestPathLengthFunctionData(ClientContext &context, int32_t csr_id) : context(context), csr_id(csr_id) { 20 | } 21 | static unique_ptr CheapestPathLengthBind(ClientContext &context, ScalarFunction &bound_function, 22 | vector> &arguments); 23 | 24 | unique_ptr Copy() const override; 25 | bool Equals(const FunctionData &other_p) const override; 26 | }; 27 | 28 | } // namespace duckdb 29 | -------------------------------------------------------------------------------- /test/python/duckpgq_test.py: -------------------------------------------------------------------------------- 1 | import duckdb 2 | import os 3 | import pytest 4 | 5 | 6 | # Get a fresh connection to DuckDB with the duckpgq extension binary loaded 7 | @pytest.fixture 8 | def duckdb_conn(): 9 | extension_binary = os.getenv('DUCKPGQ_EXTENSION_BINARY_PATH') 10 | if extension_binary == '': 11 | raise Exception('Please make sure the `DUCKPGQ_EXTENSION_BINARY_PATH` is set to run the python tests') 12 | conn = duckdb.connect('', config={'allow_unsigned_extensions': 'true'}) 13 | conn.execute(f"load '{extension_binary}'") 14 | return conn 15 | 16 | 17 | def test_duckpgq(duckdb_conn): 18 | duckdb_conn.execute("SELECT duckpgq('Sam') as value;") 19 | res = duckdb_conn.fetchall() 20 | assert res[0][0] == "Duckpgq Sam 🐥" 21 | 22 | 23 | def test_property_graph(duckdb_conn): 24 | duckdb_conn.execute("CREATE TABLE foo(i bigint)") 25 | duckdb_conn.execute("INSERT INTO foo(i) VALUES (1)") 26 | duckdb_conn.execute("-CREATE PROPERTY GRAPH t VERTEX TABLES (foo);") 27 | duckdb_conn.execute("-FROM GRAPH_TABLE(t MATCH (f:foo))") 28 | res = duckdb_conn.fetchall() 29 | assert res[0][0] == 1 30 | -------------------------------------------------------------------------------- /test/sql/create_pg/create_pg_on_view.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/create_pg_on_view.test 2 | # group: [create_pg] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | CREATE TABLE vdata(id TEXT PRIMARY KEY); 8 | 9 | statement ok 10 | CREATE VIEW v AS SELECT * FROM vdata; 11 | 12 | statement ok 13 | CREATE TABLE w(id TEXT PRIMARY KEY); 14 | 15 | statement ok 16 | CREATE TABLE v_w(v_id TEXT, w_id TEXT); 17 | 18 | statement ok 19 | create view v_w_view as select * from v_w; 20 | 21 | statement error 22 | -CREATE PROPERTY GRAPH g1 23 | VERTEX TABLES (v, w) 24 | EDGE TABLES ( 25 | v_w SOURCE KEY (v_id) REFERENCES v (id) 26 | DESTINATION KEY (w_id) REFERENCES w (id) 27 | LABEL hasW 28 | ); 29 | ---- 30 | Invalid Error: Found a view with name v. Creating property graph tables over views is currently not supported. 31 | 32 | statement error 33 | -CREATE PROPERTY GRAPH g1 34 | VERTEX TABLES (vdata, w) 35 | EDGE TABLES ( 36 | v_w_view SOURCE KEY (v_id) REFERENCES vdata (id) 37 | DESTINATION KEY (w_id) REFERENCES w (id) 38 | LABEL hasW 39 | ); 40 | ---- 41 | Invalid Error: Found a view with name v_w_view. Creating property graph tables over views is currently not supported. 42 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/function_data/local_clustering_coefficient_function_data.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/functions/function_data/local_clustering_coefficient_function_data.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckdb/main/client_context.hpp" 11 | #include "duckpgq/common.hpp" 12 | 13 | namespace duckdb { 14 | 15 | struct LocalClusteringCoefficientFunctionData final : FunctionData { 16 | ClientContext &context; 17 | int32_t csr_id; 18 | 19 | LocalClusteringCoefficientFunctionData(ClientContext &context, int32_t csr_id); 20 | static unique_ptr LocalClusteringCoefficientBind(ClientContext &context, 21 | ScalarFunction &bound_function, 22 | vector> &arguments); 23 | 24 | unique_ptr Copy() const override; 25 | bool Equals(const FunctionData &other_p) const override; 26 | }; 27 | 28 | } // namespace duckdb 29 | -------------------------------------------------------------------------------- /test/nodejs/duckpgq_test.js: -------------------------------------------------------------------------------- 1 | var duckdb = require('../../duckdb/tools/nodejs'); 2 | var assert = require('assert'); 3 | 4 | describe(`duckpgq extension`, () => { 5 | let db; 6 | let conn; 7 | before((done) => { 8 | db = new duckdb.Database(':memory:', {"allow_unsigned_extensions":"true"}); 9 | conn = new duckdb.Connection(db); 10 | conn.exec(`LOAD '${process.env.DUCKPGQ_EXTENSION_BINARY_PATH}';`, function (err) { 11 | if (err) throw err; 12 | done(); 13 | }); 14 | }); 15 | 16 | it('duckpgq function should return expected string', function (done) { 17 | db.all("SELECT duckpgq('Sam') as value;", function (err, res) { 18 | if (err) throw err; 19 | assert.deepEqual(res, [{value: "Duckpgq Sam 🐥"}]); 20 | done(); 21 | }); 22 | }); 23 | 24 | it('duckpgq_openssl_version function should return expected string', function (done) { 25 | db.all("SELECT duckpgq_openssl_version('Michael') as value;", function (err, res) { 26 | if (err) throw err; 27 | assert(res[0].value.startsWith('Duckpgq Michael, my linked OpenSSL version is OpenSSL')); 28 | done(); 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/function_data/weakly_connected_component_function_data.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/functions/function_data/weakly_connected_component_function_data.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckdb/main/client_context.hpp" 11 | #include "duckpgq/common.hpp" 12 | 13 | namespace duckdb { 14 | 15 | struct WeaklyConnectedComponentFunctionData final : FunctionData { 16 | ClientContext &context; 17 | int32_t csr_id; 18 | std::mutex wcc_lock; 19 | std::mutex initialize_lock; 20 | bool state_converged; 21 | bool state_initialized; 22 | vector forest; 23 | 24 | WeaklyConnectedComponentFunctionData(ClientContext &context, int32_t csr_id); 25 | 26 | static unique_ptr WeaklyConnectedComponentBind(ClientContext &context, ScalarFunction &bound_function, 27 | vector> &arguments); 28 | 29 | unique_ptr Copy() const override; 30 | bool Equals(const FunctionData &other_p) const override; 31 | }; 32 | 33 | } // namespace duckdb 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | set(TARGET_NAME duckpgq) 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | # DuckDB's extension distribution supports vcpkg. As such, dependencies can be 7 | # added in ./vcpkg.json and then used in cmake with find_package. Feel free to 8 | # remove or replace with other dependencies. Note that it should also be removed 9 | # from vcpkg.json to prevent needlessly installing it.. 10 | find_package(OpenSSL REQUIRED) 11 | 12 | set(EXTENSION_NAME ${TARGET_NAME}_extension) 13 | set(LOADABLE_EXTENSION_NAME ${TARGET_NAME}_loadable_extension) 14 | 15 | project(${TARGET_NAME}) 16 | include_directories(src/include) 17 | add_subdirectory(src) 18 | include_directories(../duckdb/third_party/libpg_query/include) 19 | 20 | build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES}) 21 | build_loadable_extension(${TARGET_NAME} " " ${EXTENSION_SOURCES}) 22 | 23 | # Link OpenSSL in both the static library as the loadable extension 24 | target_link_libraries(${EXTENSION_NAME} OpenSSL::SSL OpenSSL::Crypto) 25 | target_link_libraries(${LOADABLE_EXTENSION_NAME} OpenSSL::SSL OpenSSL::Crypto) 26 | 27 | install( 28 | TARGETS ${EXTENSION_NAME} 29 | EXPORT "${DUCKDB_EXPORT_SET}" 30 | LIBRARY DESTINATION "${INSTALL_LIB_DIR}" 31 | ARCHIVE DESTINATION "${INSTALL_LIB_DIR}") 32 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/utils/duckpgq_utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "duckpgq_state.hpp" 3 | 4 | namespace duckdb { 5 | 6 | #define LANE_LIMIT 512 7 | #define VISIT_SIZE_DIVISOR 2 8 | 9 | // Function to get DuckPGQState from ClientContext 10 | shared_ptr GetDuckPGQState(ClientContext &context, bool throw_error_not_found = false); 11 | CreatePropertyGraphInfo *GetPropertyGraphInfo(const shared_ptr &duckpgq_state, const string &pg_name); 12 | shared_ptr ValidateSourceNodeAndEdgeTable(CreatePropertyGraphInfo *pg_info, 13 | const std::string &node_table, 14 | const std::string &edge_table); 15 | unique_ptr CreateSelectNode(const shared_ptr &edge_pg_entry, 16 | const string &function_name, const string &function_alias); 17 | unique_ptr CreateBaseTableRef(const string &table_name, const string &alias = ""); 18 | unique_ptr CreateColumnRefExpression(const string &column_name, const string &table_name = "", 19 | const string &alias = ""); 20 | 21 | } // namespace duckdb 22 | -------------------------------------------------------------------------------- /src/core/functions/scalar/csr_deletion.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb/main/client_data.hpp" 2 | #include "duckdb/planner/expression/bound_function_expression.hpp" 3 | #include "duckpgq/common.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace duckdb { 9 | 10 | static void DeleteCsrFunction(DataChunk &args, ExpressionState &state, Vector &result) { 11 | auto &func_expr = state.expr.Cast(); 12 | auto &info = func_expr.bind_info->Cast(); 13 | 14 | auto duckpgq_state = GetDuckPGQState(info.context); 15 | 16 | auto flag = duckpgq_state->csr_list.erase(info.id); 17 | result.SetVectorType(VectorType::CONSTANT_VECTOR); 18 | auto result_data = ConstantVector::GetData(result); 19 | result_data[0] = flag == 1; 20 | } 21 | 22 | //------------------------------------------------------------------------------ 23 | // Register functions 24 | //------------------------------------------------------------------------------ 25 | void CoreScalarFunctions::RegisterCSRDeletionScalarFunction(ExtensionLoader &loader) { 26 | loader.RegisterFunction(ScalarFunction("delete_csr", {LogicalType::INTEGER}, LogicalType::BOOLEAN, 27 | DeleteCsrFunction, CSRFunctionData::CSRBind)); 28 | } 29 | 30 | } // namespace duckdb 31 | -------------------------------------------------------------------------------- /.github/workflows/MainDistributionPipeline.yml: -------------------------------------------------------------------------------- 1 | # 2 | # This workflow calls the main distribution pipeline from DuckDB to build, test and (optionally) release the extension 3 | # 4 | name: Main Extension Distribution Pipeline 5 | on: 6 | push: 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || '' }}-${{ github.base_ref || '' }}-${{ github.ref != 'refs/heads/main' && github.sha || '' }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | duckdb-next-build: 16 | name: Build extension binaries 17 | uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@main 18 | with: 19 | duckdb_version: main 20 | ci_tools_version: main 21 | extension_name: duckpgq 22 | 23 | duckdb-stable-build: 24 | name: Build extension binaries 25 | uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@v1.4.1 26 | with: 27 | duckdb_version: v1.4.1 28 | ci_tools_version: v1.4.1 29 | extension_name: duckpgq 30 | 31 | code-quality-check: 32 | name: Code Quality Check 33 | uses: duckdb/extension-ci-tools/.github/workflows/_extension_code_quality.yml@v1.4.1 34 | with: 35 | duckdb_version: v1.4.1 36 | ci_tools_version: main 37 | extension_name: duckpgq 38 | format_checks: 'format;tidy' 39 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/function_data/pagerank_function_data.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/functions/function_data/pagerank_function_data.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckdb/main/client_context.hpp" 11 | #include "duckpgq/common.hpp" 12 | 13 | namespace duckdb { 14 | 15 | struct PageRankFunctionData final : FunctionData { 16 | ClientContext &context; 17 | int32_t csr_id; 18 | vector rank; 19 | vector temp_rank; 20 | double_t damping_factor; 21 | double_t convergence_threshold; 22 | int64_t iteration_count; 23 | std::mutex state_lock; // Lock for state 24 | bool state_initialized; 25 | bool converged; 26 | 27 | PageRankFunctionData(ClientContext &context, int32_t csr_id); 28 | PageRankFunctionData(ClientContext &context, int32_t csr_id, const vector &componentId); 29 | static unique_ptr PageRankBind(ClientContext &context, ScalarFunction &bound_function, 30 | vector> &arguments); 31 | 32 | unique_ptr Copy() const override; 33 | bool Equals(const FunctionData &other_p) const override; 34 | }; 35 | 36 | } // namespace duckdb 37 | -------------------------------------------------------------------------------- /test/sql/create_pg/209_property_undefined.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/209_property_undefined.test 2 | # group: [create_pg] 3 | 4 | require duckpgq 5 | 6 | statement ok con1 7 | create table person (id bigint, firstName varchar, lastName varchar); 8 | 9 | statement ok con1 10 | create table person_knows_person(person1id bigint, person2id bigint); 11 | 12 | statement ok con1 13 | -CREATE PROPERTY GRAPH snb 14 | VERTEX TABLES ( 15 | Person 16 | ) 17 | EDGE TABLES ( 18 | Person_knows_person 19 | SOURCE KEY ( person1id ) REFERENCES Person ( id ) 20 | DESTINATION KEY ( person2id ) REFERENCES Person ( id ) 21 | LABEL Knows 22 | ); 23 | 24 | statement ok con1 25 | -FROM GRAPH_TABLE(snb 26 | MATCH (a:Person WHERE a.firstName = 'Jan')-[k:Knows]->(b:Person) 27 | COLUMNS (b.firstName) 28 | ); 29 | 30 | statement ok con2 31 | -FROM GRAPH_TABLE(snb 32 | MATCH (a:Person WHERE a.firstName = 'Bob')-[k:Knows]->(b:Person) 33 | COLUMNS (b.firstName) 34 | ); 35 | 36 | statement ok con2 37 | -FROM GRAPH_TABLE(snb 38 | MATCH (a:Person WHERE a.firstName = 'Bob')-[k:Knows]->(b:Person) 39 | COLUMNS (b.FIRSTNAME) 40 | ); 41 | 42 | statement error con2 43 | -FROM GRAPH_TABLE(snb 44 | MATCH (a:Person WHERE a.firstName = 'Bob')-[k:Knows]->(b:Person) 45 | COLUMNS (b.nonregisteredproperty) 46 | ); 47 | ---- 48 | Binder Error: Property b.nonregisteredproperty is never registered! 49 | -------------------------------------------------------------------------------- /src/core/functions/function_data/iterative_length_function_data.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/function_data/iterative_length_function_data.hpp" 2 | #include "duckdb/execution/expression_executor.hpp" 3 | #include "duckpgq/common.hpp" 4 | 5 | #include 6 | 7 | namespace duckdb { 8 | 9 | unique_ptr IterativeLengthFunctionData::Copy() const { 10 | return make_uniq(context, csr_id); 11 | } 12 | 13 | bool IterativeLengthFunctionData::Equals(const FunctionData &other_p) const { 14 | auto &other = other_p.Cast(); 15 | return other.csr_id == csr_id; 16 | } 17 | 18 | unique_ptr IterativeLengthFunctionData::IterativeLengthBind(ClientContext &context, 19 | ScalarFunction &bound_function, 20 | vector> &arguments) { 21 | if (!arguments[0]->IsFoldable()) { 22 | throw InvalidInputException("Id must be constant."); 23 | } 24 | 25 | int32_t csr_id = ExpressionExecutor::EvaluateScalar(context, *arguments[0]).GetValue(); 26 | auto duckpgq_state = GetDuckPGQState(context); 27 | duckpgq_state->csr_to_delete.insert(csr_id); 28 | 29 | return make_uniq(context, csr_id); 30 | } 31 | 32 | } // namespace duckdb 33 | -------------------------------------------------------------------------------- /src/core/operator/duckpgq_bind.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/operator/duckpgq_bind.hpp" 2 | #include "duckpgq/common.hpp" 3 | 4 | #include 5 | #include "duckpgq/core/operator/duckpgq_operator.hpp" 6 | #include 7 | 8 | namespace duckdb { 9 | 10 | BoundStatement duckpgq_bind(ClientContext &context, Binder &binder, OperatorExtensionInfo *info, 11 | SQLStatement &statement) { 12 | auto duckpgq_state = context.registered_state->Get("duckpgq"); 13 | if (!duckpgq_state) { 14 | throw; // Throw the original error that got us here if DuckPGQ is not loaded 15 | } 16 | 17 | auto duckpgq_binder = Binder::CreateBinder(context, &binder); 18 | auto duckpgq_parse_data = dynamic_cast(duckpgq_state->parse_data.get()); 19 | if (duckpgq_parse_data) { 20 | return duckpgq_binder->Bind(*(duckpgq_parse_data->statement)); 21 | } 22 | throw; 23 | } 24 | 25 | //------------------------------------------------------------------------------ 26 | // Register functions 27 | //------------------------------------------------------------------------------ 28 | void CorePGQOperator::RegisterPGQBindOperator(ExtensionLoader &loader) { 29 | auto &db = loader.GetDatabaseInstance(); 30 | auto &config = DBConfig::GetConfig(db); 31 | config.operator_extensions.push_back(make_uniq()); 32 | } 33 | 34 | } // namespace duckdb 35 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/table.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "duckpgq/common.hpp" 3 | 4 | namespace duckdb { 5 | 6 | struct CoreTableFunctions { 7 | static void Register(ExtensionLoader &loader) { 8 | RegisterCreatePropertyGraphTableFunction(loader); 9 | RegisterMatchTableFunction(loader); 10 | RegisterDropPropertyGraphTableFunction(loader); 11 | RegisterDescribePropertyGraphTableFunction(loader); 12 | RegisterLocalClusteringCoefficientTableFunction(loader); 13 | RegisterScanTableFunctions(loader); 14 | RegisterSummarizePropertyGraphTableFunction(loader); 15 | RegisterWeaklyConnectedComponentTableFunction(loader); 16 | RegisterPageRankTableFunction(loader); 17 | } 18 | 19 | private: 20 | static void RegisterCreatePropertyGraphTableFunction(ExtensionLoader &loader); 21 | static void RegisterMatchTableFunction(ExtensionLoader &loader); 22 | static void RegisterDropPropertyGraphTableFunction(ExtensionLoader &loader); 23 | static void RegisterDescribePropertyGraphTableFunction(ExtensionLoader &loader); 24 | static void RegisterLocalClusteringCoefficientTableFunction(ExtensionLoader &loader); 25 | static void RegisterScanTableFunctions(ExtensionLoader &loader); 26 | static void RegisterWeaklyConnectedComponentTableFunction(ExtensionLoader &loader); 27 | static void RegisterPageRankTableFunction(ExtensionLoader &loader); 28 | static void RegisterSummarizePropertyGraphTableFunction(ExtensionLoader &loader); 29 | }; 30 | 31 | } // namespace duckdb 32 | -------------------------------------------------------------------------------- /src/core/functions/function_data/local_clustering_coefficient_function_data.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/function_data/local_clustering_coefficient_function_data.hpp" 2 | #include "duckdb/execution/expression_executor.hpp" 3 | 4 | #include 5 | 6 | namespace duckdb { 7 | 8 | LocalClusteringCoefficientFunctionData::LocalClusteringCoefficientFunctionData(ClientContext &context, int32_t csr_id) 9 | : context(context), csr_id(csr_id) { 10 | } 11 | 12 | unique_ptr LocalClusteringCoefficientFunctionData::LocalClusteringCoefficientBind( 13 | ClientContext &context, ScalarFunction &bound_function, vector> &arguments) { 14 | if (!arguments[0]->IsFoldable()) { 15 | throw InvalidInputException("Id must be constant."); 16 | } 17 | 18 | int32_t csr_id = ExpressionExecutor::EvaluateScalar(context, *arguments[0]).GetValue(); 19 | auto duckpgq_state = GetDuckPGQState(context); 20 | duckpgq_state->csr_to_delete.insert(csr_id); 21 | return make_uniq(context, csr_id); 22 | } 23 | 24 | unique_ptr LocalClusteringCoefficientFunctionData::Copy() const { 25 | return make_uniq(context, csr_id); 26 | } 27 | 28 | bool LocalClusteringCoefficientFunctionData::Equals(const FunctionData &other_p) const { 29 | auto &other = other_p.Cast(); 30 | return other.csr_id == csr_id; 31 | } 32 | 33 | } // namespace duckdb 34 | -------------------------------------------------------------------------------- /src/include/duckpgq_state.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckpgq/common.hpp" 4 | #include "duckdb/common/case_insensitive_map.hpp" 5 | 6 | #include 7 | 8 | namespace duckdb { 9 | 10 | class DuckPGQState : public ClientContextState { 11 | public: 12 | explicit DuckPGQState() {}; 13 | 14 | static void InitializeInternalTable(ClientContext &context); 15 | void QueryEnd() override; 16 | CreatePropertyGraphInfo *GetPropertyGraph(const string &pg_name); 17 | CSR *GetCSR(int32_t id); 18 | 19 | void RetrievePropertyGraphs(const shared_ptr &context); 20 | void ProcessPropertyGraphs(unique_ptr &property_graphs, bool is_vertex); 21 | void PopulateEdgeSpecificFields(unique_ptr &chunk, idx_t row_idx, PropertyGraphTable &table); 22 | static void ExtractListValues(const Value &list_value, vector &output); 23 | void RegisterPropertyGraph(const shared_ptr &table, const string &graph_name, bool is_vertex); 24 | 25 | public: 26 | unique_ptr parse_data; 27 | unordered_map> transform_expression; 28 | int32_t match_index = 0; 29 | 30 | //! Property graphs that are registered 31 | case_insensitive_map_t> registered_property_graphs; 32 | 33 | //! Used to build the CSR data structures required for path-finding queries 34 | std::unordered_map> csr_list; 35 | std::mutex csr_lock; 36 | std::unordered_set csr_to_delete; 37 | }; 38 | 39 | } // namespace duckdb 40 | -------------------------------------------------------------------------------- /test/sql/create_pg/create_or_replace_pg.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/create_or_replace_pg.test 2 | # description: Testing create or replace property graph syntax 3 | # group: [create_pg] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | import database 'duckdb/data/SNB0.003'; 9 | 10 | statement ok 11 | -CREATE PROPERTY GRAPH snb 12 | VERTEX TABLES ( 13 | Person LABEL Person 14 | ) 15 | EDGE TABLES ( 16 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 17 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 18 | LABEL Knows 19 | ); 20 | 21 | # Fails because University is not registered 22 | statement error 23 | -FROM GRAPH_TABLE(snb MATCH (a:Person)-[w:workAt_Organisation]->(u:University)) limit 10; 24 | ---- 25 | Binder Error: The label university is not registered in property graph snb 26 | 27 | statement ok 28 | -CREATE OR REPLACE PROPERTY GRAPH snb 29 | VERTEX TABLES ( 30 | Person LABEL Person, 31 | Organisation LABEL Organisation IN typemask(company, university) 32 | ) 33 | EDGE TABLES ( 34 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 35 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 36 | LABEL Knows, 37 | person_workAt_Organisation SOURCE KEY (PersonId) REFERENCES Person (id) 38 | DESTINATION KEY (OrganisationId) REFERENCES Organisation (id) 39 | LABEL workAt_Organisation 40 | ); 41 | 42 | statement ok 43 | -FROM GRAPH_TABLE(snb MATCH (a:Person)-[w:workAt_Organisation]->(u:University)) limit 10; 44 | -------------------------------------------------------------------------------- /test/sql/create_pg/except_properties.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/except_properties.test 2 | # description: Testing the except properties 3 | # group: [create_pg] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 15 | 16 | statement ok 17 | CREATE TABLE School(school_name VARCHAR, school_id BIGINT, school_kind BIGINT); 18 | 19 | statement ok 20 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'); 21 | 22 | statement ok 23 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (1,2, 14), (1,3, 15), (2,3, 16); 24 | 25 | # ARE ALL COLUMNS EXCEPT 26 | statement ok 27 | -CREATE PROPERTY GRAPH pg_are_all_except_properties 28 | VERTEX TABLES ( 29 | Student LABEL Person, 30 | School PROPERTIES ARE ALL COLUMNS EXCEPT (school_id) LABEL School IN School_kind (Hogeschool, University) 31 | ) 32 | EDGE TABLES ( 33 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 34 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 35 | LABEL Knows 36 | ) 37 | 38 | # ALL COLUMNS EXCEPT 39 | statement ok 40 | -CREATE PROPERTY GRAPH pg_all_except_properties 41 | VERTEX TABLES ( 42 | Student LABEL Person, 43 | School PROPERTIES ALL COLUMNS EXCEPT (school_id) LABEL School IN School_kind (Hogeschool, University) 44 | ) 45 | EDGE TABLES ( 46 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 47 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 48 | LABEL Knows 49 | ) 50 | -------------------------------------------------------------------------------- /src/core/functions/function_data/weakly_connected_component_function_data.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/function_data/weakly_connected_component_function_data.hpp" 2 | 3 | #include 4 | 5 | namespace duckdb { 6 | 7 | WeaklyConnectedComponentFunctionData::WeaklyConnectedComponentFunctionData(ClientContext &context, int32_t csr_id) 8 | : context(context), csr_id(csr_id) { 9 | state_converged = false; // Initialize state 10 | state_initialized = false; 11 | } 12 | 13 | unique_ptr WeaklyConnectedComponentFunctionData::WeaklyConnectedComponentBind( 14 | ClientContext &context, ScalarFunction &bound_function, vector> &arguments) { 15 | if (!arguments[0]->IsFoldable()) { 16 | throw InvalidInputException("Id must be constant."); 17 | } 18 | 19 | int32_t csr_id = ExpressionExecutor::EvaluateScalar(context, *arguments[0]).GetValue(); 20 | auto duckpgq_state = GetDuckPGQState(context); 21 | duckpgq_state->csr_to_delete.insert(csr_id); 22 | 23 | return make_uniq(context, csr_id); 24 | } 25 | 26 | unique_ptr WeaklyConnectedComponentFunctionData::Copy() const { 27 | auto result = make_uniq(context, csr_id); 28 | return std::move(result); 29 | } 30 | 31 | bool WeaklyConnectedComponentFunctionData::Equals(const FunctionData &other_p) const { 32 | auto &other = other_p.Cast(); 33 | if (csr_id != other.csr_id) { 34 | return false; 35 | } 36 | if (state_converged != other.state_converged) { 37 | return false; 38 | } 39 | return true; 40 | } 41 | 42 | } // namespace duckdb 43 | -------------------------------------------------------------------------------- /test/sql/non_existing_table.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/non_existing_table.test 2 | # description: Testing creating a property graph on non-existing tables 3 | # group: [sql] 4 | 5 | # https://github.com/cwida/duckpgq-extension/issues/95 6 | 7 | require duckpgq 8 | 9 | # https://github.com/cwida/duckpgq-extension/issues/96 10 | statement error 11 | select * from table_that_does_not_exist; 12 | ---- 13 | Catalog Error: Table with name table_that_does_not_exist does not exist! 14 | 15 | statement ok 16 | CREATE TABLE test (a INTEGER); 17 | 18 | statement error 19 | SELECT b from test; 20 | ---- 21 | Binder Error: Referenced column "b" not found in FROM clause! 22 | 23 | statement ok 24 | import database 'duckdb/data/SNB0.003'; 25 | 26 | statement ok 27 | -CREATE PROPERTY GRAPH snb 28 | VERTEX TABLES ( 29 | Person, 30 | Organisation IN typemask(company, university) 31 | ) 32 | EDGE TABLES ( 33 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 34 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 35 | LABEL Knows, 36 | person_workAt_Organisation SOURCE KEY (PersonId) REFERENCES Person (id) 37 | DESTINATION KEY (OrganisationId) REFERENCES Organisation (id) 38 | LABEL workAt_Organisation 39 | ); 40 | 41 | statement error 42 | -FROM GRAPH_TABLE (snb 43 | MATCH (a:Kind) 44 | COLUMNS (*) 45 | ); 46 | ---- 47 | Binder Error: The label kind is not registered in property graph snb 48 | 49 | statement error 50 | -FROM GRAPH_TABLE (abc 51 | MATCH (a:Kind) 52 | COLUMNS (*) 53 | ); 54 | ---- 55 | Binder Error: Property graph abc does not exist 56 | -------------------------------------------------------------------------------- /src/core/functions/function_data/cheapest_path_length_function_data.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/function_data/cheapest_path_length_function_data.hpp" 2 | #include "duckpgq/core/utils/duckpgq_utils.hpp" 3 | #include "duckdb/execution/expression_executor.hpp" 4 | 5 | namespace duckdb { 6 | 7 | unique_ptr 8 | CheapestPathLengthFunctionData::CheapestPathLengthBind(ClientContext &context, ScalarFunction &bound_function, 9 | vector> &arguments) { 10 | 11 | if (!arguments[0]->IsFoldable()) { 12 | throw InvalidInputException("Id must be constant."); 13 | } 14 | 15 | auto duckpgq_state = GetDuckPGQState(context); 16 | 17 | int32_t csr_id = ExpressionExecutor::EvaluateScalar(context, *arguments[0]).GetValue(); 18 | CSR *csr = duckpgq_state->GetCSR(csr_id); 19 | duckpgq_state->csr_to_delete.insert(csr_id); 20 | 21 | if (!(csr->initialized_v && csr->initialized_e && csr->initialized_w)) { 22 | throw ConstraintException("Need to initialize CSR before doing cheapest path"); 23 | } 24 | 25 | if (csr->w.empty()) { 26 | bound_function.return_type = LogicalType::DOUBLE; 27 | } else { 28 | bound_function.return_type = LogicalType::BIGINT; 29 | } 30 | 31 | return make_uniq(context, csr_id); 32 | } 33 | 34 | unique_ptr CheapestPathLengthFunctionData::Copy() const { 35 | return make_uniq(context, csr_id); 36 | } 37 | 38 | bool CheapestPathLengthFunctionData::Equals(const FunctionData &other_p) const { 39 | auto &other = other_p.Cast(); 40 | return other.csr_id == csr_id; 41 | } 42 | 43 | } // namespace duckdb 44 | -------------------------------------------------------------------------------- /src/core/functions/table/pagerank.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/table/pagerank.hpp" 2 | #include "duckdb/function/table_function.hpp" 3 | #include "duckdb/parser/tableref/subqueryref.hpp" 4 | 5 | #include 6 | #include 7 | #include "duckdb/parser/tableref/basetableref.hpp" 8 | 9 | namespace duckdb { 10 | 11 | // Main binding function 12 | unique_ptr PageRankFunction::PageRankBindReplace(ClientContext &context, TableFunctionBindInput &input) { 13 | auto pg_name = StringUtil::Lower(StringValue::Get(input.inputs[0])); 14 | auto node_table = StringUtil::Lower(StringValue::Get(input.inputs[1])); 15 | auto edge_table = StringUtil::Lower(StringValue::Get(input.inputs[2])); 16 | 17 | auto duckpgq_state = GetDuckPGQState(context); 18 | auto pg_info = GetPropertyGraphInfo(duckpgq_state, pg_name); 19 | auto edge_pg_entry = ValidateSourceNodeAndEdgeTable(pg_info, node_table, edge_table); 20 | 21 | auto select_node = CreateSelectNode(edge_pg_entry, "pagerank", "pagerank"); 22 | 23 | select_node->cte_map.map["csr_cte"] = CreateDirectedCSRCTE(edge_pg_entry, "src", "edge", "dst"); 24 | 25 | auto subquery = make_uniq(); 26 | subquery->node = std::move(select_node); 27 | 28 | auto result = make_uniq(std::move(subquery)); 29 | result->alias = "pagerank"; 30 | return std::move(result); 31 | } 32 | 33 | //------------------------------------------------------------------------------ 34 | // Register functions 35 | //------------------------------------------------------------------------------ 36 | void CoreTableFunctions::RegisterPageRankTableFunction(ExtensionLoader &loader) { 37 | loader.RegisterFunction(PageRankFunction()); 38 | } 39 | 40 | } // namespace duckdb 41 | -------------------------------------------------------------------------------- /test/sql/create_pg/no_properties.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/no_properties.test 2 | # description: Testing property graphs with no properties 3 | # group: [create_pg] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 15 | 16 | statement ok 17 | CREATE TABLE School(school_name VARCHAR, school_id BIGINT, school_kind BIGINT); 18 | 19 | statement ok 20 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'); 21 | 22 | statement ok 23 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (1,2, 14), (1,3, 15), (2,3, 16); 24 | 25 | 26 | # No properties keyword 27 | statement ok 28 | -CREATE PROPERTY GRAPH pg_no_properties 29 | VERTEX TABLES ( 30 | Student LABEL Person, 31 | School NO PROPERTIES LABEL School IN School_kind (Hogeschool, University) 32 | ) 33 | EDGE TABLES ( 34 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 35 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 36 | LABEL Knows 37 | ) 38 | 39 | statement ok 40 | -CREATE PROPERTY GRAPH g VERTEX TABLES (Student PROPERTIES (id)); 41 | 42 | # Query on unspecified columns in the graph throws error. 43 | statement error 44 | -SELECT * FROM GRAPH_TABLE (g MATCH (s:Student) COLUMNS (s.id, s.name)); 45 | ---- 46 | Binder Error: Property s.name is never registered! 47 | 48 | # Columns to query is only allowed to be or ., which we cannot prefix catalog or schema. 49 | statement error 50 | -SELECT * FROM GRAPH_TABLE (g MATCH (s:Student) COLUMNS (main.s.id)); 51 | ---- 52 | Binder Error: Property main.s.id is never registered! 53 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/table/drop_property_graph.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/functions/tablefunctions/drop_property_graph.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckpgq/common.hpp" 11 | #include "duckdb/function/table_function.hpp" 12 | #include "duckdb/parser/statement/drop_statement.hpp" 13 | 14 | #include 15 | 16 | namespace duckdb { 17 | 18 | class DropPropertyGraphFunction : public TableFunction { 19 | public: 20 | DropPropertyGraphFunction() { 21 | name = "drop_property_graph"; 22 | bind = DropPropertyGraphBind; 23 | init_global = DropPropertyGraphInit; 24 | function = DropPropertyGraphFunc; 25 | } 26 | 27 | struct DropPropertyGraphBindData : TableFunctionData { 28 | explicit DropPropertyGraphBindData(DropPropertyGraphInfo *pg_info) : drop_pg_info(pg_info) { 29 | } 30 | 31 | DropPropertyGraphInfo *drop_pg_info; 32 | }; 33 | 34 | struct DropPropertyGraphGlobalData : GlobalTableFunctionState { 35 | DropPropertyGraphGlobalData() = default; 36 | }; 37 | 38 | static unique_ptr DropPropertyGraphBind(ClientContext &context, TableFunctionBindInput &input, 39 | vector &return_types, vector &names); 40 | 41 | static unique_ptr DropPropertyGraphInit(ClientContext &context, 42 | TableFunctionInitInput &input); 43 | 44 | static void DropPropertyGraphFunc(ClientContext &context, TableFunctionInput &data_p, DataChunk &output); 45 | }; 46 | 47 | } // namespace duckdb 48 | -------------------------------------------------------------------------------- /src/core/functions/scalar/csr_get_w_type.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb/planner/expression/bound_function_expression.hpp" 2 | #include "duckpgq/common.hpp" 3 | 4 | #include 5 | #include 6 | 7 | namespace duckdb { 8 | 9 | enum class CSRWType : int32_t { 10 | // possible weight types of a csr 11 | UNWEIGHTED, //! unweighted 12 | INTWEIGHT, //! integer 13 | DOUBLEWEIGHT, //! double 14 | }; 15 | 16 | static void GetCsrWTypeFunction(DataChunk &args, ExpressionState &state, Vector &result) { 17 | auto &func_expr = state.expr.Cast(); 18 | auto &info = func_expr.bind_info->Cast(); 19 | 20 | auto duckpgq_state = GetDuckPGQState(info.context); 21 | 22 | result.SetVectorType(VectorType::CONSTANT_VECTOR); 23 | auto result_data = ConstantVector::GetData(result); 24 | auto csr = duckpgq_state->GetCSR(info.id); 25 | int32_t flag; 26 | if (!csr->initialized_w) { 27 | flag = static_cast(CSRWType::UNWEIGHTED); 28 | } else if (!csr->w.empty()) { 29 | flag = static_cast(CSRWType::INTWEIGHT); 30 | } else if (!csr->w_double.empty()) { 31 | flag = static_cast(CSRWType::DOUBLEWEIGHT); 32 | } else { 33 | throw InternalException("Corrupted weight vector"); 34 | } 35 | result_data[0] = flag; 36 | } 37 | 38 | //------------------------------------------------------------------------------ 39 | // Register functions 40 | //------------------------------------------------------------------------------ 41 | void CoreScalarFunctions::RegisterGetCSRWTypeScalarFunction(ExtensionLoader &loader) { 42 | loader.RegisterFunction(ScalarFunction("csr_get_w_type", {LogicalType::INTEGER}, LogicalType::INTEGER, 43 | GetCsrWTypeFunction, CSRFunctionData::CSRBind)); 44 | } 45 | 46 | } // namespace duckdb 47 | -------------------------------------------------------------------------------- /test/sql/path_finding/top_k.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/top_k.test 2 | # description: Testing top-k functionality, not implemented yet. 3 | # group: [path_finding] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | CREATE TABLE Student(id BIGINT, name VARCHAR); INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 9 | 10 | statement ok 11 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 12 | 13 | statement ok 14 | -CREATE PROPERTY GRAPH pg 15 | VERTEX TABLES ( 16 | Student LABEL person 17 | ) 18 | EDGE TABLES ( 19 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 20 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 21 | label knows 22 | ); 23 | 24 | statement error 25 | -FROM GRAPH_TABLE (pg 26 | MATCH 27 | p = ANY SHORTEST 5 WALK (a:Person)-[k:knows]-> *(b:Person) 28 | WHERE a.name = 'Daniel' 29 | COLUMNS (p, a.name as name, b.name as school) 30 | ) study; 31 | ---- 32 | Parser Error: syntax error at or near "5" 33 | 34 | 35 | statement error 36 | -FROM GRAPH_TABLE (pg 37 | MATCH 38 | p = SHORTEST 5 (a:Person)-[k:knows]-> *(b:Person) 39 | WHERE a.name = 'Daniel'); 40 | ---- 41 | Not implemented Error: TopK has not been implemented yet. 42 | 43 | statement error 44 | -FROM GRAPH_TABLE (pg 45 | MATCH 46 | p = SHORTEST 5 WALK (a:Person)-[k:knows]-> *(b:Person) 47 | WHERE a.name = 'Daniel'); 48 | ---- 49 | Not implemented Error: TopK has not been implemented yet. 50 | 51 | statement error 52 | -FROM GRAPH_TABLE (pg 53 | MATCH 54 | p = ANY SHORTEST 5 WALK (a:Person)-[k:knows]-> *(b:Person) 55 | WHERE a.name = 'Daniel'); 56 | ---- 57 | Parser Error: syntax error at or near "5" 58 | -------------------------------------------------------------------------------- /scripts/test_header_generation.py: -------------------------------------------------------------------------------- 1 | import os 2 | def generate_headers(base_dir): 3 | # Ensure to include the 'test' directory in the path 4 | test_dir = os.path.join(base_dir, "test") # Adjust this if the base_dir does not already include 'test' 5 | 6 | for root, dirs, files in os.walk(test_dir): 7 | for file in files: 8 | if ".test" not in file: 9 | continue 10 | file_path = os.path.join(root, file) 11 | relative_path = os.path.relpath(file_path, base_dir) # This assumes base_dir is the parent of 'test' 12 | group = "duckpgq" 13 | subpath = os.path.dirname(relative_path) 14 | if subpath: # Check if there is a subdirectory path 15 | group += "_" + subpath.replace('/', '_').replace('test_', '') # Correct the group to exclude 'test_' 16 | 17 | with open(file_path, 'r+') as f: 18 | content = f.read() 19 | 20 | # Create the header content 21 | header = f"# name: {relative_path}\n" \ 22 | f"# description: ""\n" \ 23 | f"# group: [{group}]\n\n" 24 | 25 | # Search and replace old header if exists 26 | if header.split("\n")[0] in content: 27 | continue 28 | if content.startswith("# name:"): 29 | end_of_header = content.find('\n\n') + 2 30 | content = content[end_of_header:] # Remove old header 31 | f.seek(0) 32 | f.write(header + content) # Write new header and original content 33 | f.truncate() # Truncate file in case new content is shorter than old 34 | 35 | 36 | # Usage 37 | base_dir = "/Users/dljtw/git/duckpgq" # Adjust this path to the correct directory which includes the 'test' folder 38 | generate_headers(base_dir) -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/scalar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "duckpgq/common.hpp" 3 | 4 | namespace duckdb { 5 | 6 | struct CoreScalarFunctions { 7 | static void Register(ExtensionLoader &loader) { 8 | RegisterCheapestPathLengthScalarFunction(loader); 9 | RegisterCSRCreationScalarFunctions(loader); 10 | RegisterCSRDeletionScalarFunction(loader); 11 | RegisterGetCSRWTypeScalarFunction(loader); 12 | RegisterIterativeLengthScalarFunction(loader); 13 | RegisterIterativeLength2ScalarFunction(loader); 14 | RegisterIterativeLengthBidirectionalScalarFunction(loader); 15 | RegisterLocalClusteringCoefficientScalarFunction(loader); 16 | RegisterReachabilityScalarFunction(loader); 17 | RegisterShortestPathScalarFunction(loader); 18 | RegisterWeaklyConnectedComponentScalarFunction(loader); 19 | RegisterPageRankScalarFunction(loader); 20 | } 21 | 22 | private: 23 | static void RegisterCheapestPathLengthScalarFunction(ExtensionLoader &loader); 24 | static void RegisterCSRCreationScalarFunctions(ExtensionLoader &loader); 25 | static void RegisterCSRDeletionScalarFunction(ExtensionLoader &loader); 26 | static void RegisterGetCSRWTypeScalarFunction(ExtensionLoader &loader); 27 | static void RegisterIterativeLengthScalarFunction(ExtensionLoader &loader); 28 | static void RegisterIterativeLength2ScalarFunction(ExtensionLoader &loader); 29 | static void RegisterIterativeLengthBidirectionalScalarFunction(ExtensionLoader &loader); 30 | static void RegisterLocalClusteringCoefficientScalarFunction(ExtensionLoader &loader); 31 | static void RegisterReachabilityScalarFunction(ExtensionLoader &loader); 32 | static void RegisterShortestPathScalarFunction(ExtensionLoader &loader); 33 | static void RegisterWeaklyConnectedComponentScalarFunction(ExtensionLoader &loader); 34 | static void RegisterPageRankScalarFunction(ExtensionLoader &loader); 35 | }; 36 | 37 | } // namespace duckdb 38 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/table/pagerank.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/functions/table/pagerank.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #include "duckpgq/common.hpp" 10 | 11 | namespace duckdb { 12 | 13 | class PageRankFunction : public TableFunction { 14 | public: 15 | PageRankFunction() { 16 | name = "pagerank"; 17 | arguments = {LogicalType::VARCHAR, LogicalType::VARCHAR, LogicalType::VARCHAR}; 18 | bind_replace = PageRankBindReplace; 19 | } 20 | 21 | static unique_ptr PageRankBindReplace(ClientContext &context, TableFunctionBindInput &input); 22 | }; 23 | 24 | struct PageRankData : TableFunctionData { 25 | static unique_ptr PageRankBind(ClientContext &context, TableFunctionBindInput &input, 26 | vector &return_types, vector &names) { 27 | auto result = make_uniq(); 28 | result->pg_name = StringValue::Get(input.inputs[0]); 29 | result->node_table = StringValue::Get(input.inputs[1]); 30 | result->edge_table = StringValue::Get(input.inputs[2]); 31 | return_types.emplace_back(LogicalType::BIGINT); 32 | return_types.emplace_back(LogicalType::BIGINT); 33 | names.emplace_back("rowid"); 34 | names.emplace_back("pagerank"); 35 | return std::move(result); 36 | } 37 | 38 | string pg_name; 39 | string node_table; 40 | string edge_table; 41 | }; 42 | 43 | struct PageRankScanState : GlobalTableFunctionState { 44 | static unique_ptr Init(ClientContext &context, TableFunctionInitInput &input) { 45 | auto result = make_uniq(); 46 | return std::move(result); 47 | } 48 | 49 | bool finished = false; 50 | }; 51 | 52 | } // namespace duckdb 53 | -------------------------------------------------------------------------------- /scripts/copy_tests.py: -------------------------------------------------------------------------------- 1 | from os import listdir, mkdir 2 | from os.path import isfile, join, exists 3 | 4 | import shutil 5 | from textwrap import dedent 6 | import sys 7 | import getopt 8 | import os 9 | 10 | 11 | def main(argv): 12 | mode = '' 13 | opts, args = getopt.getopt(argv, "hm:", ["mode=", "ofile="]) 14 | for opt, arg in opts: 15 | if opt == '-h': 16 | print('copy_tests.py -m ') 17 | sys.exit() 18 | elif opt in ("-m", "--mode"): 19 | mode = arg 20 | 21 | if mode != "release" and mode != "debug": 22 | raise Exception("Invalid parameter, --mode should be release or debug") 23 | abspath = os.path.abspath(__file__) 24 | dname = os.path.dirname(abspath) 25 | os.chdir(dname) 26 | test_path_duckpgq = "../test/sql" 27 | test_path_duckdb = "../duckdb/test/extension/duckpgq" 28 | 29 | onlyfiles = [str(f) for f in listdir(test_path_duckpgq) if isfile(join(test_path_duckpgq, f))] 30 | 31 | if not exists(test_path_duckdb): 32 | mkdir(test_path_duckdb) 33 | else: 34 | shutil.rmtree(test_path_duckdb) 35 | mkdir(test_path_duckdb) 36 | 37 | for file in onlyfiles: 38 | f = open(test_path_duckpgq + "/" + file, "r") 39 | content = f.read() 40 | content = content.replace("require duckpgq\n", 41 | dedent("statement ok\n" 42 | "force install '__BUILD_DIRECTORY__/../../../build/"+mode+"/extension/duckpgq/duckpgq.duckdb_extension';\n" 43 | "\n" 44 | "statement ok\n" 45 | "load 'duckpgq';\n")) 46 | 47 | new_file = open(test_path_duckdb + "/" + file, "w") 48 | new_file.write(content) 49 | new_file.close() 50 | 51 | 52 | if __name__ == "__main__": 53 | main(sys.argv[1:]) 54 | -------------------------------------------------------------------------------- /src/core/functions/table/weakly_connected_component.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/table/weakly_connected_component.hpp" 2 | #include "duckdb/function/table_function.hpp" 3 | #include "duckdb/parser/tableref/subqueryref.hpp" 4 | 5 | #include 6 | #include 7 | #include "duckdb/parser/tableref/basetableref.hpp" 8 | 9 | namespace duckdb { 10 | 11 | // Main binding function 12 | unique_ptr 13 | WeaklyConnectedComponentFunction::WeaklyConnectedComponentBindReplace(ClientContext &context, 14 | TableFunctionBindInput &input) { 15 | auto pg_name = StringUtil::Lower(StringValue::Get(input.inputs[0])); 16 | auto node_table = StringUtil::Lower(StringValue::Get(input.inputs[1])); 17 | auto edge_table = StringUtil::Lower(StringValue::Get(input.inputs[2])); 18 | 19 | auto duckpgq_state = GetDuckPGQState(context); 20 | auto pg_info = GetPropertyGraphInfo(duckpgq_state, pg_name); 21 | auto edge_pg_entry = ValidateSourceNodeAndEdgeTable(pg_info, node_table, edge_table); 22 | 23 | auto select_node = CreateSelectNode(edge_pg_entry, "weakly_connected_component", "componentId"); 24 | 25 | select_node->cte_map.map["csr_cte"] = CreateUndirectedCSRCTE(edge_pg_entry, select_node); 26 | 27 | auto subquery = make_uniq(); 28 | subquery->node = std::move(select_node); 29 | 30 | auto result = make_uniq(std::move(subquery)); 31 | result->alias = "wcc"; 32 | return std::move(result); 33 | } 34 | 35 | //------------------------------------------------------------------------------ 36 | // Register functions 37 | //------------------------------------------------------------------------------ 38 | void CoreTableFunctions::RegisterWeaklyConnectedComponentTableFunction(ExtensionLoader &loader) { 39 | loader.RegisterFunction(WeaklyConnectedComponentFunction()); 40 | } 41 | 42 | } // namespace duckdb 43 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/table/describe_property_graph.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/functions/tablefunctions/describe_property_graph.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckpgq/common.hpp" 11 | #include "duckdb/function/table_function.hpp" 12 | #include "duckdb/parser/parsed_data/create_property_graph_info.hpp" 13 | #include "duckdb/catalog/catalog_entry/table_catalog_entry.hpp" 14 | 15 | namespace duckdb { 16 | 17 | class DescribePropertyGraphFunction : public TableFunction { 18 | public: 19 | DescribePropertyGraphFunction() { 20 | name = "describe_property_graph"; 21 | bind = DescribePropertyGraphBind; 22 | init_global = DescribePropertyGraphInit; 23 | function = DescribePropertyGraphFunc; 24 | } 25 | 26 | struct DescribePropertyGraphBindData : public TableFunctionData { 27 | explicit DescribePropertyGraphBindData(CreatePropertyGraphInfo *pg_info) : describe_pg_info(pg_info) { 28 | } 29 | CreatePropertyGraphInfo *describe_pg_info; 30 | }; 31 | 32 | struct DescribePropertyGraphGlobalData : public GlobalTableFunctionState { 33 | DescribePropertyGraphGlobalData() = default; 34 | bool done = false; 35 | }; 36 | 37 | static unique_ptr DescribePropertyGraphBind(ClientContext &context, TableFunctionBindInput &input, 38 | vector &return_types, vector &names); 39 | 40 | static unique_ptr DescribePropertyGraphInit(ClientContext &context, 41 | TableFunctionInitInput &input); 42 | 43 | static void DescribePropertyGraphFunc(ClientContext &context, TableFunctionInput &data_p, DataChunk &output); 44 | }; 45 | 46 | } // namespace duckdb 47 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/parser/duckpgq_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "duckpgq/common.hpp" 3 | 4 | #include 5 | 6 | namespace duckdb { 7 | 8 | struct CorePGQParser { 9 | static void Register(ExtensionLoader &loader) { 10 | RegisterPGQParserExtension(loader); 11 | } 12 | 13 | private: 14 | static void RegisterPGQParserExtension(ExtensionLoader &loader); 15 | }; 16 | 17 | struct DuckPGQParserExtensionInfo : ParserExtensionInfo { 18 | DuckPGQParserExtensionInfo() : ParserExtensionInfo() {}; 19 | ~DuckPGQParserExtensionInfo() override = default; 20 | }; 21 | 22 | ParserExtensionParseResult duckpgq_parse(ParserExtensionInfo *info, const std::string &query); 23 | 24 | ParserExtensionPlanResult duckpgq_plan(ParserExtensionInfo *info, ClientContext &, 25 | unique_ptr); 26 | 27 | ParserExtensionPlanResult duckpgq_find_select_statement(SQLStatement *statement, DuckPGQState &duckpgq_state); 28 | 29 | ParserExtensionPlanResult duckpgq_handle_statement(SQLStatement *statement, DuckPGQState &duckpgq_state); 30 | 31 | void duckpgq_find_match_function(TableRef *table_ref, DuckPGQState &duckpgq_state); 32 | 33 | struct DuckPGQParserExtension : ParserExtension { 34 | DuckPGQParserExtension() : ParserExtension() { 35 | parse_function = duckpgq_parse; 36 | plan_function = duckpgq_plan; 37 | parser_info = make_shared_ptr(); 38 | } 39 | }; 40 | 41 | struct DuckPGQParseData : ParserExtensionParseData { 42 | unique_ptr statement; 43 | 44 | unique_ptr Copy() const override { 45 | return make_uniq_base(statement->Copy()); 46 | } 47 | 48 | string ToString() const override { 49 | return statement->ToString(); 50 | }; 51 | 52 | explicit DuckPGQParseData(unique_ptr statement) : statement(std::move(statement)) { 53 | } 54 | }; 55 | 56 | } // namespace duckdb 57 | -------------------------------------------------------------------------------- /test/sql/create_pg/create_if_not_exists.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/create_if_not_exists.test 2 | # group: [create_pg] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | CREATE TABLE Student(id BIGINT, name VARCHAR); 8 | 9 | statement ok 10 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 11 | 12 | statement ok 13 | CREATE TABLE School(school_name VARCHAR, school_id BIGINT, school_kind BIGINT); 14 | 15 | statement ok 16 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'); 17 | 18 | statement ok 19 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (1,2, 14), (1,3, 15), (2,3, 16); 20 | 21 | statement ok 22 | -CREATE PROPERTY GRAPH IF NOT EXISTS pg_all_properties 23 | VERTEX TABLES ( 24 | Student, 25 | School LABEL School IN School_kind (Hogeschool, University) 26 | ) 27 | EDGE TABLES ( 28 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 29 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 30 | LABEL Knows 31 | ) 32 | 33 | query I 34 | select count(*) from __duckpgq_internal where is_vertex_table; 35 | ---- 36 | 2 37 | 38 | statement ok 39 | -CREATE PROPERTY GRAPH IF NOT EXISTS pg_all_properties 40 | VERTEX TABLES ( 41 | Student 42 | ) 43 | EDGE TABLES ( 44 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 45 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 46 | LABEL Knows 47 | ) 48 | 49 | query I 50 | select count(*) from __duckpgq_internal where is_vertex_table; 51 | ---- 52 | 2 53 | 54 | statement ok 55 | -CREATE PROPERTY GRAPH IF NOT EXISTS snb 56 | VERTEX TABLES ( 57 | Student 58 | ) 59 | EDGE TABLES ( 60 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 61 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 62 | LABEL Knows 63 | ) 64 | 65 | query I 66 | select distinct property_graph from __duckpgq_internal order by property_graph desc; 67 | ---- 68 | snb 69 | pg_all_properties 70 | -------------------------------------------------------------------------------- /test/sql/altering_table.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/altering_table.test 2 | # description: Testing altering a table after creating a property graph over it 3 | # group: [sql] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 15 | 16 | statement ok 17 | CREATE TABLE School(name VARCHAR, Id BIGINT, Kind VARCHAR); 18 | 19 | statement ok 20 | CREATE TABLE StudyAt(personId BIGINT, schoolId BIGINT); 21 | 22 | statement ok 23 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 24 | 25 | statement ok 26 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 27 | 28 | statement ok 29 | INSERT INTO School VALUES ('VU', 0, 'University'), ('UVA', 1, 'University'); 30 | 31 | statement ok 32 | INSERT INTO StudyAt VALUES (0, 0), (1, 0), (2, 1), (3, 1), (4, 1); 33 | 34 | statement ok 35 | -CREATE PROPERTY GRAPH pg 36 | VERTEX TABLES ( 37 | Student PROPERTIES ( id, name ) LABEL Person, 38 | School LABEL SCHOOL 39 | ) 40 | EDGE TABLES ( 41 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 42 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 43 | LABEL Knows, 44 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 45 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 46 | LABEL StudyAt 47 | ); 48 | 49 | statement ok 50 | -FROM GRAPH_TABLE (pg 51 | MATCH 52 | (a:Person)-[s:StudyAt]->(b:School) 53 | WHERE a.name = 'Daniel' 54 | COLUMNS (a.id) 55 | ) study; 56 | 57 | statement ok 58 | ALTER TABLE student RENAME id TO jd; 59 | 60 | statement error 61 | -FROM GRAPH_TABLE (pg 62 | MATCH 63 | (a:Person)-[s:StudyAt]->(b:School) 64 | WHERE a.name = 'Daniel' 65 | COLUMNS (a.jd) 66 | ) study; 67 | ---- 68 | -------------------------------------------------------------------------------- /docs/UPDATING.md: -------------------------------------------------------------------------------- 1 | # Extension updating 2 | When cloning this template, the target version of DuckDB should be the latest stable release of DuckDB. However, there 3 | will inevitably come a time when a new DuckDB is released and the extension repository needs updating. This process goes 4 | as follows: 5 | 6 | - Bump submodules 7 | - `./duckdb` should be set to latest tagged release 8 | - `./extension-ci-tools` should be set to updated branch corresponding to latest DuckDB release. So if you're building for DuckDB `v1.1.0` there will be a branch in `extension-ci-tools` named `v1.1.0` to which you should check out. 9 | - Bump versions in `./github/workflows` 10 | - `duckdb_version` input in `duckdb-stable-build` job in `MainDistributionPipeline.yml` should be set to latest tagged release 11 | - `duckdb_version` input in `duckdb-stable-deploy` job in `MainDistributionPipeline.yml` should be set to latest tagged release 12 | - the reusable workflow `duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml` for the `duckdb-stable-build` job should be set to latest tagged release 13 | 14 | # API changes 15 | DuckDB extensions built with this extension template are built against the internal C++ API of DuckDB. This API is not guaranteed to be stable. 16 | What this means for extension development is that when updating your extensions DuckDB target version using the above steps, you may run into the fact that your extension no longer builds properly. 17 | 18 | Currently, DuckDB does not (yet) provide a specific change log for these API changes, but it is generally not too hard to figure out what has changed. 19 | 20 | For figuring out how and why the C++ API changed, we recommend using the following resources: 21 | - DuckDB's [Release Notes](https://github.com/duckdb/duckdb/releases) 22 | - DuckDB's history of [Core extension patches](https://github.com/duckdb/duckdb/commits/main/.github/patches/extensions) 23 | - The git history of the relevant C++ Header file of the API that has changed -------------------------------------------------------------------------------- /scripts/s3_availability.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | 3 | s3_client = boto3.client('s3') 4 | bucket_name = 'duckpgq' 5 | prefix = 'v' 6 | 7 | def list_extensions(bucket, prefix): 8 | response = s3_client.list_objects_v2(Bucket=bucket, Prefix=prefix) 9 | extensions = {} 10 | 11 | for obj in response.get('Contents', []): 12 | path_parts = obj['Key'].split('/') 13 | if len(path_parts) == 3: 14 | version = path_parts[0] 15 | os_arch = path_parts[1] 16 | parts = os_arch.split('_') 17 | if len(parts) > 2: 18 | os = parts[0] 19 | arch = '_'.join(parts[1:]) 20 | else: 21 | os, arch = parts 22 | url = f'https://{bucket}.s3.eu-north-1.amazonaws.com/{obj["Key"]}' 23 | 24 | if version not in extensions: 25 | extensions[version] = {} 26 | if os not in extensions[version]: 27 | extensions[version][os] = [] 28 | extensions[version][os].append((arch, url)) 29 | 30 | return extensions 31 | 32 | def generate_markdown_table(extensions): 33 | table = '## DuckPGQ Extension Availability\n\n' 34 | 35 | for version in sorted(extensions.keys(), reverse=True): 36 | os_dict = extensions[version] 37 | table += f'
\nVersion {version}\n\n' 38 | for os, builds in os_dict.items(): 39 | table += f'### {os.capitalize()}\n\n' 40 | table += '| Architecture | Download Link |\n' 41 | table += '|--------------|---------------|\n' 42 | for arch, url in builds: 43 | table += f'| {arch} | [{os}_{arch}](<{url}>) |\n' 44 | table += '\n' 45 | table += '
\n\n' 46 | 47 | return table 48 | 49 | extensions = list_extensions(bucket_name, prefix) 50 | markdown_table = generate_markdown_table(extensions) 51 | 52 | with open('extension_availability.md', 'w') as readme_file: 53 | readme_file.write(markdown_table) 54 | -------------------------------------------------------------------------------- /test/sql/pgq_keywords.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/pgq_keywords.test 2 | # description: Testing PGQ reserved keywords in other queries 3 | # group: [sql] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | select 1 as path; 12 | 13 | statement ok 14 | select 1 as group; 15 | 16 | statement ok 17 | SELECT database_oid AS seq, database_name AS name, path AS file FROM duckdb_databases() WHERE NOT internal ORDER BY 1 18 | 19 | statement ok 20 | CREATE TABLE Student(id BIGINT, name VARCHAR); 21 | 22 | statement ok 23 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 24 | 25 | statement ok 26 | CREATE TABLE School(name VARCHAR, Id BIGINT, Kind VARCHAR); 27 | 28 | statement ok 29 | CREATE TABLE StudyAt(personId BIGINT, schoolId BIGINT); 30 | 31 | statement ok 32 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 33 | 34 | statement ok 35 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 36 | 37 | statement ok 38 | INSERT INTO School VALUES ('VU', 0, 'University'), ('UVA', 1, 'University'); 39 | 40 | statement ok 41 | INSERT INTO StudyAt VALUES (0, 0), (1, 0), (2, 1), (3, 1), (4, 1); 42 | 43 | statement ok 44 | -CREATE PROPERTY GRAPH pg 45 | VERTEX TABLES ( 46 | Student PROPERTIES ( id, name ) LABEL Person, 47 | School LABEL SCHOOL 48 | ) 49 | EDGE TABLES ( 50 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 51 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 52 | LABEL Knows, 53 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 54 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 55 | LABEL StudyAt 56 | ); 57 | 58 | query II 59 | -SELECT study.name, study.school 60 | FROM GRAPH_TABLE (pg 61 | MATCH 62 | (a:Person)-[s:StudyAt]->(b:School) 63 | WHERE a.name = 'Daniel' 64 | COLUMNS (a.name as name, b.name as school) 65 | ) study; 66 | ---- 67 | Daniel VU 68 | -------------------------------------------------------------------------------- /test/sql/pragma/create_vertex_table.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/pragma/create_vertex_table.test 2 | # description: Testing the pragma create_vertex_table 3 | # group: [pragma] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT);INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 9 | 10 | statement ok 11 | pragma create_vertex_table(know, src, dst, v, id); 12 | 13 | statement ok 14 | select * from v; 15 | 16 | query I 17 | select count(*) from v; 18 | ---- 19 | 5 20 | 21 | statement error 22 | pragma create_vertex_table(know, src, dst, group, id); 23 | ---- 24 | Parser Error: syntax error at or near "group" 25 | 26 | statement error 27 | pragma create_vertex_table(know, nonexistingcolumn, dst, v2, id); 28 | ---- 29 | Binder Error: Referenced column "nonexistingcolumn" not found in FROM clause! 30 | 31 | statement error 32 | pragma create_vertex_table(know, src, nonexistingcolumn, v3, id); 33 | ---- 34 | Binder Error: Referenced column "nonexistingcolumn" not found in FROM clause! 35 | 36 | statement error 37 | pragma create_vertex_table(nonexistingtable, src, dst, v3, id); 38 | ---- 39 | Catalog Error: Table with name nonexistingtable does not exist! 40 | 41 | statement error 42 | pragma create_vertex_table(know, src, dst, v, id); 43 | ---- 44 | Catalog Error: Table with name "v" already exists! 45 | 46 | statement error 47 | pragma create_vertex_table(know, src, dst, v); 48 | ---- 49 | Binder Error: No function matches the given name and argument types 'create_vertex_table(VARCHAR, VARCHAR, VARCHAR, VARCHAR)'. You might need to add explicit type casts. 50 | 51 | statement ok 52 | create table person_knows_person(creationDate, Person1id, Person2id) as from 'duckdb/data/SNB0.003/person_knows_person.csv'; 53 | 54 | query I 55 | select count(*) from person_knows_person; 56 | ---- 57 | 83 58 | 59 | statement ok 60 | pragma create_vertex_table(person_knows_person, person1id, person2id, v4, id); 61 | 62 | query I 63 | select count(*) from v4; 64 | ---- 65 | 39 66 | 67 | -------------------------------------------------------------------------------- /test/sql/scalar/pagerank.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scalar/pagerank.test 2 | # description: Testing the pagerank implementation 3 | # group: [scalar] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | CREATE TABLE Student(id BIGINT, name VARCHAR);INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 9 | 10 | statement ok 11 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT);INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 12 | 13 | statement ok 14 | -CREATE PROPERTY GRAPH pg 15 | VERTEX TABLES ( 16 | Student 17 | ) 18 | EDGE TABLES ( 19 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 20 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 21 | ); 22 | 23 | query II 24 | select id, pagerank from pagerank(pg, student, know); 25 | ---- 26 | 0 0.30722555839452875 27 | 1 0.11534940106637968 28 | 2 0.16437299553018173 29 | 3 0.32814638463154105 30 | 4 0.028301886792456276 31 | 32 | 33 | statement ok 34 | CREATE OR REPLACE TABLE Student ( 35 | id BIGINT 36 | ); 37 | 38 | statement ok 39 | INSERT INTO Student (id) VALUES 40 | (0), 41 | (1), 42 | (2), 43 | (3), 44 | (4); 45 | 46 | statement ok 47 | CREATE OR REPLACE TABLE know ( 48 | src BIGINT, 49 | dst BIGINT, 50 | edge BIGINT 51 | ); 52 | 53 | statement ok 54 | INSERT INTO know (src, dst, edge) VALUES 55 | (2, 1, 4), 56 | (3, 1, 5), 57 | (3, 2, 6), 58 | (1, 2, 4), 59 | (1, 0, 0), 60 | (2, 0, 1), 61 | (3, 0, 2), 62 | (0, 1, 0), 63 | (4, 3, 7), 64 | (0, 3, 3), 65 | (1, 3, 5), 66 | (2, 3, 6), 67 | (3, 4, 7), 68 | (0, 2, 1); 69 | 70 | statement ok 71 | -CREATE OR REPLACE PROPERTY GRAPH pg 72 | VERTEX TABLES ( 73 | Student 74 | ) 75 | EDGE TABLES ( 76 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 77 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 78 | ); 79 | 80 | query II 81 | select id, pagerank from pagerank(pg, student, know); 82 | ---- 83 | 0 0.19672392385442233 84 | 1 0.19672392385442233 85 | 2 0.19672392385442233 86 | 3 0.26797750004549203 87 | 4 0.08524695480585476 -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/table/local_clustering_coefficient.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckpgq/common.hpp" 4 | #include "duckdb/function/table_function.hpp" 5 | 6 | namespace duckdb { 7 | 8 | class LocalClusteringCoefficientFunction : public TableFunction { 9 | public: 10 | LocalClusteringCoefficientFunction() { 11 | name = "local_clustering_coefficient"; 12 | arguments = {LogicalType::VARCHAR, LogicalType::VARCHAR, LogicalType::VARCHAR}; 13 | bind_replace = LocalClusteringCoefficientBindReplace; 14 | } 15 | 16 | static unique_ptr LocalClusteringCoefficientBindReplace(ClientContext &context, 17 | TableFunctionBindInput &input); 18 | }; 19 | 20 | struct LocalClusteringCoefficientData : TableFunctionData { 21 | static unique_ptr LocalClusteringCoefficientBind(ClientContext &context, 22 | TableFunctionBindInput &input, 23 | vector &return_types, 24 | vector &names) { 25 | auto result = make_uniq(); 26 | result->pg_name = StringValue::Get(input.inputs[0]); 27 | result->node_table = StringValue::Get(input.inputs[1]); 28 | result->edge_table = StringValue::Get(input.inputs[2]); 29 | return_types.emplace_back(LogicalType::BIGINT); 30 | return_types.emplace_back(LogicalType::FLOAT); 31 | names.emplace_back("rowid"); 32 | names.emplace_back("local_clustering_coefficient"); 33 | return std::move(result); 34 | } 35 | 36 | string pg_name; 37 | string node_table; 38 | string edge_table; 39 | }; 40 | 41 | struct LocalClusteringCoefficientScanState : GlobalTableFunctionState { 42 | static unique_ptr Init(ClientContext &context, TableFunctionInitInput &input) { 43 | auto result = make_uniq(); 44 | return std::move(result); 45 | } 46 | 47 | bool finished = false; 48 | }; 49 | 50 | } // namespace duckdb 51 | -------------------------------------------------------------------------------- /scripts/set_extension_name.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys, os 4 | from pathlib import Path 5 | 6 | if (len(sys.argv) != 2): 7 | raise Exception('usage: python3 set_extension_name.py ') 8 | 9 | string_to_find = "quack" 10 | string_to_replace = sys.argv[1] 11 | 12 | def replace(file_name, to_find, to_replace): 13 | with open(file_name, 'r', encoding="utf8") as file : 14 | filedata = file.read() 15 | filedata = filedata.replace(to_find, to_replace) 16 | with open(file_name, 'w', encoding="utf8") as file: 17 | file.write(filedata) 18 | 19 | files_to_search = [] 20 | files_to_search.extend(Path('./.github').rglob('./**/*.yml')) 21 | files_to_search.extend(Path('./test').rglob('./**/*.py')) 22 | files_to_search.extend(Path('./test').rglob('./**/*.test')) 23 | files_to_search.extend(Path('./test').rglob('./**/*.js')) 24 | files_to_search.extend(Path('./src').rglob('./**/*.hpp')) 25 | files_to_search.extend(Path('./src').rglob('./**/*.cpp')) 26 | files_to_search.extend(Path('./src').rglob('./**/*.txt')) 27 | files_to_search.extend(Path('./src').rglob('./**/*.md')) 28 | for path in files_to_search: 29 | replace(path, string_to_find, string_to_replace) 30 | replace(path, string_to_find.capitalize(), string_to_replace.capitalize()) 31 | 32 | replace("./CMakeLists.txt", string_to_find, string_to_replace) 33 | replace("./Makefile", string_to_find, string_to_replace) 34 | replace("./Makefile", string_to_find.capitalize(), string_to_replace.capitalize()) 35 | replace("./Makefile", string_to_find.upper(), string_to_replace.upper()) 36 | replace("./README.md", string_to_find, string_to_replace) 37 | 38 | # rename files 39 | os.rename(f'test/python/{string_to_find}_test.py', f'test/python/{string_to_replace}_test.py') 40 | os.rename(f'test/sql/{string_to_find}.test', f'test/sql/{string_to_replace}.test') 41 | os.rename(f'src/{string_to_find}_extension.cpp', f'src/{string_to_replace}_extension.cpp') 42 | os.rename(f'src/include/{string_to_find}_extension.hpp', f'src/include/{string_to_replace}_extension.hpp') 43 | os.rename(f'test/nodejs/{string_to_find}_test.js', f'test/nodejs/{string_to_replace}_test.js') 44 | -------------------------------------------------------------------------------- /test/sql/unnamed_subquery.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/unnamed_subquery.test 2 | # description: Testing unnamed subquery support 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | import database 'duckdb/data/SNB0.003'; 9 | 10 | statement ok 11 | -CREATE PROPERTY GRAPH snb 12 | VERTEX TABLES ( 13 | Person 14 | ) 15 | EDGE TABLES ( 16 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 17 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 18 | ); 19 | 20 | 21 | query II 22 | -FROM GRAPH_TABLE (snb 23 | MATCH (p:Person)-[k:Person_knows_Person]->(p2:Person) 24 | COLUMNS (p.firstname, p2.firstname) 25 | ) 26 | limit 10; 27 | ---- 28 | Hossein Ken 29 | Hossein Alim 30 | Hossein Alexei 31 | Jan Ali 32 | Jan Otto 33 | Jan Bryn 34 | Jan Hans 35 | Miguel Ali 36 | Miguel Celso 37 | Miguel Ali 38 | 39 | query II 40 | -FROM GRAPH_TABLE (snb 41 | MATCH (p:Person)-[k:Person_knows_Person]->(p2:Person) 42 | COLUMNS (p.firstname, p2.firstname) 43 | ) tmp 44 | limit 10; 45 | ---- 46 | Hossein Ken 47 | Hossein Alim 48 | Hossein Alexei 49 | Jan Ali 50 | Jan Otto 51 | Jan Bryn 52 | Jan Hans 53 | Miguel Ali 54 | Miguel Celso 55 | Miguel Ali 56 | 57 | # Be a bit more explicit 58 | query II 59 | -SELECT tmp.p_firstname, tmp.p2_firstname 60 | FROM GRAPH_TABLE (snb 61 | MATCH (p:Person)-[k:Person_knows_Person]->(p2:Person) 62 | COLUMNS (p.firstname as p_firstname, p2.firstname as p2_firstname) 63 | ) tmp 64 | limit 10; 65 | ---- 66 | Hossein Ken 67 | Hossein Alim 68 | Hossein Alexei 69 | Jan Ali 70 | Jan Otto 71 | Jan Bryn 72 | Jan Hans 73 | Miguel Ali 74 | Miguel Celso 75 | Miguel Ali 76 | 77 | 78 | # Be a bit more explicit 79 | query II 80 | -SELECT unnamed_subquery.p_firstname, unnamed_subquery.p2_firstname 81 | FROM GRAPH_TABLE (snb 82 | MATCH (p:Person)-[k:Person_knows_Person]->(p2:Person) 83 | COLUMNS (p.firstname as p_firstname, p2.firstname as p2_firstname) 84 | ) 85 | limit 10; 86 | ---- 87 | Hossein Ken 88 | Hossein Alim 89 | Hossein Alexei 90 | Jan Ali 91 | Jan Otto 92 | Jan Bryn 93 | Jan Hans 94 | Miguel Ali 95 | Miguel Celso 96 | Miguel Ali 97 | -------------------------------------------------------------------------------- /test/sql/csr_segfault.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/csr_segfault.test 2 | # group: [sql] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | create or replace table student(id bigint); insert into student from range(0, 5000); 8 | 9 | statement ok 10 | CREATE or replace TABLE know(src BIGINT, dst BIGINT); insert into know select s.id as src, s2.id as dst from student s positional join student s2; 11 | 12 | statement ok 13 | -CREATE PROPERTY GRAPH pg 14 | VERTEX TABLES ( 15 | Student 16 | ) 17 | EDGE TABLES ( 18 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 19 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 20 | ); 21 | 22 | statement ok 23 | SELECT CREATE_CSR_EDGE( 24 | 0, 25 | (SELECT count(a.id) FROM Student a), 26 | CAST ( 27 | (SELECT sum(CREATE_CSR_VERTEX( 28 | 0, 29 | (SELECT count(a.id) FROM Student a), 30 | sub.dense_id, 31 | sub.cnt) 32 | ) 33 | FROM ( 34 | SELECT a.rowid as dense_id, count(k.src) as cnt 35 | FROM Student a 36 | LEFT JOIN Know k ON k.src = a.id 37 | GROUP BY a.rowid) sub 38 | ) 39 | AS BIGINT), 40 | (select count() FROM Know k JOIN student a on a.id = k.src JOIN student c on c.id = k.dst), 41 | a.rowid, 42 | c.rowid, 43 | k.rowid) as temp 44 | FROM Know k 45 | JOIN student a on a.id = k.src 46 | JOIN student c on c.id = k.dst; 47 | 48 | query I 49 | SELECT count(csrv) FROM get_csr_v(0); 50 | ---- 51 | 5002 52 | 53 | query I 54 | SELECT count(csre) FROM get_csr_e(0); 55 | ---- 56 | 5000 57 | 58 | statement ok 59 | COPY (SELECT csrv FROM get_csr_v(0)) TO '__TEST_DIR__/v.csv'; 60 | 61 | statement ok 62 | COPY (SELECT csre FROM get_csr_e(0)) TO '__TEST_DIR__/e.csv'; 63 | 64 | query I 65 | SELECT count(*) FROM read_csv('__TEST_DIR__/v.csv'); 66 | ---- 67 | 5002 68 | 69 | query I 70 | SELECT count(*) FROM read_csv('__TEST_DIR__/e.csv'); 71 | ---- 72 | 5000 -------------------------------------------------------------------------------- /test/sql/pattern_matching/undirected_edges.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/pattern_matching/undirected_edges.test 2 | # description: Testing the undirected edge path pattern matching 3 | # group: [pattern_matching] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR);INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT);INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17), (4, 0, 18); 15 | 16 | statement ok 17 | -CREATE PROPERTY GRAPH pg 18 | VERTEX TABLES (Student) 19 | EDGE TABLES ( 20 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 21 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 22 | ); 23 | 24 | query II 25 | SELECT a.name AS person, b.name AS friend 26 | FROM ((SELECT know.src, know.dst FROM know) UNION ALL (SELECT know.dst, know.src FROM know)) AS k , Student AS b , Student AS a 27 | WHERE ((a.id = k.src) AND (b.id = k.dst) AND (a.name = 'Daniel')) 28 | ORDER BY person, friend; 29 | ---- 30 | Daniel David 31 | Daniel Gabor 32 | Daniel Peter 33 | Daniel Peter 34 | Daniel Tavneet 35 | 36 | # Daniel has 3 outgoing edges and 2 incoming edges, so there should be 5 tuples 37 | query II 38 | -SELECT person, friend 39 | FROM GRAPH_TABLE (pg 40 | MATCH 41 | (a:Student)-[k:know]-(b:Student) 42 | WHERE a.name = 'Daniel' 43 | COLUMNS (a.name as person, b.name as friend) 44 | ) 45 | ORDER BY person, friend; 46 | ---- 47 | Daniel David 48 | Daniel Gabor 49 | Daniel Peter 50 | Daniel Peter 51 | Daniel Tavneet 52 | 53 | # Daniel has 3 outgoing edges and 2 incoming edges, so there should be 5 tuples 54 | query III 55 | -FROM GRAPH_TABLE (pg 56 | MATCH 57 | (a:Student)-[k:know]-(b:Student) 58 | WHERE a.name = 'Daniel' 59 | COLUMNS (a.name as person, b.name as friend, k.createDate as date) 60 | ) 61 | ORDER BY person, friend, date; 62 | ---- 63 | Daniel David 18 64 | Daniel Gabor 11 65 | Daniel Peter 12 66 | Daniel Peter 13 67 | Daniel Tavneet 10 68 | 69 | -------------------------------------------------------------------------------- /test/sql/get_csr_ptr.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/get_csr_ptr.test 2 | # description: Test getting the CSR pointer 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | CREATE TABLE Student(id BIGINT, name VARCHAR); 9 | 10 | statement ok 11 | CREATE TABLE know(src BIGINT, dst BIGINT, id BIGINT); 12 | 13 | statement ok 14 | CREATE TABLE School(school_name VARCHAR, school_id BIGINT, school_kind BIGINT); 15 | 16 | statement ok 17 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 18 | 19 | statement ok 20 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17), (2, 4, 18); 21 | 22 | statement ok 23 | -CREATE PROPERTY GRAPH pg 24 | VERTEX TABLES ( 25 | Student PROPERTIES ( id, name ) LABEL Person 26 | ) 27 | EDGE TABLES ( 28 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 29 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 30 | PROPERTIES ( id ) LABEL Knows 31 | ); 32 | 33 | statement ok 34 | SELECT CREATE_CSR_EDGE( 35 | 0, 36 | (SELECT count(a.id) FROM Student a), 37 | CAST ( 38 | (SELECT sum(CREATE_CSR_VERTEX( 39 | 0, 40 | (SELECT count(a.id) FROM Student a), 41 | sub.dense_id, 42 | sub.cnt) 43 | ) 44 | FROM ( 45 | SELECT a.rowid as dense_id, count(k.src) as cnt 46 | FROM Student a 47 | LEFT JOIN Know k ON k.src = a.id 48 | GROUP BY a.rowid) sub 49 | ) 50 | AS BIGINT), 51 | (select count() FROM Know k JOIN student a on a.id = k.src JOIN student c on c.id = k.dst), 52 | a.rowid, 53 | c.rowid, 54 | k.rowid) as temp 55 | FROM Know k 56 | JOIN student a on a.id = k.src 57 | JOIN student c on c.id = k.dst; 58 | 59 | statement ok 60 | SELECT * FROM get_csr_ptr(0); 61 | 62 | statement error 63 | SELECT * FROM get_csr_ptr(10); 64 | ---- 65 | Constraint Error: CSR not found with ID 10 66 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/table/weakly_connected_component.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/functions/table/weakly_connected_component.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #include "duckpgq/common.hpp" 10 | 11 | namespace duckdb { 12 | 13 | class WeaklyConnectedComponentFunction : public TableFunction { 14 | public: 15 | WeaklyConnectedComponentFunction() { 16 | name = "weakly_connected_component"; 17 | arguments = {LogicalType::VARCHAR, LogicalType::VARCHAR, LogicalType::VARCHAR}; 18 | bind_replace = WeaklyConnectedComponentBindReplace; 19 | } 20 | 21 | static unique_ptr WeaklyConnectedComponentBindReplace(ClientContext &context, 22 | TableFunctionBindInput &input); 23 | }; 24 | 25 | struct WeaklyConnectedComponentData : TableFunctionData { 26 | static unique_ptr WeaklyConnectedComponentBind(ClientContext &context, TableFunctionBindInput &input, 27 | vector &return_types, 28 | vector &names) { 29 | auto result = make_uniq(); 30 | result->pg_name = StringValue::Get(input.inputs[0]); 31 | result->node_table = StringValue::Get(input.inputs[1]); 32 | result->edge_table = StringValue::Get(input.inputs[2]); 33 | return_types.emplace_back(LogicalType::BIGINT); 34 | return_types.emplace_back(LogicalType::BIGINT); 35 | names.emplace_back("rowid"); 36 | names.emplace_back("componentId"); 37 | return std::move(result); 38 | } 39 | 40 | string pg_name; 41 | string node_table; 42 | string edge_table; 43 | }; 44 | 45 | struct WeaklyConnectedComponentScanState : GlobalTableFunctionState { 46 | static unique_ptr Init(ClientContext &context, TableFunctionInitInput &input) { 47 | auto result = make_uniq(); 48 | return std::move(result); 49 | } 50 | 51 | bool finished = false; 52 | }; 53 | 54 | } // namespace duckdb 55 | -------------------------------------------------------------------------------- /src/core/pragma/create_vertex_table.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb/function/pragma_function.hpp" 2 | #include 3 | 4 | namespace duckdb { 5 | 6 | static string PragmaCreateVertexTable(ClientContext &context, const FunctionParameters ¶meters) { 7 | if (parameters.values.size() != 5) { 8 | throw InvalidInputException("PRAGMA create_vertex_table requires exactly five parameters: edge_table, " 9 | "source_column, destination_column, vertex_table_name, id_column_name"); 10 | } 11 | 12 | string edge_table = parameters.values[0].GetValue(); 13 | string source_column = parameters.values[1].GetValue(); 14 | string destination_column = parameters.values[2].GetValue(); 15 | string vertex_table_name = parameters.values[3].GetValue(); 16 | string id_column_name = parameters.values[4].GetValue(); 17 | 18 | auto result_query = "CREATE TABLE " + vertex_table_name + " AS " + "SELECT DISTINCT " + id_column_name + " FROM " + 19 | "(SELECT " + source_column + " AS " + id_column_name + " FROM " + edge_table + " UNION ALL " + 20 | "SELECT " + destination_column + " AS " + id_column_name + " FROM " + edge_table + ")"; 21 | return result_query; 22 | } 23 | 24 | void CorePGQPragma::RegisterCreateVertexTable(ExtensionLoader &loader) { 25 | // Define the pragma function 26 | auto pragma_func = PragmaFunction::PragmaCall("create_vertex_table", // Name of the pragma 27 | PragmaCreateVertexTable, // Query substitution function 28 | { 29 | LogicalType::VARCHAR, // Edge table 30 | LogicalType::VARCHAR, // Source column 31 | LogicalType::VARCHAR, // Destination column 32 | LogicalType::VARCHAR, // Vertex table name 33 | LogicalType::VARCHAR // ID column name 34 | }); 35 | 36 | // Register the pragma function 37 | loader.RegisterFunction(pragma_func); 38 | } 39 | 40 | } // namespace duckdb 41 | -------------------------------------------------------------------------------- /src/core/functions/table/local_clustering_coefficient.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/table/local_clustering_coefficient.hpp" 2 | #include "duckdb/function/table_function.hpp" 3 | #include "duckpgq_extension.hpp" 4 | #include "duckpgq/core/utils/duckpgq_utils.hpp" 5 | 6 | #include "duckdb/parser/expression/constant_expression.hpp" 7 | #include "duckdb/parser/expression/function_expression.hpp" 8 | #include "duckpgq/core/utils/compressed_sparse_row.hpp" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace duckdb { 15 | 16 | // Main binding function 17 | unique_ptr 18 | LocalClusteringCoefficientFunction::LocalClusteringCoefficientBindReplace(ClientContext &context, 19 | TableFunctionBindInput &input) { 20 | auto pg_name = StringUtil::Lower(StringValue::Get(input.inputs[0])); 21 | auto node_label = StringUtil::Lower(StringValue::Get(input.inputs[1])); 22 | auto edge_label = StringUtil::Lower(StringValue::Get(input.inputs[2])); 23 | 24 | auto duckpgq_state = GetDuckPGQState(context); 25 | auto pg_info = GetPropertyGraphInfo(duckpgq_state, pg_name); 26 | auto edge_pg_entry = ValidateSourceNodeAndEdgeTable(pg_info, node_label, edge_label); 27 | 28 | auto select_node = CreateSelectNode(edge_pg_entry, "local_clustering_coefficient", "local_clustering_coefficient"); 29 | 30 | select_node->cte_map.map["csr_cte"] = CreateUndirectedCSRCTE(edge_pg_entry, select_node); 31 | 32 | auto subquery = make_uniq(); 33 | subquery->node = std::move(select_node); 34 | 35 | auto result = make_uniq(std::move(subquery)); 36 | result->alias = "lcc"; 37 | // input.ref.alias = "lcc"; 38 | return std::move(result); 39 | } 40 | //------------------------------------------------------------------------------ 41 | // Register functions 42 | //------------------------------------------------------------------------------ 43 | void CoreTableFunctions::RegisterLocalClusteringCoefficientTableFunction(ExtensionLoader &loader) { 44 | loader.RegisterFunction(LocalClusteringCoefficientFunction()); 45 | } 46 | 47 | } // namespace duckdb 48 | -------------------------------------------------------------------------------- /test/sql/source_keyword.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/source_keyword.test 2 | # description: Testing the SOURCE keyword 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | #statement ok 8 | #select 1 source; 9 | 10 | statement ok 11 | FROM duckdb_constraints() 12 | 13 | statement ok 14 | SELECT 15 | *, 16 | regexp_extract(constraint_text, 'FOREIGN KEY \\(([a-zA-Z_0-9]+)\\) REFERENCES ([a-zA-Z_0-9]+)\\(([a-zA-Z_0-9]+)\\)', ['source', 'target', 'target_column']) AS name_extract 17 | FROM duckdb_constraints() 18 | WHERE constraint_type = 'FOREIGN KEY' 19 | 20 | statement ok 21 | SELECT 22 | *, 23 | name_extract['source'] AS source, 24 | name_extract['target'] AS target, 25 | name_extract['target_column'] AS target_column 26 | FROM ( 27 | SELECT 28 | *, 29 | regexp_extract(constraint_text, 'FOREIGN KEY \\(([a-zA-Z_0-9]+)\\) REFERENCES ([a-zA-Z_0-9]+)\\(([a-zA-Z_0-9]+)\\)', ['source', 'target', 'target_column']) AS name_extract 30 | FROM duckdb_constraints() 31 | WHERE constraint_type = 'FOREIGN KEY' 32 | ); 33 | 34 | 35 | statement ok 36 | SELECT 37 | f.database_name AS constraint_catalog, 38 | f.schema_name AS constraint_schema, 39 | CONCAT(f.source, '_', f.target, '_', f.target_column, '_fkey') AS constraint_name, 40 | current_database() AS unique_constraint_catalog, 41 | c.schema_name AS unique_constraint_schema, 42 | CONCAT(c.table_name, '_', f.target_column, '_', 43 | CASE WHEN c.constraint_type = 'UNIQUE' THEN 'key' ELSE 'pkey' END) AS unique_constraint_name, 44 | 'NONE' AS match_option, 45 | 'NO ACTION' AS update_rule, 46 | 'NO ACTION' AS delete_rule 47 | FROM duckdb_constraints() c 48 | JOIN ( 49 | SELECT 50 | *, 51 | name_extract['source'] AS source, 52 | name_extract['target'] AS target, 53 | name_extract['target_column'] AS target_column 54 | FROM ( 55 | SELECT 56 | *, 57 | regexp_extract(constraint_text, 'FOREIGN KEY \\(([a-zA-Z_0-9]+)\\) REFERENCES ([a-zA-Z_0-9]+)\\(([a-zA-Z_0-9]+)\\)', ['source', 'target', 'target_column']) AS name_extract 58 | FROM duckdb_constraints() 59 | WHERE constraint_type = 'FOREIGN KEY' 60 | ) 61 | ) f ON name_extract['target'] = c.table_name 62 | AND (c.constraint_type = 'UNIQUE' OR c.constraint_type = 'PRIMARY KEY'); 63 | 64 | 65 | statement ok 66 | FROM information_schema.tables; 67 | -------------------------------------------------------------------------------- /test/sql/optional_columns.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/optional_columns.test 2 | # description: Testing the optional columns syntax improvement 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | import database 'duckdb/data/SNB0.003' 9 | 10 | statement ok 11 | -CREATE PROPERTY GRAPH snb 12 | VERTEX TABLES ( 13 | Person LABEL Person, 14 | Organisation LABEL Organisation IN typemask(company, university) 15 | ) 16 | EDGE TABLES ( 17 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 18 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 19 | LABEL Knows, 20 | person_workAt_Organisation SOURCE KEY (PersonId) REFERENCES Person (id) 21 | DESTINATION KEY (OrganisationId) REFERENCES Organisation (id) 22 | LABEL workAt_Organisation 23 | ); 24 | 25 | query IIIIIIIIIII 26 | -FROM GRAPH_TABLE (snb MATCH (p:Person)) limit 1; 27 | ---- 28 | 1166 1984-03-11 Firefox 2010-01-03 23:10:31.499+00 Hossein14@hotmail.com Hossein male 14 Forouhar 77.245.239.11 fa;ku;en 29 | 30 | query I 31 | -FROM GRAPH_TABLE (snb MATCH (p:Person) COLUMNS (p.id)) limit 10; 32 | ---- 33 | 14 34 | 16 35 | 32 36 | 2199023255557 37 | 2199023255573 38 | 2199023255594 39 | 4398046511139 40 | 6597069766702 41 | 8796093022234 42 | 8796093022237 43 | 44 | query I 45 | -SELECT p_id FROM GRAPH_TABLE (snb MATCH (p:Person) COLUMNS (p.id as p_id,)) limit 10; 46 | ---- 47 | 14 48 | 16 49 | 32 50 | 2199023255557 51 | 2199023255573 52 | 2199023255594 53 | 4398046511139 54 | 6597069766702 55 | 8796093022234 56 | 8796093022237 57 | 58 | query I 59 | -FROM GRAPH_TABLE (snb MATCH (p:Person) COLUMNS (p.id as p_id)) limit 10; 60 | ---- 61 | 14 62 | 16 63 | 32 64 | 2199023255557 65 | 2199023255573 66 | 2199023255594 67 | 4398046511139 68 | 6597069766702 69 | 8796093022234 70 | 8796093022237 71 | 72 | query II 73 | -FROM GRAPH_TABLE (snb MATCH (p:Person) COLUMNS (p.id, p.firstname as first_name)) limit 10; 74 | ---- 75 | 14 Hossein 76 | 16 Jan 77 | 32 Miguel 78 | 2199023255557 Eric 79 | 2199023255573 Arbaaz 80 | 2199023255594 Ali 81 | 4398046511139 Ayesha 82 | 6597069766702 Alejandro 83 | 8796093022234 Rahul 84 | 8796093022237 Lei 85 | 86 | query I 87 | -SELECT count(*) FROM GRAPH_TABLE (snb MATCH (p:Person)) GROUP BY ALL limit 10; 88 | ---- 89 | 50 90 | -------------------------------------------------------------------------------- /test/sql/with_statement_duckpgq.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/with_statement_duckpgq.test 2 | # description: Testing PGQ query and WITH in single query 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | import database 'duckdb/data/SNB0.003'; 9 | 10 | statement ok 11 | -CREATE PROPERTY GRAPH snb_projected 12 | VERTEX TABLES (Message); 13 | 14 | query IIIIIII 15 | -WITH message_count AS ( 16 | SELECT count(*) as m_count 17 | FROM Message m 18 | WHERE m.creationDate < '2010-05-27 11:16:36.013' 19 | ) 20 | SELECT year, isComment, 21 | CASE WHEN m_length < 40 THEN 0 22 | WHEN m_length < 80 THEN 1 23 | WHEN m_length < 160 THEN 2 24 | ELSE 3 END as lengthCategory, 25 | count(*) as messageCount, 26 | avg(m_length) as averageMessageLength, 27 | sum(m_length) as sumMessageLength, 28 | count(*) / mc.m_count as percentageOfMessages 29 | FROM GRAPH_TABLE(snb_projected 30 | MATCH (message:Message where message.creationDate < '2010-05-27 11:16:36.013') 31 | COLUMNS (date_part('year', message.creationDate::TIMESTAMP) as year, message.ImageFile is NULL as isComment, message.length as m_length, message.id) 32 | ) tmp, message_count mc 33 | GROUP BY year, isComment, lengthCategory, m_count 34 | ORDER BY year DESC, isComment ASC, lengthCategory ASC; 35 | ---- 36 | 2010 false 0 63 0.0 0 0.9692307692307692 37 | 2010 true 2 2 109.0 218 0.03076923076923077 38 | 39 | 40 | query II 41 | -FROM GRAPH_TABLE (snb_projected 42 | MATCH (m:message) 43 | COLUMNS (m.id) 44 | ) tmp, (SELECT id from message limit 1) 45 | LIMIT 10; 46 | ---- 47 | 618475290624 618475290624 48 | 343597383683 618475290624 49 | 343597383684 618475290624 50 | 962072674309 618475290624 51 | 962072674310 618475290624 52 | 962072674311 618475290624 53 | 962072674312 618475290624 54 | 962072674313 618475290624 55 | 962072674314 618475290624 56 | 962072674315 618475290624 57 | 58 | query II 59 | -FROM (SELECT id from message limit 1), GRAPH_TABLE (snb_projected 60 | MATCH (m:message) 61 | COLUMNS (m.id) 62 | ) tmp 63 | LIMIT 10; 64 | ---- 65 | 618475290624 618475290624 66 | 618475290624 343597383683 67 | 618475290624 343597383684 68 | 618475290624 962072674309 69 | 618475290624 962072674310 70 | 618475290624 962072674311 71 | 618475290624 962072674312 72 | 618475290624 962072674313 73 | 618475290624 962072674314 74 | 618475290624 962072674315 75 | -------------------------------------------------------------------------------- /src/core/functions/function_data/pagerank_function_data.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/function_data/pagerank_function_data.hpp" 2 | 3 | #include 4 | 5 | namespace duckdb { 6 | 7 | // Constructor 8 | PageRankFunctionData::PageRankFunctionData(ClientContext &ctx, int32_t csr) 9 | : context(ctx), csr_id(csr), damping_factor(0.85), convergence_threshold(1e-6), iteration_count(0), 10 | state_initialized(false), converged(false) { 11 | } 12 | 13 | unique_ptr PageRankFunctionData::PageRankBind(ClientContext &context, ScalarFunction &bound_function, 14 | vector> &arguments) { 15 | if (!arguments[0]->IsFoldable()) { 16 | throw InvalidInputException("Id must be constant."); 17 | } 18 | 19 | int32_t csr_id = ExpressionExecutor::EvaluateScalar(context, *arguments[0]).GetValue(); 20 | auto duckpgq_state = GetDuckPGQState(context); 21 | duckpgq_state->csr_to_delete.insert(csr_id); 22 | 23 | return make_uniq(context, csr_id); 24 | } 25 | 26 | // Copy method 27 | unique_ptr PageRankFunctionData::Copy() const { 28 | auto result = make_uniq(context, csr_id); 29 | result->rank = rank; // Deep copy of rank vector 30 | result->temp_rank = temp_rank; // Deep copy of temp_rank vector 31 | result->damping_factor = damping_factor; 32 | result->convergence_threshold = convergence_threshold; 33 | result->iteration_count = iteration_count; 34 | result->state_initialized = state_initialized; 35 | result->converged = converged; 36 | // Note: state_lock is not copied as mutexes are not copyable 37 | return std::move(result); 38 | } 39 | 40 | // Equals method 41 | bool PageRankFunctionData::Equals(const FunctionData &other_p) const { 42 | auto &other = other_p.Cast(); 43 | if (csr_id != other.csr_id) { 44 | return false; 45 | } 46 | if (rank != other.rank) { 47 | return false; 48 | } 49 | if (temp_rank != other.temp_rank) { 50 | return false; 51 | } 52 | if (damping_factor != other.damping_factor) { 53 | return false; 54 | } 55 | if (convergence_threshold != other.convergence_threshold) { 56 | return false; 57 | } 58 | if (iteration_count != other.iteration_count) { 59 | return false; 60 | } 61 | if (state_initialized != other.state_initialized) { 62 | return false; 63 | } 64 | if (converged != other.converged) { 65 | return false; 66 | } 67 | return true; 68 | } 69 | 70 | } // namespace duckdb 71 | -------------------------------------------------------------------------------- /test/sql/create_pg/create_pg_multiple_connections.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/create_pg_multiple_connections.test 2 | # description: Testing the creation of property graphs across multiple connections 3 | # group: [create_pg] 4 | 5 | require duckpgq 6 | 7 | statement ok con1 8 | CREATE TABLE Student(id BIGINT, name VARCHAR); 9 | 10 | statement ok con1 11 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 12 | 13 | statement ok con1 14 | CREATE TABLE School(school_name VARCHAR, school_id BIGINT, school_kind BIGINT); 15 | 16 | statement ok con1 17 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'); 18 | 19 | statement ok con1 20 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (1,2, 14), (1,3, 15), (2,3, 16); 21 | 22 | # all properties 23 | statement ok con1 24 | -CREATE PROPERTY GRAPH pg_all_properties 25 | VERTEX TABLES ( 26 | Student, 27 | School LABEL School IN School_kind (Hogeschool, University) 28 | ) 29 | EDGE TABLES ( 30 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 31 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 32 | LABEL Knows 33 | ) 34 | 35 | statement ok con2 36 | select * from local_clustering_coefficient(pg_all_properties, student, knows); 37 | 38 | statement ok con2 39 | -from graph_table (pg_all_properties match (a:student)) 40 | 41 | statement ok con1 42 | -from graph_table (pg_all_properties match (a:student)) 43 | 44 | statement ok con3 45 | -from graph_table (pg_all_properties match (a:student)) 46 | 47 | statement ok con1 48 | -DROP PROPERTY GRAPH pg_all_properties 49 | 50 | statement error con3 51 | -from graph_table (pg_all_properties match (a:student)) 52 | ---- 53 | Binder Error: Property graph pg_all_properties does not exist 54 | 55 | statement error con4 56 | -from graph_table (pg_all_properties match (a:student)) 57 | ---- 58 | Binder Error: Property graph pg_all_properties does not exist 59 | 60 | statement error con2 61 | -from graph_table (pg_all_properties match (a:student)) 62 | ---- 63 | Binder Error: Property graph pg_all_properties does not exist 64 | 65 | statement ok con1 66 | -CREATE PROPERTY GRAPH pg_all_properties 67 | VERTEX TABLES ( 68 | Student, 69 | School LABEL School IN School_kind (Hogeschool, University) 70 | ) 71 | EDGE TABLES ( 72 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 73 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 74 | LABEL Knows 75 | ) 76 | 77 | # connection 2 already exists, but pg has been dropped and recreated 78 | statement ok con2 79 | -from graph_table (pg_all_properties match (a:student)) -------------------------------------------------------------------------------- /test/sql/pragma/show_property_graphs.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/pragma/show_property_graphs.test 2 | # group: [pragma] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | import database 'duckdb/data/SNB0.003'; 8 | 9 | statement ok 10 | -CREATE PROPERTY GRAPH snb 11 | VERTEX TABLES ( 12 | Person LABEL Person, 13 | Forum LABEL Forum, 14 | Organisation LABEL Organisation IN typemask(company, university), 15 | Place LABEL Place, 16 | Tag LABEL Tag, 17 | TagClass LABEL TagClass, 18 | Country LABEL Country, 19 | City LABEL City, 20 | Message LABEL Message 21 | ) 22 | EDGE TABLES ( 23 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 24 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 25 | LABEL Knows, 26 | Forum_hasMember_Person SOURCE KEY (ForumId) REFERENCES Forum (id) 27 | DESTINATION KEY (PersonId) REFERENCES Person (id) 28 | LABEL hasMember, 29 | Forum_hasTag_Tag SOURCE KEY (ForumId) REFERENCES Forum (id) 30 | DESTINATION KEY (TagId) REFERENCES Tag (id) 31 | LABEL Forum_hasTag, 32 | Person_hasInterest_Tag SOURCE KEY (PersonId) REFERENCES Person (id) 33 | DESTINATION KEY (TagId) REFERENCES Tag (id) 34 | LABEL hasInterest, 35 | person_workAt_Organisation SOURCE KEY (PersonId) REFERENCES Person (id) 36 | DESTINATION KEY (OrganisationId) REFERENCES Organisation (id) 37 | LABEL workAt_Organisation, 38 | Person_likes_Message SOURCE KEY (PersonId) REFERENCES Person (id) 39 | DESTINATION KEY (id) REFERENCES Message (id) 40 | LABEL likes_Message, 41 | Message_hasTag_Tag SOURCE KEY (id) REFERENCES Message (id) 42 | DESTINATION KEY (TagId) REFERENCES Tag (id) 43 | LABEL message_hasTag, 44 | Message_hasAuthor_Person SOURCE KEY (messageId) REFERENCES Message (id) 45 | DESTINATION KEY (PersonId) REFERENCES Person (id) 46 | LABEL hasAuthor, 47 | Message_replyOf_Message SOURCE KEY (messageId) REFERENCES Message (id) 48 | DESTINATION KEY (ParentMessageId) REFERENCES Message (id) 49 | LABEL replyOf 50 | ); 51 | 52 | query I 53 | pragma show_property_graphs; 54 | ---- 55 | snb 56 | 57 | statement ok 58 | -drop property graph snb; 59 | 60 | query I 61 | pragma show_property_graphs; 62 | ---- 63 | 64 | -------------------------------------------------------------------------------- /test/sql/summarize_property_graph.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/summarize_property_graph.test 2 | # group: [sql] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | import database 'duckdb/data/SNB0.003'; 8 | 9 | statement ok 10 | -CREATE PROPERTY GRAPH snb 11 | VERTEX TABLES ( 12 | Person LABEL Person 13 | ) 14 | EDGE TABLES ( 15 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 16 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 17 | LABEL Knows 18 | ); 19 | 20 | query IIIIIIIIIIIIIIIIIIIIII 21 | FROM summarize_property_graph(snb) order by table_name; 22 | ---- 23 | Person 1 NULL NULL 50 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 24 | Person_knows_person 0 Person Person NULL 83 28 29 22 21 2.8620689655172415 1 10 1 2 3 2.9642857142857144 1 13 1 2 4 25 | 26 | statement ok 27 | -CREATE PROPERTY GRAPH snb1 28 | VERTEX TABLES ( 29 | Person LABEL Person, 30 | Message LABEL Message 31 | ) 32 | EDGE TABLES ( 33 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 34 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 35 | LABEL Knows, 36 | Person_likes_Message SOURCE KEY (PersonId) REFERENCES Person (id) 37 | DESTINATION KEY (id) REFERENCES Message (id) 38 | LABEL likes_Message, 39 | Message_replyOf_Message SOURCE KEY (messageId) REFERENCES Message (id) 40 | DESTINATION KEY (ParentMessageId) REFERENCES Message (id) 41 | LABEL replyOf 42 | ); 43 | 44 | query IIIIIIIIIIIIIIIIIIIIII 45 | FROM summarize_property_graph(snb1) order by table_name; 46 | ---- 47 | Message true NULL NULL 3660 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 48 | Message_replyOf_Message false Message Message NULL 471 471 150 3189 3510 3.14 1 11 2 3 4 1.0 1 1 1 1 1 49 | Person true NULL NULL 50 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 50 | Person_knows_person false Person Person NULL 83 28 29 22 21 2.8620689655172415 1 10 1 2 3 2.9642857142857144 1 13 1 2 4 51 | Person_likes_Message false Person Message NULL 492 48 234 2 3426 2.1025641025641026 1 41 1 1 1 10.25 1 57 2 6 15 52 | 53 | statement ok 54 | -CREATE PROPERTY GRAPH snb2 55 | VERTEX TABLES ( 56 | Person LABEL Person 57 | ); 58 | 59 | query IIIIIIIIIIIIIIIIIIIIII 60 | FROM summarize_property_graph(snb2) order by table_name; 61 | ---- 62 | Person true NULL NULL 50 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 63 | 64 | statement error 65 | from summarize_property_graph(pgdoesnotexist) order by table_name; 66 | ---- 67 | Binder Error: Property graph pgdoesnotexist does not exist -------------------------------------------------------------------------------- /test/sql/snb/bi.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/snb/bi.test 2 | # description: Testing the SNB bi queries 3 | # group: [snb] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | import database 'duckdb/data/SNB0.003'; 9 | 10 | statement ok 11 | -CREATE PROPERTY GRAPH snb 12 | VERTEX TABLES ( 13 | Person LABEL Person, 14 | Forum LABEL Forum, 15 | Organisation LABEL Organisation IN typemask(company, university), 16 | Place LABEL Place, 17 | Tag LABEL Tag, 18 | TagClass LABEL TagClass, 19 | Country LABEL Country, 20 | City LABEL City, 21 | Message LABEL Message 22 | ) 23 | EDGE TABLES ( 24 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 25 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 26 | LABEL Knows, 27 | Forum_hasMember_Person SOURCE KEY (ForumId) REFERENCES Forum (id) 28 | DESTINATION KEY (PersonId) REFERENCES Person (id) 29 | LABEL hasMember, 30 | Forum_hasTag_Tag SOURCE KEY (ForumId) REFERENCES Forum (id) 31 | DESTINATION KEY (TagId) REFERENCES Tag (id) 32 | LABEL Forum_hasTag, 33 | Person_hasInterest_Tag SOURCE KEY (PersonId) REFERENCES Person (id) 34 | DESTINATION KEY (TagId) REFERENCES Tag (id) 35 | LABEL hasInterest, 36 | person_workAt_Organisation SOURCE KEY (PersonId) REFERENCES Person (id) 37 | DESTINATION KEY (OrganisationId) REFERENCES Organisation (id) 38 | LABEL workAt_Organisation, 39 | Person_likes_Message SOURCE KEY (PersonId) REFERENCES Person (id) 40 | DESTINATION KEY (id) REFERENCES Message (id) 41 | LABEL likes_Message, 42 | Message_hasTag_Tag SOURCE KEY (id) REFERENCES Message (id) 43 | DESTINATION KEY (TagId) REFERENCES Tag (id) 44 | LABEL message_hasTag, 45 | Message_hasAuthor_Person SOURCE KEY (messageId) REFERENCES Message (id) 46 | DESTINATION KEY (PersonId) REFERENCES Person (id) 47 | LABEL hasAuthor, 48 | Message_replyOf_Message SOURCE KEY (messageId) REFERENCES Message (id) 49 | DESTINATION KEY (ParentMessageId) REFERENCES Message (id) 50 | LABEL replyOf 51 | ); 52 | 53 | statement ok 54 | -FROM GRAPH_TABLE (snb 55 | MATCH (personA:Person)-[kAB:Knows where kAB.creationDate BETWEEN '2012-10-04' AND '2013-01-16']- 56 | (personB:Person)-[kBC:Knows where kBC.creationDate BETWEEN '2012-10-04' AND '2013-01-16']- 57 | (personC:Person)-[kCA:Knows where kCA.creationDate BETWEEN '2012-10-04' AND '2013-01-16'] 58 | -(personA:Person)); 59 | -------------------------------------------------------------------------------- /test/sql/create_pg/all_properties.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/all_properties.test 2 | # description: Testing the creation of property graphs with all properties 3 | # group: [create_pg] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 15 | 16 | statement ok 17 | CREATE TABLE School(school_name VARCHAR, school_id BIGINT, school_kind BIGINT); 18 | 19 | statement ok 20 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'); 21 | 22 | statement ok 23 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (1,2, 14), (1,3, 15), (2,3, 16); 24 | 25 | # all properties 26 | statement ok 27 | -CREATE PROPERTY GRAPH pg_all_properties 28 | VERTEX TABLES ( 29 | Student LABEL Person, 30 | School LABEL School IN School_kind (Hogeschool, University) 31 | ) 32 | EDGE TABLES ( 33 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 34 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 35 | LABEL Knows 36 | ); 37 | 38 | 39 | # all properties 40 | statement ok 41 | -CREATE PROPERTY GRAPH pg_only_id 42 | VERTEX TABLES ( 43 | Student PROPERTIES (id) LABEL Person 44 | ) 45 | EDGE TABLES ( 46 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 47 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 48 | PROPERTIES (src) 49 | LABEL Knows 50 | ); 51 | 52 | query II 53 | -FROM GRAPH_TABLE (pg_only_id MATCH (p:Person)-[k:Knows]->(p2:Person) COLUMNS (p.id as id, p2.id as friend_id)); 54 | ---- 55 | 0 1 56 | 0 2 57 | 0 3 58 | 1 2 59 | 1 3 60 | 2 3 61 | 62 | statement error 63 | -FROM GRAPH_TABLE (pg_only_id MATCH (p:Person)-[k:Knows]->(p2:Person) COLUMNS (p.id as id, p2.name as friend_name)); 64 | ---- 65 | Binder Error: Property p2.name is never registered! 66 | 67 | 68 | query III 69 | -FROM GRAPH_TABLE (pg_only_id MATCH p = any shortest (p:Person)-[k:Knows]->*(p2:Person) COLUMNS (p.id as id, p2.id as friend_id, vertices(p))); 70 | ---- 71 | 0 0 [0] 72 | 0 1 [0, 1] 73 | 0 2 [0, 2] 74 | 0 3 [0, 3] 75 | 1 1 [1] 76 | 1 2 [1, 2] 77 | 1 3 [1, 3] 78 | 2 2 [2] 79 | 2 3 [2, 3] 80 | 3 3 [3] 81 | 82 | 83 | statement error 84 | -FROM GRAPH_TABLE (pg_only_id MATCH (p:Person)-[k:Knows]->(p2:Person) COLUMNS (dst)); 85 | ---- 86 | Binder Error: Property dst is never registered! 87 | 88 | query I 89 | -FROM GRAPH_TABLE (pg_only_id MATCH (p:Person)-[k:Knows]->(p2:Person) COLUMNS (src)); 90 | ---- 91 | 0 92 | 0 93 | 0 94 | 1 95 | 1 96 | 2 97 | 98 | query I 99 | -FROM GRAPH_TABLE (pg_only_id MATCH (p:Person)-[k:Knows]->(p2:Person) COLUMNS (k.*)); 100 | ---- 101 | 0 102 | 0 103 | 0 104 | 1 105 | 1 106 | 2 107 | 108 | query II 109 | -FROM GRAPH_TABLE (pg_only_id MATCH (p:Person)-[k:Knows]->(p2:Person) COLUMNS (p.*, k.*)); 110 | ---- 111 | 0 0 112 | 0 0 113 | 0 0 114 | 1 1 115 | 1 1 116 | 2 2 117 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/table/summarize_property_graph.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/functions/tablefunctions/summarize_property_graph.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckdb/function/table_function.hpp" 11 | #include "duckdb/parser/parsed_data/create_property_graph_info.hpp" 12 | #include "duckpgq/common.hpp" 13 | 14 | namespace duckdb { 15 | 16 | class SummarizePropertyGraphFunction : public TableFunction { 17 | public: 18 | SummarizePropertyGraphFunction() { 19 | name = "summarize_property_graph"; 20 | arguments.push_back(LogicalType::VARCHAR); 21 | bind_replace = SummarizePropertyGraphBindReplace; 22 | } 23 | 24 | struct SummarizePropertyGraphBindData : public TableFunctionData { 25 | explicit SummarizePropertyGraphBindData(CreatePropertyGraphInfo *pg_info) : summarize_pg_info(pg_info) { 26 | } 27 | CreatePropertyGraphInfo *summarize_pg_info; 28 | }; 29 | 30 | struct SummarizePropertyGraphGlobalData : public GlobalTableFunctionState { 31 | SummarizePropertyGraphGlobalData() = default; 32 | bool done = false; 33 | }; 34 | 35 | static unique_ptr SummarizePropertyGraphInit(ClientContext &context, 36 | TableFunctionInitInput &input); 37 | 38 | static unique_ptr CreateGroupBySubquery(const shared_ptr &pg_table, 39 | bool is_in_degree, const string °ree_column); 40 | static unique_ptr GetDegreeStatistics(const string &aggregate_function, bool is_in_degree); 41 | static unique_ptr 42 | CreateDegreeStatisticsCTE(const shared_ptr &pg_table, const string °ree_column, 43 | bool is_in_degree); 44 | static unique_ptr GetIsolatedNodes(shared_ptr &pg_table, const string &alias, 45 | bool is_source); 46 | static unique_ptr GetDistinctCount(const shared_ptr &pg_table, 47 | const string &alias, bool is_source); 48 | 49 | static unique_ptr 50 | CreateVertexTableCTE(const shared_ptr &vertex_table); 51 | static unique_ptr CreateEdgeTableCTE(shared_ptr &edge_table); 52 | 53 | static unique_ptr HandleSingleVertexTable(const shared_ptr &vertex_table, 54 | const string &stat_table_alias); 55 | static unique_ptr SummarizePropertyGraphBindReplace(ClientContext &context, 56 | TableFunctionBindInput &input); 57 | }; 58 | 59 | } // namespace duckdb 60 | -------------------------------------------------------------------------------- /test/sql/path_finding/path-finding-cte.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/path-finding-cte.test 2 | # description: Testing the optimization to move the shortest path function to a materialized CTE 3 | # group: [path_finding] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | CREATE TABLE Student(id BIGINT, name VARCHAR); INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 9 | 10 | statement ok 11 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 12 | 13 | statement ok 14 | CREATE TABLE School(name VARCHAR, Id BIGINT, Kind VARCHAR); INSERT INTO School VALUES ('VU', 0, 'University'), ('UVA', 1, 'University'); 15 | 16 | statement ok 17 | CREATE TABLE StudyAt(personId BIGINT, schoolId BIGINT); INSERT INTO StudyAt VALUES (0, 0), (1, 0), (2, 1), (3, 1), (4, 1); 18 | 19 | statement ok 20 | -CREATE PROPERTY GRAPH pg 21 | VERTEX TABLES ( 22 | Student PROPERTIES ( id, name ) LABEL Person, 23 | School LABEL SCHOOL 24 | ) 25 | EDGE TABLES ( 26 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 27 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 28 | LABEL Knows, 29 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 30 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 31 | LABEL StudyAt 32 | ); 33 | 34 | statement ok 35 | -WITH cte1 AS ( 36 | SELECT CREATE_CSR_EDGE( 37 | 0, 38 | (SELECT count(a.id) FROM Student a), 39 | CAST ( 40 | (SELECT sum(CREATE_CSR_VERTEX( 41 | 0, 42 | (SELECT count(a.id) FROM Student a), 43 | sub.dense_id, 44 | sub.cnt) 45 | ) 46 | FROM ( 47 | SELECT a.rowid as dense_id, count(k.src) as cnt 48 | FROM Student a 49 | LEFT JOIN Know k ON k.src = a.id 50 | GROUP BY a.rowid) sub 51 | ) 52 | AS BIGINT), 53 | (select count(*) from know k join student a on a.id = k.src join student c on c.id = k.dst), 54 | a.rowid, 55 | c.rowid, 56 | k.rowid) as temp 57 | FROM Know k 58 | JOIN student a on a.id = k.src 59 | JOIN student c on c.id = k.dst 60 | ), shortest_path_p AS MATERIALIZED ( 61 | SELECT shortestpath(0, (select count(*) from student), a.rowid, b.rowid) as path, a.rowid as src_rowid, b.rowid as dst_rowid 62 | FROM student a, student b, (select count(cte1.temp) * 0 as temp from cte1) __x 63 | WHERE a.name = 'Daniel' and __x.temp * 0 + iterativelength(0, (select count(*) from student), a.rowid, b.rowid) between 1 and 3) 64 | SELECT path, a.name, b.name, 65 | len(path) // 2, 66 | path[1:-:2], 67 | path[2:-:2] 68 | FROM shortest_path_p, student a, student b where a.name = 'Daniel' and a.rowid = src_rowid and b.rowid = dst_rowid; 69 | -------------------------------------------------------------------------------- /test/sql/path_finding/non-unique-vertices.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/non-unique-vertices.test 2 | # group: [path_finding] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | CREATE TABLE v (x VARCHAR);INSERT INTO v VALUES ('a'), ('b'), ('b'); 8 | 9 | statement ok 10 | CREATE TABLE e (x1 VARCHAR, x2 VARCHAR);INSERT INTO e VALUES ('a', 'b'); 11 | 12 | statement ok 13 | -CREATE PROPERTY GRAPH g 14 | VERTEX TABLES ( 15 | v 16 | ) 17 | EDGE TABLES ( 18 | e 19 | SOURCE KEY (x1) REFERENCES v (x) 20 | DESTINATION KEY (x2) REFERENCES v (x) 21 | ); 22 | 23 | # v-[e]->(v) has no error: 24 | # Output has duplicate `x` records with the value `b` returned as expected. They can be distinguished by rowid in vertices() 25 | statement ok 26 | -FROM GRAPH_TABLE(g 27 | MATCH p =(v1:v)-[e:e]->(v2:v) 28 | COLUMNS (vertices(p), v2.x) 29 | ); 30 | 31 | # ANY SHORTEST v-[e]->(v) has no error: 32 | # Output again has duplicate `x` records are returned as expected 33 | statement ok 34 | -FROM GRAPH_TABLE(g 35 | MATCH p = ANY SHORTEST (v1:v)-[e:e]->(v2:v) 36 | COLUMNS (path_length(p), vertices(p), v2.x) 37 | ); 38 | 39 | ## ANY SHORTEST v-[e]-> +(v) fails with "INTERNAL Error: Attempted to access index 1 within vector of size 1" 40 | statement error 41 | -FROM GRAPH_TABLE(g 42 | MATCH p = ANY SHORTEST (v1:v)-[e:e]-> +(v2:v) 43 | COLUMNS (path_length(p), vertices(p), v2.x) 44 | ); 45 | ---- 46 | Constraint Error: Non-existent/non-unique vertices detected. Make sure all vertices referred by edge tables exist and are unique for path-finding queries. 47 | 48 | # ANY SHORTEST v-[e]->{1,2}(v) also fails with "INTERNAL Error: Attempted to access index 1 within vector of size 1" 49 | statement error 50 | -FROM GRAPH_TABLE(g 51 | MATCH p = ANY SHORTEST (v1:v)-[e:e]->{1,2}(v2:v) 52 | COLUMNS (path_length(p), vertices(p), v2.x) 53 | ); 54 | ---- 55 | Constraint Error: Non-existent/non-unique vertices detected. Make sure all vertices referred by edge tables exist and are unique for path-finding queries. 56 | 57 | statement ok 58 | CREATE TABLE v2 (x VARCHAR);INSERT INTO v2 VALUES ('a'), ('b'), ('c'), ('c'), ('b'); 59 | 60 | statement ok 61 | CREATE TABLE e2 (x1 VARCHAR, x2 VARCHAR);INSERT INTO e2 VALUES ('a', 'b'), ('b', 'c'); 62 | 63 | statement ok 64 | -CREATE PROPERTY GRAPH g2 65 | VERTEX TABLES ( 66 | v2 67 | ) 68 | EDGE TABLES ( 69 | e2 70 | SOURCE KEY (x1) REFERENCES v2 (x) 71 | DESTINATION KEY (x2) REFERENCES v2 (x) 72 | ); 73 | 74 | # ANY SHORTEST v-[e]->{1,2}(v) also fails with "INTERNAL Error: Attempted to access index 1 within vector of size 1" 75 | statement error 76 | -FROM GRAPH_TABLE(g2 77 | MATCH p = ANY SHORTEST (v1:v2)-[e:e2]->{1,2}(v2:v2) 78 | COLUMNS (path_length(p), vertices(p), v2.x) 79 | ); 80 | ---- 81 | Constraint Error: Non-existent/non-unique vertices detected. Make sure all vertices referred by edge tables exist and are unique for path-finding queries. 82 | 83 | statement error 84 | from weakly_connected_component(g2, v2, e2); 85 | ---- 86 | Constraint Error: Non-existent/non-unique vertices detected. Make sure all vertices referred by edge tables exist and are unique for path-finding queries. -------------------------------------------------------------------------------- /test/sql/nested_subquery.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/nested_subquery.test 2 | # group: [sql] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | CREATE TABLE Student(id BIGINT, name VARCHAR);INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 8 | 9 | statement ok 10 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT);INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 11 | 12 | statement ok 13 | CREATE TABLE School(name VARCHAR, Id BIGINT, Kind VARCHAR);INSERT INTO School VALUES ('VU', 0, 'University'), ('UVA', 1, 'University'); 14 | 15 | statement ok 16 | CREATE TABLE StudyAt(personId BIGINT, schoolId BIGINT);INSERT INTO StudyAt VALUES (0, 0), (1, 0), (2, 1), (3, 1), (4, 1); 17 | 18 | statement ok 19 | -CREATE PROPERTY GRAPH pg 20 | VERTEX TABLES ( 21 | Student PROPERTIES ( id, name ) LABEL Person, 22 | School LABEL SCHOOL 23 | ) 24 | EDGE TABLES ( 25 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 26 | DESTINATION KEY ( dst ) REFERENCES Student ( id ), 27 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 28 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 29 | ); 30 | 31 | 32 | statement ok 33 | -select * from (select id, id_1 from graph_table ( pg match (p:person)-[k:know]->(p2:person) columns (p.id, p2.id))); 34 | 35 | statement ok 36 | -SELECT id, friend_id 37 | FROM ( 38 | SELECT id, friend_id 39 | FROM GRAPH_TABLE ( 40 | pg MATCH (p:Person)-[k:know]->(p2:Person) COLUMNS (p.id as id, p2.id as friend_id) 41 | ) graph 42 | ); 43 | 44 | statement ok 45 | -SELECT id, friend_id 46 | FROM ( 47 | SELECT id, friend_id 48 | FROM GRAPH_TABLE ( 49 | pg MATCH (p:Person)-[k:know]->(p2:Person) COLUMNS (p.id as id, p2.id as friend_id) 50 | ) graph 51 | WHERE id > 1 52 | ); 53 | 54 | statement ok 55 | -SELECT Student.name, friend_id 56 | FROM Student 57 | JOIN ( 58 | SELECT student_id, friend_id 59 | FROM GRAPH_TABLE ( 60 | pg MATCH (p:Person)-[k:know]->(p2:Person) COLUMNS (p.id as student_id, p2.id as friend_id) 61 | ) graph 62 | ) AS subquery 63 | ON Student.id = subquery.student_id; 64 | 65 | statement ok 66 | -SELECT id, nested_friend_id 67 | FROM ( 68 | SELECT id, friend_id AS nested_friend_id 69 | FROM ( 70 | SELECT id, friend_id 71 | FROM GRAPH_TABLE ( 72 | pg MATCH (p:Person)-[k:know]->(p2:Person) COLUMNS (p.id as id, p2.id as friend_id) 73 | ) 74 | ) 75 | ); 76 | 77 | statement ok 78 | -SELECT id, friend_count 79 | FROM ( 80 | SELECT id, COUNT(friend_id) AS friend_count 81 | FROM GRAPH_TABLE ( 82 | pg MATCH (p:Person)-[k:know]->(p2:Person) COLUMNS (p.id as id, p2.id as friend_id) 83 | ) 84 | GROUP BY id 85 | ); 86 | 87 | statement ok 88 | -WITH Friendships AS ( 89 | SELECT person_id, friend_id 90 | FROM ( 91 | SELECT person_id, friend_id 92 | FROM GRAPH_TABLE ( 93 | pg MATCH (p:Person)-[k:know]->(p2:Person) COLUMNS (p.id as person_id, p2.id as friend_id) 94 | ) 95 | ) AS Subquery 96 | ) 97 | SELECT * FROM Friendships; -------------------------------------------------------------------------------- /src/core/functions/table/drop_property_graph.cpp: -------------------------------------------------------------------------------- 1 | #include "duckpgq/core/functions/table/drop_property_graph.hpp" 2 | 3 | #include "duckdb/parser/parsed_data/drop_property_graph_info.hpp" 4 | #include 5 | #include 6 | #include 7 | #include "duckdb/main/connection_manager.hpp" 8 | 9 | namespace duckdb { 10 | 11 | unique_ptr DropPropertyGraphFunction::DropPropertyGraphBind(ClientContext &context, 12 | TableFunctionBindInput &, 13 | vector &return_types, 14 | vector &names) { 15 | names.emplace_back("success"); 16 | return_types.emplace_back(LogicalType::VARCHAR); 17 | auto duckpgq_state = GetDuckPGQState(context); 18 | 19 | auto duckpgq_parse_data = dynamic_cast(duckpgq_state->parse_data.get()); 20 | 21 | if (!duckpgq_parse_data) { 22 | return {}; 23 | } 24 | auto statement = dynamic_cast(duckpgq_parse_data->statement.get()); 25 | auto info = dynamic_cast(statement->info.get()); 26 | return make_uniq(info); 27 | } 28 | 29 | unique_ptr DropPropertyGraphFunction::DropPropertyGraphInit(ClientContext &, 30 | TableFunctionInitInput &) { 31 | return make_uniq(); 32 | } 33 | 34 | void DropPropertyGraphFunction::DropPropertyGraphFunc(ClientContext &context, TableFunctionInput &data_p, DataChunk &) { 35 | auto &bind_data = data_p.bind_data->Cast(); 36 | 37 | auto pg_info = bind_data.drop_pg_info; 38 | auto duckpgq_state = GetDuckPGQState(context); 39 | 40 | auto registered_pg = duckpgq_state->registered_property_graphs.find(pg_info->property_graph_name); 41 | if (registered_pg == duckpgq_state->registered_property_graphs.end()) { 42 | if (pg_info->missing_ok) { 43 | return; // Do nothing 44 | } 45 | throw BinderException("Property graph %s does not exist.", pg_info->property_graph_name); 46 | } 47 | 48 | for (auto &connection : ConnectionManager::Get(*context.db).GetConnectionList()) { 49 | auto local_state = connection->registered_state->Get("duckpgq"); 50 | if (!local_state) { 51 | continue; 52 | } 53 | local_state->registered_property_graphs.erase(pg_info->property_graph_name); 54 | } 55 | 56 | auto new_conn = make_shared_ptr(*context.db); 57 | new_conn->Query("DELETE FROM __duckpgq_internal where property_graph = '" + pg_info->property_graph_name + "'"); 58 | } 59 | 60 | //------------------------------------------------------------------------------ 61 | // Register functions 62 | //------------------------------------------------------------------------------ 63 | void CoreTableFunctions::RegisterDropPropertyGraphTableFunction(ExtensionLoader &loader) { 64 | loader.RegisterFunction(DropPropertyGraphFunction()); 65 | } 66 | 67 | } // namespace duckdb 68 | -------------------------------------------------------------------------------- /test/sql/create_pg/drop_property_graph.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/create_pg/drop_property_graph.test 2 | # description: Testing the drop property graph syntax 3 | # group: [create_pg] 4 | 5 | require duckpgq 6 | 7 | #statement ok 8 | #pragma enable_verification 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 15 | 16 | statement ok 17 | CREATE TABLE School(name VARCHAR, Id BIGINT, Kind VARCHAR); 18 | 19 | statement ok 20 | CREATE TABLE StudyAt(personId BIGINT, schoolId BIGINT); 21 | 22 | statement ok 23 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 24 | 25 | statement ok 26 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 27 | 28 | statement ok 29 | INSERT INTO School VALUES ('VU', 0, 'University'), ('UVA', 1, 'University'); 30 | 31 | statement ok 32 | INSERT INTO StudyAt VALUES (0, 0), (1, 0), (2, 1), (3, 1), (4, 1); 33 | 34 | statement ok 35 | -CREATE PROPERTY GRAPH pg 36 | VERTEX TABLES ( 37 | Student PROPERTIES ( id, name ) LABEL Person, 38 | School LABEL SCHOOL 39 | ) 40 | EDGE TABLES ( 41 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 42 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 43 | LABEL Knows, 44 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 45 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 46 | LABEL StudyAt 47 | ); 48 | 49 | statement ok 50 | -DROP PROPERTY GRAPH pg; 51 | 52 | statement error 53 | -DROP PROPERTY GRAPH pg; 54 | ---- 55 | Binder Error: Property graph pg does not exist 56 | 57 | statement error 58 | -DROP PROPERTY GRAPH pgdoesntexist; 59 | ---- 60 | Binder Error: Property graph pgdoesntexist does not exist 61 | 62 | statement error 63 | -SELECT study.id 64 | FROM GRAPH_TABLE (pg 65 | MATCH 66 | (a:Person) 67 | COLUMNS (a.id) 68 | ) study; 69 | ---- 70 | Binder Error: Property graph pg does not exist 71 | 72 | statement ok 73 | -CREATE PROPERTY GRAPH pg 74 | VERTEX TABLES ( 75 | Student PROPERTIES ( id, name ) LABEL Person, 76 | School LABEL SCHOOL 77 | ) 78 | EDGE TABLES ( 79 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 80 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 81 | LABEL Knows, 82 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 83 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 84 | LABEL StudyAt 85 | ); 86 | 87 | 88 | statement ok 89 | -SELECT * 90 | FROM GRAPH_TABLE (pg 91 | MATCH 92 | (a:Person) 93 | COLUMNS (a.id) 94 | ) study; 95 | 96 | 97 | # should drop the property graph 98 | statement ok 99 | -DROP PROPERTY GRAPH if exists pg; 100 | 101 | # should not give an error as the property graph is already dropped 102 | statement ok 103 | -DROP PROPERTY GRAPH if exists pg; 104 | 105 | # should not give an error as the property graph is already dropped 106 | statement error 107 | -DROP PROPERTY GRAPH pg; 108 | ---- 109 | Binder Error: Property graph pg does not exist 110 | -------------------------------------------------------------------------------- /scripts/extension-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Extension upload script 4 | 5 | # Usage: ./extension-upload.sh 6 | # : Name of the extension 7 | # : Version (commit / version tag) of the extension 8 | # : Version (commit / version tag) of DuckDB 9 | # : Architecture target of the extension binary 10 | # : S3 bucket to upload to 11 | # : Set this as the latest version ("true" / "false", default: "false") 12 | # : Set this as a versioned version that will prevent its deletion 13 | 14 | set -e 15 | 16 | if [[ $4 == wasm* ]]; then 17 | ext="/tmp/extension/$1.duckdb_extension.wasm" 18 | else 19 | ext="/tmp/extension/$1.duckdb_extension" 20 | fi 21 | 22 | echo $ext 23 | 24 | script_dir="$(dirname "$(readlink -f "$0")")" 25 | 26 | # calculate SHA256 hash of extension binary 27 | cat $ext > $ext.append 28 | 29 | if [[ $4 == wasm* ]]; then 30 | # 0 for custom section 31 | # 113 in hex = 275 in decimal, total lenght of what follows (1 + 16 + 2 + 256) 32 | # [1(continuation) + 0010011(payload) = \x93, 0(continuation) + 10(payload) = \x02] 33 | echo -n -e '\x00' >> $ext.append 34 | echo -n -e '\x93\x02' >> $ext.append 35 | # 10 in hex = 16 in decimal, lenght of name, 1 byte 36 | echo -n -e '\x10' >> $ext.append 37 | echo -n -e 'duckdb_signature' >> $ext.append 38 | # the name of the WebAssembly custom section, 16 bytes 39 | # 100 in hex, 256 in decimal 40 | # [1(continuation) + 0000000(payload) = ff, 0(continuation) + 10(payload)], 41 | # for a grand total of 2 bytes 42 | echo -n -e '\x80\x02' >> $ext.append 43 | fi 44 | 45 | # (Optionally) Sign binary 46 | if [ "$DUCKDB_EXTENSION_SIGNING_PK" != "" ]; then 47 | echo "$DUCKDB_EXTENSION_SIGNING_PK" > private.pem 48 | $script_dir/../duckdb/scripts/compute-extension-hash.sh $ext.append > $ext.hash 49 | openssl pkeyutl -sign -in $ext.hash -inkey private.pem -pkeyopt digest:sha256 -out $ext.sign 50 | rm -f private.pem 51 | fi 52 | 53 | # Signature is always there, potentially defaulting to 256 zeros 54 | truncate -s 256 $ext.sign 55 | 56 | # compress extension binary 57 | if [[ $4 == wasm_* ]]; then 58 | brotli < $ext.append > "$ext.compressed" 59 | else 60 | gzip < $ext.append > "$ext.compressed" 61 | fi 62 | 63 | set -e 64 | 65 | # Abort if AWS key is not set 66 | if [ -z "$AWS_ACCESS_KEY_ID" ]; then 67 | echo "No AWS key found, skipping.." 68 | exit 0 69 | fi 70 | 71 | # upload versioned version 72 | if [[ $7 = 'true' ]]; then 73 | if [[ $4 == wasm* ]]; then 74 | aws s3 cp $ext.compressed s3://$5/$1/$2/$3/$4/$1.duckdb_extension.wasm --acl public-read --content-encoding br --content-type="application/wasm" 75 | else 76 | aws s3 cp $ext.compressed s3://$5/$1/$2/$3/$4/$1.duckdb_extension.gz --acl public-read 77 | fi 78 | fi 79 | 80 | # upload to latest version 81 | if [[ $6 = 'true' ]]; then 82 | if [[ $4 == wasm* ]]; then 83 | aws s3 cp $ext.compressed s3://$5/$3/$4/$1.duckdb_extension.wasm --acl public-read --content-encoding br --content-type="application/wasm" 84 | else 85 | aws s3 cp $ext.compressed s3://$5/$3/$4/$1.duckdb_extension.gz --acl public-read 86 | fi 87 | fi 88 | -------------------------------------------------------------------------------- /test/sql/explain_duckpgq.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/explain_duckpgq.test 2 | # description: Testing the EXPLAIN statements 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | import database 'duckdb/data/SNB0.003'; 9 | 10 | statement ok 11 | -CREATE PROPERTY GRAPH snb 12 | VERTEX TABLES ( 13 | Person LABEL Person, 14 | Forum LABEL Forum, 15 | Organisation LABEL Organisation IN typemask(company, university), 16 | Place LABEL Place, 17 | Tag LABEL Tag, 18 | TagClass LABEL TagClass, 19 | Country LABEL Country, 20 | City LABEL City, 21 | Message LABEL Message 22 | ) 23 | EDGE TABLES ( 24 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 25 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 26 | LABEL Knows, 27 | Forum_hasMember_Person SOURCE KEY (ForumId) REFERENCES Forum (id) 28 | DESTINATION KEY (PersonId) REFERENCES Person (id) 29 | LABEL hasMember, 30 | Forum_hasTag_Tag SOURCE KEY (ForumId) REFERENCES Forum (id) 31 | DESTINATION KEY (TagId) REFERENCES Tag (id) 32 | LABEL Forum_hasTag, 33 | Person_hasInterest_Tag SOURCE KEY (PersonId) REFERENCES Person (id) 34 | DESTINATION KEY (TagId) REFERENCES Tag (id) 35 | LABEL hasInterest, 36 | person_workAt_Organisation SOURCE KEY (PersonId) REFERENCES Person (id) 37 | DESTINATION KEY (OrganisationId) REFERENCES Organisation (id) 38 | LABEL workAt_Organisation, 39 | Person_likes_Message SOURCE KEY (PersonId) REFERENCES Person (id) 40 | DESTINATION KEY (id) REFERENCES Message (id) 41 | LABEL likes_Message, 42 | Message_hasTag_Tag SOURCE KEY (id) REFERENCES Message (id) 43 | DESTINATION KEY (TagId) REFERENCES Tag (id) 44 | LABEL message_hasTag, 45 | Message_hasAuthor_Person SOURCE KEY (messageId) REFERENCES Message (id) 46 | DESTINATION KEY (PersonId) REFERENCES Person (id) 47 | LABEL hasAuthor, 48 | Message_replyOf_Message SOURCE KEY (messageId) REFERENCES Message (id) 49 | DESTINATION KEY (ParentMessageId) REFERENCES Message (id) 50 | LABEL replyOf 51 | ); 52 | 53 | #IC 2 54 | statement ok 55 | -EXPLAIN FROM GRAPH_TABLE (snb 56 | MATCH (a:Person WHERE a.id = 17592186044461)-[k:knows]-(b:Person)<-[au:hasAuthor]-(m:message WHERE m.creationDate < '2010-10-16') 57 | COLUMNS (a.id, a.firstName, a.lastName, m.id as messageId, coalesce(m.imageFile, m.content), m.creationDate) 58 | ) tmp 59 | ORDER BY creationDate DESC, Messageid ASC 60 | LIMIT 20; 61 | 62 | #IC 2 63 | statement ok 64 | -EXPLAIN ANALYZE FROM GRAPH_TABLE (snb 65 | MATCH (a:Person WHERE a.id = 17592186044461)-[k:knows]-(b:Person)<-[au:hasAuthor]-(m:message WHERE m.creationDate < '2010-10-16') 66 | COLUMNS (a.id, a.firstName, a.lastName, m.id as messageId, coalesce(m.imageFile, m.content), m.creationDate) 67 | ) tmp 68 | ORDER BY creationDate DESC, Messageid ASC 69 | LIMIT 20; 70 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/functions/table/create_property_graph.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/functions/tablefunctions/create_property_graph.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckpgq/common.hpp" 11 | #include "duckdb/function/table_function.hpp" 12 | #include "duckdb/parser/parsed_data/create_property_graph_info.hpp" 13 | #include "duckdb/catalog/catalog_entry/table_catalog_entry.hpp" 14 | 15 | namespace duckdb { 16 | 17 | class CreatePropertyGraphFunction : public TableFunction { 18 | public: 19 | CreatePropertyGraphFunction() { 20 | name = "create_property_graph"; 21 | bind = CreatePropertyGraphBind; 22 | init_global = CreatePropertyGraphInit; 23 | function = CreatePropertyGraphFunc; 24 | } 25 | 26 | struct CreatePropertyGraphBindData : public TableFunctionData { 27 | explicit CreatePropertyGraphBindData(CreatePropertyGraphInfo *pg_info) : create_pg_info(pg_info) { 28 | } 29 | 30 | CreatePropertyGraphInfo *create_pg_info; 31 | }; 32 | 33 | struct CreatePropertyGraphGlobalData : public GlobalTableFunctionState { 34 | CreatePropertyGraphGlobalData() = default; 35 | }; 36 | 37 | static void CheckPropertyGraphTableLabels(const shared_ptr &pg_table, 38 | optional_ptr &table); 39 | 40 | static void CheckPropertyGraphTableColumns(const shared_ptr &pg_table, 41 | optional_ptr &table); 42 | 43 | static reference GetTableCatalogEntry(ClientContext &context, 44 | shared_ptr &pg_table); 45 | 46 | static unique_ptr CreatePropertyGraphBind(ClientContext &context, TableFunctionBindInput &input, 47 | vector &return_types, vector &names); 48 | 49 | static void ValidateVertexTableRegistration(shared_ptr &pg_table, 50 | const case_insensitive_set_t &v_table_names); 51 | 52 | static void ValidatePrimaryKeyInTable(ClientContext &context, shared_ptr &pg_table, 53 | const vector &pk_columns); 54 | 55 | static void ValidateKeys(shared_ptr &edge_table, const string &reference, 56 | const string &key_type, vector &pk_columns, vector &fk_columns, 57 | const vector> &table_constraints); 58 | 59 | static void ValidateForeignKeyColumns(shared_ptr &edge_table, const vector &fk_columns, 60 | optional_ptr &table); 61 | 62 | static unique_ptr CreatePropertyGraphInit(ClientContext &context, 63 | TableFunctionInitInput &input); 64 | 65 | static void CreatePropertyGraphFunc(ClientContext &context, TableFunctionInput &data_p, DataChunk &output); 66 | }; 67 | 68 | } // namespace duckdb 69 | -------------------------------------------------------------------------------- /test/sql/scalar/delete_csr.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scalar/delete_csr.test 2 | # description: Testing the delete csr UDF 3 | # group: [scalar] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | CREATE TABLE Student(id BIGINT, name VARCHAR); 9 | 10 | statement ok 11 | CREATE TABLE know(src BIGINT, dst BIGINT, id BIGINT); 12 | 13 | statement ok 14 | CREATE TABLE School(school_name VARCHAR, school_id BIGINT, school_kind BIGINT); 15 | 16 | statement ok 17 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 18 | 19 | statement ok 20 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17), (2, 4, 18); 21 | 22 | statement ok 23 | -CREATE PROPERTY GRAPH pg 24 | VERTEX TABLES ( 25 | Student PROPERTIES ( id, name ) LABEL Person 26 | ) 27 | EDGE TABLES ( 28 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 29 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 30 | PROPERTIES ( id ) LABEL Knows 31 | ); 32 | 33 | statement ok 34 | SELECT CREATE_CSR_EDGE( 35 | 0, 36 | (SELECT count(a.id) FROM Student a), 37 | CAST ( 38 | (SELECT sum(CREATE_CSR_VERTEX( 39 | 0, 40 | (SELECT count(a.id) FROM Student a), 41 | sub.dense_id, 42 | sub.cnt) 43 | ) 44 | FROM ( 45 | SELECT a.rowid as dense_id, count(k.src) as cnt 46 | FROM Student a 47 | LEFT JOIN Know k ON k.src = a.id 48 | GROUP BY a.rowid) sub 49 | ) 50 | AS BIGINT), 51 | (select count() FROM Know k JOIN student a on a.id = k.src JOIN student c on c.id = k.dst), 52 | a.rowid, 53 | c.rowid, 54 | k.rowid) as temp 55 | FROM Know k 56 | JOIN student a on a.id = k.src 57 | JOIN student c on c.id = k.dst; 58 | 59 | statement ok 60 | SELECT CREATE_CSR_EDGE( 61 | 1, 62 | (SELECT count(a.id) FROM Student a), 63 | CAST ( 64 | (SELECT sum(CREATE_CSR_VERTEX( 65 | 1, 66 | (SELECT count(a.id) FROM Student a), 67 | sub.dense_id, 68 | sub.cnt) 69 | ) 70 | FROM ( 71 | SELECT a.rowid as dense_id, count(k.src) as cnt 72 | FROM Student a 73 | LEFT JOIN Know k ON k.src = a.id 74 | GROUP BY a.rowid) sub 75 | ) 76 | AS BIGINT), 77 | (select count() FROM Know k JOIN student a on a.id = k.src JOIN student c on c.id = k.dst), 78 | a.rowid, 79 | c.rowid, 80 | k.rowid) as temp 81 | FROM Know k 82 | JOIN student a on a.id = k.src 83 | JOIN student c on c.id = k.dst; 84 | 85 | query I 86 | SELECT delete_csr(0) as flag; 87 | ---- 88 | true 89 | 90 | query I 91 | SELECT delete_csr(1) as flag; 92 | ---- 93 | true 94 | 95 | query I 96 | SELECT delete_csr(0) as flag; 97 | ---- 98 | false 99 | 100 | query I 101 | SELECT delete_csr(3) as flag; 102 | ---- 103 | false 104 | -------------------------------------------------------------------------------- /test/sql/path_finding/kleene_star.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/kleene_star.test 2 | # group: [path_finding] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | CREATE TABLE nodes (id INTEGER); 8 | 9 | statement ok 10 | CREATE TABLE edges (src INTEGER, dst INTEGER); 11 | 12 | statement ok 13 | INSERT INTO nodes VALUES (1), (2), (3); 14 | 15 | statement ok 16 | -CREATE PROPERTY GRAPH testgraph 17 | VERTEX TABLES ( 18 | nodes LABEL N 19 | ) 20 | EDGE TABLES ( 21 | edges SOURCE KEY (src) REFERENCES nodes (id) 22 | DESTINATION KEY (dst) REFERENCES nodes (id) 23 | LABEL E 24 | ); 25 | 26 | query IIIII 27 | -FROM GRAPH_TABLE(testgraph 28 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->*(n2:N) 29 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 30 | ); 31 | ---- 32 | 1 1 [0] [] 0 33 | 2 2 [1] [] 0 34 | 3 3 [2] [] 0 35 | 36 | query IIIII 37 | -FROM GRAPH_TABLE(testgraph 38 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->+(n2:N) 39 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 40 | ); 41 | ---- 42 | 43 | query IIIII 44 | -FROM GRAPH_TABLE(testgraph 45 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->{1,3}(n2:N) 46 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 47 | ); 48 | ---- 49 | 50 | query IIIII 51 | -FROM GRAPH_TABLE(testgraph 52 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->{0,3}(n2:N) 53 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 54 | ); 55 | ---- 56 | 1 1 [0] [] 0 57 | 2 2 [1] [] 0 58 | 3 3 [2] [] 0 59 | 60 | query IIIII 61 | -FROM GRAPH_TABLE(testgraph 62 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->{,3}(n2:N) 63 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 64 | ); 65 | ---- 66 | 1 1 [0] [] 0 67 | 2 2 [1] [] 0 68 | 3 3 [2] [] 0 69 | 70 | 71 | statement error 72 | -FROM GRAPH_TABLE(testgraph 73 | MATCH p = ANY SHORTEST (n1:N)*<-[e:E]->(n2:N) 74 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 75 | ); 76 | ---- 77 | Parser Error: syntax error at or near "*<" 78 | 79 | statement error 80 | -FROM GRAPH_TABLE(testgraph 81 | MATCH p = ANY SHORTEST (n1:N)*-[e:E]->(n2:N) 82 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 83 | ); 84 | ---- 85 | Parser Error: syntax error at or near "*" 86 | 87 | statement error 88 | -FROM GRAPH_TABLE(testgraph 89 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->{3,1}(n2:N) 90 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 91 | ); 92 | ---- 93 | Constraint Error: Lower bound greater than upper bound 94 | 95 | query IIIII 96 | -FROM GRAPH_TABLE(testgraph 97 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->{,}(n2:N) 98 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 99 | ); 100 | ---- 101 | 1 1 [0] [] 0 102 | 2 2 [1] [] 0 103 | 3 3 [2] [] 0 104 | 105 | query IIIII 106 | -FROM GRAPH_TABLE(testgraph 107 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->{1,1}(n2:N) 108 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 109 | ); 110 | ---- 111 | 112 | statement error 113 | -FROM GRAPH_TABLE(testgraph 114 | MATCH p = ANY SHORTEST (n1:N)-[e:E]->*(n2:N 115 | COLUMNS (n1.id, n2.id, element_id(p), edges(p) AS path_edges, path_length(p)) 116 | ); 117 | ---- 118 | Parser Error: syntax error at or near "COLUMNS" 119 | 120 | -------------------------------------------------------------------------------- /src/core/functions/scalar/local_clustering_coefficient.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb/planner/expression/bound_function_expression.hpp" 2 | #include "duckpgq/common.hpp" 3 | #include "duckpgq/core/functions/function_data/local_clustering_coefficient_function_data.hpp" 4 | #include "duckpgq/core/utils/duckpgq_bitmap.hpp" 5 | #include "duckpgq/core/utils/duckpgq_utils.hpp" 6 | 7 | #include 8 | 9 | namespace duckdb { 10 | 11 | static void LocalClusteringCoefficientFunction(DataChunk &args, ExpressionState &state, Vector &result) { 12 | auto &func_expr = state.expr.Cast(); 13 | auto &info = func_expr.bind_info->Cast(); 14 | auto duckpgq_state = GetDuckPGQState(info.context); 15 | 16 | auto csr_entry = duckpgq_state->csr_list.find(info.csr_id); 17 | if (csr_entry == duckpgq_state->csr_list.end()) { 18 | throw ConstraintException("CSR not found. Is the graph populated?"); 19 | } 20 | 21 | if (!(csr_entry->second->initialized_v && csr_entry->second->initialized_e)) { 22 | throw ConstraintException("Need to initialize CSR before doing local clustering coefficient."); 23 | } 24 | int64_t *v = reinterpret_cast(duckpgq_state->csr_list[info.csr_id]->v); 25 | vector &e = duckpgq_state->csr_list[info.csr_id]->e; 26 | size_t v_size = duckpgq_state->csr_list[info.csr_id]->vsize; 27 | // get src and dst vectors for searches 28 | auto &src = args.data[1]; 29 | UnifiedVectorFormat vdata_src; 30 | src.ToUnifiedFormat(args.size(), vdata_src); 31 | auto src_data = reinterpret_cast(vdata_src.data); 32 | 33 | ValidityMask &result_validity = FlatVector::Validity(result); 34 | // create result vector 35 | result.SetVectorType(VectorType::FLAT_VECTOR); 36 | auto result_data = FlatVector::GetData(result); 37 | 38 | DuckPGQBitmap neighbors(v_size); 39 | 40 | for (idx_t n = 0; n < args.size(); n++) { 41 | auto src_sel = vdata_src.sel->get_index(n); 42 | if (!vdata_src.validity.RowIsValid(src_sel)) { 43 | result_validity.SetInvalid(n); 44 | } 45 | int64_t src_node = src_data[src_sel]; 46 | int64_t number_of_edges = v[src_node + 1] - v[src_node]; 47 | if (number_of_edges < 2) { 48 | result_data[n] = static_cast(0.0); 49 | continue; 50 | } 51 | neighbors.reset(); 52 | for (int64_t offset = v[src_node]; offset < v[src_node + 1]; offset++) { 53 | neighbors.set(e[offset]); 54 | } 55 | 56 | // Count connections between neighbors 57 | int64_t count = 0; 58 | for (int64_t offset = v[src_node]; offset < v[src_node + 1]; offset++) { 59 | int64_t neighbor = e[offset]; 60 | for (int64_t offset2 = v[neighbor]; offset2 < v[neighbor + 1]; offset2++) { 61 | int is_connected = neighbors.test(e[offset2]); 62 | count += is_connected; // Add 1 if connected, 0 otherwise 63 | } 64 | } 65 | 66 | const float num_edges_float = static_cast(number_of_edges); 67 | float local_result = static_cast(count) / (num_edges_float * (num_edges_float - 1.0f)); 68 | 69 | result_data[n] = local_result; 70 | } 71 | duckpgq_state->csr_to_delete.insert(info.csr_id); 72 | } 73 | 74 | //------------------------------------------------------------------------------ 75 | // Register functions 76 | //------------------------------------------------------------------------------ 77 | void CoreScalarFunctions::RegisterLocalClusteringCoefficientScalarFunction(ExtensionLoader &loader) { 78 | loader.RegisterFunction(ScalarFunction("local_clustering_coefficient", {LogicalType::INTEGER, LogicalType::BIGINT}, 79 | LogicalType::FLOAT, LocalClusteringCoefficientFunction, 80 | LocalClusteringCoefficientFunctionData::LocalClusteringCoefficientBind)); 81 | } 82 | 83 | } // namespace duckdb 84 | -------------------------------------------------------------------------------- /test/sql/pattern_matching/path_modes.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/pattern_matching/path_modes.test 2 | # description: Testing the path modes, most have not been implemented yet 3 | # group: [pattern_matching] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); 15 | 16 | statement ok 17 | CREATE TABLE School(name VARCHAR, Id BIGINT, Kind VARCHAR); 18 | 19 | statement ok 20 | CREATE TABLE StudyAt(personId BIGINT, schoolId BIGINT); 21 | 22 | statement ok 23 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 24 | 25 | statement ok 26 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 27 | 28 | statement ok 29 | INSERT INTO School VALUES ('VU', 0, 'University'), ('UVA', 1, 'University'); 30 | 31 | statement ok 32 | INSERT INTO StudyAt VALUES (0, 0), (1, 0), (2, 1), (3, 1), (4, 1); 33 | 34 | statement ok 35 | -CREATE PROPERTY GRAPH pg 36 | VERTEX TABLES ( 37 | Student PROPERTIES ( id, name ) LABEL Person, 38 | School LABEL SCHOOL 39 | ) 40 | EDGE TABLES ( 41 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 42 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 43 | LABEL Knows, 44 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 45 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 46 | LABEL StudyAt 47 | ); 48 | 49 | #statement ok 50 | #-FROM GRAPH_TABLE (pg 51 | # MATCH 52 | # p = ANY SHORTEST WALK PATH (a:Person)-[k:knows]-> *(b:Person) 53 | # WHERE a.name = 'Daniel' 54 | # COLUMNS (p, a.name as name, b.name as school) 55 | # ) study; 56 | 57 | #statement ok 58 | #-FROM GRAPH_TABLE (pg 59 | # MATCH 60 | # p = ANY SHORTEST (a:Person)-[k:knows]-> *(b:Person) 61 | # WHERE a.name = 'Daniel' 62 | # COLUMNS (p, a.name as name, b.name as school) 63 | # ) study; 64 | 65 | statement error 66 | -FROM GRAPH_TABLE (pg 67 | MATCH 68 | p = ALL SHORTEST (a:Person)-[k:knows]-> *(b:Person) 69 | WHERE a.name = 'Daniel' 70 | COLUMNS (p, a.name as name, b.name as school) 71 | ) study; 72 | ---- 73 | Not implemented Error: ALL SHORTEST has not been implemented yet. 74 | 75 | statement error 76 | -FROM GRAPH_TABLE (pg 77 | MATCH 78 | p = ANY SHORTEST TRAIL (a:Person)-[k:knows]-> *(b:Person) 79 | WHERE a.name = 'Daniel' 80 | COLUMNS (p, a.name as name, b.name as school) 81 | ) study; 82 | ---- 83 | Not implemented Error: Path modes other than WALK have not been implemented yet. 84 | 85 | statement error 86 | -FROM GRAPH_TABLE (pg 87 | MATCH 88 | p = ANY SHORTEST ACYCLIC (a:Person)-[k:knows]-> *(b:Person) 89 | WHERE a.name = 'Daniel' 90 | COLUMNS (p, a.name as name, b.name as school) 91 | ) study; 92 | ---- 93 | Not implemented Error: Path modes other than WALK have not been implemented yet. 94 | 95 | ## https://github.com/cwida/duckpgq-extension/issues/46 96 | ##statement error 97 | ##-FROM GRAPH_TABLE (pg 98 | ## MATCH 99 | ## p = ANY SHORTEST 5 WALK (a:Person)-[k:knows]-> *(b:Person) 100 | ## WHERE a.name = 'Daniel' 101 | ## COLUMNS (p, a.name as name, b.name as school) 102 | ## ) study; 103 | ##---- 104 | ##Not implemented Error: TopK has not been implemented yet. 105 | 106 | 107 | statement error 108 | -FROM GRAPH_TABLE (pg 109 | MATCH 110 | p = ANY SHORTEST SIMPLE (a:Person)-[k:knows]-> *(b:Person) 111 | WHERE a.name = 'Daniel' 112 | COLUMNS (p, a.name as name, b.name as school) 113 | ) study; 114 | ---- 115 | Not implemented Error: Path modes other than WALK have not been implemented yet. 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /test/sql/copy_to_duckpgq.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/copy_to_duckpgq.test 2 | # description: Testing the COPY TO query with a PGQ pattern 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | import database 'duckdb/data/SNB0.003' 9 | 10 | statement ok 11 | -CREATE PROPERTY GRAPH snb 12 | VERTEX TABLES ( 13 | Person LABEL Person, 14 | Forum LABEL Forum, 15 | Organisation LABEL Organisation IN typemask(company, university), 16 | Place LABEL Place, 17 | Tag LABEL Tag, 18 | TagClass LABEL TagClass, 19 | Country LABEL Country, 20 | City LABEL City, 21 | Message LABEL Message 22 | ) 23 | EDGE TABLES ( 24 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 25 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 26 | LABEL Knows, 27 | Forum_hasMember_Person SOURCE KEY (ForumId) REFERENCES Forum (id) 28 | DESTINATION KEY (PersonId) REFERENCES Person (id) 29 | LABEL hasMember, 30 | Forum_hasTag_Tag SOURCE KEY (ForumId) REFERENCES Forum (id) 31 | DESTINATION KEY (TagId) REFERENCES Tag (id) 32 | LABEL Forum_hasTag, 33 | Person_hasInterest_Tag SOURCE KEY (PersonId) REFERENCES Person (id) 34 | DESTINATION KEY (TagId) REFERENCES Tag (id) 35 | LABEL hasInterest, 36 | person_workAt_Organisation SOURCE KEY (PersonId) REFERENCES Person (id) 37 | DESTINATION KEY (OrganisationId) REFERENCES Organisation (id) 38 | LABEL workAt_Organisation, 39 | Person_likes_Message SOURCE KEY (PersonId) REFERENCES Person (id) 40 | DESTINATION KEY (id) REFERENCES Message (id) 41 | LABEL likes_Message, 42 | Message_hasTag_Tag SOURCE KEY (id) REFERENCES Message (id) 43 | DESTINATION KEY (TagId) REFERENCES Tag (id) 44 | LABEL message_hasTag, 45 | Message_hasAuthor_Person SOURCE KEY (messageId) REFERENCES Message (id) 46 | DESTINATION KEY (PersonId) REFERENCES Person (id) 47 | LABEL hasAuthor, 48 | Message_replyOf_Message SOURCE KEY (messageId) REFERENCES Message (id) 49 | DESTINATION KEY (ParentMessageId) REFERENCES Message (id) 50 | LABEL replyOf 51 | ); 52 | 53 | # IS1 54 | statement ok 55 | -COPY (FROM GRAPH_TABLE (snb 56 | MATCH (a is person where a.id = 17592186044461) 57 | COLUMNS(a.firstName, a.lastName, a.birthday, a.locationIP, a.browserUsed, a.LocationCityId, a.gender) 58 | ) tmp) TO '__TEST_DIR__/is1.csv' (HEADER FALSE); 59 | 60 | query IIIIIII 61 | SELECT * FROM '__TEST_DIR__/is1.csv'; 62 | ---- 63 | Ali Abouba 1987-05-29 41.203.147.168 Internet Explorer 1264 male 64 | 65 | statement ok 66 | -CREATE TABLE result as (FROM GRAPH_TABLE (snb 67 | MATCH (a is person where a.id = 17592186044461) 68 | COLUMNS(a.firstName, a.lastName, a.birthday, a.locationIP, a.browserUsed, a.LocationCityId, a.gender, a.creationDate) 69 | ) tmp); 70 | 71 | query IIIIIIII 72 | SELECT * FROM result; 73 | ---- 74 | Ali Abouba 1987-05-29 41.203.147.168 Internet Explorer 1264 male 2011-05-12 02:46:47.595+00 75 | 76 | statement ok 77 | -INSERT INTO result (FROM GRAPH_TABLE (snb 78 | MATCH (a is person where a.id = 17592186044461) 79 | COLUMNS(a.firstName, a.lastName, a.birthday, a.locationIP, a.browserUsed, a.LocationCityId, a.gender, a.creationDate) 80 | ) tmp) 81 | 82 | query IIIIIIII 83 | SELECT * FROM result; 84 | ---- 85 | Ali Abouba 1987-05-29 41.203.147.168 Internet Explorer 1264 male 2011-05-12 02:46:47.595+00 86 | Ali Abouba 1987-05-29 41.203.147.168 Internet Explorer 1264 male 2011-05-12 02:46:47.595+00 87 | -------------------------------------------------------------------------------- /test/sql/path_finding/undirected_paths.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/undirected_paths.test 2 | # description: Testing undirected path-finding 3 | # group: [path_finding] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | CREATE TABLE Student(id BIGINT, name VARCHAR);INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 9 | 10 | statement ok 11 | CREATE TABLE know(src BIGINT, dst BIGINT, id BIGINT);INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17), (2, 4, 18); 12 | 13 | statement ok 14 | -CREATE PROPERTY GRAPH pg 15 | VERTEX TABLES ( 16 | Student 17 | ) 18 | EDGE TABLES ( 19 | know SOURCE KEY (src) REFERENCES Student (id) 20 | DESTINATION KEY (dst) REFERENCES Student (id) 21 | ); 22 | 23 | query III 24 | -FROM GRAPH_TABLE (pg 25 | MATCH 26 | o = ANY SHORTEST (a:Student WHERE a.id = 0)-[e:know]- *(b:Student) 27 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 28 | ) study 29 | ORDER BY a_id, b_id; 30 | ---- 31 | 0 0 0 32 | 0 1 1 33 | 0 2 1 34 | 0 3 1 35 | 0 4 2 36 | 37 | query III 38 | -FROM GRAPH_TABLE (pg 39 | MATCH 40 | o = ANY SHORTEST (a:Student WHERE a.id = 4)-[e:know]- *(b:Student) 41 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 42 | ) study 43 | ORDER BY a_id, b_id; 44 | ---- 45 | 4 0 2 46 | 4 1 2 47 | 4 2 1 48 | 4 3 1 49 | 4 4 0 50 | 51 | statement error 52 | -FROM GRAPH_TABLE (pg 53 | MATCH 54 | o = ANY SHORTEST (a:Student WHERE a.id = 4)<-[e:know]- *(b:Student) 55 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 56 | ) study 57 | ORDER BY a_id, b_id; 58 | ---- 59 | Cannot do shortest path for edge type MATCH_EDGE_LEFT 60 | 61 | statement error 62 | -FROM GRAPH_TABLE (pg 63 | MATCH 64 | o = ANY SHORTEST (a:Student WHERE a.id = 4)<-[e:know]-> *(b:Student) 65 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 66 | ) study 67 | ORDER BY a_id, b_id; 68 | ---- 69 | Cannot do shortest path for edge type MATCH_EDGE_LEFT_RIGHT 70 | 71 | query II 72 | -FROM GRAPH_TABLE (pg 73 | MATCH 74 | (a:Student WHERE a.id = 4)-[e:know]-{0,1}(b:Student) 75 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 76 | ) study 77 | ORDER BY a_id, b_id; 78 | ---- 79 | 4 2 80 | 4 3 81 | 82 | query III 83 | -FROM GRAPH_TABLE (pg 84 | MATCH 85 | o = ANY SHORTEST (a:Student WHERE a.id = 999)-[e:know]- *(b:Student) 86 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 87 | ) study 88 | ORDER BY a_id, b_id; 89 | ---- 90 | 91 | query III 92 | -FROM GRAPH_TABLE (pg 93 | MATCH 94 | o = ANY SHORTEST (a:Student)-[e:know]- *(b:Student) 95 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 96 | ) study 97 | ORDER BY a_id, b_id; 98 | ---- 99 | 0 0 0 100 | 0 1 1 101 | 0 2 1 102 | 0 3 1 103 | 0 4 2 104 | 1 0 1 105 | 1 1 0 106 | 1 2 1 107 | 1 3 1 108 | 1 4 2 109 | 2 0 1 110 | 2 1 1 111 | 2 2 0 112 | 2 3 1 113 | 2 4 1 114 | 3 0 1 115 | 3 1 1 116 | 3 2 1 117 | 3 3 0 118 | 3 4 1 119 | 4 0 2 120 | 4 1 2 121 | 4 2 1 122 | 4 3 1 123 | 4 4 0 124 | 125 | query III 126 | -FROM GRAPH_TABLE (pg 127 | MATCH 128 | o = ANY SHORTEST (a:Student WHERE a.id = 3)-[e:know]- *(b:Student WHERE b.id = 3) 129 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 130 | ) study 131 | ORDER BY a_id, b_id; 132 | ---- 133 | 3 3 0 134 | 135 | query III 136 | -FROM GRAPH_TABLE (pg 137 | MATCH 138 | o = ANY SHORTEST (a:Student WHERE a.id = 0)-[e:know]- *(b:Student WHERE b.id = 5) 139 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 140 | ) study 141 | ORDER BY a_id, b_id; 142 | ---- 143 | 144 | query III 145 | -FROM GRAPH_TABLE (pg 146 | MATCH 147 | o = ANY SHORTEST (a:Student WHERE a.id = 0)-[e:know]-{0,2}(b:Student) 148 | COLUMNS (a.id as a_id, b.id as b_id, path_length(o)) 149 | ) study 150 | ORDER BY a_id, b_id; 151 | ---- 152 | 0 0 0 153 | 0 1 1 154 | 0 2 1 155 | 0 3 1 156 | 0 4 2 157 | -------------------------------------------------------------------------------- /test/sql/path_finding/parser_arrow_kleene.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/parser_arrow_kleene.test 2 | # group: [path_finding] 3 | 4 | require duckpgq 5 | 6 | statement ok 7 | CREATE TABLE nodes (id INTEGER); INSERT INTO nodes VALUES (1), (2), (3); 8 | 9 | statement ok 10 | CREATE TABLE edges (src INTEGER, dst INTEGER); 11 | 12 | statement ok 13 | -CREATE PROPERTY GRAPH testgraph 14 | VERTEX TABLES ( 15 | nodes LABEL N 16 | ) 17 | EDGE TABLES ( 18 | edges SOURCE KEY (src) REFERENCES nodes (id) 19 | DESTINATION KEY (dst) REFERENCES nodes (id) 20 | LABEL E 21 | ); 22 | 23 | statement ok 24 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E]->*(n2:N) COLUMNS (n1.*, n2.*)); 25 | 26 | statement ok 27 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E]-> *(n2:N) COLUMNS (n1.*, n2.*)); 28 | 29 | statement ok 30 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E]- > *(n2:N) COLUMNS (n1.*, n2.*)); 31 | 32 | statement ok 33 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E] - > *(n2:N) COLUMNS (n1.*, n2.*)); 34 | 35 | statement ok 36 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E] -> *(n2:N) COLUMNS (n1.*, n2.*)); 37 | 38 | statement error 39 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E]< -> *(n2:N) COLUMNS (n1.*, n2.*)); 40 | ---- 41 | Parser Error: syntax error at or near "<" 42 | 43 | statement error 44 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E] /-> *(n2:N)); 45 | ---- 46 | Parser Error: syntax error at or near "/->" 47 | 48 | # Not yet supported 49 | statement error 50 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-> *(n2:N)); 51 | ---- 52 | Constraint Error: All patterns must bind to a label 53 | 54 | statement error 55 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[]-> *(n2:N)); 56 | ---- 57 | Constraint Error: All patterns must bind to a label 58 | 59 | statement error 60 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[]- > *(n2:N)); 61 | ---- 62 | Constraint Error: All patterns must bind to a label 63 | 64 | statement error 65 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e2:E] > *(n2:N)); 66 | ---- 67 | Parser Error: syntax error at or near ">" 68 | 69 | statement error 70 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E]*(n2:N)); 71 | ---- 72 | Parser Error: syntax error at or near "*" 73 | 74 | statement ok 75 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E]- > *(n2:N) COLUMNS (n1.*, n2.*)); 76 | 77 | statement error 78 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)-[e:E]<- *(n2:N)); 79 | ---- 80 | Parser Error: syntax error at or near "<" 81 | 82 | statement error 83 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)<-> *(n2:N)); 84 | ---- 85 | Constraint Error: All patterns must bind to a label 86 | 87 | statement error 88 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)< - > *(n2:N)); 89 | ---- 90 | Constraint Error: All patterns must bind to a label 91 | 92 | statement error 93 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)<- *(n2:N)); 94 | ---- 95 | Constraint Error: All patterns must bind to a label 96 | 97 | statement error 98 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)< - *(n2:N)); 99 | ---- 100 | Constraint Error: All patterns must bind to a label 101 | 102 | statement error 103 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)< -*(n2:N)); 104 | ---- 105 | Constraint Error: All patterns must bind to a label 106 | 107 | statement error 108 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)< -?(n2:N)); 109 | ---- 110 | Constraint Error: All patterns must bind to a label 111 | 112 | statement ok 113 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)<-[e1:E]-?(n2:N)); 114 | 115 | statement ok 116 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)<-[e1:E] - ?(n2:N)); 117 | 118 | statement ok 119 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)<-[e1:E] -> ?(n2:N)); 120 | 121 | statement ok 122 | -FROM GRAPH_TABLE (testgraph MATCH ANY SHORTEST (n1:N)<-[e1:E] - >?(n2:N)); -------------------------------------------------------------------------------- /test/sql/path_finding/subpath_match.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/subpath_match.test 2 | # description: Testing the subpath matching 3 | # group: [path_finding] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, id BIGINT); 15 | 16 | statement ok 17 | CREATE TABLE School(school_name VARCHAR, school_id BIGINT, school_kind BIGINT); 18 | 19 | statement ok 20 | INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 21 | 22 | statement ok 23 | INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17), (2, 4, 18); 24 | 25 | statement ok 26 | -CREATE PROPERTY GRAPH pg 27 | VERTEX TABLES ( 28 | Student PROPERTIES ( id, name ) LABEL Person 29 | ) 30 | EDGE TABLES ( 31 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 32 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 33 | PROPERTIES ( id ) LABEL Knows 34 | ); 35 | 36 | query II 37 | -SELECT study.a_id, study.name 38 | FROM GRAPH_TABLE (pg 39 | MATCH 40 | (a:Person WHERE a.id = 0) 41 | COLUMNS (a.id as a_id, a.name) 42 | ) study 43 | ---- 44 | 0 Daniel 45 | 46 | query II 47 | -SELECT study.a_id, study.b_id 48 | FROM GRAPH_TABLE (pg 49 | MATCH 50 | (a:Person)-[k:Knows WHERE k.id = 10]->(b:Person) 51 | COLUMNS (a.id as a_id, b.id as b_id) 52 | ) study 53 | ---- 54 | 0 1 55 | 56 | #query II 57 | #WITH cte1 AS ( 58 | # SELECT CREATE_CSR_EDGE( 59 | # 0, 60 | # (SELECT count(a.id) FROM Student a), 61 | # CAST ( 62 | # (SELECT sum(CREATE_CSR_VERTEX( 63 | # 0, 64 | # (SELECT count(a.id) FROM Student a), 65 | # sub.dense_id, 66 | # sub.cnt) 67 | # ) 68 | # FROM ( 69 | # SELECT a.rowid as dense_id, count(k.src) as cnt 70 | # FROM Student a 71 | # LEFT JOIN Know k ON k.src = a.id 72 | # GROUP BY a.rowid) sub 73 | # ) 74 | # AS BIGINT), 75 | # a.rowid, 76 | # c.rowid, 77 | # k.rowid) as temp 78 | # FROM Know k 79 | # JOIN student a on a.id = k.src 80 | # JOIN student c on c.id = k.dst 81 | #) SELECT __p.a_name, __p.b_name 82 | #FROM (SELECT count(temp) * 0 AS temp FROM cte1) x, (SELECT a.name as a_name, a.rowid as __src, b.name as b_name, b.rowid as __dst FROM student a, student b WHERE a.name = 'Peter') __p 83 | #WHERE x.temp + iterativelength(0, (SELECT count(c.id) FROM student c), __p.__src, __p.__dst) BETWEEN 0 and 10000 84 | #---- 85 | #Peter Daniel 86 | #Peter Tavneet 87 | #Peter Gabor 88 | #Peter Peter 89 | #Peter David 90 | 91 | statement error 92 | -SELECT study.a_name, study.b_name 93 | FROM GRAPH_TABLE (pg 94 | MATCH 95 | (a:Person WHERE a.name = 'Peter')-[k:Knows]-> *(b:Person) 96 | COLUMNS (a.name as a_name, b.name as b_name) 97 | ) study 98 | ---- 99 | Constraint Error: ALL unbounded with path mode WALK is not possible as this could lead to infinite results. Consider specifying an upper bound or path mode other than WALK 100 | 101 | 102 | query II 103 | -SELECT study.a_name, study.b_name 104 | FROM GRAPH_TABLE (pg 105 | MATCH 106 | (a:Person)-[k:Knows]->{1,2}(b:Person) 107 | WHERE a.name = 'Peter' 108 | COLUMNS (a.name as a_name, b.name as b_name) 109 | ) study 110 | ---- 111 | Peter Daniel 112 | Peter Tavneet 113 | Peter Gabor 114 | 115 | statement error 116 | -SELECT study.a_name, study.b_name 117 | FROM GRAPH_TABLE (pg 118 | MATCH 119 | (a:Person)-[k:Knows]-> +(b:Person) 120 | WHERE a.name = 'Peter' 121 | COLUMNS (a.name as a_name, b.name as b_name) 122 | ) study 123 | ---- 124 | Constraint Error: ALL unbounded with path mode WALK is not possible as this could lead to infinite results. Consider specifying an upper bound or path mode other than WALK 125 | -------------------------------------------------------------------------------- /src/include/duckpgq/core/utils/compressed_sparse_row.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckPGQ 3 | // 4 | // duckpgq/core/utils/compressed_sparse_row.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | #include "duckdb/function/function.hpp" 11 | 12 | #include "duckdb/parser/expression/cast_expression.hpp" 13 | #include "duckdb/parser/expression/function_expression.hpp" 14 | #include "duckdb/parser/expression/subquery_expression.hpp" 15 | #include "duckdb/parser/query_node/set_operation_node.hpp" 16 | 17 | #include "duckdb/parser/expression/columnref_expression.hpp" 18 | #include "duckdb/parser/property_graph_table.hpp" 19 | #include "duckdb/parser/query_node/select_node.hpp" 20 | #include "duckdb/parser/tableref/joinref.hpp" 21 | #include "duckpgq/common.hpp" 22 | 23 | namespace duckdb { 24 | 25 | class CSR { 26 | public: 27 | CSR() = default; 28 | ~CSR() { 29 | delete[] v; 30 | } 31 | 32 | atomic *v {}; 33 | 34 | vector e; 35 | vector edge_ids; 36 | 37 | vector w; 38 | vector w_double; 39 | 40 | bool initialized_v = false; 41 | bool initialized_e = false; 42 | bool initialized_w = false; 43 | 44 | size_t vsize {}; 45 | 46 | string ToString() const; 47 | }; 48 | 49 | struct CSRFunctionData : FunctionData { 50 | CSRFunctionData(ClientContext &context, int32_t id, const LogicalType &weight_type); 51 | unique_ptr Copy() const override; 52 | bool Equals(const FunctionData &other_p) const override; 53 | static unique_ptr CSRVertexBind(ClientContext &context, ScalarFunction &bound_function, 54 | vector> &arguments); 55 | static unique_ptr CSREdgeBind(ClientContext &context, ScalarFunction &bound_function, 56 | vector> &arguments); 57 | static unique_ptr CSRBind(ClientContext &context, ScalarFunction &bound_function, 58 | vector> &arguments); 59 | 60 | ClientContext &context; 61 | const int32_t id; 62 | const LogicalType weight_type; 63 | }; 64 | 65 | // CSR BindReplace functions 66 | unique_ptr CreateUndirectedCSRCTE(const shared_ptr &edge_table, 67 | const unique_ptr &select_node); 68 | unique_ptr CreateDirectedCSRCTE(const shared_ptr &edge_table, 69 | const string &prev_binding, const string &edge_binding, 70 | const string &next_binding); 71 | 72 | // Helper functions 73 | unique_ptr MakeEdgesCTE(const shared_ptr &edge_table); 74 | unique_ptr CreateDirectedCSRVertexSubquery(const shared_ptr &edge_table, 75 | const string &binding); 76 | unique_ptr CreateUndirectedCSRVertexSubquery(const shared_ptr &edge_table, 77 | const string &binding); 78 | unique_ptr CreateOuterSelectEdgesNode(); 79 | unique_ptr CreateOuterSelectNode(unique_ptr create_csr_edge_function); 80 | unique_ptr GetJoinRef(const shared_ptr &edge_table, const string &edge_binding, 81 | const string &prev_binding, const string &next_binding); 82 | unique_ptr GetCountTable(const shared_ptr &table, const string &table_alias, 83 | const string &primary_key); 84 | void SetupSelectNode(unique_ptr &select_node, const shared_ptr &edge_table, 85 | bool reverse = false); 86 | unique_ptr CreateCountCTESubquery(); 87 | unique_ptr GetCountUndirectedEdgeTable(); 88 | unique_ptr GetCountEdgeTable(const shared_ptr &edge_table); 89 | 90 | } // namespace duckdb 91 | -------------------------------------------------------------------------------- /test/sql/multiple_graph_table.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/multiple_graph_table.test 2 | # description: Testing multiple graph tables in a single query 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | statement ok 8 | CREATE TABLE Student(id BIGINT, name VARCHAR);INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 9 | 10 | statement ok 11 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT);INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 12 | 13 | statement ok 14 | CREATE TABLE School(name VARCHAR, Id BIGINT, Kind VARCHAR);INSERT INTO School VALUES ('VU', 0, 'University'), ('UVA', 1, 'University'); 15 | 16 | statement ok 17 | CREATE TABLE StudyAt(personId BIGINT, schoolId BIGINT);INSERT INTO StudyAt VALUES (0, 0), (1, 0), (2, 1), (3, 1), (4, 1); 18 | 19 | statement ok 20 | -CREATE PROPERTY GRAPH pg 21 | VERTEX TABLES ( 22 | Student, 23 | School 24 | ) 25 | EDGE TABLES ( 26 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 27 | DESTINATION KEY ( dst ) REFERENCES Student ( id ), 28 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 29 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 30 | ); 31 | 32 | query II 33 | -select a.id, b.id FROM GRAPH_TABLE(pg MATCH (a:student)) a, GRAPH_TABLE(pg MATCH (b:student)) b; 34 | ---- 35 | 0 0 36 | 1 0 37 | 2 0 38 | 3 0 39 | 4 0 40 | 0 1 41 | 1 1 42 | 2 1 43 | 3 1 44 | 4 1 45 | 0 2 46 | 1 2 47 | 2 2 48 | 3 2 49 | 4 2 50 | 0 3 51 | 1 3 52 | 2 3 53 | 3 3 54 | 4 3 55 | 0 4 56 | 1 4 57 | 2 4 58 | 3 4 59 | 4 4 60 | 61 | query II 62 | -select unnamed_subquery.id, unnamed_subquery2.id FROM GRAPH_TABLE(pg MATCH (a:student)), GRAPH_TABLE(pg MATCH (b:student)); 63 | ---- 64 | 0 0 65 | 1 0 66 | 2 0 67 | 3 0 68 | 4 0 69 | 0 1 70 | 1 1 71 | 2 1 72 | 3 1 73 | 4 1 74 | 0 2 75 | 1 2 76 | 2 2 77 | 3 2 78 | 4 2 79 | 0 3 80 | 1 3 81 | 2 3 82 | 3 3 83 | 4 3 84 | 0 4 85 | 1 4 86 | 2 4 87 | 3 4 88 | 4 4 89 | 90 | 91 | query IIIIII 92 | -select a.id, a.name, unnamed_subquery.b_id, unnamed_subquery.b_name, unnamed_subquery.C_id, unnamed_subquery.c_name 93 | FROM GRAPH_TABLE(pg MATCH (a:student)) a, 94 | GRAPH_TABLE(pg 95 | MATCH (b:student)-[r:know]->(c:student) 96 | COLUMNS (b.id as b_id, b.name as b_name, c.name as c_name, c.id as c_id) 97 | ); 98 | ---- 99 | 0 Daniel 0 Daniel 1 Tavneet 100 | 0 Daniel 0 Daniel 2 Gabor 101 | 0 Daniel 0 Daniel 3 Peter 102 | 0 Daniel 3 Peter 0 Daniel 103 | 0 Daniel 1 Tavneet 2 Gabor 104 | 0 Daniel 1 Tavneet 3 Peter 105 | 0 Daniel 2 Gabor 3 Peter 106 | 0 Daniel 4 David 3 Peter 107 | 1 Tavneet 0 Daniel 1 Tavneet 108 | 1 Tavneet 0 Daniel 2 Gabor 109 | 1 Tavneet 0 Daniel 3 Peter 110 | 1 Tavneet 3 Peter 0 Daniel 111 | 1 Tavneet 1 Tavneet 2 Gabor 112 | 1 Tavneet 1 Tavneet 3 Peter 113 | 1 Tavneet 2 Gabor 3 Peter 114 | 1 Tavneet 4 David 3 Peter 115 | 2 Gabor 0 Daniel 1 Tavneet 116 | 2 Gabor 0 Daniel 2 Gabor 117 | 2 Gabor 0 Daniel 3 Peter 118 | 2 Gabor 3 Peter 0 Daniel 119 | 2 Gabor 1 Tavneet 2 Gabor 120 | 2 Gabor 1 Tavneet 3 Peter 121 | 2 Gabor 2 Gabor 3 Peter 122 | 2 Gabor 4 David 3 Peter 123 | 3 Peter 0 Daniel 1 Tavneet 124 | 3 Peter 0 Daniel 2 Gabor 125 | 3 Peter 0 Daniel 3 Peter 126 | 3 Peter 3 Peter 0 Daniel 127 | 3 Peter 1 Tavneet 2 Gabor 128 | 3 Peter 1 Tavneet 3 Peter 129 | 3 Peter 2 Gabor 3 Peter 130 | 3 Peter 4 David 3 Peter 131 | 4 David 0 Daniel 1 Tavneet 132 | 4 David 0 Daniel 2 Gabor 133 | 4 David 0 Daniel 3 Peter 134 | 4 David 3 Peter 0 Daniel 135 | 4 David 1 Tavneet 2 Gabor 136 | 4 David 1 Tavneet 3 Peter 137 | 4 David 2 Gabor 3 Peter 138 | 4 David 4 David 3 Peter 139 | 140 | query II 141 | -select unnamed_subquery.id, unnamed_subquery2.id 142 | FROM GRAPH_TABLE(pg MATCH (a:student)), (select 1 as id); 143 | ---- 144 | 0 1 145 | 1 1 146 | 2 1 147 | 3 1 148 | 4 1 149 | 150 | statement ok 151 | CREATE TABLE cities ( 152 | name VARCHAR, 153 | lat DECIMAL, 154 | lon DECIMAL 155 | ); 156 | 157 | statement ok 158 | CREATE TABLE cities_are_adjacent ( 159 | city1name VARCHAR, 160 | city2name VARCHAR 161 | ); 162 | 163 | 164 | statement ok 165 | -CREATE PROPERTY GRAPH citymap 166 | VERTEX TABLES ( 167 | cities PROPERTIES (name,lat,lon) LABEL city 168 | ) 169 | EDGE TABLES ( 170 | cities_are_adjacent SOURCE KEY ( city1name ) REFERENCES cities ( name ) 171 | DESTINATION KEY ( city2name ) REFERENCES cities ( name ) 172 | LABEL adjacent 173 | ); 174 | 175 | statement ok 176 | -select * from GRAPH_TABLE (citymap MATCH (s:city)-[r:adjacent]->(t:city)) g1, GRAPH_TABLE (citymap MATCH (s:city)-[r:adjacent]->(t:city)) g2; 177 | 178 | -------------------------------------------------------------------------------- /test/sql/path_finding/shortest_path.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/path_finding/shortest_path.test 2 | # description: Testing the shortest path matching 3 | # group: [path_finding] 4 | 5 | #statement ok 6 | #pragma enable_verification 7 | 8 | require duckpgq 9 | 10 | statement ok 11 | CREATE TABLE Student(id BIGINT, name VARCHAR); INSERT INTO Student VALUES (0, 'Daniel'), (1, 'Tavneet'), (2, 'Gabor'), (3, 'Peter'), (4, 'David'); 12 | 13 | statement ok 14 | CREATE TABLE know(src BIGINT, dst BIGINT, createDate BIGINT); INSERT INTO know VALUES (0,1, 10), (0,2, 11), (0,3, 12), (3,0, 13), (1,2, 14), (1,3, 15), (2,3, 16), (4,3, 17); 15 | 16 | statement ok 17 | CREATE TABLE School(name VARCHAR, Id BIGINT, Kind VARCHAR); INSERT INTO School VALUES ('VU', 0, 'University'), ('UVA', 1, 'University'); 18 | 19 | statement ok 20 | CREATE TABLE StudyAt(personId BIGINT, schoolId BIGINT); INSERT INTO StudyAt VALUES (0, 0), (1, 0), (2, 1), (3, 1), (4, 1); 21 | 22 | statement ok 23 | -CREATE PROPERTY GRAPH pg 24 | VERTEX TABLES ( 25 | Student PROPERTIES ( id, name ) LABEL Person, 26 | School LABEL SCHOOL 27 | ) 28 | EDGE TABLES ( 29 | know SOURCE KEY ( src ) REFERENCES Student ( id ) 30 | DESTINATION KEY ( dst ) REFERENCES Student ( id ) 31 | LABEL Knows, 32 | studyAt SOURCE KEY ( personId ) REFERENCES Student ( id ) 33 | DESTINATION KEY ( SchoolId ) REFERENCES School ( id ) 34 | LABEL StudyAt 35 | ); 36 | 37 | query II 38 | -FROM GRAPH_TABLE (pg 39 | MATCH 40 | ANY SHORTEST (a:Person)-[s:StudyAt]->(b:School) 41 | WHERE a.name = 'Daniel' 42 | COLUMNS (a.name as name, b.name as school) 43 | ) study; 44 | ---- 45 | Daniel VU 46 | 47 | query III 48 | -FROM GRAPH_TABLE (pg 49 | MATCH 50 | p = ANY SHORTEST (a:Person WHERE a.name = 'Daniel')-[k:knows]->{1,3}(b:Person) 51 | COLUMNS (element_id(p), a.name as name, b.name as b_name) 52 | ) study 53 | ORDER BY name, b_name; 54 | ---- 55 | [0, 1, 2] Daniel Gabor 56 | [0, 2, 3] Daniel Peter 57 | [0, 0, 1] Daniel Tavneet 58 | 59 | query IIII 60 | -FROM GRAPH_TABLE (pg 61 | MATCH 62 | p = ANY SHORTEST (a:Person)-[k:knows]->{1,3}(b:Person) 63 | COLUMNS (path_length(p), element_id(p), a.name as name, b.name as b_name) 64 | ) study 65 | order by study.name, study.b_name; 66 | ---- 67 | 1 [0, 1, 2] Daniel Gabor 68 | 1 [0, 2, 3] Daniel Peter 69 | 1 [0, 0, 1] Daniel Tavneet 70 | 2 [4, 7, 3, 3, 0] David Daniel 71 | 3 [4, 7, 3, 3, 0, 1, 2] David Gabor 72 | 1 [4, 7, 3] David Peter 73 | 3 [4, 7, 3, 3, 0, 0, 1] David Tavneet 74 | 2 [2, 6, 3, 3, 0] Gabor Daniel 75 | 1 [2, 6, 3] Gabor Peter 76 | 3 [2, 6, 3, 3, 0, 0, 1] Gabor Tavneet 77 | 1 [3, 3, 0] Peter Daniel 78 | 2 [3, 3, 0, 1, 2] Peter Gabor 79 | 2 [3, 3, 0, 0, 1] Peter Tavneet 80 | 2 [1, 5, 3, 3, 0] Tavneet Daniel 81 | 1 [1, 4, 2] Tavneet Gabor 82 | 1 [1, 5, 3] Tavneet Peter 83 | 84 | 85 | statement error 86 | -FROM GRAPH_TABLE (pg 87 | MATCH 88 | p = ANY SHORTEST (a:Person)-[k:knows]->{1,3}(b:Person) 89 | WHERE a.name = 'Daniel' 90 | COLUMNS (p, a.name as name, b.name as b_name) 91 | ) study; 92 | ---- 93 | Binder Error: Property p is never registered! 94 | 95 | 96 | query III 97 | WITH cte1 AS ( 98 | SELECT CREATE_CSR_EDGE( 99 | 0, 100 | (SELECT count(a.id) FROM Student a), 101 | CAST ( 102 | (SELECT sum(CREATE_CSR_VERTEX( 103 | 0, 104 | (SELECT count(a.id) FROM Student a), 105 | sub.dense_id, 106 | sub.cnt) 107 | ) 108 | FROM ( 109 | SELECT a.rowid as dense_id, count(k.src) as cnt 110 | FROM Student a 111 | LEFT JOIN Know k ON k.src = a.id 112 | GROUP BY a.rowid) sub 113 | ) 114 | AS BIGINT), 115 | (select count(*) from know k JOIN student a on a.id = k.src JOIN student c on c.id = k.dst), 116 | a.rowid, 117 | c.rowid, 118 | k.rowid) as temp 119 | FROM Know k 120 | JOIN student a on a.id = k.src 121 | JOIN student c on c.id = k.dst 122 | ) SELECT shortestpath(0, (select count(*) from student), a.rowid, b.rowid) as path, a.name as a_name, b.name as b_name 123 | FROM student a, student b, (select count(cte1.temp) * 0 as temp from cte1) __x 124 | WHERE a.name = 'Daniel' and __x.temp * 0 + iterativelength(0, (select count(*) from student), a.rowid, b.rowid) between 1 and 3 125 | ---- 126 | [0, 0, 1] Daniel Tavneet 127 | [0, 1, 2] Daniel Gabor 128 | [0, 2, 3] Daniel Peter 129 | -------------------------------------------------------------------------------- /test/sql/label_optional.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/label_optional.test 2 | # description: Testing the optional label for property graph creation 3 | # group: [sql] 4 | 5 | require duckpgq 6 | 7 | # Test with a different number of vertices and edges 8 | statement ok 9 | CREATE TABLE VariedStudent(id BIGINT, name VARCHAR);INSERT INTO VariedStudent VALUES (0, 'Alice'), (1, 'Bob'), (2, 'Charlie'), (3, 'Dave'), (4, 'Eve'), (5, 'Frank'); 10 | 11 | statement ok 12 | CREATE TABLE VariedKnow(src BIGINT, dst BIGINT);INSERT INTO VariedKnow VALUES (0,1), (0,2), (0,3), (1,2), (2,3), (3,4), (4,5); 13 | 14 | query II 15 | SELECT * FROM (WITH edges_cte AS (SELECT src_table.rowid AS src, dst_table.rowid AS dst, VariedKnow.rowid AS edges FROM VariedKnow INNER JOIN VariedStudent AS src_table ON ((VariedKnow.src = src_table.id)) INNER JOIN VariedStudent AS dst_table ON ((VariedKnow.dst = dst_table.id))), csr_cte AS (SELECT create_csr_edge(0, (SELECT count(VariedStudent.id) FROM VariedStudent AS VariedStudent), CAST((SELECT multiply(2, sum(create_csr_vertex(0, (SELECT count(VariedStudent.id) FROM VariedStudent AS VariedStudent), sub.dense_id, sub.cnt))) FROM (SELECT dense_id, count(outgoing_edges) AS cnt FROM ((SELECT VariedStudent.rowid AS dense_id, VariedKnow.src AS outgoing_edges, VariedKnow.dst AS incoming_edges FROM VariedKnow INNER JOIN VariedStudent ON ((VariedKnow.src = VariedStudent.id))) UNION BY NAME (SELECT VariedStudent.rowid AS dense_id, VariedKnow.dst AS outgoing_edges, VariedKnow.src AS incoming_edges FROM VariedKnow INNER JOIN VariedStudent ON ((VariedKnow.dst = VariedStudent.id)))) AS unique_edges GROUP BY dense_id) AS sub) AS BIGINT), (SELECT multiply(2, count()) FROM ((SELECT src, dst FROM edges_cte) UNION BY NAME (SELECT dst AS src, src AS dst FROM edges_cte))), src, dst, edge) AS "temp" FROM (SELECT src, dst, any_value(edges) AS edge FROM ((SELECT src, dst, edges FROM edges_cte) UNION ALL (SELECT dst, src, edges FROM edges_cte)) GROUP BY src, dst))SELECT VariedStudent.id, "add"(__x."temp", local_clustering_coefficient(0, VariedStudent.rowid)) AS local_clustering_coefficient FROM VariedStudent CROSS JOIN (SELECT multiply(0, count(csr_cte."temp")) AS "temp" FROM csr_cte) AS __x) AS lcc; 16 | ---- 17 | 0 0.6666667 18 | 1 1.0 19 | 2 0.6666667 20 | 3 0.33333334 21 | 4 0.0 22 | 5 0.0 23 | 24 | statement ok 25 | -CREATE PROPERTY GRAPH varied_pg_label_a 26 | VERTEX TABLES ( 27 | VariedStudent label a 28 | ) 29 | EDGE TABLES ( 30 | VariedKnow SOURCE KEY ( src ) REFERENCES VariedStudent ( id ) 31 | DESTINATION KEY ( dst ) REFERENCES VariedStudent ( id ) 32 | ); 33 | 34 | 35 | query II 36 | select id, local_clustering_coefficient from local_clustering_coefficient(varied_pg_label_a, a, variedknow); 37 | ---- 38 | 0 0.6666667 39 | 1 1.0 40 | 2 0.6666667 41 | 3 0.33333334 42 | 4 0.0 43 | 5 0.0 44 | 45 | statement ok 46 | select * from pagerank(varied_pg_label_a, a, variedknow); 47 | 48 | statement error 49 | select id, local_clustering_coefficient from local_clustering_coefficient(varied_pg_label_a, variedStudent, variedknow); 50 | ---- 51 | Invalid Error: Label 'variedstudent' not found. Did you mean the vertex label 'a'? 52 | 53 | 54 | statement ok 55 | import database 'duckdb/data/SNB0.003'; 56 | 57 | statement ok 58 | -CREATE PROPERTY GRAPH snb 59 | VERTEX TABLES ( 60 | Person, 61 | Organisation IN typemask(company, university) 62 | ) 63 | EDGE TABLES ( 64 | Person_knows_person SOURCE KEY (Person1Id) REFERENCES Person (id) 65 | DESTINATION KEY (Person2Id) REFERENCES Person (id) 66 | LABEL Knows, 67 | person_workAt_Organisation SOURCE KEY (PersonId) REFERENCES Person (id) 68 | DESTINATION KEY (OrganisationId) REFERENCES Organisation (id) 69 | LABEL workAt_Organisation 70 | ); 71 | 72 | query III 73 | -FROM GRAPH_TABLE (snb 74 | MATCH (p:Person)-[w:workAt_Organisation]->(u:University) 75 | COLUMNS (p.id as p_id, u.id as u_id, u.type) 76 | ) tmp 77 | ORDER BY p_id, u_id 78 | limit 10; 79 | ---- 80 | 14 4593 University 81 | 16 5809 University 82 | 32 5047 University 83 | 2199023255557 1953 University 84 | 2199023255573 5263 University 85 | 2199023255594 1597 University 86 | 4398046511139 4929 University 87 | 6597069766702 5038 University 88 | 8796093022234 3008 University 89 | 8796093022244 3008 University 90 | 91 | 92 | query II 93 | -FROM GRAPH_TABLE (snb 94 | MATCH (p:Person)-[k:knows]->(p2:Person) 95 | COLUMNS (p.id as p_id, p2.id as p2_id) 96 | ) tmp 97 | order by p_id, p2_id 98 | limit 10; 99 | ---- 100 | 14 10995116277782 101 | 14 24189255811081 102 | 14 26388279066668 103 | 16 2199023255594 104 | 16 26388279066655 105 | 16 28587302322180 106 | 16 28587302322204 107 | 32 2199023255594 108 | 32 13194139533352 109 | 32 17592186044461 110 | -------------------------------------------------------------------------------- /src/core/functions/scalar/weakly_connected_component.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb/planner/expression/bound_function_expression.hpp" 2 | #include "duckpgq/common.hpp" 3 | #include "duckpgq/core/functions/function_data/local_clustering_coefficient_function_data.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace duckdb { 13 | 14 | // Helper function to find the root of a node with path compression 15 | const static int64_t FindTreeRoot(std::vector &forest, int64_t node) { 16 | while (true) { 17 | int64_t parent = forest[node]; 18 | if (parent == node) { 19 | return node; // Found the root 20 | } 21 | forest[node] = forest[parent]; 22 | node = parent; 23 | } 24 | } 25 | 26 | // Helper function to link two nodes in the same connected component 27 | const static void Link(std::vector &forest, int64_t nodeA, int64_t nodeB) { 28 | int64_t rootA = FindTreeRoot(forest, nodeA); 29 | int64_t rootB = FindTreeRoot(forest, nodeB); 30 | 31 | if (rootA != rootB) { 32 | forest[rootA] = rootB; 33 | } 34 | } 35 | 36 | static void WeaklyConnectedComponentFunction(DataChunk &args, ExpressionState &state, Vector &result) { 37 | auto &func_expr = state.expr.Cast(); 38 | auto &info = func_expr.bind_info->Cast(); 39 | auto duckpgq_state = GetDuckPGQState(info.context); 40 | 41 | auto csr_entry = duckpgq_state->csr_list.find(info.csr_id); 42 | if (csr_entry == duckpgq_state->csr_list.end()) { 43 | throw ConstraintException("CSR not found. Is the graph populated?"); 44 | } 45 | 46 | if (!(csr_entry->second->initialized_v && csr_entry->second->initialized_e)) { 47 | throw ConstraintException("Need to initialize CSR before doing weakly connected components."); 48 | } 49 | 50 | // Retrieve CSR data 51 | int64_t *v = reinterpret_cast(duckpgq_state->csr_list[info.csr_id]->v); 52 | vector &e = duckpgq_state->csr_list[info.csr_id]->e; 53 | size_t v_size = duckpgq_state->csr_list[info.csr_id]->vsize; 54 | 55 | // Get source vector for searches 56 | auto &src = args.data[1]; 57 | UnifiedVectorFormat vdata_src; 58 | src.ToUnifiedFormat(args.size(), vdata_src); 59 | auto src_data = reinterpret_cast(vdata_src.data); 60 | ValidityMask &result_validity = FlatVector::Validity(result); 61 | 62 | // Create result vector 63 | result.SetVectorType(VectorType::FLAT_VECTOR); 64 | auto result_data = FlatVector::GetData(result); 65 | 66 | if (!info.state_initialized) { 67 | std::lock_guard guard(info.initialize_lock); // Thread safety 68 | if (!info.state_converged) { 69 | info.forest.resize(v_size); 70 | info.state_initialized = true; // Initialize state 71 | } 72 | } 73 | 74 | // Check if already converged 75 | if (!info.state_converged) { 76 | std::lock_guard guard(info.wcc_lock); // Thread safety 77 | if (!info.state_converged) { 78 | // Initialize the forest for connected components 79 | for (int64_t i = 0; i < v_size - 1; ++i) { 80 | info.forest[i] = i; // Each node points to itself 81 | } 82 | // Process edges to link nodes 83 | for (int64_t i = 0; i < v_size - 1; i++) { 84 | for (int64_t edge_idx = v[i]; edge_idx < v[i + 1]; edge_idx++) { 85 | int64_t neighbor = e[edge_idx]; 86 | Link(info.forest, i, neighbor); 87 | } 88 | } 89 | info.state_converged = true; 90 | } 91 | } 92 | // Assign component IDs for the source nodes 93 | for (size_t i = 0; i < args.size(); i++) { 94 | int64_t src_node = src_data[i]; 95 | if (src_node >= 0 && src_node < v_size) { 96 | result_data[i] = FindTreeRoot(info.forest, src_node); // Assign component ID to the result 97 | } else { 98 | result_validity.SetInvalid(i); 99 | } 100 | } 101 | 102 | // Mark CSR for deletion 103 | duckpgq_state->csr_to_delete.insert(info.csr_id); 104 | } 105 | 106 | //------------------------------------------------------------------------------ 107 | // Register functions 108 | //------------------------------------------------------------------------------ 109 | void CoreScalarFunctions::RegisterWeaklyConnectedComponentScalarFunction(ExtensionLoader &loader) { 110 | loader.RegisterFunction(ScalarFunction("weakly_connected_component", {LogicalType::INTEGER, LogicalType::BIGINT}, 111 | LogicalType::BIGINT, WeaklyConnectedComponentFunction, 112 | WeaklyConnectedComponentFunctionData::WeaklyConnectedComponentBind)); 113 | } 114 | 115 | } // namespace duckdb 116 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | labels: 4 | - needs triage 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Disclaimer:** Please note that this is a research project. 10 | While I am committed to improving the project, responses to issues and bug fixes might take longer than expected. 11 | I appreciate your patience and understanding as I work to address issues. Thank you for helping make this project better! 12 | 13 | 14 | - type: textarea 15 | attributes: 16 | label: What happens? 17 | description: A short, clear and concise description of what the bug is. 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | attributes: 23 | label: To Reproduce 24 | description: | 25 | Please provide steps to reproduce the behavior, preferably a [minimal reproducible example](https://en.wikipedia.org/wiki/Minimal_reproducible_example). Please adhere the following guidelines: 26 | 27 | * Format the code and the output as [code blocks](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) using triple backticks: 28 | 29 | ```` 30 | ``` 31 | CODE HERE 32 | ``` 33 | ```` 34 | * Add all required imports for scripts, e.g., `import duckdb`, `import pandas as pd`. 35 | * Remove all prompts from the scripts. This include DuckDB's 'D' prompt and Python's `>>>` prompt. Removing these prompts makes reproduction attempts quicker. 36 | * Make sure that the script and its outputs are provided in separate code blocks. 37 | * If applicable, please check whether the issue is reproducible via running plain SQL queries from the DuckDB CLI client. 38 | validations: 39 | required: true 40 | 41 | - type: markdown 42 | attributes: 43 | value: "# Environment (please complete the following information):" 44 | - type: input 45 | attributes: 46 | label: "OS:" 47 | placeholder: e.g., iOS 48 | description: Please include operating system version and architecture (e.g., aarch64, x86, x64, etc) 49 | validations: 50 | required: true 51 | - type: input 52 | attributes: 53 | label: "DuckDB Version:" 54 | placeholder: e.g., 22 55 | validations: 56 | required: true 57 | - type: input 58 | attributes: 59 | label: "DuckDB Client:" 60 | placeholder: e.g., Python 61 | validations: 62 | required: true 63 | 64 | - type: markdown 65 | attributes: 66 | value: "# Identity Disclosure:" 67 | - type: input 68 | attributes: 69 | label: "Full Name:" 70 | placeholder: e.g., John Doe 71 | validations: 72 | required: true 73 | - type: input 74 | attributes: 75 | label: "Affiliation:" 76 | placeholder: e.g., Acme Corporation 77 | validations: 78 | required: true 79 | 80 | - type: markdown 81 | attributes: 82 | value: | 83 | If the above is not given and is not obvious from your GitHub profile page, we might close your issue without further review. Please refer to the [reasoning behind this rule](https://berthub.eu/articles/posts/anonymous-help/) if you have questions. 84 | 85 | # Before Submitting: 86 | 87 | - type: dropdown 88 | attributes: 89 | label: How did you load the extension? 90 | description: | 91 | Visit [Loading DuckPGQ](https://duckpgq.notion.site/Loading-DuckPGQ-29eda93a97b140e1861614cce1f5498c) and [Building DuckPGQ](https://www.notion.so/duckpgq/Building-DuckPGQ-619783a5af604efbb7c93f09811d996f) for more information. 92 | options: 93 | - Community extension version 94 | - Latest version 95 | - Built from source 96 | validations: 97 | required: true 98 | 99 | - type: dropdown 100 | attributes: 101 | label: Did you include all relevant data sets for reproducing the issue? 102 | options: 103 | - "No - Other reason (please specify in the issue body)" 104 | - "No - I cannot share the data sets because they are confidential" 105 | - "No - I cannot easily share my data sets due to their large size" 106 | - "Not applicable - the reproduction does not require a data set" 107 | - "Yes" 108 | default: 0 109 | validations: 110 | required: true 111 | 112 | - type: checkboxes 113 | attributes: 114 | label: Did you include all code required to reproduce the issue? 115 | options: 116 | - label: Yes, I have 117 | 118 | - type: checkboxes 119 | attributes: 120 | label: Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue? 121 | options: 122 | - label: Yes, I have 123 | --------------------------------------------------------------------------------