├── sdk-python ├── test_db │ ├── db │ └── conf ├── pyproject.toml └── src │ └── graphlite_sdk │ ├── __init__.py │ └── error.py ├── graphlite ├── src │ ├── schema │ │ ├── catalog │ │ │ └── mod.rs │ │ ├── enforcement │ │ │ └── mod.rs │ │ ├── integration │ │ │ └── mod.rs │ │ ├── executor │ │ │ └── mod.rs │ │ ├── parser │ │ │ ├── mod.rs │ │ │ └── ast.rs │ │ ├── introspection │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── standalone_test.rs │ ├── catalog │ │ ├── storage │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── providers │ │ │ ├── index.rs │ │ │ └── mod.rs │ │ └── error.rs │ ├── ast │ │ └── mod.rs │ ├── exec │ │ ├── write_stmt │ │ │ ├── transaction │ │ │ │ ├── mod.rs │ │ │ │ ├── transaction_base.rs │ │ │ │ ├── coordinator.rs │ │ │ │ ├── set_characteristics.rs │ │ │ │ └── commit.rs │ │ │ ├── mod.rs │ │ │ ├── data_stmt │ │ │ │ └── mod.rs │ │ │ ├── ddl_stmt │ │ │ │ ├── mod.rs │ │ │ │ ├── drop_graph_type.rs │ │ │ │ ├── ddl_statement_base.rs │ │ │ │ ├── drop_user.rs │ │ │ │ ├── drop_role.rs │ │ │ │ ├── create_user.rs │ │ │ │ └── create_role.rs │ │ │ └── statement_base.rs │ │ ├── mod.rs │ │ ├── error.rs │ │ └── lock_tracker.rs │ ├── plan │ │ ├── pattern_optimization │ │ │ └── mod.rs │ │ └── mod.rs │ ├── storage │ │ ├── indexes │ │ │ ├── mod.rs │ │ │ ├── errors.rs │ │ │ └── manager.rs │ │ ├── mod.rs │ │ └── persistent │ │ │ ├── mod.rs │ │ │ └── factory.rs │ ├── coordinator │ │ └── mod.rs │ ├── session │ │ └── mod.rs │ ├── txn │ │ └── mod.rs │ ├── lib.rs │ ├── cache │ │ └── mod.rs │ ├── types │ │ └── mod.rs │ └── functions │ │ └── numeric_functions.rs ├── tests │ ├── testutils │ │ ├── sample_data_generator.rs │ │ ├── test_config.toml │ │ └── mod.rs │ ├── simple_let_test.rs │ ├── cli_fixture_tests.rs │ ├── simple_insert_test.rs │ ├── list_graphs_bug_test_simple.rs │ ├── unknown_procedure_test.rs │ ├── simple_union_test.rs │ ├── debug_fraud_fixture.rs │ ├── rollback_simple_test.rs │ ├── rollback_batch_test.rs │ ├── intersect_debug_test.rs │ ├── simple_role_test.rs │ ├── transactional_set_test.rs │ ├── with_clause_property_access_bug.rs │ ├── stored_procedure_no_prefix_test.rs │ └── duplicate_edge_warning_test.rs └── Cargo.toml ├── bindings ├── python │ ├── graphlite │ │ └── __init__.py │ ├── setup.py │ └── examples │ │ └── basic_usage.py └── java │ ├── src │ └── main │ │ └── java │ │ └── com │ │ └── deepgraph │ │ └── graphlite │ │ └── QueryResult.java │ └── pom.xml ├── gql-cli ├── src │ ├── cli │ │ └── mod.rs │ └── main.rs └── Cargo.toml ├── .actrc ├── .cargo └── config.toml ├── graphlite-ffi ├── build.rs └── Cargo.toml ├── .gitignore ├── sdk-rust ├── Cargo.toml ├── src │ ├── error.rs │ └── lib.rs └── examples │ └── basic_usage.rs ├── Cargo.toml ├── examples ├── python │ ├── bindings │ │ ├── drug_discovery_README.md │ │ └── basic_usage.py │ └── sdk │ │ └── README.md ├── README.md └── rust │ └── sdk │ └── basic_usage.rs ├── entrypoint.sh ├── NOTICE ├── .dockerignore ├── .github ├── workflows │ ├── dependencies.yml │ └── benchmark.yml └── TESTING_CI.md ├── DOCKER_QUICK_START.md ├── Dockerfile └── scripts └── README.md /sdk-python/test_db/db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GraphLite-AI/GraphLite/HEAD/sdk-python/test_db/db -------------------------------------------------------------------------------- /sdk-python/test_db/conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GraphLite-AI/GraphLite/HEAD/sdk-python/test_db/conf -------------------------------------------------------------------------------- /graphlite/src/schema/catalog/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Schema catalog module 5 | 6 | pub mod graph_type; 7 | -------------------------------------------------------------------------------- /graphlite/src/schema/enforcement/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Schema enforcement module 5 | 6 | pub mod config; 7 | -------------------------------------------------------------------------------- /graphlite/tests/testutils/sample_data_generator.rs: -------------------------------------------------------------------------------- 1 | //! Sample data generation utilities for test cases 2 | //! 3 | //! This module provides functions to generate test graphs with realistic 4 | //! structures and data for comprehensive testing. 5 | -------------------------------------------------------------------------------- /graphlite/tests/testutils/test_config.toml: -------------------------------------------------------------------------------- 1 | [storage] 2 | data_dir = "/tmp/graphlite_shared_test_env" 3 | method = "RocksDB" 4 | storage_type = "Disk" 5 | 6 | [logging] 7 | level = "info" 8 | log_dir = "/tmp/graphlite_shared_test_env/logs" 9 | 10 | [server] 11 | host = "127.0.0.1" 12 | port = 8080 -------------------------------------------------------------------------------- /graphlite/src/catalog/storage/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Catalog storage implementations 5 | //! 6 | //! This module contains storage backends for different catalog types, 7 | //! including persistent graph-based storage for system catalogs. 8 | -------------------------------------------------------------------------------- /bindings/python/graphlite/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GraphLite Python Bindings 3 | 4 | High-level Python API for GraphLite graph database using FFI. 5 | """ 6 | 7 | from .graphlite import GraphLite, GraphLiteError, ErrorCode, QueryResult 8 | 9 | __version__ = "0.1.0" 10 | __all__ = ["GraphLite", "GraphLiteError", "ErrorCode", "QueryResult"] 11 | -------------------------------------------------------------------------------- /graphlite/src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! AST subsystem: Lexer, parser, AST nodes, and query validation for GQL 5 | 6 | #[allow(clippy::module_inception)] 7 | mod ast; 8 | pub use ast::*; 9 | pub mod lexer; 10 | pub mod parser; 11 | pub mod pretty_printer; 12 | pub mod validator; 13 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/transaction/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | pub mod commit; 5 | pub mod coordinator; 6 | pub mod rollback; 7 | pub mod set_characteristics; 8 | pub mod start; 9 | pub mod transaction_base; 10 | 11 | pub use coordinator::TransactionCoordinator; 12 | pub use transaction_base::TransactionStatementExecutor; 13 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | pub mod data_stmt; 5 | pub mod ddl_stmt; 6 | pub mod statement_base; 7 | pub mod transaction; 8 | 9 | pub use crate::exec::context::ExecutionContext; 10 | pub use statement_base::StatementExecutor; 11 | pub use transaction::{TransactionCoordinator, TransactionStatementExecutor}; 12 | -------------------------------------------------------------------------------- /graphlite/tests/testutils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Test utilities for GraphLite integration tests 2 | //! 3 | //! Two fixture types available: 4 | //! - TestFixture: Direct component access (legacy) 5 | //! - CliFixture: CLI-based testing (recommended for new tests) 6 | //! 7 | //! Both provide schema isolation for test independence. 8 | 9 | #![allow(dead_code)] 10 | 11 | pub mod cli_fixture; 12 | pub mod sample_data_generator; 13 | pub mod test_fixture; 14 | -------------------------------------------------------------------------------- /graphlite/src/schema/integration/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Schema integration module for runtime validation 5 | 6 | pub mod index_validator; 7 | pub mod ingestion_validator; 8 | pub mod runtime_validator; 9 | 10 | // pub use runtime_validator::RuntimeValidator; 11 | // pub use index_validator::IndexSchemaValidator; 12 | // pub use ingestion_validator::IngestionSchemaValidator; 13 | -------------------------------------------------------------------------------- /graphlite/src/schema/executor/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Schema executor module for ISO GQL Graph Type statements 5 | 6 | pub mod alter_graph_type; 7 | pub mod create_graph_type; 8 | pub mod drop_graph_type; 9 | 10 | // pub use create_graph_type::CreateGraphTypeExecutor; 11 | // pub use drop_graph_type::DropGraphTypeExecutor; 12 | // pub use alter_graph_type::AlterGraphTypeExecutor; 13 | -------------------------------------------------------------------------------- /gql-cli/src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! CLI module for GraphLite 5 | //! 6 | //! Provides command-line interface for database initialization, 7 | //! interactive GQL console (REPL), and one-off query execution. 8 | 9 | pub mod commands; 10 | pub mod gqlcli; 11 | pub mod output; 12 | 13 | pub use commands::{Cli, Commands}; 14 | pub use gqlcli::{handle_gql, handle_install, handle_query}; 15 | -------------------------------------------------------------------------------- /sdk-python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "graphlite-sdk" 7 | version = "0.1.0" 8 | description = "Python SDK for the GraphLite embedded graph database" 9 | authors = [{ name = "GraphLite AI" }] 10 | readme = "README.md" 11 | requires-python = ">=3.8" 12 | dependencies = [] 13 | 14 | [tool.setuptools] 15 | package-dir = {"" = "src"} 16 | packages = ["graphlite_sdk"] 17 | -------------------------------------------------------------------------------- /.actrc: -------------------------------------------------------------------------------- 1 | # act configuration for local GitHub Actions testing 2 | # See: https://github.com/nektos/act 3 | 4 | # Use larger runner images (recommended for Rust projects) 5 | -P ubuntu-latest=catthehacker/ubuntu:act-latest 6 | 7 | # Set container architecture 8 | --container-architecture linux/amd64 9 | 10 | # Bind workspace for better performance 11 | --bind 12 | 13 | # Use .secrets file for secrets (create this file locally, add to .gitignore) 14 | # --secret-file .secrets 15 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # GraphLite Cargo Configuration 2 | # This file configures cargo behavior for the GraphLite project 3 | 4 | [env] 5 | # Run tests single-threaded by default to avoid database state conflicts 6 | # GraphLite tests share database instances and must not run in parallel 7 | # to prevent race conditions and state interference between tests. 8 | # 9 | # Users can override this setting if needed: 10 | # RUST_TEST_THREADS=4 cargo test 11 | RUST_TEST_THREADS = "1" 12 | -------------------------------------------------------------------------------- /graphlite/src/plan/pattern_optimization/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Pattern optimization module for logical planning 5 | //! 6 | //! This module contains pattern analysis components for optimizing 7 | //! comma-separated patterns in MATCH clauses. 8 | 9 | pub mod cost_estimation; 10 | pub mod integration; 11 | pub mod logical_integration; 12 | pub mod pattern_analysis; 13 | pub mod pattern_analyzer; 14 | pub mod physical_generation; 15 | -------------------------------------------------------------------------------- /graphlite/src/schema/parser/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Schema parser module for ISO GQL Graph Type statements 5 | 6 | pub mod ast; 7 | pub mod graph_type; 8 | 9 | // pub use ast::{ 10 | // CreateGraphTypeStatement, 11 | // DropGraphTypeStatement, 12 | // AlterGraphTypeStatement, 13 | // }; 14 | 15 | // pub use graph_type::{ 16 | // parse_create_graph_type, 17 | // parse_drop_graph_type, 18 | // parse_alter_graph_type, 19 | // }; 20 | -------------------------------------------------------------------------------- /graphlite/src/schema/introspection/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Schema Introspection Module 5 | // 6 | // Provides queries and utilities for introspecting graph type schemas. 7 | // Allows users to discover schema metadata, explore type definitions, 8 | // and understand constraints and relationships. 9 | 10 | pub mod queries; 11 | 12 | // pub use queries::{ 13 | // SchemaIntrospection, 14 | // IntrospectionQuery, 15 | // IntrospectionResult, 16 | // }; 17 | -------------------------------------------------------------------------------- /graphlite/src/plan/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Query planning and optimization for GQL queries 5 | //! 6 | //! This module provides query planning capabilities that convert AST queries 7 | //! into optimized execution plans. It includes logical plan generation, 8 | //! physical plan optimization, and cost estimation. 9 | 10 | pub mod cost; 11 | pub mod insert_planner; 12 | pub mod logical; 13 | pub mod optimizer; 14 | pub mod pattern_optimization; 15 | pub mod physical; 16 | pub mod trace; 17 | -------------------------------------------------------------------------------- /graphlite-ffi/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 5 | 6 | cbindgen::Builder::new() 7 | .with_crate(crate_dir) 8 | .with_language(cbindgen::Language::C) 9 | .with_documentation(true) 10 | .with_include_guard("GRAPHLITE_H") 11 | .with_no_includes() 12 | .with_pragma_once(true) 13 | .generate() 14 | .expect("Unable to generate C bindings") 15 | .write_to_file("graphlite.h"); 16 | 17 | println!("cargo:rerun-if-changed=src/lib.rs"); 18 | } 19 | -------------------------------------------------------------------------------- /graphlite/src/storage/indexes/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Graph indexing system for GraphLite 5 | //! 6 | //! This module provides indexing support for: 7 | //! - Graph indexes (adjacency lists, paths, reachability) 8 | //! 9 | //! All indexes are designed to be partition-aware for future distribution. 10 | 11 | pub mod errors; 12 | pub mod manager; 13 | pub mod metrics; 14 | pub mod traits; 15 | pub mod types; 16 | 17 | // Re-export core types 18 | pub use errors::*; 19 | pub use manager::*; 20 | pub use types::*; 21 | -------------------------------------------------------------------------------- /graphlite/src/coordinator/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Query Coordinator - Central orchestration for query execution 5 | //! 6 | //! The QueryCoordinator provides a unified entry point for query execution, 7 | //! properly coordinating all database components (session, storage, catalog, execution). 8 | 9 | pub mod query_coordinator; 10 | 11 | pub use query_coordinator::{QueryCoordinator, QueryInfo, QueryPlan, QueryType}; 12 | 13 | // Re-export types needed for the public API 14 | pub use crate::exec::{QueryResult, Row}; 15 | -------------------------------------------------------------------------------- /graphlite-ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphlite-ffi" 3 | version = "0.0.1" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | authors = ["DeepGraph Inc."] 7 | description = "C-compatible FFI layer for GraphLite graph database" 8 | repository = "https://github.com/GraphLite-AI/GraphLite" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "staticlib", "rlib"] # Support dynamic, static, and Rust linking 12 | doctest = false 13 | 14 | [dependencies] 15 | graphlite = { path = "../graphlite", version = "0.0.1" } 16 | libc = "0.2" 17 | serde_json = "1.0" 18 | 19 | [build-dependencies] 20 | cbindgen = "0.27" 21 | 22 | [features] 23 | default = [] 24 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/data_stmt/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | pub mod coordinator; 5 | pub mod data_statement_base; 6 | pub mod delete; 7 | pub mod insert; 8 | pub mod match_delete; 9 | pub mod match_insert; 10 | pub mod match_remove; 11 | pub mod match_set; 12 | pub mod planned_insert; 13 | pub mod remove; 14 | pub mod set; 15 | 16 | pub use coordinator::*; 17 | pub use data_statement_base::*; 18 | pub use delete::*; 19 | pub use match_delete::*; 20 | pub use match_insert::*; 21 | pub use match_remove::*; 22 | pub use match_set::*; 23 | pub use remove::*; 24 | pub use set::*; 25 | -------------------------------------------------------------------------------- /graphlite/src/catalog/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Pluggable catalog system 5 | //! 6 | //! This module implements a modular, pluggable catalog architecture that allows 7 | //! adding new catalog types by implementing a trait and registering them. 8 | //! All catalogs follow the same interface patterns and integrate seamlessly 9 | //! with the existing storage system. 10 | 11 | // Core catalog system exports 12 | pub mod error; 13 | pub mod manager; 14 | pub mod operations; 15 | pub mod providers; 16 | pub mod registry; 17 | pub mod traits; 18 | // pub mod metadata; // Removed - not part of ISO GQL 19 | pub mod storage; 20 | pub mod system_procedures; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | /target/ 3 | **/*.rs.bk 4 | *.pdb 5 | 6 | # Database files and runtime data 7 | *.db 8 | mydb/ 9 | *.wal 10 | *.log 11 | 12 | # IDE and editor files 13 | .vscode/ 14 | .idea/ 15 | .gradle/ 16 | *.swp 17 | *.swo 18 | *~ 19 | *.iml 20 | 21 | # OS-specific files 22 | .DS_Store 23 | Thumbs.db 24 | desktop.ini 25 | 26 | # Python artifacts (for bindings) 27 | __pycache__/ 28 | *.pyc 29 | *.pyo 30 | *.egg-info/ 31 | .pytest_cache/ 32 | .mypy_cache/ 33 | dist/ 34 | build/ 35 | .venv/ 36 | venv/ 37 | *.so 38 | 39 | # Java artifacts (for bindings) 40 | *.class 41 | .gradle/ 42 | gradle/ 43 | 44 | # Temporary files 45 | *.tmp 46 | *.temp 47 | *.bak 48 | *.backup 49 | *.orig 50 | 51 | # Runtime database directories 52 | graphlite-ffi/data/ 53 | my_db/ 54 | 55 | # GitHub Actions local testing 56 | .secrets 57 | .env 58 | -------------------------------------------------------------------------------- /graphlite/src/session/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Session management for multi-graph database operations 5 | //! 6 | //! This module provides session management functionality for: 7 | //! - User session contexts with graph preferences 8 | //! - Session-scoped graph context switching (SET SESSION GRAPH) 9 | //! - User authentication and authorization 10 | //! - Session isolation and concurrent access 11 | //! 12 | //! Features supported: 13 | //! - Session creation and destruction 14 | //! - Current graph context per session 15 | //! - Home graph assignment per user 16 | //! - Session parameter management 17 | //! - Multi-tenancy and user isolation 18 | 19 | pub mod manager; 20 | pub mod models; 21 | pub mod transaction_state; 22 | 23 | pub use manager::{get_session_manager, SessionManager}; 24 | pub use models::{SessionPermissionCache, UserSession}; 25 | pub use transaction_state::SessionTransactionState; 26 | -------------------------------------------------------------------------------- /graphlite/tests/simple_let_test.rs: -------------------------------------------------------------------------------- 1 | //! Simple LET statement test 2 | //! 3 | //! Tests the basic LET + RETURN functionality 4 | 5 | #![allow(unused_variables)] 6 | 7 | #[path = "testutils/mod.rs"] 8 | mod testutils; 9 | 10 | use testutils::test_fixture::TestFixture; 11 | 12 | #[test] 13 | fn test_simple_let_return() { 14 | let fixture = TestFixture::new().expect("Failed to create test fixture"); 15 | fixture 16 | .setup_graph("let_test") 17 | .expect("Failed to setup graph"); 18 | 19 | let result = fixture.query("LET test_list = [1, 2, 3, 4, 5] RETURN test_list"); 20 | 21 | match result { 22 | Ok(query_result) => { 23 | for (i, row) in query_result.rows.iter().enumerate() {} 24 | 25 | // Assert expected behavior 26 | assert_eq!(query_result.rows.len(), 1, "Should return exactly 1 row"); 27 | } 28 | Err(e) => { 29 | panic!("Query failed: {:?}", e); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gql-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gql-cli" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "Command-line interface for GraphLite" 9 | documentation = "https://docs.rs/gql-cli" 10 | readme = "../README.md" 11 | keywords = ["gql", "graph", "cli", "database"] 12 | categories = ["command-line-utilities", "database"] 13 | 14 | [[bin]] 15 | name = "graphlite" 16 | path = "src/main.rs" 17 | 18 | [dependencies] 19 | # Core library dependency 20 | graphlite = { path = "../graphlite", version = "0.0.1" } 21 | 22 | # CLI-specific dependencies 23 | clap = { workspace = true } 24 | rustyline = { workspace = true } 25 | colored = { workspace = true } 26 | comfy-table = { workspace = true } 27 | rpassword = { workspace = true } 28 | tokio = { workspace = true } 29 | serde = { workspace = true } 30 | serde_json = { workspace = true } 31 | env_logger = { workspace = true } 32 | log = { workspace = true } 33 | thiserror = { workspace = true } 34 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/transaction/transaction_base.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor}; 5 | use crate::exec::ExecutionError; 6 | use crate::exec::QueryResult; 7 | 8 | /// Base trait for transaction statement executors 9 | pub trait TransactionStatementExecutor: StatementExecutor { 10 | /// Execute the transaction statement 11 | /// Returns a QueryResult with status information 12 | fn execute_transaction_operation( 13 | &self, 14 | context: &ExecutionContext, 15 | ) -> Result; 16 | 17 | /// Transaction statements typically don't require write permissions on graphs 18 | /// They manage transaction state instead 19 | #[allow(dead_code)] // ROADMAP v0.6.0 - Permission-based transaction control 20 | fn requires_write_permission(&self) -> bool { 21 | false // Transaction statements manage transaction state, not graph data 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/ddl_stmt/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | pub mod clear_graph; 5 | pub mod coordinator; 6 | pub mod create_graph; 7 | pub mod create_graph_type; 8 | pub mod create_role; 9 | pub mod create_schema; 10 | pub mod create_user; 11 | pub mod ddl_statement_base; 12 | pub mod drop_graph; 13 | pub mod drop_graph_type; 14 | pub mod drop_role; 15 | pub mod drop_schema; 16 | pub mod drop_user; 17 | pub mod grant_role; 18 | pub mod index_operations; 19 | pub mod revoke_role; 20 | pub mod truncate_graph; 21 | 22 | pub use clear_graph::*; 23 | pub use coordinator::*; 24 | pub use create_graph::*; 25 | pub use create_graph_type::*; 26 | pub use create_role::*; 27 | pub use create_schema::*; 28 | pub use create_user::*; 29 | pub use ddl_statement_base::*; 30 | pub use drop_graph::*; 31 | pub use drop_graph_type::*; 32 | pub use drop_role::*; 33 | pub use drop_schema::*; 34 | pub use drop_user::*; 35 | pub use grant_role::*; 36 | pub use index_operations::*; 37 | pub use revoke_role::*; 38 | pub use truncate_graph::*; 39 | -------------------------------------------------------------------------------- /sdk-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphlite-rust-sdk" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "High-level SDK for GraphLite - ergonomic API on top of GraphLite core" 9 | documentation = "https://docs.rs/graphlite-rust-sdk" 10 | readme = "../README.md" 11 | keywords = ["gql", "graph", "sdk", "api", "graphlite"] 12 | categories = ["database", "api-bindings"] 13 | 14 | [lib] 15 | name = "graphlite_sdk" 16 | path = "src/lib.rs" 17 | doctest = false 18 | 19 | [dependencies] 20 | # Core GraphLite library 21 | graphlite = { path = "../graphlite", version = "0.0.1" } 22 | 23 | # Re-export commonly needed dependencies 24 | serde = { workspace = true } 25 | serde_json = { workspace = true } 26 | tokio = { workspace = true } 27 | thiserror = { workspace = true } 28 | 29 | [dev-dependencies] 30 | tokio = { workspace = true } 31 | 32 | [[example]] 33 | name = "basic_usage" 34 | path = "../examples/rust/sdk/basic_usage.rs" 35 | 36 | [[example]] 37 | name = "drug_discovery" 38 | path = "../examples/rust/sdk/drug_discovery/main.rs" 39 | -------------------------------------------------------------------------------- /graphlite/src/exec/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Query execution engine 5 | //! 6 | //! This module provides the execution engine that takes physical query plans 7 | //! and executes them against graph storage to produce query results. 8 | 9 | pub mod context; 10 | pub mod error; 11 | pub mod executor; 12 | pub mod lock_tracker; 13 | pub mod result; 14 | pub mod row_iterator; 15 | pub mod unwind_preprocessor; 16 | pub mod with_clause_processor; 17 | pub mod write_stmt; // Phase 4: Week 6.5 - Memory Optimization 18 | // Text search not supported in GraphLite 19 | // pub mod text_search_iterator; // Phase 4: Week 6.5 - Lazy text search 20 | pub mod memory_budget; 21 | pub mod streaming_topk; // Phase 4: Week 6.5 - Streaming top-K // Phase 4: Week 6.5 - Memory limit enforcement 22 | 23 | // Re-export the main types for convenience 24 | pub use context::ExecutionContext; 25 | pub use error::ExecutionError; 26 | pub use executor::{ExecutionRequest, QueryExecutor}; 27 | pub use result::{QueryResult, Row, SessionResult}; 28 | // Text search not supported in GraphLite 29 | // pub use text_search_iterator::TextSearchIterator; 30 | -------------------------------------------------------------------------------- /graphlite/src/txn/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Transaction management module for GQL database system 5 | //! 6 | //! This module provides transaction control and ACID properties for the GQL database. 7 | //! Currently implements basic transaction control commands with READ_COMMITTED isolation level. 8 | //! 9 | //! # Features 10 | //! - Transaction lifecycle management (BEGIN/START, COMMIT, ROLLBACK) 11 | //! - Transaction isolation levels (READ_COMMITTED supported, others planned) 12 | //! - Transaction access modes (READ ONLY, READ WRITE) 13 | //! - Transaction state tracking and management 14 | //! 15 | //! # Planned Features 16 | //! - Full ACID properties implementation 17 | //! - Multiple isolation levels (READ_UNCOMMITTED, REPEATABLE_READ, SERIALIZABLE) 18 | //! - Deadlock detection and resolution 19 | //! - Transaction logging and recovery 20 | //! - Nested transaction support 21 | 22 | pub mod isolation; 23 | pub mod log; 24 | pub mod manager; 25 | pub mod recovery; 26 | pub mod state; 27 | pub mod wal; 28 | 29 | pub use isolation::IsolationLevel; 30 | pub use log::{TransactionLog, UndoOperation}; 31 | pub use manager::TransactionManager; 32 | pub use state::TransactionId; 33 | -------------------------------------------------------------------------------- /graphlite/src/schema/parser/ast.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Schema-specific AST nodes for ISO GQL Graph Type statements 5 | 6 | use crate::schema::types::{ 7 | EdgeTypeDefinition, GraphTypeVersion, NodeTypeDefinition, SchemaChange, 8 | }; 9 | 10 | /// CREATE GRAPH TYPE statement AST 11 | #[derive(Debug, Clone)] 12 | pub struct CreateGraphTypeStatement { 13 | pub name: String, 14 | pub if_not_exists: bool, 15 | pub version: Option, 16 | pub node_types: Vec, 17 | pub edge_types: Vec, 18 | } 19 | 20 | /// DROP GRAPH TYPE statement AST 21 | #[derive(Debug, Clone)] 22 | pub struct DropGraphTypeStatement { 23 | pub name: String, 24 | pub if_exists: bool, 25 | pub cascade: bool, 26 | } 27 | 28 | /// ALTER GRAPH TYPE statement AST 29 | #[derive(Debug, Clone)] 30 | pub struct AlterGraphTypeStatement { 31 | #[allow(dead_code)] // ROADMAP v0.4.0 - Graph type name for ALTER GRAPH TYPE DDL 32 | pub name: String, 33 | #[allow(dead_code)] // ROADMAP v0.4.0 - Version specification for schema evolution tracking 34 | pub version: Option, 35 | #[allow(dead_code)] 36 | // ROADMAP v0.4.0 - Schema change operations (ADD/DROP/ALTER node/edge types) 37 | pub changes: Vec, 38 | } 39 | -------------------------------------------------------------------------------- /graphlite/src/storage/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Graph storage implementation for in-memory graph data 5 | //! 6 | //! This module provides: 7 | //! - Value type system for graph properties 8 | //! - In-memory graph storage with adjacency lists 9 | //! - Efficient indexing for nodes and edges by label 10 | //! - Graph operations (add, get, find) 11 | //! - Sample fraud data generation 12 | //! - Pluggable storage backend trait for different KV stores 13 | 14 | mod data_adapter; 15 | pub mod graph_cache; 16 | pub mod indexes; 17 | pub mod multi_graph; 18 | mod persistent; 19 | pub mod storage_manager; 20 | pub mod type_mapping; 21 | pub mod types; 22 | pub mod value; 23 | 24 | pub use graph_cache::GraphCache; 25 | pub use types::{Edge, Node, StorageError}; 26 | pub use value::{TimeWindow, Value}; 27 | // Only expose StorageType for configuration 28 | pub use persistent::StorageType; 29 | // Public exports for examples and tests 30 | pub use persistent::{StorageDriver, StorageTree}; 31 | // Public interface - only StorageManager should be used externally 32 | pub use storage_manager::{StorageManager, StorageMethod}; 33 | // Index system (stub) 34 | // TTL management 35 | // pub use ttl_manager::{TTLManager, TTLCleanupStats}; // TODO: Not yet extracted 36 | 37 | // Re-export common types for convenience 38 | -------------------------------------------------------------------------------- /graphlite/src/catalog/providers/index.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Index Catalog Provider - Stub implementation 5 | 6 | use crate::catalog::error::{CatalogError, CatalogResult}; 7 | use crate::catalog::operations::{CatalogOperation, CatalogResponse}; 8 | use crate::catalog::traits::{CatalogProvider, CatalogSchema}; 9 | use crate::storage::StorageManager; 10 | use std::sync::Arc; 11 | 12 | pub struct IndexCatalog {} 13 | 14 | impl IndexCatalog { 15 | pub fn new() -> Box { 16 | Box::new(Self {}) 17 | } 18 | } 19 | 20 | impl CatalogProvider for IndexCatalog { 21 | fn init(&mut self, _storage: Arc) -> CatalogResult<()> { 22 | Ok(()) 23 | } 24 | 25 | fn execute(&mut self, _op: CatalogOperation) -> CatalogResult { 26 | Err(CatalogError::NotSupported( 27 | "Index catalog not yet implemented".to_string(), 28 | )) 29 | } 30 | 31 | fn save(&self) -> CatalogResult> { 32 | Ok(Vec::new()) 33 | } 34 | 35 | fn load(&mut self, _data: &[u8]) -> CatalogResult<()> { 36 | Ok(()) 37 | } 38 | 39 | fn schema(&self) -> CatalogSchema { 40 | CatalogSchema { 41 | name: "index".to_string(), 42 | version: "0.1.0".to_string(), 43 | entities: vec![], 44 | operations: vec![], 45 | } 46 | } 47 | 48 | fn supported_operations(&self) -> Vec { 49 | vec![] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /graphlite/tests/cli_fixture_tests.rs: -------------------------------------------------------------------------------- 1 | //! Tests for CliFixture infrastructure 2 | 3 | #[path = "testutils/mod.rs"] 4 | mod testutils; 5 | 6 | use testutils::cli_fixture::CliFixture; 7 | 8 | #[test] 9 | fn test_cli_fixture_initialization() { 10 | let fixture = CliFixture::empty().expect("Failed to create CLI fixture"); 11 | assert!(fixture.db_path().exists()); 12 | } 13 | 14 | #[test] 15 | fn test_simple_create_schema() { 16 | let fixture = CliFixture::empty().expect("Failed to create CLI fixture"); 17 | let schema_name = fixture.schema_name(); 18 | 19 | let result = fixture.assert_query_succeeds(&format!("CREATE SCHEMA /{};", schema_name)); 20 | assert!(!result.is_empty()); 21 | } 22 | 23 | #[test] 24 | fn test_catalog_persistence() { 25 | let fixture = CliFixture::empty().expect("Failed to create CLI fixture"); 26 | let schema_name = fixture.schema_name(); 27 | 28 | // Create schema in command 1 29 | fixture.assert_query_succeeds(&format!("CREATE SCHEMA /{};", schema_name)); 30 | 31 | // Create graph in command 2 - this will FAIL if schema didn't persist 32 | let result = fixture.assert_query_succeeds(&format!("CREATE GRAPH /{}/test;", schema_name)); 33 | assert!(!result.is_empty()); 34 | 35 | // Verify we can reference the graph in command 3 36 | let result2 = 37 | fixture.assert_query_succeeds(&format!("SESSION SET GRAPH /{}/test;", schema_name)); 38 | // SESSION SET GRAPH returns empty result on success 39 | assert!(result2.is_empty() || result2.len() == 0 || result2.rows.is_empty()); 40 | } 41 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/ddl_stmt/drop_graph_type.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // TODO: Implement DropGraphTypeExecutor 5 | use crate::ast::DropGraphTypeStatement; 6 | use crate::catalog::manager::CatalogManager; 7 | use crate::exec::write_stmt::ddl_stmt::DDLStatementExecutor; 8 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor}; 9 | use crate::exec::ExecutionError; 10 | use crate::storage::StorageManager; 11 | use crate::txn::state::OperationType; 12 | 13 | pub struct DropGraphTypeExecutor; 14 | 15 | impl DropGraphTypeExecutor { 16 | pub fn new(_statement: DropGraphTypeStatement) -> Self { 17 | Self 18 | } 19 | } 20 | 21 | impl StatementExecutor for DropGraphTypeExecutor { 22 | fn operation_type(&self) -> OperationType { 23 | OperationType::DropTable // TODO: Add DropGraphType to OperationType enum 24 | } 25 | 26 | fn operation_description(&self, _context: &ExecutionContext) -> String { 27 | "DROP GRAPH TYPE".to_string() 28 | } 29 | } 30 | 31 | impl DDLStatementExecutor for DropGraphTypeExecutor { 32 | fn execute_ddl_operation( 33 | &self, 34 | _context: &ExecutionContext, 35 | _catalog_manager: &mut CatalogManager, 36 | _storage: &StorageManager, 37 | ) -> Result<(String, usize), ExecutionError> { 38 | // TODO: Implement drop graph type logic 39 | Err(ExecutionError::RuntimeError( 40 | "DropGraphTypeExecutor not yet implemented".to_string(), 41 | )) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /graphlite/src/exec/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Execution error types 5 | 6 | use crate::storage::StorageError; 7 | use thiserror::Error; 8 | 9 | /// Execution errors 10 | #[derive(Error, Debug)] 11 | pub enum ExecutionError { 12 | #[error("Storage error: {0}")] 13 | StorageError(String), 14 | 15 | #[error("Expression evaluation error: {0}")] 16 | ExpressionError(String), 17 | 18 | #[error("Type error: {0}")] 19 | TypeError(String), 20 | 21 | #[error("Runtime error: {0}")] 22 | RuntimeError(String), 23 | 24 | #[error("Unsupported operator: {0}")] 25 | UnsupportedOperator(String), 26 | 27 | #[error("Invalid query: {0}")] 28 | InvalidQuery(String), 29 | 30 | #[error("Catalog error: {0}")] 31 | CatalogError(String), 32 | 33 | #[error("Syntax error: {0}")] 34 | SyntaxError(String), 35 | 36 | #[error("Planning error: {0}")] 37 | PlanningError(String), 38 | 39 | #[error("Validation error: {0}")] 40 | ValidationError(String), 41 | 42 | #[error("Schema validation error: {0}")] 43 | SchemaValidation(String), 44 | 45 | #[error("Not found: {0}")] 46 | NotFound(String), 47 | 48 | #[error("Memory limit exceeded: requested {requested} bytes, limit {limit} bytes")] 49 | MemoryLimitExceeded { limit: usize, requested: usize }, 50 | } 51 | 52 | impl From for ExecutionError { 53 | fn from(error: StorageError) -> Self { 54 | ExecutionError::StorageError(error.to_string()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /graphlite/src/storage/persistent/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Persistent storage backends 5 | //! 6 | //! This module provides trait-based abstractions for persistent key-value storage, 7 | //! allowing different storage backends (RocksDB, Sled, etc.) to be used interchangeably. 8 | //! 9 | //! These drivers handle raw key-value operations for persistent disk-based storage. 10 | //! 11 | //! # Architecture 12 | //! 13 | //! ```text 14 | //! DataAdapter (application data structures) 15 | //! ↓ 16 | //! StorageDriver (key-value abstraction) 17 | //! ↓ 18 | //! Concrete Implementations (Sled, RocksDB) 19 | //! ``` 20 | //! 21 | //! # Example Usage 22 | //! 23 | //! ```ignore 24 | //! use crate::storage::persistent::{create_storage_driver, StorageType}; 25 | //! 26 | //! // Create a driver 27 | //! let driver = create_storage_driver(StorageType::Sled, "./data")?; 28 | //! 29 | //! // Open a tree (like a table or collection) 30 | //! let tree = driver.open_tree("my_data")?; 31 | //! 32 | //! // Basic operations 33 | //! tree.insert(b"key", b"value")?; 34 | //! let value = tree.get(b"key")?; 35 | //! tree.remove(b"key")?; 36 | //! ``` 37 | 38 | // Core modules 39 | pub mod factory; 40 | pub mod traits; 41 | pub mod types; 42 | 43 | // Driver implementations 44 | pub mod sled; 45 | // pub mod rocksdb; // TODO: Not yet extracted 46 | pub mod memory; 47 | 48 | // Public API re-exports 49 | pub use factory::create_storage_driver; 50 | pub use traits::{StorageDriver, StorageTree}; 51 | pub use types::StorageType; 52 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "graphlite", 5 | "gql-cli", 6 | "sdk-rust", 7 | "graphlite-ffi", 8 | ] 9 | 10 | [workspace.package] 11 | version = "0.0.1" 12 | edition = "2021" 13 | authors = ["GraphLite Contributors"] 14 | license = "Apache-2.0" 15 | repository = "https://github.com/GraphLite-AI/GraphLite" 16 | description = "GraphLite - A lightweight ISO GQL Graph Database" 17 | 18 | [workspace.dependencies] 19 | # Core parsing and AST 20 | nom = { version = "7.1", features = ["alloc"] } 21 | serde = { version = "1.0", features = ["derive"] } 22 | serde_json = "1.0" 23 | thiserror = "1.0" 24 | regex = "1.0" 25 | 26 | # Date/time support (for GQL temporal types) 27 | chrono = { version = "0.4", features = ["serde"] } 28 | chrono-tz = "0.8" 29 | 30 | # Async runtime and concurrency 31 | tokio = { version = "1.0", features = ["full"] } 32 | async-trait = "0.1" 33 | parking_lot = "0.12" 34 | rayon = "1.8" 35 | 36 | # Storage and serialization 37 | bincode = "1.3" 38 | uuid = { version = "1.0", features = ["v4", "serde"] } 39 | 40 | # Logging 41 | log = "0.4" 42 | env_logger = "0.10" 43 | 44 | # Graph-specific 45 | fastrand = "2.0" 46 | 47 | # CLI dependencies 48 | clap = { version = "4.5", features = ["derive"] } 49 | rustyline = "13.0" 50 | rpassword = "7.0" 51 | colored = "2.1" 52 | comfy-table = "7.0" 53 | 54 | # Storage backend 55 | sled = { version = "0.34" } 56 | 57 | # Additional utilities 58 | once_cell = "1.21.3" 59 | lazy_static = "1.4" 60 | crc32fast = "1.3" 61 | petgraph = "0.6" 62 | 63 | # Dev dependencies 64 | tempfile = "3.8" 65 | -------------------------------------------------------------------------------- /graphlite/tests/simple_insert_test.rs: -------------------------------------------------------------------------------- 1 | #[path = "testutils/mod.rs"] 2 | mod testutils; 3 | 4 | use testutils::test_fixture::TestFixture; 5 | 6 | #[test] 7 | fn test_simple_insert() { 8 | let fixture = TestFixture::new().expect("Failed to create fixture"); 9 | 10 | // Setup fresh graph for this test 11 | fixture 12 | .setup_graph("test_graph") 13 | .expect("Failed to setup graph"); 14 | 15 | // Try simplest possible INSERT 16 | fixture 17 | .query("INSERT (n:TestNode {id: 1})") 18 | .expect("Failed to insert simple node"); 19 | 20 | // Test each property type individually 21 | fixture 22 | .query("INSERT (a1:Account {id: 1})") 23 | .expect("Failed with just id"); 24 | 25 | fixture 26 | .query("INSERT (a2:Account {name: 'Account1'})") 27 | .expect("Failed with name property"); 28 | 29 | fixture 30 | .query("INSERT (a3:Account {balance: 100.0})") 31 | .expect("Failed with balance property"); 32 | 33 | // Try with quoted property name 34 | fixture 35 | .query("INSERT (a4:Account {`status`: 'active'})") 36 | .expect("Failed with quoted status property"); 37 | 38 | fixture 39 | .query("INSERT (a5:Account {account_type: 'savings'})") 40 | .expect("Failed with account_type property"); 41 | 42 | // Try full Account node like fraud fixture uses 43 | fixture.query("INSERT (a:Account {id: 1, name: 'Account1', balance: 100.0, status: 'active', account_type: 'savings'})") 44 | .expect("Failed to insert full Account node"); 45 | } 46 | -------------------------------------------------------------------------------- /examples/python/bindings/drug_discovery_README.md: -------------------------------------------------------------------------------- 1 | # GraphLite Python SDK Examples 2 | 3 | This repository contains examples for using the GraphLite Python SDK. 4 | 5 | ## Setup 6 | 7 | The GraphLite Python SDK uses the GraphLite FFI library. Make sure GraphLite is built first: 8 | 9 | ```bash 10 | # Build GraphLite (from the GraphLite repository) 11 | cd ~/github/deepgraphai/GraphLite 12 | cargo build --release -p graphlite-ffi 13 | ``` 14 | 15 | The Python bindings will automatically find the library in the GraphLite build directory. 16 | 17 | ## Examples 18 | 19 | ### Drug Discovery Example (Recommended Start) 20 | 21 | A comprehensive pharmaceutical research example demonstrating: 22 | - Modeling proteins (disease targets), compounds (drugs), and assays (experiments) 23 | - Creating relationships: TESTED_IN, MEASURES_ACTIVITY_ON, INHIBITS 24 | - Real-world data: IC50 measurements, clinical trial phases 25 | - Analytical queries: IC50 filtering, pathway traversal, aggregation 26 | 27 | **Run:** 28 | ```bash 29 | python3 drug_discovery.py 30 | ``` 31 | 32 | ### Basic Examples 33 | 34 | - `examples/basic_usage.py` - Basic graph operations 35 | - `examples/advanced_usage.py` - Advanced features and queries 36 | 37 | ## Requirements 38 | 39 | - Python 3.8+ 40 | - GraphLite FFI library (built from GraphLite repository) 41 | 42 | ## Domain Model (Drug Discovery) 43 | 44 | ``` 45 | Compound → TESTED_IN → Assay → MEASURES_ACTIVITY_ON → Target (Protein) 46 | Compound → INHIBITS → Target (with IC50 measurements) 47 | ``` 48 | 49 | **Use Cases:** Target-based drug discovery, compound optimization, clinical trial tracking, pharmaceutical knowledge graphs. 50 | -------------------------------------------------------------------------------- /graphlite/tests/list_graphs_bug_test_simple.rs: -------------------------------------------------------------------------------- 1 | // Test for bug fix: CALL gql.list_graphs() returning NULL values 2 | // Simplified version that avoids complex syntax 3 | 4 | #[path = "testutils/mod.rs"] 5 | mod testutils; 6 | 7 | use testutils::test_fixture::TestFixture; 8 | 9 | #[test] 10 | fn test_list_graphs_bug_fix_simple() { 11 | // Use existing fixture with fraud data which already has graphs 12 | let fixture = TestFixture::with_fraud_data().expect("Should create fixture with fraud data"); 13 | 14 | // List all graphs 15 | let result = fixture.query("CALL gql.list_graphs()").unwrap(); 16 | 17 | // Should have at least one graph 18 | assert!(!result.rows.is_empty(), "Should have at least one graph"); 19 | 20 | // Verify that first row has non-NULL values 21 | let graph_name = result.rows[0] 22 | .values 23 | .get("graph_name") 24 | .expect("Should have graph_name column"); 25 | 26 | // This was the bug - graph_name was NULL 27 | assert!( 28 | !matches!(graph_name, graphlite::Value::Null), 29 | "BUG FIXED: graph_name should not be NULL, got: {:?}", 30 | graph_name 31 | ); 32 | 33 | let schema_name = result.rows[0] 34 | .values 35 | .get("schema_name") 36 | .expect("Should have schema_name column"); 37 | 38 | assert!( 39 | !matches!(schema_name, graphlite::Value::Null), 40 | "BUG FIXED: schema_name should not be NULL, got: {:?}", 41 | schema_name 42 | ); 43 | 44 | // Print the values to show they're real 45 | if let graphlite::Value::String(_name) = graph_name {} 46 | if let graphlite::Value::String(_schema) = schema_name {} 47 | } 48 | -------------------------------------------------------------------------------- /sdk-rust/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types for the GraphLite SDK 2 | 3 | use thiserror::Error; 4 | 5 | /// Result type alias for SDK operations 6 | pub type Result = std::result::Result; 7 | 8 | /// Main error type for GraphLite SDK operations 9 | #[derive(Error, Debug)] 10 | pub enum Error { 11 | /// Error from the core GraphLite library 12 | #[error("GraphLite error: {0}")] 13 | GraphLite(String), 14 | 15 | /// Session-related errors 16 | #[error("Session error: {0}")] 17 | Session(String), 18 | 19 | /// Query execution errors 20 | #[error("Query error: {0}")] 21 | Query(String), 22 | 23 | /// Transaction errors 24 | #[error("Transaction error: {0}")] 25 | Transaction(String), 26 | 27 | /// Serialization/deserialization errors 28 | #[error("Serialization error: {0}")] 29 | Serialization(#[from] serde_json::Error), 30 | 31 | /// Type conversion errors 32 | #[error("Type conversion error: {0}")] 33 | TypeConversion(String), 34 | 35 | /// Invalid operation errors 36 | #[error("Invalid operation: {0}")] 37 | InvalidOperation(String), 38 | 39 | /// Resource not found errors 40 | #[error("Not found: {0}")] 41 | NotFound(String), 42 | 43 | /// Connection errors 44 | #[error("Connection error: {0}")] 45 | Connection(String), 46 | 47 | /// I/O errors 48 | #[error("I/O error: {0}")] 49 | Io(#[from] std::io::Error), 50 | } 51 | 52 | impl From for Error { 53 | fn from(s: String) -> Self { 54 | Error::GraphLite(s) 55 | } 56 | } 57 | 58 | impl From<&str> for Error { 59 | fn from(s: &str) -> Self { 60 | Error::GraphLite(s.to_string()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /graphlite/src/exec/lock_tracker.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Lock wait time tracking for query execution 5 | 6 | use std::sync::{Mutex, RwLock}; 7 | use std::time::Duration; 8 | 9 | /// Lock tracker 10 | #[derive(Debug, Clone)] 11 | pub struct LockTracker {} 12 | 13 | impl LockTracker { 14 | pub fn new() -> Self { 15 | Self {} 16 | } 17 | 18 | #[allow(dead_code)] // ROADMAP v0.6.0 - Lock contention tracking for performance observability 19 | pub fn track_operation(&self, operation: F) -> T 20 | where 21 | F: FnOnce() -> T, 22 | { 23 | operation() 24 | } 25 | 26 | #[allow(dead_code)] // ROADMAP v0.6.0 - Lock wait time measurement for query profiling 27 | pub fn execute_with_lock_tracking(&self, operation: F) -> T 28 | where 29 | F: FnOnce() -> T, 30 | { 31 | operation() 32 | } 33 | 34 | #[allow(dead_code)] // ROADMAP v0.6.0 - Accumulate lock wait durations for metrics 35 | pub fn add_lock_wait_time(&self, _duration: Duration) {} 36 | 37 | #[allow(dead_code)] // ROADMAP v0.6.0 - Total lock wait time reporting for diagnostics 38 | pub fn get_total_lock_wait_time(&self) -> Duration { 39 | Duration::from_secs(0) 40 | } 41 | } 42 | 43 | impl Default for LockTracker { 44 | fn default() -> Self { 45 | Self::new() 46 | } 47 | } 48 | 49 | // Stub types for TrackedLock and TrackedRwLock 50 | #[allow(dead_code)] // ROADMAP v0.6.0 - Lock wrapper with performance tracking instrumentation 51 | pub type TrackedLock = Mutex; 52 | #[allow(dead_code)] // ROADMAP v0.6.0 - RwLock wrapper with read/write lock metrics 53 | pub type TrackedRwLock = RwLock; 54 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/statement_base.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | use crate::exec::write_stmt::ExecutionContext; 5 | use crate::exec::ExecutionError; 6 | use crate::txn::state::OperationType; 7 | use async_trait::async_trait; 8 | 9 | /// Base trait for all statement executors providing common infrastructure 10 | #[async_trait] 11 | pub trait StatementExecutor: Send + Sync { 12 | /// Get the operation type for this executor 13 | fn operation_type(&self) -> OperationType; 14 | 15 | /// Get a description of the operation 16 | fn operation_description(&self, context: &ExecutionContext) -> String; 17 | 18 | /// Check if this statement requires write permissions 19 | #[allow(dead_code)] // ROADMAP v0.6.0 - Permission-based statement authorization 20 | fn requires_write_permission(&self) -> bool { 21 | true // Default to requiring write permission for safety 22 | } 23 | 24 | /// Pre-execution: WAL logging (permissions are checked at higher level) 25 | fn pre_execute(&self, context: &ExecutionContext) -> Result<(), ExecutionError> { 26 | // Note: Permission checks are handled at higher level before executor is called 27 | 28 | // Log to WAL using context's method 29 | let description = self.operation_description(context); 30 | context.log_operation_to_wal(self.operation_type(), description)?; 31 | 32 | Ok(()) 33 | } 34 | 35 | /// Post-execution: optional cleanup 36 | fn post_execute( 37 | &self, 38 | _context: &ExecutionContext, 39 | _rows_affected: usize, 40 | ) -> Result<(), ExecutionError> { 41 | // Default: no post-execution actions needed 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/transaction/coordinator.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | use crate::ast::TransactionStatement; 5 | use crate::exec::write_stmt::{ExecutionContext, TransactionStatementExecutor}; 6 | use crate::exec::{ExecutionError, QueryExecutor, QueryResult}; 7 | 8 | use super::{ 9 | commit::CommitExecutor, rollback::RollbackExecutor, 10 | set_characteristics::SetTransactionCharacteristicsExecutor, start::StartTransactionExecutor, 11 | }; 12 | 13 | pub struct TransactionCoordinator; 14 | 15 | impl TransactionCoordinator { 16 | pub fn execute_transaction_statement( 17 | statement: &TransactionStatement, 18 | context: &ExecutionContext, 19 | _executor: &QueryExecutor, 20 | ) -> Result { 21 | // Pre-execute: WAL logging and permissions 22 | let stmt_executor: Box = match statement { 23 | TransactionStatement::StartTransaction(start_stmt) => { 24 | Box::new(StartTransactionExecutor::new(start_stmt.clone())) 25 | } 26 | TransactionStatement::Commit(commit_stmt) => { 27 | Box::new(CommitExecutor::new(commit_stmt.clone())) 28 | } 29 | TransactionStatement::Rollback(rollback_stmt) => { 30 | Box::new(RollbackExecutor::new(rollback_stmt.clone())) 31 | } 32 | TransactionStatement::SetTransactionCharacteristics(set_stmt) => { 33 | Box::new(SetTransactionCharacteristicsExecutor::new(set_stmt.clone())) 34 | } 35 | }; 36 | stmt_executor.pre_execute(context)?; 37 | stmt_executor.execute_transaction_operation(context) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /graphlite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphlite" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | description.workspace = true 9 | documentation = "https://docs.rs/graphlite" 10 | readme = "../README.md" 11 | keywords = ["gql", "graph", "query", "database", "iso-gql"] 12 | categories = ["database", "parser-implementations"] 13 | 14 | [lib] 15 | name = "graphlite" 16 | path = "src/lib.rs" 17 | doctest = false 18 | 19 | [features] 20 | default = ["sled-backend"] 21 | memory = [] 22 | sled-backend = ["dep:sled"] 23 | 24 | [dependencies] 25 | # Use workspace dependencies 26 | nom = { workspace = true } 27 | serde = { workspace = true } 28 | serde_json = { workspace = true } 29 | thiserror = { workspace = true } 30 | regex = { workspace = true } 31 | 32 | chrono = { workspace = true } 33 | chrono-tz = { workspace = true } 34 | 35 | tokio = { workspace = true } 36 | async-trait = { workspace = true } 37 | parking_lot = { workspace = true } 38 | rayon = { workspace = true } 39 | 40 | bincode = { workspace = true } 41 | uuid = { workspace = true } 42 | 43 | log = { workspace = true } 44 | env_logger = { workspace = true } 45 | 46 | fastrand = { workspace = true } 47 | 48 | sled = { workspace = true, optional = true } 49 | 50 | once_cell = { workspace = true } 51 | lazy_static = { workspace = true } 52 | crc32fast = { workspace = true } 53 | petgraph = { workspace = true } 54 | 55 | [dev-dependencies] 56 | tempfile = { workspace = true } 57 | 58 | [[example]] 59 | name = "simple_usage" 60 | path = "../examples/rust/bindings/simple_usage.rs" 61 | 62 | [[example]] 63 | name = "drug_discovery" 64 | path = "../examples/rust/bindings/drug_discovery/main.rs" 65 | 66 | [package.metadata.docs.rs] 67 | all-features = true 68 | rustdoc-args = ["--cfg", "docsrs"] 69 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Default to GQL shell if no command provided 5 | if [ $# -eq 0 ]; then 6 | # Check if database path is set and exists 7 | if [ -n "$GRAPHLITE_DB_PATH" ] && [ -d "$GRAPHLITE_DB_PATH" ]; then 8 | # Start GQL shell with database 9 | if [ -n "$GRAPHLITE_USER" ] && [ -n "$GRAPHLITE_PASSWORD" ]; then 10 | exec graphlite gql --path "$GRAPHLITE_DB_PATH" -u "$GRAPHLITE_USER" -p "$GRAPHLITE_PASSWORD" 11 | else 12 | echo "==========================================" 13 | echo "GraphLite Interactive GQL Shell" 14 | echo "==========================================" 15 | echo "Database path: $GRAPHLITE_DB_PATH" 16 | echo "" 17 | echo "Please provide credentials:" 18 | exec graphlite gql --path "$GRAPHLITE_DB_PATH" 19 | fi 20 | else 21 | # No database configured, show help 22 | echo "==========================================" 23 | echo "GraphLite - Graph Database" 24 | echo "==========================================" 25 | echo "" 26 | echo "No database configured. Please either:" 27 | echo " 1. Initialize a new database:" 28 | echo " docker run -it -v \$(pwd)/mydb:/data graphlite:latest \\" 29 | echo " graphlite install --path /data/mydb --admin-user admin --admin-password secret" 30 | echo "" 31 | echo " 2. Mount existing database and set environment variables:" 32 | echo " docker run -it -v \$(pwd)/mydb:/data \\" 33 | echo " -e GRAPHLITE_DB_PATH=/data/mydb \\" 34 | echo " -e GRAPHLITE_USER=admin \\" 35 | echo " -e GRAPHLITE_PASSWORD=secret \\" 36 | echo " graphlite:latest" 37 | echo "" 38 | exec graphlite --help 39 | fi 40 | else 41 | # Execute provided command 42 | exec "$@" 43 | fi 44 | -------------------------------------------------------------------------------- /graphlite/tests/unknown_procedure_test.rs: -------------------------------------------------------------------------------- 1 | //! Test that unknown procedures give proper error messages 2 | 3 | #[path = "testutils/mod.rs"] 4 | mod testutils; 5 | 6 | use testutils::test_fixture::TestFixture; 7 | 8 | #[test] 9 | fn test_unknown_gql_procedure_error() { 10 | let fixture = TestFixture::new().expect("Should create test fixture"); 11 | 12 | // Try to call a non-existent gql.* procedure 13 | let result = fixture.query("CALL gql.nonexistent_procedure();"); 14 | 15 | assert!(result.is_err(), "Should fail for non-existent procedure"); 16 | 17 | let err = result.unwrap_err(); 18 | 19 | // Should get "procedure not found" error, NOT "No graph context" error 20 | assert!( 21 | err.contains("procedure not found") || err.contains("not supported"), 22 | "Error should mention procedure not found, got: {}", 23 | err 24 | ); 25 | 26 | assert!( 27 | !err.contains("No graph context"), 28 | "Should not get graph context error for unknown procedure, got: {}", 29 | err 30 | ); 31 | 32 | // Should say "Available system procedures" not "ISO GQL procedures" 33 | assert!( 34 | err.contains("Available system procedures"), 35 | "Error should say 'Available system procedures', got: {}", 36 | err 37 | ); 38 | } 39 | 40 | #[test] 41 | fn test_list_available_procedures_in_error() { 42 | let fixture = TestFixture::new().expect("Should create test fixture"); 43 | 44 | let result = fixture.query("CALL gql.bad_procedure_name();"); 45 | 46 | assert!(result.is_err(), "Should fail for non-existent procedure"); 47 | 48 | let err = result.unwrap_err(); 49 | 50 | // Error should list available procedures to help the user 51 | assert!( 52 | err.contains("list_schemas") || err.contains("list_graphs"), 53 | "Error should list available procedures to help user, got: {}", 54 | err 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /graphlite/tests/simple_union_test.rs: -------------------------------------------------------------------------------- 1 | #[path = "testutils/mod.rs"] 2 | mod testutils; 3 | use graphlite::Value; 4 | use testutils::test_fixture::TestFixture; 5 | 6 | #[test] 7 | fn test_simple_union_without_relationships() { 8 | let fixture = TestFixture::empty().expect("Failed to create test fixture"); 9 | 10 | // Create schema (required before creating graphs - ISO GQL compliant) 11 | fixture.assert_query_succeeds(&format!( 12 | "CREATE SCHEMA IF NOT EXISTS /{}", 13 | fixture.schema_name() 14 | )); 15 | 16 | // Create a test graph 17 | let graph_name = "simple_union_test"; 18 | fixture.assert_query_succeeds(&format!( 19 | "CREATE GRAPH /{}/{}", 20 | fixture.schema_name(), 21 | graph_name 22 | )); 23 | fixture.assert_query_succeeds(&format!( 24 | "SESSION SET GRAPH /{}/{}", 25 | fixture.schema_name(), 26 | graph_name 27 | )); 28 | 29 | // Insert simple test data 30 | fixture.assert_query_succeeds("INSERT (p1:Person {name: 'Alice', age: 25})"); 31 | fixture.assert_query_succeeds("INSERT (p2:Person {name: 'Bob', age: 35})"); 32 | fixture.assert_query_succeeds("INSERT (p3:Person {name: 'Charlie', age: 45})"); 33 | 34 | // Test UNION operation 35 | let query = "MATCH (p:Person) WHERE p.age < 30 RETURN p.name UNION MATCH (p:Person) WHERE p.age > 40 RETURN p.name"; 36 | 37 | let result = fixture.assert_query_succeeds(query); 38 | 39 | // Should find Alice (age 25) and Charlie (age 45) 40 | assert_eq!(result.rows.len(), 2, "UNION should return 2 rows"); 41 | 42 | let names: Vec = result 43 | .rows 44 | .iter() 45 | .filter_map(|row| { 46 | if let Some(Value::String(name)) = row.values.get("p.name") { 47 | Some(name.clone()) 48 | } else { 49 | None 50 | } 51 | }) 52 | .collect(); 53 | 54 | assert!(names.contains(&"Alice".to_string())); 55 | assert!(names.contains(&"Charlie".to_string())); 56 | } 57 | -------------------------------------------------------------------------------- /bindings/python/setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup script for GraphLite Python bindings 3 | """ 4 | 5 | from setuptools import setup, find_packages 6 | from pathlib import Path 7 | 8 | # Read README 9 | readme_file = Path(__file__).parent / "README.md" 10 | long_description = readme_file.read_text() if readme_file.exists() else "" 11 | 12 | setup( 13 | name="graphlite", 14 | version="0.1.0", 15 | author="DeepGraph Inc.", 16 | author_email="info@deepgraph.ai", 17 | description="Python bindings for GraphLite embedded graph database", 18 | long_description=long_description, 19 | long_description_content_type="text/markdown", 20 | url="https://github.com/deepgraph/graphlite", 21 | packages=find_packages(), 22 | classifiers=[ 23 | "Development Status :: 3 - Alpha", 24 | "Intended Audience :: Developers", 25 | "License :: OSI Approved :: Apache Software License", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.8", 28 | "Programming Language :: Python :: 3.9", 29 | "Programming Language :: Python :: 3.10", 30 | "Programming Language :: Python :: 3.11", 31 | "Programming Language :: Python :: 3.12", 32 | "Operating System :: OS Independent", 33 | "Topic :: Database", 34 | "Topic :: Software Development :: Libraries :: Python Modules", 35 | ], 36 | python_requires=">=3.8", 37 | install_requires=[ 38 | # No dependencies - uses ctypes from standard library 39 | ], 40 | extras_require={ 41 | "dev": [ 42 | "pytest>=7.0.0", 43 | "pytest-cov>=4.0.0", 44 | "black>=23.0.0", 45 | "mypy>=1.0.0", 46 | ], 47 | }, 48 | keywords="graph database gql embedded graphlite", 49 | project_urls={ 50 | "Bug Reports": "https://github.com/deepgraph/graphlite/issues", 51 | "Source": "https://github.com/deepgraph/graphlite", 52 | "Documentation": "https://github.com/deepgraph/graphlite/tree/main/bindings/python", 53 | }, 54 | ) 55 | -------------------------------------------------------------------------------- /gql-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! GraphLite CLI entry point 5 | 6 | use clap::Parser; 7 | use colored::Colorize; 8 | 9 | mod cli; 10 | use cli::{Cli, Commands}; 11 | 12 | fn main() -> Result<(), Box> { 13 | // Parse command line arguments first to get log level 14 | let cli = Cli::parse(); 15 | 16 | // Determine log level from CLI args or environment variable 17 | let log_level = if cli.verbose { 18 | // -v/--verbose flag takes precedence 19 | log::LevelFilter::Debug 20 | } else if let Some(level) = cli.log_level { 21 | // --log-level flag 22 | level.to_level_filter() 23 | } else { 24 | // Default to Warn (can still be overridden by RUST_LOG env var) 25 | log::LevelFilter::Warn 26 | }; 27 | 28 | // Initialize logger 29 | env_logger::Builder::from_default_env() 30 | .filter_level(log_level) 31 | .init(); 32 | 33 | // Handle commands 34 | match cli.command { 35 | Commands::Version => { 36 | println!("{} {}", "GraphLite".bold().green(), graphlite::VERSION); 37 | println!("ISO GQL Graph Database"); 38 | Ok(()) 39 | } 40 | 41 | Commands::Install { 42 | path, 43 | admin_user, 44 | admin_password, 45 | force, 46 | yes, 47 | } => cli::handle_install(path, admin_user, admin_password, force, yes), 48 | 49 | Commands::Gql { path, sample } => cli::handle_gql(path, cli.user, cli.password, sample), 50 | 51 | Commands::Query { 52 | query, 53 | path, 54 | format, 55 | explain, 56 | ast, 57 | } => cli::handle_query(path, query, cli.user, cli.password, format, explain, ast), 58 | 59 | Commands::Session { action: _, path: _ } => { 60 | println!("{}", "Session management not yet implemented".yellow()); 61 | Ok(()) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/ddl_stmt/ddl_statement_base.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | use crate::catalog::manager::CatalogManager; 5 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor}; 6 | use crate::exec::ExecutionError; 7 | use crate::storage::StorageManager; 8 | 9 | /// Base trait for all DDL statement executors 10 | pub trait DDLStatementExecutor: StatementExecutor { 11 | /// Execute the DDL operation 12 | /// Returns a description message and the number of affected entities 13 | fn execute_ddl_operation( 14 | &self, 15 | context: &ExecutionContext, 16 | catalog_manager: &mut CatalogManager, 17 | storage: &StorageManager, 18 | ) -> Result<(String, usize), ExecutionError>; 19 | 20 | /// Main execution method - handles the complete DDL operation flow 21 | fn execute( 22 | &self, 23 | context: &ExecutionContext, 24 | catalog_manager: &mut CatalogManager, 25 | storage: &StorageManager, 26 | ) -> Result<(String, usize), ExecutionError> { 27 | // Pre-execution: check permissions, log to WAL 28 | self.pre_execute(context)?; 29 | 30 | // Execute the DDL operation 31 | let (message, affected) = self.execute_ddl_operation(context, catalog_manager, storage)?; 32 | 33 | // Post-execution: any cleanup 34 | self.post_execute(context, affected)?; 35 | 36 | Ok((message, affected)) 37 | } 38 | } 39 | 40 | /// Enum for different DDL statement types 41 | #[derive(Debug, Clone, Copy)] 42 | #[allow(dead_code)] // ROADMAP v0.5.0 - DDL statement classification for schema management and audit 43 | pub enum DDLStatementType { 44 | CreateSchema, 45 | DropSchema, 46 | CreateGraph, 47 | DropGraph, 48 | CreateGraphType, 49 | DropGraphType, 50 | TruncateGraph, 51 | ClearGraph, 52 | CreateUser, 53 | DropUser, 54 | CreateRole, 55 | DropRole, 56 | // Index DDL types 57 | CreateIndex, 58 | DropIndex, 59 | AlterIndex, 60 | OptimizeIndex, 61 | } 62 | -------------------------------------------------------------------------------- /graphlite/src/catalog/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Error types for the pluggable catalog system 5 | 6 | use thiserror::Error; 7 | 8 | #[derive(Error, Debug, Clone)] 9 | pub enum CatalogError { 10 | #[error("Catalog not found: {0}")] 11 | CatalogNotFound(String), 12 | 13 | #[error("Catalog operation failed: {0}")] 14 | OperationFailed(String), 15 | 16 | #[error("Serialization error: {0}")] 17 | SerializationError(String), 18 | 19 | #[error("Deserialization error: {0}")] 20 | DeserializationError(String), 21 | 22 | #[error("Storage error: {0}")] 23 | StorageError(String), 24 | 25 | #[error("Entity not found: {0}")] 26 | EntityNotFound(String), 27 | 28 | #[error("Entity already exists: {0}")] 29 | EntityAlreadyExists(String), 30 | 31 | #[error("Invalid parameters: {0}")] 32 | InvalidParameters(String), 33 | 34 | #[error("Permission denied: {0}")] 35 | PermissionDenied(String), 36 | 37 | #[error("IO error: {0}")] 38 | IoError(String), 39 | 40 | #[error("Not supported: {0}")] 41 | NotSupported(String), 42 | 43 | #[error("Duplicate entry: {0}")] 44 | DuplicateEntry(String), 45 | 46 | #[error("Not found: {0}")] 47 | NotFound(String), 48 | 49 | #[error("Invalid operation: {0}")] 50 | InvalidOperation(String), 51 | } 52 | 53 | impl From for CatalogError { 54 | fn from(err: std::io::Error) -> Self { 55 | CatalogError::IoError(err.to_string()) 56 | } 57 | } 58 | 59 | impl From for CatalogError { 60 | fn from(err: serde_json::Error) -> Self { 61 | CatalogError::SerializationError(err.to_string()) 62 | } 63 | } 64 | 65 | impl From for CatalogError { 66 | fn from(err: bincode::Error) -> Self { 67 | CatalogError::SerializationError(err.to_string()) 68 | } 69 | } 70 | 71 | impl From for CatalogError { 72 | fn from(err: crate::storage::StorageError) -> Self { 73 | CatalogError::StorageError(err.to_string()) 74 | } 75 | } 76 | 77 | pub type CatalogResult = Result; 78 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | GraphLite 2 | Copyright 2024-2025 DeepGraph Inc. and contributors 3 | 4 | This product includes software developed by DeepGraph Inc. (https://deepgraphai.com/) 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | ================================================================================ 19 | 20 | This product is based on grammar optimized from the OpenGQL project: 21 | * OpenGQL (https://github.com/opengql/grammar) 22 | * Copyright: OpenGQL Contributors 23 | * License: Apache License 2.0 24 | 25 | ================================================================================ 26 | 27 | AI-Assisted Development: 28 | 29 | This software was developed using claude code as a pair programmer, with occasional contributions from other similar coding agents. However, there was rigorous human oversight of AI generated code including thorough testing and manual review. 30 | 31 | All AI-generated code was reviewed, tested, and approved by human contributors. 32 | 33 | The use of AI tools does not alter the terms of the Apache License, Version 2.0, under which this project is licensed. All contributors—past, present, and future—provide their contributions under the same license and disclaimers. 34 | 35 | ================================================================================ 36 | 37 | Third-Party Dependencies: 38 | 39 | For a complete list of dependencies and their licenses, run: 40 | cargo license 41 | 42 | All dependencies use licenses compatible with Apache-2.0. 43 | See LICENSE file for full Apache License 2.0 text. 44 | 45 | ================================================================================ 46 | 47 | For questions, see: https://github.com/GraphLite-AI/GraphLite 48 | -------------------------------------------------------------------------------- /graphlite/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! GraphLite - A lightweight ISO GQL Graph Database 5 | //! 6 | //! GraphLite is a standalone graph database that implements the ISO GQL standard. 7 | //! 8 | //! # Features 9 | //! 10 | //! - **ISO GQL Compliance**: Full implementation of the ISO GQL standard 11 | //! - **Pattern Matching**: Powerful graph pattern matching with MATCH clauses 12 | //! - **ACID Transactions**: Full transaction support with isolation levels 13 | //! - **Embedded Database**: Uses Sled for embedded, serverless storage 14 | //! - **Type System**: Strong type system with validation and inference 15 | //! - **Query Optimization**: Cost-based query optimization 16 | //! 17 | //! # Usage 18 | //! 19 | //! GraphLite is primarily used as a standalone database via the CLI: 20 | //! 21 | //! ```bash 22 | //! # Install database 23 | //! graphlite install --path ./mydb --admin-user admin 24 | //! 25 | //! # Start interactive console 26 | //! graphlite gql --path ./mydb -u admin 27 | //! 28 | //! # Execute queries 29 | //! graphlite query --path ./mydb -u admin "MATCH (n:Person) RETURN n" 30 | //! ``` 31 | //! 32 | //! See the documentation for more details: 33 | //! - [Getting Started Guide](../docs/tutorials/Getting-started.md) 34 | //! - [System Procedures](../docs/reference/System-procedures.md) 35 | 36 | // Public modules - exposed to external users 37 | pub mod coordinator; 38 | 39 | // Internal modules - only visible within graphlite crate 40 | pub(crate) mod ast; 41 | pub(crate) mod cache; 42 | pub(crate) mod catalog; 43 | pub(crate) mod exec; 44 | pub(crate) mod functions; 45 | pub(crate) mod plan; 46 | pub(crate) mod schema; 47 | pub(crate) mod session; 48 | pub(crate) mod storage; 49 | pub(crate) mod txn; 50 | pub(crate) mod types; 51 | 52 | // Re-export the public API - QueryCoordinator is the only entry point 53 | pub use coordinator::{QueryCoordinator, QueryInfo, QueryPlan, QueryResult, QueryType, Row}; 54 | 55 | // Re-export Value type (needed for inspecting query results in Row.values) 56 | pub use storage::Value; 57 | 58 | /// GraphLite version 59 | pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 60 | 61 | /// GraphLite crate name 62 | pub const CRATE_NAME: &str = env!("CARGO_PKG_NAME"); 63 | -------------------------------------------------------------------------------- /graphlite/tests/debug_fraud_fixture.rs: -------------------------------------------------------------------------------- 1 | #[path = "testutils/mod.rs"] 2 | mod testutils; 3 | 4 | use testutils::test_fixture::TestFixture; 5 | 6 | #[test] 7 | fn test_fraud_fixture_creation_debug() { 8 | // Try to create a regular fixture first 9 | let fixture = TestFixture::new().expect("Failed to create basic fixture"); 10 | 11 | // Test CREATE GRAPH 12 | let create_result = fixture.query(&format!( 13 | "CREATE GRAPH /{}/fraud_graph", 14 | fixture.schema_name() 15 | )); 16 | if let Err(e) = create_result { 17 | panic!("✗ CREATE GRAPH failed: {}", e); 18 | } 19 | 20 | // Test SESSION SET 21 | let session_result = fixture.query(&format!( 22 | "SESSION SET GRAPH /{}/fraud_graph", 23 | fixture.schema_name() 24 | )); 25 | if let Err(e) = session_result { 26 | panic!("✗ SESSION SET failed: {}", e); 27 | } 28 | 29 | // Test Account INSERT 30 | let schema_id = fixture 31 | .schema_name() 32 | .replace("test_", "t") 33 | .replace("_", "x"); 34 | let account_query = format!( 35 | "INSERT (a{}x1:Account {{id: 1, name: 'Account1', balance: 101.0, status: 'active', account_type: 'savings'}})", 36 | schema_id 37 | ); 38 | if let Err(e) = fixture.query(&account_query) { 39 | panic!("✗ Account INSERT failed: {}", e); 40 | } 41 | 42 | // Test Merchant INSERT 43 | let merchant_query = format!( 44 | "INSERT (m{}x1:Merchant {{id: 1, name: 'Merchant1', category: 'retail'}})", 45 | schema_id 46 | ); 47 | if let Err(e) = fixture.query(&merchant_query) { 48 | panic!("✗ Merchant INSERT failed: {}", e); 49 | } 50 | 51 | // Test MATCH + INSERT 52 | let match_query = format!( 53 | "MATCH (a:Account {{id: 1}}), (m:Merchant {{id: 1}}) INSERT (a)-[:Transaction {{id: '{}x1', amount: 2.5, timestamp: '2024-01-01T00:00:00Z'}}]->(m)", 54 | schema_id 55 | ); 56 | if let Err(e) = fixture.query(&match_query) { 57 | panic!("✗ MATCH + INSERT failed: {}", e); 58 | } 59 | 60 | // Now test the full fraud fixture 61 | let full_fixture = TestFixture::with_fraud_data(); 62 | match full_fixture { 63 | Ok(_) => {} 64 | Err(e) => panic!("❌ Full fraud fixture failed: {}", e), 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # .dockerignore - Optimize Docker build context 2 | # Exclude files not needed for building the Docker image 3 | 4 | # Build artifacts and cache 5 | target/ 6 | **/target/ 7 | **/*.rs.bk 8 | *.pdb 9 | # Cargo.lock - NEEDED for reproducible builds 10 | 11 | # Git repository 12 | .git/ 13 | .gitignore 14 | .gitattributes 15 | .github/ 16 | 17 | # IDE and editor files 18 | .vscode/ 19 | .idea/ 20 | .gradle/ 21 | *.swp 22 | *.swo 23 | *~ 24 | *.iml 25 | .claude/ 26 | 27 | # OS-specific files 28 | .DS_Store 29 | Thumbs.db 30 | desktop.ini 31 | 32 | # Documentation and assets (comment out if needed in image) 33 | *.md 34 | docs/ 35 | CONTRIBUTING.md 36 | CHANGELOG.md 37 | README.md 38 | LICENSE 39 | NOTICE 40 | 41 | # Scripts (exclude unless needed in image) 42 | scripts/ 43 | *.sh 44 | !entrypoint.sh 45 | 46 | # Test files and data 47 | **/tests/ 48 | **/*_test.rs 49 | **/*_tests.rs 50 | **/testdata/ 51 | **/test_data/ 52 | *.test 53 | test_db/ 54 | testdb/ 55 | 56 | # Database files and runtime data 57 | *.db 58 | mydb/ 59 | my_db/ 60 | *.wal 61 | *.log 62 | graphlite-ffi/data/ 63 | 64 | # Python artifacts 65 | __pycache__/ 66 | *.pyc 67 | *.pyo 68 | *.egg-info/ 69 | .pytest_cache/ 70 | .mypy_cache/ 71 | dist/ 72 | build/ 73 | .venv/ 74 | venv/ 75 | *.so 76 | 77 | # Java artifacts 78 | *.class 79 | .gradle/ 80 | gradle/ 81 | 82 | # Bindings (exclude if not needed) 83 | bindings/ 84 | 85 | # Examples (exclude if not needed) 86 | examples/ 87 | examples-core/ 88 | 89 | # Temporary files 90 | *.tmp 91 | *.temp 92 | *.bak 93 | *.backup 94 | *.orig 95 | 96 | # Docker files themselves 97 | Dockerfile* 98 | docker-compose*.yml 99 | .dockerignore 100 | 101 | # Environment and secrets 102 | .env 103 | .env.* 104 | .secrets 105 | *.key 106 | *.pem 107 | *.crt 108 | 109 | # CI/CD 110 | .actrc 111 | .github/workflows/ 112 | 113 | # Grammar files (if not needed at runtime) 114 | grammar/ 115 | 116 | # Cargo metadata cache 117 | .cargo/ 118 | !.cargo/config.toml 119 | 120 | # macOS specific 121 | .Trashes 122 | .Spotlight-V100 123 | .TemporaryItems 124 | 125 | # Windows specific 126 | $RECYCLE.BIN/ 127 | 128 | # Linux specific 129 | .directory 130 | 131 | # Logs and monitoring 132 | *.log 133 | logs/ 134 | *.out 135 | *.err 136 | 137 | # Coverage reports 138 | coverage/ 139 | *.profraw 140 | *.profdata 141 | -------------------------------------------------------------------------------- /graphlite/src/catalog/providers/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Catalog providers registration system 5 | //! 6 | //! This module contains the registration system for all catalog providers. 7 | //! Adding a new catalog requires only implementing the CatalogProvider trait 8 | //! and adding one line to the register_all_catalogs function. 9 | 10 | use super::registry::CatalogRegistry; 11 | 12 | // Individual catalog provider modules 13 | pub mod graph_metadata; // Graph definitions (not metadata tracking) - needed for CREATE GRAPH 14 | pub mod index; 15 | pub mod schema; 16 | pub mod security; 17 | 18 | // Re-export GraphTypeCatalog from schema module 19 | pub use crate::schema::catalog::graph_type::GraphTypeCatalog; 20 | 21 | // These will be implemented as the catalog providers are created 22 | // pub mod timeseries; 23 | // pub mod document; 24 | // pub mod spatial; 25 | 26 | /// Register all available catalogs 27 | /// 28 | /// This function is called during catalog registry initialization to register 29 | /// all available catalog providers. Adding a new catalog is as simple as 30 | /// adding one line to this function. 31 | /// 32 | /// # Arguments 33 | /// * `registry` - Mutable reference to the catalog registry 34 | /// 35 | /// # Example 36 | /// To add a new catalog, simply add a line like: 37 | /// ```ignore 38 | /// registry.register("mycatalog", mycatalog::MyCatalog::new()); 39 | /// ``` 40 | pub fn register_all_catalogs(registry: &mut CatalogRegistry) { 41 | // Register implemented catalog providers 42 | registry.register("index", index::IndexCatalog::new()); 43 | registry.register( 44 | "graph_metadata", 45 | graph_metadata::GraphMetadataCatalog::new(), 46 | ); // Graph definitions (needed for CREATE GRAPH) 47 | registry.register("security", security::SecurityCatalog::new()); 48 | registry.register("schema", schema::SchemaCatalog::new()); 49 | registry.register("graph_type", Box::new(GraphTypeCatalog::new())); 50 | 51 | // TODO: Register additional catalog providers as they are implemented: 52 | // registry.register("timeseries", timeseries::TimeSeriesCatalog::new()); 53 | // registry.register("document", document::DocumentCatalog::new()); 54 | // registry.register("spatial", spatial::SpatialCatalog::new()); 55 | 56 | log::info!("Catalog provider registration complete"); 57 | } 58 | -------------------------------------------------------------------------------- /graphlite/src/schema/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Schema module - ISO GQL Graph Type Definitions and Validation 5 | // 6 | // This module implements persistent graph schema support per ISO GQL standards, 7 | // including CREATE GRAPH TYPE and schema validation. 8 | 9 | pub mod catalog; 10 | pub mod enforcement; 11 | pub mod executor; 12 | pub mod integration; 13 | pub mod introspection; 14 | pub mod parser; 15 | pub mod types; 16 | pub mod validator; 17 | 18 | // Re-export commonly used types - commented out until needed 19 | // pub use types::{ 20 | // GraphTypeDefinition, 21 | // NodeTypeDefinition, 22 | // EdgeTypeDefinition, 23 | // PropertyDefinition, 24 | // DataType, 25 | // Constraint, 26 | // GraphTypeVersion, 27 | // SchemaEnforcementMode, 28 | // }; 29 | 30 | pub use validator::ValidationError; 31 | 32 | // Schema module error type 33 | use thiserror::Error; 34 | 35 | #[derive(Error, Debug)] 36 | pub enum SchemaError { 37 | #[error("Schema validation error: {0}")] 38 | ValidationError(#[from] ValidationError), 39 | 40 | #[error("Schema not found: {0}")] 41 | #[allow(dead_code)] 42 | // ROADMAP v0.4.0 - Error variant for schema lookup failures in graph type catalog 43 | SchemaNotFound(String), 44 | 45 | #[error("Schema already exists: {0}")] 46 | #[allow(dead_code)] // ROADMAP v0.4.0 - Error variant for CREATE GRAPH TYPE conflicts 47 | SchemaAlreadyExists(String), 48 | 49 | #[error("Invalid schema definition: {0}")] 50 | #[allow(dead_code)] // ROADMAP v0.4.0 - Error variant for malformed graph type DDL 51 | InvalidDefinition(String), 52 | 53 | #[error("Version conflict: {0}")] 54 | #[allow(dead_code)] 55 | // ROADMAP v0.4.0 - Error variant for graph type version conflicts during ALTER 56 | VersionConflict(String), 57 | 58 | #[error("Catalog error: {0}")] 59 | #[allow(dead_code)] 60 | // ROADMAP v0.4.0 - Error variant for graph type catalog operation failures 61 | CatalogError(String), 62 | 63 | #[error("IO error: {0}")] 64 | IoError(#[from] std::io::Error), 65 | 66 | #[error("Serialization error: {0}")] 67 | SerializationError(#[from] serde_json::Error), 68 | } 69 | 70 | #[allow(dead_code)] // ROADMAP v0.4.0 - Result type for schema operations (CREATE/ALTER/DROP GRAPH TYPE) 71 | pub type SchemaResult = Result; 72 | -------------------------------------------------------------------------------- /sdk-python/src/graphlite_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GraphLite SDK - High-level Python API for GraphLite 3 | 4 | This package provides a high-level, developer-friendly SDK on top of GraphLite's core API. 5 | It offers ergonomic patterns, type safety, session management, query builders, and 6 | transaction support - everything needed to build robust graph-based applications in Python. 7 | 8 | Quick Start 9 | ----------- 10 | 11 | ```python 12 | from graphlite_sdk import GraphLite 13 | 14 | # Open database 15 | db = GraphLite.open("./mydb") 16 | 17 | # Create session 18 | session = db.session("admin") 19 | 20 | # Execute query 21 | result = session.query("MATCH (p:Person) RETURN p.name") 22 | 23 | # Use transactions 24 | with session.transaction() as tx: 25 | tx.execute("INSERT (p:Person {name: 'Alice'})") 26 | tx.commit() 27 | ``` 28 | 29 | Architecture 30 | ----------- 31 | 32 | ``` 33 | Your Application 34 | │ 35 | ▼ 36 | ┌─────────────────────────────────────────┐ 37 | │ GraphLite SDK (this package) │ 38 | │ - GraphLite (main API) │ 39 | │ - Session (session management) │ 40 | │ - Transaction (ACID support) │ 41 | │ - QueryBuilder (fluent queries) │ 42 | │ - TypedResult (deserialization) │ 43 | └─────────────────────────────────────────┘ 44 | │ 45 | ▼ 46 | ┌─────────────────────────────────────────┐ 47 | │ GraphLite FFI Bindings │ 48 | │ (Low-level ctypes wrapper) │ 49 | └─────────────────────────────────────────┘ 50 | │ 51 | ▼ 52 | ┌─────────────────────────────────────────┐ 53 | │ GraphLite Core (Rust) │ 54 | │ - QueryCoordinator │ 55 | │ - Storage Engine │ 56 | │ - Catalog Manager │ 57 | └─────────────────────────────────────────┘ 58 | ``` 59 | """ 60 | 61 | from .error import ( 62 | GraphLiteError, 63 | ConnectionError, 64 | SessionError, 65 | QueryError, 66 | TransactionError, 67 | SerializationError, 68 | ) 69 | from .connection import GraphLite, Session 70 | from .transaction import Transaction 71 | from .query import QueryBuilder 72 | from .result import TypedResult 73 | 74 | __version__ = "0.1.0" 75 | 76 | __all__ = [ 77 | "GraphLite", 78 | "Session", 79 | "Transaction", 80 | "QueryBuilder", 81 | "TypedResult", 82 | "GraphLiteError", 83 | "ConnectionError", 84 | "SessionError", 85 | "QueryError", 86 | "TransactionError", 87 | "SerializationError", 88 | ] 89 | -------------------------------------------------------------------------------- /graphlite/src/storage/persistent/factory.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Storage driver factory 5 | //! 6 | //! This module provides factory functions for creating storage drivers based on configuration. 7 | //! It handles the instantiation and setup of different storage driver types. 8 | 9 | use super::traits::{StorageDriver, StorageTree}; 10 | use super::types::{StorageDriverError, StorageResult, StorageType}; 11 | use std::path::Path; 12 | 13 | /// Factory function to create a storage driver based on configuration 14 | /// 15 | /// This is the main entry point for creating storage drivers. It takes a storage type 16 | /// and path, then returns the appropriate driver implementation as a trait object. 17 | /// 18 | /// # Arguments 19 | /// * `storage_type` - The type of storage driver to create (RocksDB, Sled, etc.) 20 | /// * `path` - The filesystem path where the database should be stored 21 | /// 22 | /// # Returns 23 | /// A boxed trait object that implements StorageDriver 24 | /// 25 | /// # Examples 26 | /// ```ignore 27 | /// use crate::storage::drivers::{create_storage_driver, StorageType}; 28 | /// 29 | /// let driver = create_storage_driver(StorageType::Sled, "./data")?; 30 | /// let tree = driver.open_tree("my_tree")?; 31 | /// ``` 32 | pub fn create_storage_driver>( 33 | storage_type: StorageType, 34 | path: P, 35 | ) -> StorageResult>>> { 36 | match storage_type { 37 | StorageType::Sled => { 38 | use crate::storage::persistent::sled::SledDriver; 39 | let driver = SledDriver::open(path)?; 40 | Ok(Box::new(driver) as Box>>) 41 | } 42 | StorageType::RocksDB => Err(StorageDriverError::BackendSpecific( 43 | "RocksDB storage backend not yet implemented".to_string(), 44 | )), 45 | StorageType::Memory => { 46 | use crate::storage::persistent::memory::MemoryStorageDriver; 47 | let driver = MemoryStorageDriver::open(path)?; 48 | Ok(Box::new(driver) as Box>>) 49 | } 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | use tempfile::TempDir; 57 | 58 | #[test] 59 | fn test_create_sled_driver() { 60 | let temp_dir = TempDir::new().unwrap(); 61 | let driver = create_storage_driver(StorageType::Sled, temp_dir.path()).unwrap(); 62 | assert_eq!(driver.storage_type(), StorageType::Sled); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /graphlite/tests/rollback_simple_test.rs: -------------------------------------------------------------------------------- 1 | //! Simple test to debug ROLLBACK behavior 2 | 3 | #![allow(unused_variables)] 4 | 5 | #[path = "testutils/mod.rs"] 6 | mod testutils; 7 | 8 | use graphlite::Value; 9 | use testutils::test_fixture::TestFixture; 10 | 11 | #[test] 12 | fn test_simple_rollback_debug() { 13 | let fixture = TestFixture::new().expect("Failed to create fixture"); 14 | 15 | fixture 16 | .setup_graph("rollback_debug_test") 17 | .expect("Failed to setup graph"); 18 | 19 | // Create a person 20 | let insert_result = fixture 21 | .query("INSERT (:Person {name: 'Dave', age: 40})") 22 | .expect("Failed to create person"); 23 | 24 | // Verify person exists before transaction 25 | let before_result = fixture 26 | .query("MATCH (p:Person {name: 'Dave'}) RETURN p.age as age") 27 | .expect("Query before transaction should succeed"); 28 | assert_eq!(before_result.rows.len(), 1); 29 | assert_eq!( 30 | before_result.rows[0].values.get("age"), 31 | Some(&Value::Number(40.0)) 32 | ); 33 | 34 | // Start a transaction 35 | let txn_result = fixture.query("START TRANSACTION"); 36 | assert!(txn_result.is_ok(), "START TRANSACTION should succeed"); 37 | 38 | // Update age in transaction 39 | let update_result = fixture.query("MATCH (p:Person {name: 'Dave'}) SET p.age = 41"); 40 | assert!(update_result.is_ok(), "SET should succeed in transaction"); 41 | 42 | // Verify change was made 43 | let mid_result = fixture 44 | .query("MATCH (p:Person {name: 'Dave'}) RETURN p.age as age") 45 | .expect("Query during transaction should succeed"); 46 | assert_eq!(mid_result.rows.len(), 1); 47 | assert_eq!( 48 | mid_result.rows[0].values.get("age"), 49 | Some(&Value::Number(41.0)) 50 | ); 51 | 52 | // Rollback 53 | let rollback_result = fixture.query("ROLLBACK"); 54 | assert!(rollback_result.is_ok(), "ROLLBACK should succeed"); 55 | 56 | // Verify change was rolled back 57 | let after_result = 58 | fixture.query("MATCH (p:Person {name: 'Dave'}) RETURN p.age as age, p.name as name"); 59 | 60 | match after_result { 61 | Ok(result) => { 62 | if result.rows.len() > 0 { 63 | assert_eq!( 64 | result.rows[0].values.get("age"), 65 | Some(&Value::Number(40.0)), 66 | "Age should be rolled back to 40" 67 | ); 68 | } else { 69 | panic!("No rows returned after ROLLBACK - node was deleted!"); 70 | } 71 | } 72 | Err(e) => { 73 | panic!("Query after ROLLBACK failed: {:?}", e); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /graphlite/tests/rollback_batch_test.rs: -------------------------------------------------------------------------------- 1 | //! Test to verify ROLLBACK works with batch undo operations from SET 2 | 3 | #[path = "testutils/mod.rs"] 4 | mod testutils; 5 | 6 | use graphlite::Value; 7 | use testutils::test_fixture::TestFixture; 8 | 9 | #[test] 10 | fn test_rollback_undoes_batch_set_operations() { 11 | let fixture = TestFixture::new().expect("Failed to create fixture"); 12 | 13 | fixture 14 | .setup_graph("rollback_batch_test") 15 | .expect("Failed to setup graph"); 16 | 17 | // Create a person 18 | fixture 19 | .query("INSERT (:Person {name: 'Charlie', age: 28, city: 'LA', status: 'active'})") 20 | .expect("Failed to create person"); 21 | 22 | // Start a transaction 23 | fixture 24 | .query("START TRANSACTION") 25 | .expect("Failed to start transaction"); 26 | 27 | // Update multiple properties (creates batch undo operations) 28 | fixture.query( 29 | "MATCH (p:Person {name: 'Charlie'}) SET p.age = 29, p.city = 'SF', p.status = 'inactive'" 30 | ).expect("SET should succeed in transaction"); 31 | 32 | // Verify changes were made 33 | let mid_result = fixture.query( 34 | "MATCH (p:Person {name: 'Charlie'}) RETURN p.age as age, p.city as city, p.status as status" 35 | ).expect("Query should succeed"); 36 | 37 | let mid_row = &mid_result.rows[0].values; 38 | assert_eq!( 39 | mid_row.get("age"), 40 | Some(&Value::Number(29.0)), 41 | "Age should be 29" 42 | ); 43 | assert_eq!( 44 | mid_row.get("city"), 45 | Some(&Value::String("SF".to_string())), 46 | "City should be SF" 47 | ); 48 | assert_eq!( 49 | mid_row.get("status"), 50 | Some(&Value::String("inactive".to_string())), 51 | "Status should be inactive" 52 | ); 53 | 54 | // Rollback the transaction 55 | fixture.query("ROLLBACK").expect("ROLLBACK should succeed"); 56 | 57 | // Verify ALL changes were rolled back (batch undo worked!) 58 | let final_result = fixture.query( 59 | "MATCH (p:Person {name: 'Charlie'}) RETURN p.age as age, p.city as city, p.status as status" 60 | ).expect("Query should succeed"); 61 | 62 | let final_row = &final_result.rows[0].values; 63 | assert_eq!( 64 | final_row.get("age"), 65 | Some(&Value::Number(28.0)), 66 | "Age should be rolled back to 28" 67 | ); 68 | assert_eq!( 69 | final_row.get("city"), 70 | Some(&Value::String("LA".to_string())), 71 | "City should be rolled back to LA" 72 | ); 73 | assert_eq!( 74 | final_row.get("status"), 75 | Some(&Value::String("active".to_string())), 76 | "Status should be rolled back to active" 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /.github/workflows/dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Updates 2 | 3 | on: 4 | schedule: 5 | # Run weekly on Monday at 9:00 AM UTC 6 | - cron: '0 9 * * 1' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | update-rust-dependencies: 11 | name: Update Rust Dependencies 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Install Rust toolchain 18 | uses: dtolnay/rust-toolchain@stable 19 | 20 | - name: Install cargo-edit 21 | run: cargo install cargo-edit 22 | 23 | - name: Update dependencies 24 | run: | 25 | cargo update 26 | cargo upgrade --workspace 27 | 28 | - name: Run tests 29 | run: | 30 | ./scripts/build_all.sh --release --test 31 | 32 | - name: Create Pull Request 33 | uses: peter-evans/create-pull-request@v5 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | commit-message: 'chore: update Rust dependencies' 37 | title: 'chore: Update Rust dependencies' 38 | body: | 39 | ## Automated Dependency Update 40 | 41 | This PR updates the Rust dependencies to their latest compatible versions. 42 | 43 | ### Changes 44 | - Updated `Cargo.lock` with latest dependency versions 45 | - Verified builds and tests pass with new versions 46 | 47 | ### Next Steps 48 | - Review the changes 49 | - Check for any breaking changes in updated dependencies 50 | - Merge if all checks pass 51 | branch: chore/update-rust-deps 52 | delete-branch: true 53 | labels: dependencies, automated 54 | 55 | security-audit: 56 | name: Security Audit 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Checkout code 60 | uses: actions/checkout@v4 61 | 62 | - name: Install Rust toolchain 63 | uses: dtolnay/rust-toolchain@stable 64 | 65 | - name: Install cargo-audit 66 | run: cargo install cargo-audit 67 | 68 | - name: Run security audit 69 | run: cargo audit 70 | 71 | - name: Create issue if vulnerabilities found 72 | if: failure() 73 | uses: actions/github-script@v7 74 | with: 75 | script: | 76 | github.rest.issues.create({ 77 | owner: context.repo.owner, 78 | repo: context.repo.repo, 79 | title: 'Security vulnerabilities found in dependencies', 80 | body: 'The scheduled security audit found vulnerabilities. Please review and update affected dependencies.\n\nRun `cargo audit` locally for details.', 81 | labels: ['security', 'dependencies', 'automated'] 82 | }) 83 | -------------------------------------------------------------------------------- /graphlite/tests/intersect_debug_test.rs: -------------------------------------------------------------------------------- 1 | //! Debug test for INTERSECT operation returning empty results 2 | 3 | #![allow(unused_variables)] 4 | 5 | #[path = "testutils/mod.rs"] 6 | mod testutils; 7 | 8 | use graphlite::Value; 9 | use testutils::test_fixture::TestFixture; 10 | 11 | #[test] 12 | fn test_intersect_identical_rows() { 13 | let fixture = TestFixture::empty().expect("Failed to create test fixture"); 14 | 15 | // Create a test graph with sample data 16 | fixture 17 | .query(&format!( 18 | "CREATE GRAPH /{}/intersect_debug", 19 | fixture.schema_name() 20 | )) 21 | .unwrap(); 22 | fixture 23 | .query(&format!( 24 | "SESSION SET GRAPH /{}/intersect_debug", 25 | fixture.schema_name() 26 | )) 27 | .unwrap(); 28 | 29 | // Insert test data 30 | fixture 31 | .query(r#"INSERT (p1:Person {name: "Alice Smith", age: 32, salary: 85000})"#) 32 | .unwrap(); 33 | fixture 34 | .query(r#"INSERT (p2:Person {name: "Eve Davis", age: 41, salary: 110000})"#) 35 | .unwrap(); 36 | fixture 37 | .query(r#"INSERT (p3:Person {name: "Charlie Brown", age: 35, salary: 95000})"#) 38 | .unwrap(); 39 | fixture 40 | .query(r#"INSERT (p4:Person {name: "Grace Chen", age: 33, salary: 88000})"#) 41 | .unwrap(); 42 | 43 | // Test the individual queries first 44 | let result1 = fixture 45 | .query("MATCH (p:Person) WHERE p.age > 30 RETURN p.name, p.age, p.salary") 46 | .unwrap(); 47 | for (i, row) in result1.rows.iter().enumerate() {} 48 | 49 | let result2 = fixture 50 | .query("MATCH (p:Person) WHERE p.salary > 80000 RETURN p.name, p.age, p.salary") 51 | .unwrap(); 52 | for (i, row) in result2.rows.iter().enumerate() {} 53 | 54 | // Test the INTERSECT query 55 | let intersect_query = " 56 | MATCH (p:Person) WHERE p.age > 30 RETURN p.name, p.age, p.salary 57 | INTERSECT 58 | MATCH (p:Person) WHERE p.salary > 80000 RETURN p.name, p.age, p.salary 59 | "; 60 | let intersect_result = match fixture.query(intersect_query) { 61 | Ok(result) => result, 62 | Err(e) => { 63 | panic!("INTERSECT query failed"); 64 | } 65 | }; 66 | 67 | for (i, row) in intersect_result.rows.iter().enumerate() { 68 | // Check for node identities 69 | for (key, value) in &row.values { 70 | if let Value::Node(node) = value {} 71 | } 72 | } 73 | 74 | // The INTERSECT should return 4 rows since both queries return the same 4 rows 75 | assert_eq!( 76 | intersect_result.rows.len(), 77 | 4, 78 | "INTERSECT should return 4 rows when both sides return identical rows" 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmarks 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | benchmark: 10 | name: Run Benchmarks 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Install Rust toolchain 17 | uses: dtolnay/rust-toolchain@stable 18 | 19 | - name: Install build dependencies 20 | run: | 21 | sudo apt-get update 22 | sudo apt-get install -y build-essential 23 | 24 | - name: Cache cargo registry 25 | uses: actions/cache@v4 26 | with: 27 | path: ~/.cargo/registry 28 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 29 | 30 | - name: Cache cargo index 31 | uses: actions/cache@v4 32 | with: 33 | path: ~/.cargo/git 34 | key: ${{ runner.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }} 35 | 36 | - name: Cache cargo build 37 | uses: actions/cache@v4 38 | with: 39 | path: target 40 | key: ${{ runner.os }}-cargo-build-bench-${{ hashFiles('**/Cargo.lock') }} 41 | 42 | - name: Run benchmarks 43 | run: | 44 | # Check if benches directory exists 45 | if [ -d "benches" ]; then 46 | cargo bench --workspace 47 | else 48 | echo "No benchmarks directory found, skipping..." 49 | fi 50 | 51 | - name: Store benchmark result 52 | uses: benchmark-action/github-action-benchmark@v1 53 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 54 | with: 55 | tool: 'cargo' 56 | output-file-path: target/criterion/*/base/estimates.json 57 | github-token: ${{ secrets.GITHUB_TOKEN }} 58 | auto-push: true 59 | alert-threshold: '150%' 60 | comment-on-alert: true 61 | fail-on-alert: false 62 | 63 | performance-test: 64 | name: Performance Test 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Checkout code 68 | uses: actions/checkout@v4 69 | 70 | - name: Install Rust toolchain 71 | uses: dtolnay/rust-toolchain@stable 72 | 73 | - name: Install build dependencies 74 | run: | 75 | sudo apt-get update 76 | sudo apt-get install -y build-essential 77 | 78 | - name: Build release binary 79 | run: ./scripts/build_all.sh --release 80 | 81 | - name: Run CLI performance tests 82 | run: | 83 | if [ -f ./scripts/test_cli.sh ]; then 84 | chmod +x ./scripts/test_cli.sh 85 | ./scripts/test_cli.sh 86 | else 87 | echo "CLI test script not found" 88 | fi 89 | 90 | - name: Measure binary size 91 | run: | 92 | ls -lh target/release/graphlite 93 | echo "Binary size (MB):" 94 | du -h target/release/graphlite | cut -f1 95 | -------------------------------------------------------------------------------- /graphlite/src/storage/indexes/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Error types for the indexing system 5 | 6 | use crate::storage::persistent::types::StorageDriverError; 7 | use crate::storage::StorageError; 8 | use thiserror::Error; 9 | 10 | /// Errors that can occur during index operations 11 | #[derive(Error, Debug)] 12 | pub enum IndexError { 13 | #[error("Index '{0}' already exists")] 14 | AlreadyExists(String), 15 | 16 | #[error("Index '{0}' not found")] 17 | NotFound(String), 18 | 19 | #[error("Invalid index configuration: {0}")] 20 | InvalidConfiguration(String), 21 | 22 | #[error("Dimension mismatch: expected {expected}, got {got}")] 23 | DimensionMismatch { expected: usize, got: usize }, 24 | 25 | #[error("Serialization error: {0}")] 26 | SerializationError(#[from] bincode::Error), 27 | 28 | #[error("Storage error: {0}")] 29 | StorageError(#[from] StorageError), 30 | 31 | #[error("Storage driver error: {0}")] 32 | StorageDriverError(#[from] StorageDriverError), 33 | 34 | #[error("IO error: {0}")] 35 | IoError(#[from] std::io::Error), 36 | 37 | #[error("Query error: {0}")] 38 | QueryError(String), 39 | 40 | #[error("Index is not ready for operations")] 41 | NotReady, 42 | 43 | #[error("Operation not supported by this index type")] 44 | UnsupportedOperation, 45 | 46 | #[error("Index maintenance failed: {0}")] 47 | MaintenanceError(String), 48 | 49 | #[error("Concurrent modification error")] 50 | ConcurrentModification, 51 | 52 | #[error("Index corruption detected: {0}")] 53 | CorruptionError(String), 54 | } 55 | 56 | impl IndexError { 57 | /// Create a query error 58 | pub fn query>(msg: S) -> Self { 59 | Self::QueryError(msg.into()) 60 | } 61 | 62 | /// Create a configuration error 63 | pub fn config>(msg: S) -> Self { 64 | Self::InvalidConfiguration(msg.into()) 65 | } 66 | 67 | /// Create a maintenance error 68 | pub fn maintenance>(msg: S) -> Self { 69 | Self::MaintenanceError(msg.into()) 70 | } 71 | 72 | /// Create an IO error 73 | pub fn io>(msg: S) -> Self { 74 | Self::IoError(std::io::Error::other(msg.into())) 75 | } 76 | 77 | /// Create a creation error (alias for config) 78 | pub fn creation>(msg: S) -> Self { 79 | Self::InvalidConfiguration(msg.into()) 80 | } 81 | 82 | /// Create a serialization error 83 | pub fn serialization>(msg: S) -> Self { 84 | Self::IoError(std::io::Error::new( 85 | std::io::ErrorKind::InvalidData, 86 | msg.into(), 87 | )) 88 | } 89 | 90 | /// Create a deserialization error 91 | pub fn deserialization>(msg: S) -> Self { 92 | Self::IoError(std::io::Error::new( 93 | std::io::ErrorKind::InvalidData, 94 | msg.into(), 95 | )) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /graphlite/tests/simple_role_test.rs: -------------------------------------------------------------------------------- 1 | //! Simple test to verify GRANT/REVOKE role functionality works 2 | //! This test focuses on the core functionality without complex dependencies 3 | 4 | #![allow(unused_variables)] 5 | 6 | #[path = "testutils/mod.rs"] 7 | mod testutils; 8 | 9 | use testutils::test_fixture::TestFixture; 10 | 11 | #[test] 12 | fn test_grant_revoke_basic_functionality() { 13 | let fixture = TestFixture::empty().expect("Failed to create test fixture"); 14 | 15 | // Create test role and users 16 | fixture.assert_query_succeeds("CREATE ROLE 'test_role'"); 17 | fixture.assert_query_succeeds("CREATE USER 'test_user' PASSWORD 'password'"); 18 | 19 | // Test GRANT ROLE 20 | let grant_result = fixture.assert_query_succeeds("GRANT ROLE 'test_role' TO 'test_user'"); 21 | 22 | // Print the result to see what we get 23 | 24 | // Test duplicate GRANT (should succeed but indicate already has role) 25 | let duplicate_grant = fixture.assert_query_succeeds("GRANT ROLE 'test_role' TO 'test_user'"); 26 | 27 | // Test REVOKE ROLE 28 | let revoke_result = fixture.assert_query_succeeds("REVOKE ROLE 'test_role' FROM 'test_user'"); 29 | 30 | // Test revoking role user doesn't have (should succeed but indicate no change) 31 | let revoke_missing = fixture.assert_query_succeeds("REVOKE ROLE 'test_role' FROM 'test_user'"); 32 | } 33 | 34 | #[test] 35 | fn test_role_error_scenarios() { 36 | let fixture = TestFixture::empty().expect("Failed to create test fixture"); 37 | 38 | // Set up test data 39 | fixture.assert_query_succeeds("CREATE ROLE 'existing_role'"); 40 | fixture.assert_query_succeeds("CREATE USER 'existing_user' PASSWORD 'password'"); 41 | 42 | // Test 1: Grant non-existent role 43 | fixture.assert_query_fails( 44 | "GRANT ROLE 'nonexistent_role' TO 'existing_user'", 45 | "does not exist", 46 | ); 47 | 48 | // Test 2: Grant role to non-existent user 49 | fixture.assert_query_fails( 50 | "GRANT ROLE 'existing_role' TO 'nonexistent_user'", 51 | "does not exist", 52 | ); 53 | 54 | // Test 3: Revoke non-existent role 55 | fixture.assert_query_fails( 56 | "REVOKE ROLE 'nonexistent_role' FROM 'existing_user'", 57 | "does not exist", 58 | ); 59 | 60 | // Test 4: Revoke role from non-existent user 61 | fixture.assert_query_fails( 62 | "REVOKE ROLE 'existing_role' FROM 'nonexistent_user'", 63 | "does not exist", 64 | ); 65 | } 66 | 67 | #[test] 68 | fn test_system_role_protection() { 69 | let fixture = TestFixture::empty().expect("Failed to create test fixture"); 70 | 71 | // Create a test user 72 | fixture.assert_query_succeeds("CREATE USER 'protected_user' PASSWORD 'password'"); 73 | 74 | // Test 1: Try to revoke 'user' role (should fail) 75 | fixture.assert_query_fails( 76 | "REVOKE ROLE 'user' FROM 'protected_user'", 77 | "Cannot revoke system role 'user'", 78 | ); 79 | 80 | // Test 2: Try to revoke 'admin' role from 'admin' user (should fail) 81 | fixture.assert_query_fails( 82 | "REVOKE ROLE 'admin' FROM 'admin'", 83 | "Cannot revoke 'admin' role from 'admin' user", 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /sdk-rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! GraphLite SDK - High-level ergonomic Rust API for GraphLite 2 | //! 3 | //! This crate provides a high-level, developer-friendly SDK on top of GraphLite's core API. 4 | //! It offers ergonomic patterns, type safety, connection management, query builders, and 5 | //! transaction support - everything needed to build robust graph-based applications in Rust. 6 | //! 7 | //! # Quick Start 8 | //! 9 | //! ```no_run 10 | //! use graphlite_sdk::{GraphLite, Error}; 11 | //! 12 | //! # fn main() -> Result<(), Error> { 13 | //! // Open database 14 | //! let db = GraphLite::open("./mydb")?; 15 | //! 16 | //! // Create session and execute query 17 | //! let session = db.create_session("admin")?; 18 | //! let result = db.query("MATCH (p:Person) RETURN p.name", &session)?; 19 | //! 20 | //! // Process results 21 | //! for row in result.rows() { 22 | //! println!("Name: {:?}", row.get("p.name")); 23 | //! } 24 | //! # Ok(()) 25 | //! # } 26 | //! ``` 27 | //! 28 | //! # Features 29 | //! 30 | //! - **Connection Management** - Simple connection API with automatic session handling 31 | //! - **Query Builder** - Fluent API for building type-safe GQL queries 32 | //! - **Transaction Support** - ACID transactions with automatic rollback 33 | //! - **Typed Results** - Deserialize query results into Rust structs 34 | //! - **Connection Pooling** - Efficient concurrent access (future) 35 | //! - **Async Support** - Full tokio integration (future) 36 | //! 37 | //! # Architecture 38 | //! 39 | //! ```text 40 | //! ┌─────────────────────────────────────────┐ 41 | //! │ Application Code (Your Rust App) │ 42 | //! └─────────────────────────────────────────┘ 43 | //! │ 44 | //! ▼ 45 | //! ┌─────────────────────────────────────────┐ 46 | //! │ GraphLite SDK (this crate) │ 47 | //! │ - GraphLite (main API) │ 48 | //! │ - Session (session management) │ 49 | //! │ - QueryBuilder (fluent queries) │ 50 | //! │ - Transaction (ACID support) │ 51 | //! │ - TypedResult (deserialization) │ 52 | //! └─────────────────────────────────────────┘ 53 | //! │ 54 | //! ▼ 55 | //! ┌─────────────────────────────────────────┐ 56 | //! │ GraphLite Core (graphlite crate) │ 57 | //! │ - QueryCoordinator │ 58 | //! │ - Storage Engine │ 59 | //! │ - Catalog Manager │ 60 | //! └─────────────────────────────────────────┘ 61 | //! ``` 62 | //! 63 | //! # Module Organization 64 | //! 65 | //! - [`connection`] - Database connection and session management 66 | //! - [`query`] - Query builder and execution 67 | //! - [`transaction`] - Transaction support 68 | //! - [`result`] - Result handling and deserialization 69 | //! - [`error`] - Error types and handling 70 | 71 | // Re-export core types for convenience 72 | pub use graphlite::{QueryInfo, QueryPlan, QueryResult, QueryType, Row, Value}; 73 | 74 | // SDK modules 75 | pub mod connection; 76 | pub mod error; 77 | pub mod query; 78 | pub mod result; 79 | pub mod transaction; 80 | 81 | // Re-export main types for convenience 82 | pub use connection::{GraphLite, Session}; 83 | pub use error::{Error, Result}; 84 | pub use query::QueryBuilder; 85 | pub use result::TypedResult; 86 | pub use transaction::Transaction; 87 | -------------------------------------------------------------------------------- /graphlite/src/cache/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Comprehensive caching system 5 | //! 6 | //! This module provides multi-level caching for: 7 | //! - Query results 8 | //! - Compiled query plans 9 | //! - Subquery results 10 | //! - Metadata lookups 11 | //! - Statistics and cardinality estimates 12 | 13 | pub mod cache_config; 14 | pub mod cache_manager; 15 | pub mod invalidation; 16 | pub mod plan_cache; 17 | pub mod result_cache; 18 | pub mod subquery_cache; 19 | 20 | pub use cache_config::{CacheConfig, EvictionPolicy}; 21 | pub use cache_manager::CacheManager; 22 | pub use invalidation::{InvalidationEvent, InvalidationManager}; 23 | pub use plan_cache::{PlanCache, PlanCacheEntry, PlanCacheKey}; 24 | pub use result_cache::ResultCache; 25 | pub use subquery_cache::{ 26 | SubqueryCache, SubqueryCacheHit, SubqueryCacheKey, SubqueryResult, SubqueryType, 27 | }; 28 | 29 | use std::time::{Duration, Instant}; 30 | 31 | /// Cache levels for different types of data 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 33 | pub enum CacheLevel { 34 | /// L1: Hot data, frequently accessed (in-memory, small, fast) 35 | L1, 36 | /// L2: Warm data, occasionally accessed (in-memory, larger, moderate speed) 37 | L2, 38 | /// L3: Cold data, infrequently accessed (disk-backed, large, slower) 39 | L3, 40 | } 41 | 42 | /// Cache entry metadata 43 | #[derive(Debug, Clone)] 44 | pub struct CacheEntryMetadata { 45 | pub created_at: Instant, 46 | pub last_accessed: Instant, 47 | pub access_count: u32, 48 | pub size_bytes: usize, 49 | pub ttl: Option, 50 | pub level: CacheLevel, 51 | pub tags: Vec, // For cache invalidation 52 | } 53 | 54 | impl CacheEntryMetadata { 55 | pub fn new(size_bytes: usize, level: CacheLevel) -> Self { 56 | let now = Instant::now(); 57 | Self { 58 | created_at: now, 59 | last_accessed: now, 60 | access_count: 0, 61 | size_bytes, 62 | ttl: None, 63 | level, 64 | tags: Vec::new(), 65 | } 66 | } 67 | 68 | pub fn with_ttl(mut self, ttl: Duration) -> Self { 69 | self.ttl = Some(ttl); 70 | self 71 | } 72 | 73 | pub fn with_tags(mut self, tags: Vec) -> Self { 74 | self.tags = tags; 75 | self 76 | } 77 | 78 | pub fn is_expired(&self) -> bool { 79 | if let Some(ttl) = self.ttl { 80 | self.created_at.elapsed() > ttl 81 | } else { 82 | false 83 | } 84 | } 85 | 86 | pub fn update_access(&mut self) { 87 | self.last_accessed = Instant::now(); 88 | self.access_count += 1; 89 | } 90 | } 91 | 92 | /// Generic cache key trait 93 | pub trait CacheKey: 94 | std::fmt::Debug + Clone + PartialEq + Eq + std::hash::Hash + Send + Sync 95 | { 96 | #[allow(dead_code)] // ROADMAP v0.5.0 - Cache key generation for query caching (see ROADMAP.md §9) 97 | fn cache_key(&self) -> String; 98 | fn tags(&self) -> Vec { 99 | Vec::new() 100 | } 101 | } 102 | 103 | /// Generic cached value trait 104 | pub trait CacheValue: std::fmt::Debug + Clone + Send + Sync { 105 | fn size_bytes(&self) -> usize; 106 | fn is_valid(&self) -> bool { 107 | true 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/ddl_stmt/drop_user.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // DropUserExecutor - Implements DROP USER statement execution 5 | use crate::ast::DropUserStatement; 6 | use crate::catalog::manager::CatalogManager; 7 | use crate::catalog::operations::{CatalogOperation, EntityType}; 8 | use crate::exec::write_stmt::ddl_stmt::DDLStatementExecutor; 9 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor}; 10 | use crate::exec::ExecutionError; 11 | use crate::storage::StorageManager; 12 | use crate::txn::state::OperationType; 13 | 14 | pub struct DropUserExecutor { 15 | statement: DropUserStatement, 16 | } 17 | 18 | impl DropUserExecutor { 19 | pub fn new(statement: DropUserStatement) -> Self { 20 | Self { statement } 21 | } 22 | } 23 | 24 | impl StatementExecutor for DropUserExecutor { 25 | fn operation_type(&self) -> OperationType { 26 | OperationType::DropUser 27 | } 28 | 29 | fn operation_description(&self, _context: &ExecutionContext) -> String { 30 | format!("DROP USER '{}'", self.statement.username) 31 | } 32 | } 33 | 34 | impl DDLStatementExecutor for DropUserExecutor { 35 | fn execute_ddl_operation( 36 | &self, 37 | _context: &ExecutionContext, 38 | catalog_manager: &mut CatalogManager, 39 | _storage: &StorageManager, 40 | ) -> Result<(String, usize), ExecutionError> { 41 | let username = &self.statement.username; 42 | 43 | // Create the catalog operation for dropping the user 44 | let drop_op = CatalogOperation::Drop { 45 | entity_type: EntityType::User, 46 | name: username.clone(), 47 | cascade: false, // User dropping doesn't support cascade yet 48 | }; 49 | 50 | // Execute the operation through the catalog manager 51 | let drop_result = catalog_manager.execute("security", drop_op); 52 | match drop_result { 53 | Ok(_response) => { 54 | // Persist the catalog changes transactionally 55 | let persist_result = catalog_manager.persist_catalog("security"); 56 | if let Err(e) = persist_result { 57 | return Err(ExecutionError::RuntimeError(format!( 58 | "Failed to persist user deletion '{}' to storage: {}", 59 | username, e 60 | ))); 61 | } 62 | 63 | let message = if self.statement.if_exists { 64 | format!("User '{}' dropped successfully (if exists)", username) 65 | } else { 66 | format!("User '{}' dropped successfully", username) 67 | }; 68 | Ok((message, 1)) 69 | } 70 | Err(catalog_error) => { 71 | // Handle "not found" errors differently for IF EXISTS 72 | let error_msg = catalog_error.to_string(); 73 | if error_msg.contains("not found") && self.statement.if_exists { 74 | let message = format!("User '{}' does not exist (if exists)", username); 75 | Ok((message, 0)) 76 | } else { 77 | Err(ExecutionError::RuntimeError(format!( 78 | "Failed to drop user '{}': {}", 79 | username, catalog_error 80 | ))) 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /graphlite/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! ISO GQL-compliant Type System Operational Components 5 | //! 6 | //! This module provides operational components for the type system that work with 7 | //! the existing TypeSpec defined in the AST. It includes type inference, validation, 8 | //! coercion, and casting engines. 9 | 10 | pub mod casting; 11 | pub mod coercion; 12 | pub mod inference; 13 | pub mod validation; 14 | 15 | use std::fmt; 16 | 17 | pub use self::casting::TypeCaster; 18 | pub use self::coercion::{CoercionStrategy, TypeCoercion}; 19 | pub use self::inference::TypeInferenceEngine as TypeInference; 20 | pub use self::validation::TypeValidator; 21 | 22 | // Re-export TypeSpec as the main type for the type system 23 | pub use crate::ast::{GraphTypeSpec, TypeSpec as GqlType}; 24 | 25 | /// Type error for type system operations 26 | #[derive(Debug, Clone)] 27 | pub enum TypeError { 28 | #[allow(dead_code)] // ROADMAP v0.5.0 - Type incompatibility error variant 29 | IncompatibleTypes(String, String), 30 | #[allow(dead_code)] // ISO GQL - Type casting operations 31 | InvalidCast(String, String), 32 | #[allow(dead_code)] // ISO GQL - Type validation and checking 33 | TypeMismatch { 34 | expected: String, 35 | actual: String, 36 | }, 37 | InvalidTypeSpecification(String), 38 | #[allow(dead_code)] // ISO GQL - NOT NULL constraint violations 39 | NullabilityViolation(String), 40 | #[allow(dead_code)] // ISO GQL - LIST and SET type mismatches 41 | CollectionTypeMismatch(String), 42 | #[allow(dead_code)] // ROADMAP v0.4.0 - Graph schema validation errors for type enforcement 43 | GraphSchemaViolation(String), 44 | #[allow(dead_code)] // ISO GQL - Numeric overflow detection in type operations 45 | NumericOverflow(String), 46 | #[allow(dead_code)] // ISO GQL - Unknown type references in schema 47 | UnknownType(String), 48 | } 49 | 50 | impl fmt::Display for TypeError { 51 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 52 | match self { 53 | TypeError::IncompatibleTypes(t1, t2) => { 54 | write!(f, "Incompatible types: {} and {}", t1, t2) 55 | } 56 | TypeError::InvalidCast(from, to) => { 57 | write!(f, "Cannot cast from {} to {}", from, to) 58 | } 59 | TypeError::TypeMismatch { expected, actual } => { 60 | write!(f, "Type mismatch: expected {}, got {}", expected, actual) 61 | } 62 | TypeError::InvalidTypeSpecification(msg) => { 63 | write!(f, "Invalid type specification: {}", msg) 64 | } 65 | TypeError::NullabilityViolation(msg) => { 66 | write!(f, "Nullability violation: {}", msg) 67 | } 68 | TypeError::CollectionTypeMismatch(msg) => { 69 | write!(f, "Collection type mismatch: {}", msg) 70 | } 71 | TypeError::GraphSchemaViolation(msg) => { 72 | write!(f, "Graph schema violation: {}", msg) 73 | } 74 | TypeError::NumericOverflow(msg) => { 75 | write!(f, "Numeric overflow: {}", msg) 76 | } 77 | TypeError::UnknownType(name) => { 78 | write!(f, "Unknown type: {}", name) 79 | } 80 | } 81 | } 82 | } 83 | 84 | impl std::error::Error for TypeError {} 85 | 86 | /// Result type for type system operations 87 | pub type TypeResult = Result; 88 | -------------------------------------------------------------------------------- /graphlite/tests/transactional_set_test.rs: -------------------------------------------------------------------------------- 1 | //! Test to verify SET transactional behavior - all properties or rollback 2 | 3 | #[path = "testutils/mod.rs"] 4 | mod testutils; 5 | 6 | use graphlite::Value; 7 | use testutils::test_fixture::TestFixture; 8 | 9 | #[test] 10 | fn test_set_transactional_all_or_nothing() { 11 | let fixture = TestFixture::new().expect("Failed to create fixture"); 12 | 13 | fixture 14 | .setup_graph("transactional_set_test") 15 | .expect("Failed to setup graph"); 16 | 17 | // Create a person 18 | fixture 19 | .query("INSERT (:Person {name: 'Alice', age: 30, city: 'NYC'})") 20 | .expect("Failed to create person"); 21 | 22 | // Try to SET multiple properties where one will fail 23 | // datetime('1992-05-15') will fail because it needs time component 24 | let result = fixture.query( 25 | "MATCH (p:Person {name: 'Alice'}) SET p.age = 31, p.birthday = datetime('1992-05-15')", 26 | ); 27 | 28 | // This should fail with an error (not a warning!) 29 | assert!( 30 | result.is_err(), 31 | "SET should fail when one property evaluation fails" 32 | ); 33 | 34 | let err_msg = format!("{:?}", result.unwrap_err()); 35 | assert!( 36 | err_msg.contains("Failed to evaluate") || err_msg.contains("datetime"), 37 | "Error should mention datetime parsing failure: {}", 38 | err_msg 39 | ); 40 | 41 | // Verify that NO properties were changed (transactional guarantee) 42 | let query_result = fixture 43 | .query("MATCH (p:Person {name: 'Alice'}) RETURN p.age as age") 44 | .expect("Query should succeed"); 45 | 46 | assert_eq!(query_result.rows.len(), 1, "Should return 1 node"); 47 | 48 | let age_value = &query_result.rows[0].values["age"]; 49 | assert_eq!( 50 | age_value, 51 | &Value::Number(30.0), 52 | "Age should still be 30 (not changed to 31)" 53 | ); 54 | } 55 | 56 | #[test] 57 | fn test_set_transactional_success_all_valid() { 58 | let fixture = TestFixture::new().expect("Failed to create fixture"); 59 | 60 | fixture 61 | .setup_graph("transactional_set_success_test") 62 | .expect("Failed to setup graph"); 63 | 64 | // Create a person 65 | fixture 66 | .query("INSERT (:Person {name: 'Bob', age: 25})") 67 | .expect("Failed to create person"); 68 | 69 | // SET multiple properties - all valid (with correct datetime format) 70 | let result = fixture.query( 71 | "MATCH (p:Person {name: 'Bob'}) SET p.age = 26, p.city = upper('seattle'), p.birthday = datetime('1992-05-15T00:00:00Z')" 72 | ); 73 | 74 | assert!( 75 | result.is_ok(), 76 | "SET should succeed when all properties are valid" 77 | ); 78 | 79 | // Verify ALL properties were changed 80 | let query_result = fixture.query( 81 | "MATCH (p:Person {name: 'Bob'}) RETURN p.age as age, p.city as city, p.birthday as birthday" 82 | ).expect("Query should succeed"); 83 | 84 | assert_eq!(query_result.rows.len(), 1, "Should return 1 node"); 85 | 86 | let row = &query_result.rows[0].values; 87 | assert_eq!( 88 | row.get("age"), 89 | Some(&Value::Number(26.0)), 90 | "Age should be updated to 26" 91 | ); 92 | assert_eq!( 93 | row.get("city"), 94 | Some(&Value::String("SEATTLE".to_string())), 95 | "City should be SEATTLE" 96 | ); 97 | assert!( 98 | row.get("birthday") != Some(&Value::Null), 99 | "Birthday should be set" 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/transaction/set_characteristics.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | use crate::ast::{IsolationLevel, SetTransactionCharacteristicsStatement}; 5 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor, TransactionStatementExecutor}; 6 | use crate::exec::{ExecutionError, QueryResult, Row}; 7 | use crate::storage::value::Value; 8 | use crate::txn::state::OperationType; 9 | use std::collections::HashMap; 10 | 11 | pub struct SetTransactionCharacteristicsExecutor { 12 | statement: SetTransactionCharacteristicsStatement, 13 | } 14 | 15 | impl SetTransactionCharacteristicsExecutor { 16 | pub fn new(statement: SetTransactionCharacteristicsStatement) -> Self { 17 | Self { statement } 18 | } 19 | } 20 | 21 | impl StatementExecutor for SetTransactionCharacteristicsExecutor { 22 | fn operation_type(&self) -> OperationType { 23 | OperationType::Begin // Using Begin as a general transaction operation type 24 | } 25 | 26 | fn operation_description(&self, _context: &ExecutionContext) -> String { 27 | "SET TRANSACTION CHARACTERISTICS".to_string() 28 | } 29 | 30 | fn requires_write_permission(&self) -> bool { 31 | false // Transaction control doesn't require graph write permissions 32 | } 33 | } 34 | 35 | impl TransactionStatementExecutor for SetTransactionCharacteristicsExecutor { 36 | fn execute_transaction_operation( 37 | &self, 38 | _context: &ExecutionContext, 39 | ) -> Result { 40 | Self::execute_set_characteristics(&self.statement) 41 | } 42 | } 43 | 44 | impl SetTransactionCharacteristicsExecutor { 45 | pub fn execute_set_characteristics( 46 | statement: &SetTransactionCharacteristicsStatement, 47 | ) -> Result { 48 | // For now, just return a success message with the characteristics that would be set 49 | // In a full implementation, this would: 50 | // 1. Validate the characteristics 51 | // 2. Set them for the next transaction 52 | 53 | let mut message = "Transaction characteristics set:".to_string(); 54 | 55 | if let Some(ref isolation_level) = statement.characteristics.isolation_level { 56 | message.push_str(&format!(" ISOLATION LEVEL {}", isolation_level.as_str())); 57 | } 58 | 59 | if let Some(ref access_mode) = statement.characteristics.access_mode { 60 | message.push_str(&format!(" {}", access_mode.as_str())); 61 | } 62 | 63 | // For now, we only support READ_COMMITTED 64 | if let Some(ref isolation_level) = statement.characteristics.isolation_level { 65 | match isolation_level { 66 | IsolationLevel::ReadCommitted => { 67 | // This is supported 68 | } 69 | _ => { 70 | return Err(ExecutionError::UnsupportedOperator( 71 | format!("Isolation level {} not yet supported. Only READ COMMITTED is currently implemented.", 72 | isolation_level.as_str()) 73 | )); 74 | } 75 | } 76 | } 77 | 78 | Ok(QueryResult { 79 | rows: vec![Row::from_values(HashMap::from([( 80 | "status".to_string(), 81 | Value::String(message), 82 | )]))], 83 | variables: vec!["status".to_string()], 84 | execution_time_ms: 0, 85 | rows_affected: 0, 86 | session_result: None, 87 | warnings: Vec::new(), 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /bindings/java/src/main/java/com/deepgraph/graphlite/QueryResult.java: -------------------------------------------------------------------------------- 1 | package com.deepgraph.graphlite; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | 6 | import java.util.*; 7 | 8 | /** 9 | * Query result wrapper with convenient access methods 10 | */ 11 | public class QueryResult { 12 | private final JSONObject data; 13 | private final List variables; 14 | private final List> rows; 15 | 16 | /** 17 | * Create QueryResult from JSON string 18 | * 19 | * @param jsonString JSON result from FFI 20 | */ 21 | public QueryResult(String jsonString) { 22 | this.data = new JSONObject(jsonString); 23 | this.variables = parseVariables(); 24 | this.rows = parseRows(); 25 | } 26 | 27 | private List parseVariables() { 28 | List vars = new ArrayList<>(); 29 | JSONArray varsArray = data.optJSONArray("variables"); 30 | if (varsArray != null) { 31 | for (int i = 0; i < varsArray.length(); i++) { 32 | vars.add(varsArray.getString(i)); 33 | } 34 | } 35 | return Collections.unmodifiableList(vars); 36 | } 37 | 38 | private List> parseRows() { 39 | List> rowList = new ArrayList<>(); 40 | JSONArray rowsArray = data.optJSONArray("rows"); 41 | 42 | if (rowsArray != null) { 43 | for (int i = 0; i < rowsArray.length(); i++) { 44 | JSONObject rowObj = rowsArray.getJSONObject(i); 45 | Map row = new HashMap<>(); 46 | 47 | for (String key : rowObj.keySet()) { 48 | row.put(key, rowObj.get(key)); 49 | } 50 | 51 | rowList.add(Collections.unmodifiableMap(row)); 52 | } 53 | } 54 | 55 | return Collections.unmodifiableList(rowList); 56 | } 57 | 58 | /** 59 | * Get column names from RETURN clause 60 | * 61 | * @return List of variable names 62 | */ 63 | public List getVariables() { 64 | return variables; 65 | } 66 | 67 | /** 68 | * Get all result rows 69 | * 70 | * @return List of rows (each row is a Map) 71 | */ 72 | public List> getRows() { 73 | return rows; 74 | } 75 | 76 | /** 77 | * Get number of rows 78 | * 79 | * @return Row count 80 | */ 81 | public int getRowCount() { 82 | return rows.size(); 83 | } 84 | 85 | /** 86 | * Get first row or null if no rows 87 | * 88 | * @return First row or null 89 | */ 90 | public Map first() { 91 | return rows.isEmpty() ? null : rows.get(0); 92 | } 93 | 94 | /** 95 | * Get all values from a specific column 96 | * 97 | * @param columnName Column name to extract 98 | * @return List of values from that column 99 | */ 100 | public List column(String columnName) { 101 | List values = new ArrayList<>(); 102 | for (Map row : rows) { 103 | values.add(row.get(columnName)); 104 | } 105 | return values; 106 | } 107 | 108 | /** 109 | * Check if result is empty 110 | * 111 | * @return true if no rows 112 | */ 113 | public boolean isEmpty() { 114 | return rows.isEmpty(); 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | return String.format("QueryResult(rows=%d, variables=%s)", rows.size(), variables); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /graphlite/tests/with_clause_property_access_bug.rs: -------------------------------------------------------------------------------- 1 | //! WITH Clause Property Access Bug Investigation 2 | //! 3 | //! Focused test to identify why property access works in RETURN but fails in WITH 4 | 5 | #[path = "testutils/mod.rs"] 6 | mod testutils; 7 | 8 | use testutils::test_fixture::TestFixture; 9 | 10 | #[test] 11 | fn test_with_clause_property_access_bug() { 12 | let fixture = TestFixture::empty().expect("Failed to create fixture"); 13 | 14 | // Setup 15 | fixture.assert_query_succeeds(&format!("CREATE GRAPH /{}/bug_test", fixture.schema_name())); 16 | fixture.assert_query_succeeds(&format!( 17 | "SESSION SET GRAPH /{}/bug_test", 18 | fixture.schema_name() 19 | )); 20 | 21 | // Insert test node 22 | fixture.assert_query_succeeds( 23 | r#" 24 | INSERT (test:Node { 25 | id: 'test123', 26 | name: 'Test Node', 27 | data: [1.0, 2.0, 3.0], 28 | number: 42, 29 | text: 'hello' 30 | }) 31 | "#, 32 | ); 33 | 34 | // Test 1: Property access in RETURN (WORKS) 35 | let _result1 = fixture.assert_query_succeeds( 36 | r#" 37 | MATCH (n:Node {id: 'test123'}) 38 | RETURN n.data, n.number, n.text, n.name 39 | "#, 40 | ); 41 | 42 | // Test 2: Property access in WITH (FAILS) 43 | let _result2 = fixture.assert_query_succeeds( 44 | r#" 45 | MATCH (n:Node {id: 'test123'}) 46 | WITH n.data as arr, n.number as num, n.text as txt, n.name as nm 47 | RETURN arr, num, txt, nm 48 | "#, 49 | ); 50 | 51 | // Test 3: Compare node access vs property access 52 | let _result3 = fixture.assert_query_succeeds( 53 | r#" 54 | MATCH (n:Node {id: 'test123'}) 55 | WITH n as node, n.data as arr 56 | RETURN node, arr 57 | "#, 58 | ); 59 | 60 | // Test 4: Workaround - pass node through WITH, access property in RETURN 61 | let _result4 = fixture.assert_query_succeeds( 62 | r#" 63 | MATCH (n:Node {id: 'test123'}) 64 | WITH n as node 65 | RETURN node.data, node.number, node.text, node.name 66 | "#, 67 | ); 68 | } 69 | 70 | #[test] 71 | fn test_with_clause_multiple_nodes_workaround() { 72 | let fixture = TestFixture::empty().expect("Failed to create fixture"); 73 | 74 | // Setup 75 | fixture.assert_query_succeeds(&format!( 76 | "CREATE GRAPH /{}/workaround", 77 | fixture.schema_name() 78 | )); 79 | fixture.assert_query_succeeds(&format!( 80 | "SESSION SET GRAPH /{}/workaround", 81 | fixture.schema_name() 82 | )); 83 | 84 | // Insert test documents 85 | fixture.assert_query_succeeds( 86 | r#" 87 | INSERT (doc1:Document { 88 | title: 'Doc1', 89 | score: 85 90 | }) 91 | "#, 92 | ); 93 | 94 | fixture.assert_query_succeeds( 95 | r#" 96 | INSERT (doc2:Document { 97 | title: 'Doc2', 98 | score: 92 99 | }) 100 | "#, 101 | ); 102 | 103 | // Workaround for multiple node property access using node passing 104 | let result = fixture.assert_query_succeeds( 105 | r#" 106 | MATCH (query_doc:Document {title: 'Doc1'}), (all_docs:Document) 107 | WITH query_doc, all_docs 108 | RETURN 109 | all_docs.title, 110 | query_doc.score as query_score, 111 | all_docs.score as doc_score 112 | "#, 113 | ); 114 | 115 | // Verify the query returns results 116 | assert!(!result.rows.is_empty(), "Query should return results"); 117 | } 118 | -------------------------------------------------------------------------------- /.github/TESTING_CI.md: -------------------------------------------------------------------------------- 1 | # Quick Start: Testing CI/CD Workflows 2 | 3 | This guide helps you test the GitHub Actions workflows **before pushing to GitHub**. 4 | 5 | ## Option 1: Quick Test (Recommended - 2 minutes) 6 | 7 | Run the automated test script: 8 | 9 | ```bash 10 | # Quick check (formatting + linting only) 11 | ./scripts/test_ci_locally.sh --quick 12 | 13 | # Full check (includes build + tests) 14 | ./scripts/test_ci_locally.sh --full 15 | ``` 16 | 17 | This tests all the same checks that CI will run! 18 | 19 | ## Option 2: Using `act` (Advanced - Local GitHub Actions) 20 | 21 | Install `act` to run GitHub Actions locally: 22 | 23 | ### Install `act` 24 | 25 | **Ubuntu/WSL:** 26 | ```bash 27 | curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash 28 | ``` 29 | 30 | **macOS:** 31 | ```bash 32 | brew install act 33 | ``` 34 | 35 | ### Run Workflows Locally 36 | 37 | ```bash 38 | # List all workflows and jobs 39 | act -l 40 | 41 | # Test the lint job (fast) 42 | act -j lint -W .github/workflows/ci.yml 43 | 44 | # Dry run - see what would happen 45 | act -n -W .github/workflows/ci.yml 46 | 47 | # Run full CI workflow (takes time) 48 | act push -W .github/workflows/ci.yml 49 | ``` 50 | 51 | **Note:** The `.actrc` file is already configured with optimal settings for GraphLite. 52 | 53 | ## Option 3: Push to Test Branch (Safest) 54 | 55 | Test on GitHub without affecting main branch: 56 | 57 | ```bash 58 | # Create test branch 59 | git checkout -b test/verify-ci 60 | 61 | # Commit workflows 62 | git add .github/ scripts/ 63 | git commit -m "test: verify CI workflows" 64 | 65 | # Push to test branch 66 | git push origin test/verify-ci 67 | 68 | # Go to GitHub Actions tab and watch it run 69 | # URL: https://github.com/GraphLite-AI/GraphLite/actions 70 | ``` 71 | 72 | Monitor the results, then: 73 | 74 | ```bash 75 | # If successful, merge back 76 | git checkout chore/implement-ci-cd 77 | git merge test/verify-ci 78 | 79 | # Clean up test branch 80 | git branch -D test/verify-ci 81 | git push origin --delete test/verify-ci 82 | ``` 83 | 84 | ## What Gets Tested 85 | 86 | **Formatting** - `cargo fmt --all -- --check` 87 | **Linting** - `cargo clippy --all-targets --all-features` 88 | **Build** - `./scripts/build_all.sh --release` 89 | **Tests** - `./scripts/run_tests.sh --release` 90 | **Docs** - `cargo doc --no-deps --all-features` 91 | **Security** - `cargo audit` (if installed) 92 | 93 | ## Troubleshooting 94 | 95 | ### Tests fail locally but should pass? 96 | 97 | **Fix formatting:** 98 | ```bash 99 | cargo fmt --all 100 | ``` 101 | 102 | **Fix clippy warnings:** 103 | Review the warnings and fix them, or allow specific ones if needed. 104 | 105 | ### Want to test on specific OS? 106 | 107 | Use the test branch method (Option 3) and check both Ubuntu and macOS results on GitHub. 108 | 109 | ### `act` fails with Docker errors? 110 | 111 | Ensure Docker is running: 112 | ```bash 113 | sudo systemctl start docker # Linux 114 | # or start Docker Desktop on macOS 115 | ``` 116 | 117 | ## Recommended Workflow 118 | 119 | 1. **Make changes** to workflows or code 120 | 2. **Quick test locally:** 121 | ```bash 122 | ./scripts/test_ci_locally.sh --quick 123 | ``` 124 | 3. **If passing, full test:** 125 | ```bash 126 | ./scripts/test_ci_locally.sh --full 127 | ``` 128 | 4. **If all local tests pass, push to test branch:** 129 | ```bash 130 | git push origin test/verify-ci 131 | ``` 132 | 5. **Monitor GitHub Actions, then merge if successful** 133 | 134 | ## Need Help? 135 | 136 | - See detailed docs: [.github/workflows/TEST_WORKFLOWS.md](.github/workflows/TEST_WORKFLOWS.md) 137 | - Check workflow configs: [.github/workflows/](.github/workflows/) 138 | - Run with help: `./scripts/test_ci_locally.sh --help` 139 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/ddl_stmt/drop_role.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // DropRoleExecutor - Implements DROP ROLE statement execution 5 | use crate::ast::DropRoleStatement; 6 | use crate::catalog::manager::CatalogManager; 7 | use crate::catalog::operations::{CatalogOperation, EntityType}; 8 | use crate::exec::write_stmt::ddl_stmt::DDLStatementExecutor; 9 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor}; 10 | use crate::exec::ExecutionError; 11 | use crate::storage::StorageManager; 12 | use crate::txn::state::OperationType; 13 | 14 | pub struct DropRoleExecutor { 15 | statement: DropRoleStatement, 16 | } 17 | 18 | impl DropRoleExecutor { 19 | pub fn new(statement: DropRoleStatement) -> Self { 20 | Self { statement } 21 | } 22 | } 23 | 24 | impl StatementExecutor for DropRoleExecutor { 25 | fn operation_type(&self) -> OperationType { 26 | OperationType::DropRole 27 | } 28 | 29 | fn operation_description(&self, _context: &ExecutionContext) -> String { 30 | format!("DROP ROLE '{}'", self.statement.role_name) 31 | } 32 | } 33 | 34 | impl DDLStatementExecutor for DropRoleExecutor { 35 | fn execute_ddl_operation( 36 | &self, 37 | _context: &ExecutionContext, 38 | catalog_manager: &mut CatalogManager, 39 | _storage: &StorageManager, 40 | ) -> Result<(String, usize), ExecutionError> { 41 | let role_name = &self.statement.role_name; 42 | 43 | // Prevent deletion of system roles 44 | if role_name == "admin" || role_name == "user" { 45 | return Err(ExecutionError::RuntimeError(format!( 46 | "Cannot drop system role '{}'. System roles (admin, user) cannot be deleted.", 47 | role_name 48 | ))); 49 | } 50 | 51 | // Create the catalog operation for dropping the role 52 | let drop_op = CatalogOperation::Drop { 53 | entity_type: EntityType::Role, 54 | name: role_name.clone(), 55 | cascade: false, // Role dropping doesn't support cascade yet 56 | }; 57 | 58 | // Execute the operation through the catalog manager 59 | let drop_result = catalog_manager.execute("security", drop_op); 60 | match drop_result { 61 | Ok(_response) => { 62 | // Persist the catalog changes transactionally 63 | let persist_result = catalog_manager.persist_catalog("security"); 64 | if let Err(e) = persist_result { 65 | return Err(ExecutionError::RuntimeError(format!( 66 | "Failed to persist role deletion '{}' to storage: {}", 67 | role_name, e 68 | ))); 69 | } 70 | 71 | let message = if self.statement.if_exists { 72 | format!("Role '{}' dropped successfully (if exists)", role_name) 73 | } else { 74 | format!("Role '{}' dropped successfully", role_name) 75 | }; 76 | Ok((message, 1)) 77 | } 78 | Err(catalog_error) => { 79 | // Handle "not found" errors differently for IF EXISTS 80 | let error_msg = catalog_error.to_string(); 81 | if error_msg.contains("not found") && self.statement.if_exists { 82 | let message = format!("Role '{}' does not exist (if exists)", role_name); 83 | Ok((message, 0)) 84 | } else { 85 | Err(ExecutionError::RuntimeError(format!( 86 | "Failed to drop role '{}': {}", 87 | role_name, catalog_error 88 | ))) 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /sdk-python/src/graphlite_sdk/error.py: -------------------------------------------------------------------------------- 1 | """ 2 | Error types for the GraphLite SDK 3 | """ 4 | 5 | from typing import Union 6 | import json 7 | 8 | class GraphLiteError(Exception): 9 | """ 10 | Base exception for all GraphLite SDK errors. 11 | """ 12 | def __init__(self, message: str): 13 | self.message = message 14 | super().__init__(message) 15 | 16 | class GraphLiteCoreError(GraphLiteError): 17 | """Error from the core GraphLite library""" 18 | def __init__(self, message: str): 19 | super().__init__(f"GraphLite error: {message}") 20 | 21 | class SessionError(GraphLiteError): 22 | """Session-related errors""" 23 | def __init__(self, message: str): 24 | super().__init__(f"Session error: {message}") 25 | 26 | class QueryError(GraphLiteError): 27 | """Query execution errors""" 28 | def __init__(self, message: str): 29 | super().__init__(f"Query error: {message}") 30 | 31 | class TransactionError(GraphLiteError): 32 | """Transaction errors""" 33 | def __init__(self, message: str): 34 | super().__init__(f"Transaction error: {message}") 35 | 36 | class SerializationError(GraphLiteError): 37 | """Serialization/deserialization errors""" 38 | def __init__(self, message: str): 39 | super().__init__(f"Serialization error: {message}") 40 | 41 | @classmethod 42 | def from_json_error(cls, error: json.JSONDecodeError) -> "SerializationError": 43 | """Create SerializationError from json.JSONDecodeError""" 44 | return cls(f"JSON error: {error.msg} at line {error.lineno}, column {error.colno}") 45 | 46 | class TypeConversionError(GraphLiteError): 47 | """Type conversion errors""" 48 | def __init__(self, message: str): 49 | super().__init__(f"Type conversion error: {message}") 50 | 51 | class InvalidOperationError(GraphLiteError): 52 | """Invalid operation errors""" 53 | def __init__(self, message: str): 54 | super().__init__(f"Invalid operation: {message}") 55 | 56 | class NotFoundError(GraphLiteError): 57 | """Resource not found errors""" 58 | def __init__(self, message: str): 59 | super().__init__(f"Not found: {message}") 60 | 61 | class ConnectionError(GraphLiteError): 62 | """Connection errors""" 63 | def __init__(self, message: str): 64 | super().__init__(f"Connection error: {message}") 65 | 66 | class IoError(GraphLiteError): 67 | """I/O errors""" 68 | def __init__(self, message: str): 69 | super().__init__(f"I/O error: {message}") 70 | 71 | @classmethod 72 | def from_io_error(cls, error: OSError) -> "IoError": 73 | """Create IoError from Python OSError (IOError in Python 3)""" 74 | filename = getattr(error, 'filename', None) or '' 75 | strerror = getattr(error, 'strerror', None) or str(error) 76 | return cls(f"{strerror}: {filename}" if filename else strerror) 77 | 78 | 79 | # ============================================================================ 80 | # Error Conversion Helpers 81 | # ============================================================================ 82 | 83 | def from_string(message: Union[str, bytes]) -> GraphLiteCoreError: 84 | """ 85 | Convert a string to GraphLiteCoreError. 86 | """ 87 | if isinstance(message, bytes): 88 | message = message.decode('utf-8', errors='replace') 89 | return GraphLiteCoreError(message) 90 | 91 | 92 | def from_json_error(error: json.JSONDecodeError) -> SerializationError: 93 | """ 94 | Convert json.JSONDecodeError to SerializationError. 95 | """ 96 | return SerializationError.from_json_error(error) 97 | 98 | 99 | def from_io_error(error: OSError) -> IoError: 100 | """ 101 | Convert OSError to IoError. 102 | """ 103 | return IoError.from_io_error(error) 104 | -------------------------------------------------------------------------------- /DOCKER_QUICK_START.md: -------------------------------------------------------------------------------- 1 | # GraphLite Docker - Quick Start Cheat Sheet 2 | 3 | ## 🚀 Start Here (5 Steps) 4 | 5 | ### 1️⃣ Start Docker 6 | ```bash 7 | open -a Docker # macOS 8 | # Wait for Docker icon in menu bar 9 | ``` 10 | 11 | ### 2️⃣ Build Image 12 | ```bash 13 | cd /Users/mac/code/github/graphlite-ai/GraphLite 14 | docker build -t graphlite:test . 15 | # ⏱️ Takes 10-15 minutes (first time) 16 | ``` 17 | 18 | ### 3️⃣ Initialize Database 19 | ```bash 20 | mkdir -p ./testdb 21 | docker run -it --rm -v $(pwd)/testdb:/data graphlite:test \ 22 | graphlite install --path /data/db --admin-user admin --admin-password pass 23 | ``` 24 | 25 | ### 4️⃣ Start GQL Shell (Automatic) ⭐ 26 | ```bash 27 | docker run -it --rm -v $(pwd)/testdb:/data \ 28 | -e GRAPHLITE_DB_PATH=/data/db \ 29 | -e GRAPHLITE_USER=admin \ 30 | -e GRAPHLITE_PASSWORD=pass \ 31 | graphlite:test 32 | 33 | # You'll see: gql> █ 34 | ``` 35 | 36 | ### 5️⃣ Try Some Queries 37 | ```gql 38 | CREATE SCHEMA /test 39 | CREATE GRAPH /test/mygraph 40 | SESSION SET GRAPH /test/mygraph 41 | INSERT (:Person {name: 'Alice', age: 30}) 42 | MATCH (p:Person) RETURN p.name, p.age 43 | EXIT 44 | ``` 45 | 46 | --- 47 | 48 | ## 📋 Essential Commands 49 | 50 | ### Build 51 | ```bash 52 | # Native architecture 53 | docker build -t graphlite:test . 54 | 55 | # Or use script 56 | ./scripts/docker-build.sh --native --load 57 | ``` 58 | 59 | ### Test 60 | ```bash 61 | # Version check 62 | docker run --rm graphlite:test graphlite --version 63 | 64 | # Run test suite 65 | ./scripts/docker-test.sh --image graphlite:test 66 | ``` 67 | 68 | ### GQL Shell 69 | ```bash 70 | # Automatic (with env vars) - RECOMMENDED 71 | docker run -it --rm -v $(pwd)/testdb:/data \ 72 | -e GRAPHLITE_DB_PATH=/data/db \ 73 | -e GRAPHLITE_USER=admin \ 74 | -e GRAPHLITE_PASSWORD=pass \ 75 | graphlite:test 76 | 77 | # Manual (explicit command) 78 | docker run -it --rm -v $(pwd)/testdb:/data graphlite:test \ 79 | graphlite gql --path /data/db -u admin -p pass 80 | ``` 81 | 82 | ### Docker Compose 83 | ```bash 84 | # Build 85 | docker-compose build 86 | 87 | # Initialize 88 | docker-compose run --rm graphlite graphlite install \ 89 | --path /data/mydb --admin-user admin --admin-password secret 90 | 91 | # GQL Shell 92 | docker-compose run --rm graphlite graphlite gql \ 93 | --path /data/mydb -u admin -p secret 94 | ``` 95 | 96 | --- 97 | 98 | ## 🏗️ Multi-Architecture 99 | 100 | ```bash 101 | # AMD64 (x86_64) 102 | ./scripts/docker-build.sh --amd64 --tag amd64 --load 103 | 104 | # ARM64 (aarch64) 105 | ./scripts/docker-build.sh --arm64 --tag arm64 106 | 107 | # Both 108 | ./scripts/docker-build.sh 109 | ``` 110 | 111 | --- 112 | 113 | ## 🧹 Cleanup 114 | 115 | ```bash 116 | # Remove image 117 | docker rmi graphlite:test 118 | 119 | # Clean system 120 | docker system prune -a 121 | 122 | # Remove test database 123 | rm -rf ./testdb 124 | ``` 125 | 126 | --- 127 | 128 | ## ❓ Troubleshooting 129 | 130 | **Docker not running?** 131 | ```bash 132 | open -a Docker # macOS 133 | ``` 134 | 135 | **Build fails?** 136 | ```bash 137 | docker system prune -a # Clean and retry 138 | ``` 139 | 140 | **Permission issues?** 141 | ```bash 142 | chmod -R 777 ./testdb 143 | ``` 144 | 145 | --- 146 | 147 | ## 📚 Full Docs 148 | 149 | - **Complete Guide:** [docs/Docker.md](docs/Docker.md) 150 | - **Manual Testing:** [MANUAL_TEST_GUIDE.md](MANUAL_TEST_GUIDE.md) 151 | - **Test Plan:** [DOCKER_TEST_PLAN.md](DOCKER_TEST_PLAN.md) 152 | 153 | --- 154 | 155 | ## ✅ Success Checklist 156 | 157 | - [ ] Docker started 158 | - [ ] Image builds successfully 159 | - [ ] Version command works 160 | - [ ] Database initializes 161 | - [ ] **GQL shell starts automatically** ⭐ 162 | - [ ] Queries execute 163 | - [ ] Docker Compose works 164 | 165 | --- 166 | 167 | **Ready?** Run this: 168 | ```bash 169 | open -a Docker && sleep 30 && docker build -t graphlite:test . 170 | ``` 171 | -------------------------------------------------------------------------------- /graphlite/tests/stored_procedure_no_prefix_test.rs: -------------------------------------------------------------------------------- 1 | //! Test that gql.* namespace is properly protected 2 | //! Only gql.* prefix is allowed for system procedures 3 | 4 | #[path = "testutils/mod.rs"] 5 | mod testutils; 6 | 7 | use testutils::test_fixture::TestFixture; 8 | 9 | #[test] 10 | fn test_only_gql_prefix_works() { 11 | // Only gql.* prefix should work for system procedures 12 | let fixture = TestFixture::new().expect("Should create test fixture"); 13 | 14 | // Test with gql. prefix - should work 15 | let result = fixture.query("CALL gql.list_schemas()"); 16 | assert!( 17 | result.is_ok(), 18 | "gql.list_schemas() should work, got: {:?}", 19 | result 20 | ); 21 | 22 | let result = fixture.query("CALL gql.list_graphs()"); 23 | assert!( 24 | result.is_ok(), 25 | "gql.list_graphs() should work, got: {:?}", 26 | result 27 | ); 28 | 29 | let result = fixture.query("CALL gql.list_functions()"); 30 | assert!( 31 | result.is_ok(), 32 | "gql.list_functions() should work, got: {:?}", 33 | result 34 | ); 35 | } 36 | 37 | #[test] 38 | fn test_system_prefix_rejected() { 39 | // system.* prefix should be rejected 40 | let fixture = TestFixture::new().expect("Should create test fixture"); 41 | 42 | let result = fixture.query("CALL system.list_schemas()"); 43 | assert!(result.is_err(), "system.list_schemas() should be rejected"); 44 | let err = result.unwrap_err(); 45 | assert!( 46 | err.contains("Invalid procedure namespace") || err.contains("gql."), 47 | "Error should mention gql.* requirement, got: {}", 48 | err 49 | ); 50 | } 51 | 52 | #[test] 53 | fn test_no_prefix_rejected() { 54 | // Plain names without prefix should be rejected 55 | let fixture = TestFixture::new().expect("Should create test fixture"); 56 | 57 | let result = fixture.query("CALL list_schemas()"); 58 | assert!( 59 | result.is_err(), 60 | "list_schemas() without prefix should be rejected" 61 | ); 62 | let err = result.unwrap_err(); 63 | assert!( 64 | err.contains("Invalid procedure namespace") || err.contains("gql."), 65 | "Error should mention gql.* requirement, got: {}", 66 | err 67 | ); 68 | } 69 | 70 | #[test] 71 | #[ignore] // CREATE PROCEDURE syntax not fully implemented yet 72 | fn test_cannot_create_gql_namespace_procedure() { 73 | // Users should not be able to create procedures in gql.* namespace 74 | // This test will be enabled once CREATE PROCEDURE parsing is fully implemented 75 | let fixture = TestFixture::new().expect("Should create test fixture"); 76 | 77 | let result = fixture.query("CREATE PROCEDURE gql.my_custom_proc() RETURN 1"); 78 | assert!( 79 | result.is_err(), 80 | "Should not allow creating procedures in gql.* namespace" 81 | ); 82 | let err = result.unwrap_err(); 83 | assert!( 84 | err.contains("gql.*") && err.contains("reserved"), 85 | "Error should mention gql.* is reserved, got: {}", 86 | err 87 | ); 88 | } 89 | 90 | #[test] 91 | #[ignore] // DROP PROCEDURE syntax not fully implemented yet 92 | fn test_cannot_drop_gql_namespace_procedure() { 93 | // Users should not be able to drop procedures in gql.* namespace 94 | // This test will be enabled once DROP PROCEDURE parsing is fully implemented 95 | let fixture = TestFixture::new().expect("Should create test fixture"); 96 | 97 | let result = fixture.query("DROP PROCEDURE gql.list_schemas"); 98 | assert!( 99 | result.is_err(), 100 | "Should not allow dropping procedures in gql.* namespace" 101 | ); 102 | let err = result.unwrap_err(); 103 | assert!( 104 | err.contains("gql.*") && err.contains("reserved"), 105 | "Error should mention gql.* is reserved, got: {}", 106 | err 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /graphlite/src/storage/indexes/manager.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Index manager for GraphLite 5 | //! 6 | //! Simplified index manager that supports only graph indexes. 7 | 8 | use log::{debug, info, warn}; 9 | use std::collections::HashSet; 10 | use std::sync::{Arc, RwLock}; 11 | 12 | use super::IndexError; 13 | use crate::storage::GraphCache; 14 | 15 | /// Manager for all indexes in the system 16 | pub struct IndexManager { 17 | /// Index names storage 18 | index_names: Arc>>, 19 | } 20 | 21 | impl Default for IndexManager { 22 | fn default() -> Self { 23 | Self::new() 24 | } 25 | } 26 | 27 | impl IndexManager { 28 | /// Create a new index manager 29 | pub fn new() -> Self { 30 | Self { 31 | index_names: Arc::new(RwLock::new(HashSet::new())), 32 | } 33 | } 34 | 35 | /// Create a new index 36 | pub async fn create_index( 37 | &self, 38 | name: String, 39 | _index_type: super::IndexType, 40 | _config: super::IndexConfig, 41 | ) -> Result<(), IndexError> { 42 | info!("Creating index '{}'", name); 43 | 44 | let mut index_names = self 45 | .index_names 46 | .write() 47 | .map_err(|e| IndexError::creation(format!("Failed to acquire lock: {}", e)))?; 48 | 49 | if index_names.contains(&name) { 50 | return Err(IndexError::AlreadyExists(name)); 51 | } 52 | 53 | // Store index name 54 | index_names.insert(name.clone()); 55 | 56 | debug!("Index '{}' created successfully", name); 57 | Ok(()) 58 | } 59 | 60 | /// Delete an index 61 | pub async fn delete_index(&self, name: &str) -> Result<(), IndexError> { 62 | info!("Deleting index '{}'", name); 63 | 64 | let mut index_names = self 65 | .index_names 66 | .write() 67 | .map_err(|e| IndexError::creation(format!("Failed to acquire lock: {}", e)))?; 68 | 69 | if !index_names.remove(name) { 70 | return Err(IndexError::NotFound(name.to_string())); 71 | } 72 | 73 | debug!("Index '{}' deleted successfully", name); 74 | Ok(()) 75 | } 76 | 77 | /// Check if an index exists 78 | pub fn index_exists(&self, name: &str) -> bool { 79 | self.index_names 80 | .read() 81 | .map(|names| names.contains(name)) 82 | .unwrap_or(false) 83 | } 84 | 85 | /// List all index names 86 | pub fn list_indexes(&self) -> Vec { 87 | self.index_names 88 | .read() 89 | .map(|names| names.iter().cloned().collect()) 90 | .unwrap_or_else(|_| Vec::new()) 91 | } 92 | 93 | /// Reindex a text index (stub for compatibility) 94 | pub fn reindex_text_index( 95 | &self, 96 | _name: &str, 97 | _graph: &Arc, 98 | ) -> Result { 99 | warn!("Text index reindexing not supported in GraphLite"); 100 | Ok(0) 101 | } 102 | 103 | /// Search an index synchronously (stub for compatibility) 104 | pub fn search_index_sync( 105 | &self, 106 | _index_name: &str, 107 | _query_text: &str, 108 | _limit: usize, 109 | ) -> Result, IndexError> { 110 | warn!("Text search not supported in GraphLite"); 111 | Ok(Vec::new()) 112 | } 113 | 114 | /// Find indexes for a label (stub for compatibility) 115 | pub fn find_indexes_for_label(&self, _label: &str) -> Vec { 116 | Vec::new() 117 | } 118 | 119 | /// Find index by label and property (stub for compatibility) 120 | pub fn find_index_by_label_and_property( 121 | &self, 122 | _label: &str, 123 | _property: &str, 124 | ) -> Option { 125 | None 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /bindings/python/examples/basic_usage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | GraphLite Python Bindings - Basic Usage Example 4 | 5 | This example demonstrates how to use GraphLite from Python. 6 | """ 7 | 8 | from graphlite import GraphLite, GraphLiteError 9 | import tempfile 10 | import shutil 11 | 12 | 13 | def main(): 14 | print("=== GraphLite Python Bindings Example ===\n") 15 | 16 | # Use temporary directory for demo 17 | temp_dir = tempfile.mkdtemp(prefix="graphlite_python_") 18 | print(f"Using temporary database: {temp_dir}\n") 19 | 20 | try: 21 | # 1. Open database 22 | print("1. Opening database...") 23 | db = GraphLite(temp_dir) 24 | print(f" ✓ GraphLite version: {GraphLite.version()}\n") 25 | 26 | # 2. Create session 27 | print("2. Creating session...") 28 | session = db.create_session("admin") 29 | print(f" ✓ Session created: {session[:20]}...\n") 30 | 31 | # 3. Create schema and graph 32 | print("3. Setting up schema and graph...") 33 | db.execute(session, "CREATE SCHEMA IF NOT EXISTS example") 34 | db.execute(session, "SESSION SET SCHEMA example") 35 | db.execute(session, "CREATE GRAPH IF NOT EXISTS social") 36 | db.execute(session, "SESSION SET GRAPH social") 37 | print(" ✓ Schema and graph created\n") 38 | 39 | # 4. Insert data 40 | print("4. Inserting data...") 41 | db.execute(session, "CREATE (p:Person {name: 'Alice', age: 30})") 42 | db.execute(session, "CREATE (p:Person {name: 'Bob', age: 25})") 43 | db.execute(session, "CREATE (p:Person {name: 'Charlie', age: 35})") 44 | print(" ✓ Inserted 3 persons\n") 45 | 46 | # 5. Query data 47 | print("5. Querying data...") 48 | result = db.query(session, "MATCH (p:Person) RETURN p.name as name, p.age as age") 49 | print(f" Found {result.row_count} persons:") 50 | for row in result.rows: 51 | print(f" - {row['name']}: {row['age']} years old") 52 | print() 53 | 54 | # 6. Filter with WHERE 55 | print("6. Filtering with WHERE clause...") 56 | result = db.query( 57 | session, 58 | "MATCH (p:Person) WHERE p.age > 25 RETURN p.name as name, p.age as age ORDER BY p.age DESC" 59 | ) 60 | print(f" Found {result.row_count} persons over 25:") 61 | for row in result.rows: 62 | print(f" - {row['name']}: {row['age']} years old") 63 | print() 64 | 65 | # 7. Aggregation 66 | print("7. Aggregation query...") 67 | result = db.query(session, "MATCH (p:Person) RETURN count(p) as total, avg(p.age) as avg_age") 68 | if result.rows: 69 | row = result.first() 70 | print(f" Total persons: {row['total']}") 71 | print(f" Average age: {row['avg_age']:.1f}") 72 | print() 73 | 74 | # 8. Get column values 75 | print("8. Extracting column values...") 76 | result = db.query(session, "MATCH (p:Person) RETURN p.name as name") 77 | names = result.column('name') 78 | print(f" All names: {names}\n") 79 | 80 | # 9. Close session 81 | print("9. Closing session...") 82 | db.close_session(session) 83 | print(" ✓ Session closed\n") 84 | 85 | # 10. Close database 86 | print("10. Closing database...") 87 | db.close() 88 | print(" ✓ Database closed\n") 89 | 90 | print("=== Example completed successfully ===") 91 | 92 | except GraphLiteError as e: 93 | print(f"\n❌ GraphLite Error: {e}") 94 | return 1 95 | 96 | except Exception as e: 97 | print(f"\n❌ Unexpected error: {e}") 98 | return 1 99 | 100 | finally: 101 | # Cleanup 102 | shutil.rmtree(temp_dir, ignore_errors=True) 103 | 104 | return 0 105 | 106 | 107 | if __name__ == "__main__": 108 | exit(main()) 109 | -------------------------------------------------------------------------------- /examples/python/bindings/basic_usage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | GraphLite Python Bindings - Basic Usage Example 4 | 5 | This example demonstrates how to use GraphLite from Python. 6 | """ 7 | 8 | from graphlite import GraphLite, GraphLiteError 9 | import tempfile 10 | import shutil 11 | 12 | 13 | def main(): 14 | print("=== GraphLite Python Bindings Example ===\n") 15 | 16 | # Use temporary directory for demo 17 | temp_dir = tempfile.mkdtemp(prefix="graphlite_python_") 18 | print(f"Using temporary database: {temp_dir}\n") 19 | 20 | try: 21 | # 1. Open database 22 | print("1. Opening database...") 23 | db = GraphLite(temp_dir) 24 | print(f" ✓ GraphLite version: {GraphLite.version()}\n") 25 | 26 | # 2. Create session 27 | print("2. Creating session...") 28 | session = db.create_session("admin") 29 | print(f" ✓ Session created: {session[:20]}...\n") 30 | 31 | # 3. Create schema and graph 32 | print("3. Setting up schema and graph...") 33 | db.execute(session, "CREATE SCHEMA IF NOT EXISTS example") 34 | db.execute(session, "SESSION SET SCHEMA example") 35 | db.execute(session, "CREATE GRAPH IF NOT EXISTS social") 36 | db.execute(session, "SESSION SET GRAPH social") 37 | print(" ✓ Schema and graph created\n") 38 | 39 | # 4. Insert data 40 | print("4. Inserting data...") 41 | db.execute(session, "CREATE (p:Person {name: 'Alice', age: 30})") 42 | db.execute(session, "CREATE (p:Person {name: 'Bob', age: 25})") 43 | db.execute(session, "CREATE (p:Person {name: 'Charlie', age: 35})") 44 | print(" ✓ Inserted 3 persons\n") 45 | 46 | # 5. Query data 47 | print("5. Querying data...") 48 | result = db.query(session, "MATCH (p:Person) RETURN p.name as name, p.age as age") 49 | print(f" Found {result.row_count} persons:") 50 | for row in result.rows: 51 | print(f" - {row['name']}: {row['age']} years old") 52 | print() 53 | 54 | # 6. Filter with WHERE 55 | print("6. Filtering with WHERE clause...") 56 | result = db.query( 57 | session, 58 | "MATCH (p:Person) WHERE p.age > 25 RETURN p.name as name, p.age as age ORDER BY p.age DESC" 59 | ) 60 | print(f" Found {result.row_count} persons over 25:") 61 | for row in result.rows: 62 | print(f" - {row['name']}: {row['age']} years old") 63 | print() 64 | 65 | # 7. Aggregation 66 | print("7. Aggregation query...") 67 | result = db.query(session, "MATCH (p:Person) RETURN count(p) as total, avg(p.age) as avg_age") 68 | if result.rows: 69 | row = result.first() 70 | print(f" Total persons: {row['total']}") 71 | print(f" Average age: {row['avg_age']:.1f}") 72 | print() 73 | 74 | # 8. Get column values 75 | print("8. Extracting column values...") 76 | result = db.query(session, "MATCH (p:Person) RETURN p.name as name") 77 | names = result.column('name') 78 | print(f" All names: {names}\n") 79 | 80 | # 9. Close session 81 | print("9. Closing session...") 82 | db.close_session(session) 83 | print(" ✓ Session closed\n") 84 | 85 | # 10. Close database 86 | print("10. Closing database...") 87 | db.close() 88 | print(" ✓ Database closed\n") 89 | 90 | print("=== Example completed successfully ===") 91 | 92 | except GraphLiteError as e: 93 | print(f"\n❌ GraphLite Error: {e}") 94 | return 1 95 | 96 | except Exception as e: 97 | print(f"\n❌ Unexpected error: {e}") 98 | return 1 99 | 100 | finally: 101 | # Cleanup 102 | shutil.rmtree(temp_dir, ignore_errors=True) 103 | 104 | return 0 105 | 106 | 107 | if __name__ == "__main__": 108 | exit(main()) 109 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # GraphLite Dockerfile 2 | # Supports: linux/amd64, linux/arm64 3 | # Base: Ubuntu 22.04 LTS 4 | 5 | ARG RUST_VERSION=1.90 6 | 7 | # ============================================================================== 8 | # Stage 1: Builder - Compile GraphLite 9 | # ============================================================================== 10 | FROM rust:${RUST_VERSION}-slim-bookworm AS builder 11 | 12 | # Install build dependencies 13 | RUN apt-get update && apt-get install -y \ 14 | build-essential \ 15 | pkg-config \ 16 | libssl-dev \ 17 | curl \ 18 | ca-certificates \ 19 | && rm -rf /var/lib/apt/lists/* 20 | 21 | # Set working directory 22 | WORKDIR /build 23 | 24 | # Copy workspace configuration 25 | COPY Cargo.toml Cargo.lock ./ 26 | COPY .cargo ./.cargo 27 | 28 | # Copy all workspace members 29 | COPY graphlite ./graphlite 30 | COPY graphlite-cli ./graphlite-cli 31 | COPY graphlite-sdk ./graphlite-sdk 32 | COPY graphlite-ffi ./graphlite-ffi 33 | 34 | # Build for native architecture 35 | RUN cargo build --release --bin graphlite 36 | 37 | # Copy binary to known location 38 | RUN cp target/release/graphlite /build/graphlite-bin && \ 39 | ls -lh /build/graphlite-bin 40 | 41 | # ============================================================================== 42 | # Stage 2: Runtime - Minimal Ubuntu image 43 | # ============================================================================== 44 | FROM ubuntu:22.04 AS runtime 45 | 46 | # Set labels for metadata 47 | LABEL org.opencontainers.image.title="GraphLite" 48 | LABEL org.opencontainers.image.description="A lightweight ISO GQL Graph Database" 49 | LABEL org.opencontainers.image.version="0.0.1" 50 | LABEL org.opencontainers.image.vendor="GraphLite Contributors" 51 | LABEL org.opencontainers.image.licenses="Apache-2.0" 52 | LABEL org.opencontainers.image.source="https://github.com/GraphLite-AI/GraphLite" 53 | 54 | # Install runtime dependencies only 55 | RUN apt-get update && apt-get install -y \ 56 | ca-certificates \ 57 | libssl3 \ 58 | bash \ 59 | && rm -rf /var/lib/apt/lists/* 60 | 61 | # Create non-root user for security 62 | RUN groupadd -r graphlite && \ 63 | useradd -r -g graphlite -s /bin/bash -m graphlite 64 | 65 | # Create directories 66 | RUN mkdir -p /data /config && \ 67 | chown -R graphlite:graphlite /data /config 68 | 69 | # Copy binary from builder 70 | COPY --from=builder /build/graphlite-bin /usr/local/bin/graphlite 71 | RUN chmod +x /usr/local/bin/graphlite 72 | 73 | # Switch to non-root user 74 | USER graphlite 75 | WORKDIR /home/graphlite 76 | 77 | # Set environment variables 78 | ENV RUST_LOG=info 79 | ENV GRAPHLITE_DATA_PATH=/data 80 | ENV GRAPHLITE_CONFIG_PATH=/config 81 | ENV GRAPHLITE_DB_PATH=/data/default_db 82 | ENV GRAPHLITE_USER=admin 83 | ENV GRAPHLITE_PASSWORD= 84 | 85 | # Copy entrypoint script for flexible startup 86 | COPY --chown=graphlite:graphlite entrypoint.sh /home/graphlite/entrypoint.sh 87 | RUN chmod +x /home/graphlite/entrypoint.sh 88 | 89 | ENTRYPOINT ["/home/graphlite/entrypoint.sh"] 90 | CMD [] 91 | 92 | # ============================================================================== 93 | # Usage Examples: 94 | # ============================================================================== 95 | # Build for native architecture: 96 | # docker build -t graphlite:latest . 97 | # 98 | # Initialize new database: 99 | # docker run -it -v $(pwd)/mydb:/data graphlite:latest \ 100 | # graphlite install --path /data/mydb --admin-user admin --admin-password secret 101 | # 102 | # Start interactive GQL shell (automatic with environment variables): 103 | # docker run -it -v $(pwd)/mydb:/data \ 104 | # -e GRAPHLITE_DB_PATH=/data/mydb \ 105 | # -e GRAPHLITE_USER=admin \ 106 | # -e GRAPHLITE_PASSWORD=secret \ 107 | # graphlite:latest 108 | # 109 | # Start GQL shell (manual command): 110 | # docker run -it -v $(pwd)/mydb:/data graphlite:latest \ 111 | # graphlite gql --path /data/mydb -u admin -p secret 112 | # 113 | # Run any other command: 114 | # docker run -it graphlite:latest graphlite --version 115 | # ============================================================================== 116 | -------------------------------------------------------------------------------- /examples/python/sdk/README.md: -------------------------------------------------------------------------------- 1 | # GraphLite Python High-Level SDK Examples 2 | 3 | This repository contains examples using the GraphLite High-Level Python SDK (from the `python-sdk` branch). 4 | 5 | ## Overview 6 | 7 | The High-Level SDK provides an ergonomic, Rust SDK-like API for GraphLite with: 8 | - Session-centric API (session objects instead of session IDs) 9 | - Typed exception hierarchy 10 | - Cleaner, more Pythonic interface 11 | - (Planned) Transaction support with context managers 12 | - (Planned) Query builder for fluent query construction 13 | - (Planned) Typed result deserialization 14 | 15 | ## Architecture 16 | 17 | ``` 18 | Your Application 19 | ↓ 20 | GraphLite SDK (python-sdk/src/) 21 | ↓ 22 | GraphLite FFI Adapter (graphlite_ffi.py) 23 | ↓ 24 | GraphLite FFI Bindings (bindings/python/) 25 | ↓ 26 | libgraphlite_ffi.so (Rust) 27 | ``` 28 | 29 | ## Setup 30 | 31 | ### Prerequisites 32 | 33 | 1. **Build the GraphLite FFI library**: 34 | ```bash 35 | cd ~/github/graphlite-ai/GraphLite 36 | cargo build --release -p graphlite-ffi 37 | ``` 38 | 39 | 2. **Install the low-level bindings**: 40 | ```bash 41 | cd ~/github/graphlite-ai/GraphLite/bindings/python 42 | pip install --break-system-packages -e . 43 | ``` 44 | 45 | 3. **Python SDK Dependency** 46 | 47 | The high-level Python SDK is currently in the `deepgraphai/GraphLite` repository on the `python-sdk` branch: 48 | 49 | ```bash 50 | # Clone and checkout python-sdk branch 51 | cd ~/github/deepgraphai 52 | git clone https://github.com/deepgraphai/GraphLite.git # if not already cloned 53 | cd GraphLite 54 | git checkout python-sdk 55 | ``` 56 | 57 | The examples will automatically find the SDK at `~/github/deepgraphai/GraphLite/python-sdk/` 58 | 59 | Alternatively, edit the `drug_discovery.py` file to update the path to your python-sdk location. 60 | 61 | ## Examples 62 | 63 | ### Drug Discovery Example 64 | 65 | A comprehensive pharmaceutical research example demonstrating: 66 | - Modeling proteins (disease targets), compounds (drugs), and assays (experiments) 67 | - Creating relationships: TESTED_IN, MEASURES_ACTIVITY_ON, INHIBITS 68 | - Real-world data: IC50 measurements, clinical trial phases 69 | - Analytical queries: IC50 filtering, pathway traversal, aggregation 70 | 71 | **Run:** 72 | ```bash 73 | python3 drug_discovery.py 74 | ``` 75 | 76 | ## API Differences from Low-Level Bindings 77 | 78 | ### Low-Level FFI Bindings 79 | ```python 80 | from graphlite import GraphLite 81 | 82 | db = GraphLite("./mydb") 83 | session_id = db.create_session("admin") 84 | result = db.query(session_id, "MATCH (n) RETURN n") 85 | ``` 86 | 87 | ### High-Level SDK (This Example) 88 | ```python 89 | from src.connection import GraphLite 90 | 91 | db = GraphLite.open("./mydb") 92 | session = db.session("admin") 93 | result = session.query("MATCH (n) RETURN n") 94 | ``` 95 | 96 | **Key Differences**: 97 | 1. Use `.open()` static method instead of constructor 98 | 2. Session object with methods instead of session ID strings 99 | 3. Cleaner, session-centric API 100 | 4. Typed exceptions (ConnectionError, SessionError, QueryError, etc.) 101 | 102 | ## Requirements 103 | 104 | - Python 3.8+ 105 | - GraphLite FFI library (built from GraphLite repository) 106 | - GraphLite FFI Python bindings 107 | 108 | ## Current Status 109 | 110 | The High-Level SDK is under development: 111 | - Database connection (GraphLite class) 112 | - Session management (Session class) 113 | - Query execution 114 | - Typed error hierarchy 115 | - Transaction support (planned) 116 | - Query builder (planned) 117 | - Typed result deserialization (planned) 118 | 119 | Currently, the SDK uses the FFI bindings' QueryResult class for results. Planned features will add transaction context managers, query builders, and dataclass deserialization. 120 | 121 | ## Domain Model (Drug Discovery) 122 | 123 | ``` 124 | Compound → TESTED_IN → Assay → MEASURES_ACTIVITY_ON → Target (Protein) 125 | Compound → INHIBITS → Target (with IC50 measurements) 126 | ``` 127 | 128 | **Use Cases**: Target-based drug discovery, compound optimization, clinical trial tracking, pharmaceutical knowledge graphs. 129 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # GraphLite Examples 2 | 3 | This directory contains examples for using GraphLite across different programming languages and API levels. 4 | 5 | ## Directory Structure 6 | 7 | ``` 8 | examples/ 9 | ├── rust/ 10 | │ ├── bindings/ # Core library examples (low-level API) 11 | │ │ ├── simple_usage.rs 12 | │ │ └── drug_discovery/ 13 | │ └── sdk/ # SDK examples (high-level ergonomic API) 14 | │ ├── basic_usage.rs 15 | │ └── drug_discovery/ 16 | ├── python/ 17 | │ ├── bindings/ # FFI bindings examples (low-level) 18 | │ │ ├── basic_usage.py 19 | │ │ └── drug_discovery.py 20 | │ └── sdk/ # High-level SDK examples 21 | │ └── drug_discovery.py 22 | └── java/ 23 | └── bindings/ # Java bindings examples 24 | └── BasicUsage.java 25 | ``` 26 | 27 | ## Quick Start by Language 28 | 29 | ### Rust Examples 30 | 31 | #### Core Library (Bindings) 32 | Low-level API with direct access to GraphLite core: 33 | ```bash 34 | # Simple usage 35 | cargo run --example simple_usage -p graphlite 36 | 37 | # Drug discovery (comprehensive) 38 | cargo run --example drug_discovery -p graphlite 39 | ``` 40 | 41 | #### Rust SDK 42 | High-level ergonomic API: 43 | ```bash 44 | # Basic usage 45 | cargo run --example basic_usage -p graphlite-rust-sdk 46 | 47 | # Drug discovery 48 | cargo run --example drug_discovery -p graphlite-rust-sdk 49 | ``` 50 | 51 | ### Python Examples 52 | 53 | #### FFI Bindings 54 | Low-level Python bindings via ctypes: 55 | ```bash 56 | cd examples/python/bindings 57 | 58 | # Basic usage 59 | python3 basic_usage.py 60 | 61 | # Drug discovery 62 | python3 drug_discovery.py 63 | ``` 64 | 65 | #### High-Level SDK 66 | Session-centric Pythonic API: 67 | ```bash 68 | cd examples/python/sdk 69 | 70 | # Drug discovery 71 | python3 drug_discovery.py 72 | ``` 73 | 74 | ### Java Examples 75 | 76 | ```bash 77 | cd examples/java/bindings 78 | # Compile and run (instructions in BasicUsage.java) 79 | ``` 80 | 81 | ## Example Descriptions 82 | 83 | ### Simple/Basic Usage 84 | Demonstrates fundamental operations: 85 | - Opening a database 86 | - Creating sessions 87 | - Executing queries 88 | - Basic CRUD operations 89 | 90 | ### Drug Discovery 91 | Comprehensive pharmaceutical research example showing: 92 | - **Domain**: Proteins (disease targets), Compounds (drugs), Assays (experiments) 93 | - **Relationships**: TESTED_IN, MEASURES_ACTIVITY_ON, INHIBITS 94 | - **Data**: IC50 measurements, clinical trial phases 95 | - **Queries**: Filtering, traversal, aggregation, sorting 96 | 97 | ## API Level Comparison 98 | 99 | | Aspect | Bindings (Low-Level) | SDK (High-Level) | 100 | |--------|---------------------|------------------| 101 | | **Abstraction** | Direct core access | Ergonomic wrapper | 102 | | **Verbosity** | More code required | Concise | 103 | | **Control** | Fine-grained | Simplified | 104 | | **Use Case** | Advanced users | Recommended start | 105 | 106 | ## Language-Specific Documentation 107 | 108 | - [Rust Bindings Examples](./rust/bindings/README.md) 109 | - [Rust SDK Examples](./rust/sdk/README.md) 110 | - [Python Bindings](./python/bindings/README.md) 111 | - [Python SDK](./python/sdk/README.md) 112 | 113 | ## Prerequisites 114 | 115 | ### Rust 116 | ```bash 117 | cargo build --release 118 | ``` 119 | 120 | ### Python 121 | ```bash 122 | # Build FFI library 123 | cargo build --release -p graphlite-ffi 124 | 125 | # Install Python bindings 126 | cd bindings/python 127 | pip install -e . 128 | ``` 129 | 130 | ### Java 131 | ```bash 132 | # Build JNI library 133 | cargo build --release -p graphlite-jni 134 | ``` 135 | 136 | ## Contributing 137 | 138 | When adding new examples: 139 | 1. Place in appropriate language/api-level directory 140 | 2. Follow naming convention: `example_name.{rs,py,java}` 141 | 3. Include inline documentation 142 | 4. Add entry to this README 143 | 5. Test across all supported platforms 144 | 145 | ## Related Documentation 146 | 147 | - [GraphLite Core Documentation](../README.md) 148 | - [Rust SDK Documentation](../graphlite-sdk/README.md) 149 | - [Python Bindings Documentation](../bindings/python/README.md) 150 | - [Java Bindings Documentation](../bindings/java/README.md) 151 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # GraphLite Scripts 2 | 3 | This directory contains utility scripts for building, testing, and managing the GraphLite project. 4 | 5 | ## Available Scripts 6 | 7 | ### Build Scripts 8 | 9 | #### `build_all.sh` 10 | Builds the GraphLite Rust library and CLI binary. 11 | 12 | ```bash 13 | # Basic build (debug mode) 14 | ./scripts/build_all.sh 15 | 16 | # Optimized release build 17 | ./scripts/build_all.sh --release 18 | 19 | # Clean build 20 | ./scripts/build_all.sh --clean --release 21 | 22 | # Build and run tests 23 | ./scripts/build_all.sh --release --test 24 | ``` 25 | 26 | **Options:** 27 | - `--release` - Build in release mode (optimized) 28 | - `--test` - Run tests after building 29 | - `--clean` - Clean before building 30 | - `--help` - Show help message 31 | 32 | **Build output locations:** 33 | - **Debug mode**: `target/debug/libgraphlite.rlib` and `target/debug/graphlite` 34 | - **Release mode**: `target/release/libgraphlite.rlib` and `target/release/graphlite` 35 | 36 | ### Cleanup Scripts 37 | 38 | #### `cleanup.sh` 39 | Uninstalls and cleans up all GraphLite project artifacts. 40 | 41 | ```bash 42 | # Show help (also shown when no options provided) 43 | ./scripts/cleanup.sh --help 44 | 45 | # Clean build artifacts only 46 | ./scripts/cleanup.sh --build 47 | 48 | # Clean Python/Java bindings 49 | ./scripts/cleanup.sh --bindings 50 | 51 | # Complete cleanup (bindings, build, data, config) 52 | ./scripts/cleanup.sh --all 53 | ``` 54 | 55 | **Options:** 56 | - `--build` - Clean build artifacts 57 | - `--bindings` - Uninstall Python/Java bindings 58 | - `--all` - Complete cleanup including data and configuration 59 | - `--help` - Show help message 60 | 61 | **Safety:** Requires explicit option - no default action to prevent accidental cleanup. 62 | 63 | **What gets cleaned:** 64 | - `--build`: Rust build artifacts (`target/`), compiled binaries (`.so`, `.dylib`, `.dll`), `Cargo.lock` 65 | - `--bindings`: Python packages (uninstall via pip), Python build artifacts (`build/`, `dist/`, `*.egg-info`), Java artifacts (Maven `target/`, JAR files) 66 | - `--all`: All of the above plus database files (`data/`, `example_db/`, `mydb/`), configuration (`.graphlite/`), log files, temporary files 67 | 68 | ### Testing Scripts 69 | 70 | #### `run_tests.sh` 71 | Runs all GraphLite tests (unit, integration, parser). 72 | 73 | ```bash 74 | ./scripts/run_tests.sh 75 | ``` 76 | 77 | #### `test_cli.sh` 78 | Tests the GraphLite CLI functionality. 79 | 80 | ```bash 81 | ./scripts/test_cli.sh 82 | ``` 83 | 84 | ### Development Scripts 85 | 86 | #### `install_hooks.sh` 87 | Installs Git hooks for the project. 88 | 89 | ```bash 90 | ./scripts/install_hooks.sh 91 | ``` 92 | 93 | #### `check_code_patterns.sh` 94 | Validates code against established patterns and anti-patterns. 95 | 96 | ```bash 97 | ./scripts/check_code_patterns.sh 98 | ``` 99 | 100 | ## Common Workflows 101 | 102 | ### Fresh Build 103 | ```bash 104 | # Clean everything and rebuild from scratch 105 | ./scripts/cleanup.sh --all 106 | ./scripts/build_all.sh --release 107 | ``` 108 | 109 | ### Development Cycle 110 | ```bash 111 | # Build in debug mode (faster compilation) 112 | ./scripts/build_all.sh 113 | 114 | # Make changes... 115 | 116 | # Clean and rebuild when needed 117 | ./scripts/cleanup.sh --build 118 | ./scripts/build_all.sh 119 | ``` 120 | 121 | ### Testing Workflow 122 | ```bash 123 | # Build and test 124 | ./scripts/build_all.sh --test 125 | 126 | # Or run tests separately 127 | ./scripts/run_tests.sh 128 | ``` 129 | 130 | ### Complete Uninstall 131 | ```bash 132 | # Remove everything (bindings, build artifacts, data, config) 133 | ./scripts/cleanup.sh --all 134 | ``` 135 | 136 | ## Script Requirements 137 | 138 | - **Bash**: All scripts require Bash shell 139 | - **Rust/Cargo**: Required for build scripts 140 | - **Python/pip**: Required for Python binding cleanup 141 | - **Java/Maven**: Required for Java binding cleanup 142 | 143 | ## Notes 144 | 145 | - All scripts include colored output for better readability 146 | - Scripts automatically detect and configure Rust/Cargo PATH when needed 147 | - Use `--help` with any script to see detailed usage information 148 | - Scripts are safe to run multiple times (idempotent) 149 | -------------------------------------------------------------------------------- /graphlite/src/functions/numeric_functions.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | //! Consolidated numeric function implementations 5 | //! 6 | //! This module contains all numeric manipulation functions: 7 | //! - ROUND: Rounds numbers to specified decimal places with Oracle-compatible logic 8 | 9 | use super::function_trait::{Function, FunctionContext, FunctionError, FunctionResult}; 10 | use crate::storage::Value; 11 | 12 | // ============================================================================== 13 | // ROUND FUNCTION 14 | // ============================================================================== 15 | 16 | /// ROUND function - rounds numbers to specified decimal places 17 | #[derive(Debug)] 18 | pub struct RoundFunction; 19 | 20 | impl RoundFunction { 21 | pub fn new() -> Self { 22 | Self 23 | } 24 | } 25 | 26 | impl Function for RoundFunction { 27 | fn name(&self) -> &str { 28 | "ROUND" 29 | } 30 | 31 | fn description(&self) -> &str { 32 | "Rounds a number to specified decimal places" 33 | } 34 | 35 | fn argument_count(&self) -> usize { 36 | 1 // ROUND(number) - can also accept optional second parameter 37 | } 38 | 39 | fn execute(&self, context: &FunctionContext) -> FunctionResult { 40 | // Handle 1 or 2 arguments 41 | let arg_count = context.arguments.len(); 42 | if arg_count == 0 || arg_count > 2 { 43 | return Err(FunctionError::InvalidArgumentType { 44 | message: "ROUND function expects 1 or 2 arguments".to_string(), 45 | }); 46 | } 47 | 48 | // Get the number argument 49 | let value = context.get_argument(0)?; 50 | 51 | if value.is_null() { 52 | return Ok(Value::Null); 53 | } 54 | 55 | // Convert to number 56 | let number = if let Some(n) = value.as_number() { 57 | n 58 | } else if let Some(s) = value.as_string() { 59 | s.parse::() 60 | .map_err(|_| FunctionError::InvalidArgumentType { 61 | message: format!("Cannot convert '{}' to number", s), 62 | })? 63 | } else { 64 | return Err(FunctionError::InvalidArgumentType { 65 | message: "ROUND argument must be a number or convertible to number".to_string(), 66 | }); 67 | }; 68 | 69 | // Get decimal places (default to 0) 70 | let decimal_places = if arg_count == 2 { 71 | let places_value = context.get_argument(1)?; 72 | if let Some(n) = places_value.as_number() { 73 | n as i32 74 | } else { 75 | return Err(FunctionError::InvalidArgumentType { 76 | message: "ROUND decimal places argument must be a number".to_string(), 77 | }); 78 | } 79 | } else { 80 | 0 81 | }; 82 | 83 | // Handle special case: if number is 0, always return 0 84 | if number == 0.0 { 85 | return Ok(Value::Number(0.0)); 86 | } 87 | 88 | // Oracle ROUND logic 89 | let rounded = oracle_round(number, decimal_places); 90 | 91 | Ok(Value::Number(rounded)) 92 | } 93 | 94 | fn return_type(&self) -> &str { 95 | "Number" 96 | } 97 | } 98 | 99 | /// Oracle-compatible ROUND function implementation 100 | /// ROUND(n, integer) follows Oracle's rounding rules: 101 | /// - If n is 0, always returns 0 102 | /// - For negative n: ROUND(n, integer) = -ROUND(-n, integer) 103 | /// - Uses "round half away from zero" behavior 104 | fn oracle_round(n: f64, decimal_places: i32) -> f64 { 105 | if n == 0.0 { 106 | return 0.0; 107 | } 108 | 109 | // Handle negative numbers: ROUND(n, integer) = -ROUND(-n, integer) 110 | if n < 0.0 { 111 | return -oracle_round(-n, decimal_places); 112 | } 113 | 114 | // Calculate the multiplier for the decimal places 115 | let multiplier = 10.0_f64.powi(decimal_places); 116 | 117 | // Apply Oracle's rounding: ROUND(n, integer) = FLOOR(n * 10^integer + 0.5) / 10^integer 118 | let scaled = n * multiplier + 0.5; 119 | let floored = scaled.floor(); 120 | 121 | floored / multiplier 122 | } 123 | -------------------------------------------------------------------------------- /graphlite/src/schema/standalone_test.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Standalone test file to verify schema module functionality 5 | // This can be run independently to test our implementation 6 | 7 | use super::types::*; 8 | use super::enforcement::config::*; 9 | 10 | fn main() { 11 | log::debug!("Running Schema Module Unit Tests\n"); 12 | 13 | // Test 1: Version parsing 14 | log::debug!("Test 1: Version Parsing"); 15 | test_version_parsing(); 16 | 17 | // Test 2: Version compatibility 18 | log::debug!("\nTest 2: Version Compatibility"); 19 | test_version_compatibility(); 20 | 21 | // Test 3: Data type compatibility 22 | log::debug!("\nTest 3: Data Type Compatibility"); 23 | test_data_type_compatibility(); 24 | 25 | // Test 4: Enforcement config 26 | log::debug!("\nTest 4: Enforcement Configuration"); 27 | test_enforcement_config(); 28 | 29 | log::debug!("\nAll tests passed!"); 30 | } 31 | 32 | fn test_version_parsing() { 33 | let version = GraphTypeVersion::parse("1.2.3").unwrap(); 34 | assert_eq!(version.major, 1); 35 | assert_eq!(version.minor, 2); 36 | assert_eq!(version.patch, 3); 37 | assert_eq!(version.to_string(), "1.2.3"); 38 | log::debug!(" ✓ Basic version parsing: 1.2.3"); 39 | 40 | let version_with_pre = GraphTypeVersion::parse("2.0.0-beta+build123").unwrap(); 41 | assert_eq!(version_with_pre.major, 2); 42 | assert_eq!(version_with_pre.minor, 0); 43 | assert_eq!(version_with_pre.patch, 0); 44 | assert_eq!(version_with_pre.pre_release, Some("beta".to_string())); 45 | assert_eq!(version_with_pre.build_metadata, Some("build123".to_string())); 46 | log::debug!(" ✓ Version with pre-release and build metadata: 2.0.0-beta+build123"); 47 | } 48 | 49 | fn test_version_compatibility() { 50 | let v1 = GraphTypeVersion::new(1, 2, 3); 51 | let v2 = GraphTypeVersion::new(1, 3, 0); 52 | let v3 = GraphTypeVersion::new(2, 0, 0); 53 | 54 | assert!(v1.is_compatible_with(&v2)); // Same major version 55 | log::debug!(" ✓ Version 1.2.3 is compatible with 1.3.0 (same major)"); 56 | 57 | assert!(!v1.is_compatible_with(&v3)); // Different major version 58 | log::debug!(" ✓ Version 1.2.3 is NOT compatible with 2.0.0 (different major)"); 59 | } 60 | 61 | fn test_data_type_compatibility() { 62 | // Compatible types 63 | assert!(DataType::String.is_compatible_with(&DataType::Text)); 64 | log::debug!(" ✓ String is compatible with Text"); 65 | 66 | assert!(DataType::Integer.is_compatible_with(&DataType::BigInt)); 67 | log::debug!(" ✓ Integer is compatible with BigInt"); 68 | 69 | assert!(DataType::Float.is_compatible_with(&DataType::Double)); 70 | log::debug!(" ✓ Float is compatible with Double"); 71 | 72 | // Incompatible types 73 | assert!(!DataType::String.is_compatible_with(&DataType::Integer)); 74 | log::debug!(" ✓ String is NOT compatible with Integer"); 75 | } 76 | 77 | fn test_enforcement_config() { 78 | // Test strict configuration 79 | let strict = SchemaEnforcementConfig::strict(); 80 | assert_eq!(strict.mode, SchemaEnforcementMode::Strict); 81 | assert!(strict.validate_on_write); 82 | assert!(strict.validate_on_read); 83 | assert!(strict.allow_schema_evolution); 84 | assert!(!strict.allow_unknown_properties); 85 | log::debug!(" ✓ Strict config: validates on write/read, no unknown properties"); 86 | 87 | // Test advisory configuration 88 | let advisory = SchemaEnforcementConfig::advisory(); 89 | assert_eq!(advisory.mode, SchemaEnforcementMode::Advisory); 90 | assert!(advisory.validate_on_write); 91 | assert!(!advisory.validate_on_read); 92 | assert!(advisory.allow_schema_evolution); 93 | assert!(advisory.allow_unknown_properties); 94 | log::debug!(" ✓ Advisory config: validates on write, allows unknown properties"); 95 | 96 | // Test disabled configuration 97 | let disabled = SchemaEnforcementConfig::disabled(); 98 | assert_eq!(disabled.mode, SchemaEnforcementMode::Disabled); 99 | assert!(!disabled.validate_on_write); 100 | assert!(!disabled.validate_on_read); 101 | assert!(disabled.allow_schema_evolution); 102 | assert!(disabled.allow_unknown_properties); 103 | log::debug!(" ✓ Disabled config: no validation, all operations allowed"); 104 | } 105 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/ddl_stmt/create_user.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // CreateUserExecutor - Implements CREATE USER statement execution 5 | use crate::ast::CreateUserStatement; 6 | use crate::catalog::manager::CatalogManager; 7 | use crate::catalog::operations::{CatalogOperation, EntityType}; 8 | use crate::exec::write_stmt::ddl_stmt::DDLStatementExecutor; 9 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor}; 10 | use crate::exec::ExecutionError; 11 | use crate::storage::StorageManager; 12 | use crate::txn::state::OperationType; 13 | use serde_json::json; 14 | 15 | pub struct CreateUserExecutor { 16 | statement: CreateUserStatement, 17 | } 18 | 19 | impl CreateUserExecutor { 20 | pub fn new(statement: CreateUserStatement) -> Self { 21 | Self { statement } 22 | } 23 | } 24 | 25 | impl StatementExecutor for CreateUserExecutor { 26 | fn operation_type(&self) -> OperationType { 27 | OperationType::CreateUser 28 | } 29 | 30 | fn operation_description(&self, _context: &ExecutionContext) -> String { 31 | format!("CREATE USER '{}'", self.statement.username) 32 | } 33 | } 34 | 35 | impl DDLStatementExecutor for CreateUserExecutor { 36 | fn execute_ddl_operation( 37 | &self, 38 | _context: &ExecutionContext, 39 | catalog_manager: &mut CatalogManager, 40 | _storage: &StorageManager, 41 | ) -> Result<(String, usize), ExecutionError> { 42 | let username = &self.statement.username; 43 | 44 | // Prepare user creation parameters 45 | let mut user_params = json!({ 46 | "username": username, 47 | }); 48 | 49 | // Add password if provided 50 | if let Some(password) = &self.statement.password { 51 | user_params["password"] = json!(password); 52 | } 53 | 54 | // Always include default "user" role, plus any additional roles specified 55 | let mut user_roles = vec!["user".to_string()]; // Default role for all users 56 | 57 | // Add any additional roles specified in the CREATE USER statement 58 | for role in &self.statement.roles { 59 | if role != "user" { 60 | // Avoid duplicates 61 | user_roles.push(role.clone()); 62 | } 63 | } 64 | 65 | user_params["roles"] = json!(user_roles); 66 | 67 | // Create the catalog operation for creating the user 68 | let create_op = CatalogOperation::Create { 69 | entity_type: EntityType::User, 70 | name: username.clone(), 71 | params: user_params, 72 | }; 73 | 74 | // Execute the operation through the catalog manager 75 | let create_result = catalog_manager.execute("security", create_op); 76 | match create_result { 77 | Ok(_response) => { 78 | // Persist the catalog changes transactionally 79 | let persist_result = catalog_manager.persist_catalog("security"); 80 | if let Err(e) = persist_result { 81 | return Err(ExecutionError::RuntimeError(format!( 82 | "Failed to persist user creation '{}' to storage: {}", 83 | username, e 84 | ))); 85 | } 86 | 87 | let message = if self.statement.if_not_exists { 88 | format!("User '{}' created successfully (if not exists)", username) 89 | } else { 90 | format!("User '{}' created successfully", username) 91 | }; 92 | Ok((message, 1)) 93 | } 94 | Err(catalog_error) => { 95 | // Handle "already exists" errors differently for IF NOT EXISTS 96 | let error_msg = catalog_error.to_string(); 97 | if error_msg.contains("already exists") && self.statement.if_not_exists { 98 | let message = format!("User '{}' already exists (if not exists)", username); 99 | Ok((message, 0)) 100 | } else { 101 | Err(ExecutionError::RuntimeError(format!( 102 | "Failed to create user '{}': {}", 103 | username, catalog_error 104 | ))) 105 | } 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /examples/rust/sdk/basic_usage.rs: -------------------------------------------------------------------------------- 1 | //! Basic usage example for GraphLite SDK 2 | //! 3 | //! This example demonstrates the core features of the GraphLite Rust SDK: 4 | //! - Opening a database 5 | //! - Creating sessions 6 | //! - Executing queries 7 | //! - Using transactions 8 | //! - Query builder API 9 | //! - Typed result deserialization 10 | //! 11 | //! Run with: cargo run --example basic_usage 12 | 13 | use graphlite_sdk::{Error, GraphLite}; 14 | use serde::Deserialize; 15 | 16 | #[derive(Debug, Deserialize)] 17 | struct Person { 18 | name: String, 19 | age: f64, 20 | } 21 | 22 | fn main() -> Result<(), Error> { 23 | println!("=== GraphLite SDK Basic Usage Example ===\n"); 24 | 25 | // 1. Open a database 26 | println!("1. Opening database..."); 27 | let db_path = "/tmp/graphlite_sdk_example"; 28 | let db = GraphLite::open(db_path)?; 29 | println!(" Database opened at {}\n", db_path); 30 | 31 | // 2. Create a session 32 | println!("2. Creating session..."); 33 | let session = db.session("admin")?; 34 | println!(" Session created for user 'admin'\n"); 35 | 36 | // 3. Execute DDL statements 37 | println!("3. Creating schema and graph..."); 38 | session.execute("CREATE SCHEMA IF NOT EXISTS /example")?; 39 | session.execute("SESSION SET SCHEMA /example")?; 40 | session.execute("CREATE GRAPH IF NOT EXISTS social")?; 41 | session.execute("SESSION SET GRAPH social")?; 42 | println!(" Schema and graph created\n"); 43 | 44 | // 4. Insert data using transactions 45 | println!("4. Inserting data with transaction..."); 46 | { 47 | let mut tx = session.transaction()?; 48 | tx.execute("CREATE (p:Person {name: 'Alice', age: 30})")?; 49 | tx.execute("CREATE (p:Person {name: 'Bob', age: 25})")?; 50 | tx.execute("CREATE (p:Person {name: 'Charlie', age: 35})")?; 51 | tx.commit()?; 52 | println!(" Inserted 3 persons\n"); 53 | } 54 | 55 | // 5. Query data directly 56 | println!("5. Querying data..."); 57 | let result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age")?; 58 | println!(" Found {} persons:", result.rows.len()); 59 | for row in &result.rows { 60 | if let (Some(name), Some(age)) = (row.get_value("name"), row.get_value("age")) { 61 | println!(" - Name: {:?}, Age: {:?}", name, age); 62 | } 63 | } 64 | println!(); 65 | 66 | // 6. Use query builder 67 | println!("6. Using query builder..."); 68 | let result = session 69 | .query_builder() 70 | .match_pattern("(p:Person)") 71 | .where_clause("p.age > 25") 72 | .return_clause("p.name as name, p.age as age") 73 | .order_by("p.age DESC") 74 | .execute()?; 75 | println!(" Found {} persons over 25:", result.rows.len()); 76 | for row in &result.rows { 77 | if let (Some(name), Some(age)) = (row.get_value("name"), row.get_value("age")) { 78 | println!(" - Name: {:?}, Age: {:?}", name, age); 79 | } 80 | } 81 | println!(); 82 | 83 | // 7. Typed deserialization 84 | println!("7. Using typed deserialization..."); 85 | let result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age")?; 86 | let typed = graphlite_sdk::TypedResult::from(result); 87 | let people: Vec = typed.deserialize_rows()?; 88 | println!(" Deserialized {} persons:", people.len()); 89 | for person in &people { 90 | println!(" - {:?}", person); 91 | } 92 | println!(); 93 | 94 | // 8. Transaction with rollback 95 | println!("8. Demonstrating transaction rollback..."); 96 | { 97 | let mut tx = session.transaction()?; 98 | tx.execute("CREATE (p:Person {name: 'David', age: 40})")?; 99 | println!(" Created person 'David' in transaction"); 100 | // Transaction is dropped without commit - automatically rolls back 101 | } 102 | println!(" Transaction rolled back (David not persisted)\n"); 103 | 104 | // 9. Verify rollback 105 | let result = session.query("MATCH (p:Person) RETURN count(p) as count")?; 106 | if let Some(row) = result.rows.first() { 107 | if let Some(count) = row.get_value("count") { 108 | println!(" Person count after rollback: {:?}\n", count); 109 | } 110 | } 111 | 112 | println!("=== Example completed successfully ==="); 113 | Ok(()) 114 | } 115 | -------------------------------------------------------------------------------- /sdk-rust/examples/basic_usage.rs: -------------------------------------------------------------------------------- 1 | //! Basic usage example for GraphLite SDK 2 | //! 3 | //! This example demonstrates the core features of the GraphLite Rust SDK: 4 | //! - Opening a database 5 | //! - Creating sessions 6 | //! - Executing queries 7 | //! - Using transactions 8 | //! - Query builder API 9 | //! - Typed result deserialization 10 | //! 11 | //! Run with: cargo run --example basic_usage 12 | 13 | use graphlite_sdk::{Error, GraphLite}; 14 | use serde::Deserialize; 15 | 16 | #[derive(Debug, Deserialize)] 17 | #[allow(dead_code)] 18 | struct Person { 19 | name: String, 20 | age: f64, 21 | } 22 | 23 | fn main() -> Result<(), Error> { 24 | println!("=== GraphLite SDK Basic Usage Example ===\n"); 25 | 26 | // 1. Open a database 27 | println!("1. Opening database..."); 28 | let db_path = "/tmp/graphlite_sdk_example"; 29 | let db = GraphLite::open(db_path)?; 30 | println!(" ✓ Database opened at {}\n", db_path); 31 | 32 | // 2. Create a session 33 | println!("2. Creating session..."); 34 | let session = db.session("admin")?; 35 | println!(" ✓ Session created for user 'admin'\n"); 36 | 37 | // 3. Execute DDL statements 38 | println!("3. Creating schema and graph..."); 39 | session.execute("CREATE SCHEMA IF NOT EXISTS example")?; 40 | session.execute("USE SCHEMA example")?; 41 | session.execute("CREATE GRAPH IF NOT EXISTS social")?; 42 | session.execute("USE GRAPH social")?; 43 | println!(" ✓ Schema and graph created\n"); 44 | 45 | // 4. Insert data using transactions 46 | println!("4. Inserting data with transaction..."); 47 | { 48 | let mut tx = session.transaction()?; 49 | tx.execute("CREATE (p:Person {name: 'Alice', age: 30})")?; 50 | tx.execute("CREATE (p:Person {name: 'Bob', age: 25})")?; 51 | tx.execute("CREATE (p:Person {name: 'Charlie', age: 35})")?; 52 | tx.commit()?; 53 | println!(" ✓ Inserted 3 persons\n"); 54 | } 55 | 56 | // 5. Query data directly 57 | println!("5. Querying data..."); 58 | let result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age")?; 59 | println!(" Found {} persons:", result.rows.len()); 60 | for row in &result.rows { 61 | if let (Some(name), Some(age)) = (row.get_value("name"), row.get_value("age")) { 62 | println!(" - Name: {:?}, Age: {:?}", name, age); 63 | } 64 | } 65 | println!(); 66 | 67 | // 6. Use query builder 68 | println!("6. Using query builder..."); 69 | let result = session 70 | .query_builder() 71 | .match_pattern("(p:Person)") 72 | .where_clause("p.age > 25") 73 | .return_clause("p.name as name, p.age as age") 74 | .order_by("p.age DESC") 75 | .execute()?; 76 | println!(" Found {} persons over 25:", result.rows.len()); 77 | for row in &result.rows { 78 | if let (Some(name), Some(age)) = (row.get_value("name"), row.get_value("age")) { 79 | println!(" - Name: {:?}, Age: {:?}", name, age); 80 | } 81 | } 82 | println!(); 83 | 84 | // 7. Typed deserialization 85 | println!("7. Using typed deserialization..."); 86 | let result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age")?; 87 | let typed = graphlite_sdk::TypedResult::from(result); 88 | let people: Vec = typed.deserialize_rows()?; 89 | println!(" Deserialized {} persons:", people.len()); 90 | for person in &people { 91 | println!(" - {:?}", person); 92 | } 93 | println!(); 94 | 95 | // 8. Transaction with rollback 96 | println!("8. Demonstrating transaction rollback..."); 97 | { 98 | let mut tx = session.transaction()?; 99 | tx.execute("CREATE (p:Person {name: 'David', age: 40})")?; 100 | println!(" Created person 'David' in transaction"); 101 | // Transaction is dropped without commit - automatically rolls back 102 | } 103 | println!(" Transaction rolled back (David not persisted)\n"); 104 | 105 | // 9. Verify rollback 106 | let result = session.query("MATCH (p:Person) RETURN count(p) as count")?; 107 | if let Some(row) = result.rows.first() { 108 | if let Some(count) = row.get_value("count") { 109 | println!(" Person count after rollback: {:?}\n", count); 110 | } 111 | } 112 | 113 | println!("=== Example completed successfully ==="); 114 | Ok(()) 115 | } 116 | -------------------------------------------------------------------------------- /bindings/java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | com.deepgraph 9 | graphlite 10 | 0.1.0 11 | jar 12 | 13 | GraphLite Java Bindings 14 | Java bindings for GraphLite embedded graph database 15 | https://github.com/deepgraph/graphlite 16 | 17 | 18 | 19 | Apache License, Version 2.0 20 | http://www.apache.org/licenses/LICENSE-2.0.txt 21 | repo 22 | 23 | 24 | 25 | 26 | 27 | DeepGraph Inc. 28 | info@deepgraph.ai 29 | DeepGraph 30 | https://deepgraph.ai 31 | 32 | 33 | 34 | 35 | scm:git:git://github.com/deepgraph/graphlite.git 36 | scm:git:ssh://github.com:deepgraph/graphlite.git 37 | https://github.com/deepgraph/graphlite/tree/main 38 | 39 | 40 | 41 | UTF-8 42 | 11 43 | 11 44 | 5.13.0 45 | 20230227 46 | 5.10.0 47 | 48 | 49 | 50 | 51 | 52 | net.java.dev.jna 53 | jna 54 | ${jna.version} 55 | 56 | 57 | 58 | 59 | org.json 60 | json 61 | ${json.version} 62 | 63 | 64 | 65 | 66 | org.junit.jupiter 67 | junit-jupiter 68 | ${junit.version} 69 | test 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-compiler-plugin 78 | 3.11.0 79 | 80 | 11 81 | 11 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-surefire-plugin 88 | 3.1.2 89 | 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-javadoc-plugin 94 | 3.5.0 95 | 96 | 97 | attach-javadocs 98 | 99 | jar 100 | 101 | 102 | 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-source-plugin 108 | 3.3.0 109 | 110 | 111 | attach-sources 112 | 113 | jar 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/ddl_stmt/create_role.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // CreateRoleExecutor - Implements CREATE ROLE statement execution 5 | use crate::ast::CreateRoleStatement; 6 | use crate::catalog::manager::CatalogManager; 7 | use crate::catalog::operations::{CatalogOperation, EntityType}; 8 | use crate::exec::write_stmt::ddl_stmt::DDLStatementExecutor; 9 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor}; 10 | use crate::exec::ExecutionError; 11 | use crate::storage::StorageManager; 12 | use crate::txn::state::OperationType; 13 | use serde_json::Value; 14 | 15 | pub struct CreateRoleExecutor { 16 | statement: CreateRoleStatement, 17 | } 18 | 19 | impl CreateRoleExecutor { 20 | pub fn new(statement: CreateRoleStatement) -> Self { 21 | Self { statement } 22 | } 23 | } 24 | 25 | impl StatementExecutor for CreateRoleExecutor { 26 | fn operation_type(&self) -> OperationType { 27 | OperationType::CreateRole 28 | } 29 | 30 | fn operation_description(&self, _context: &ExecutionContext) -> String { 31 | format!("CREATE ROLE '{}'", self.statement.role_name) 32 | } 33 | } 34 | 35 | impl DDLStatementExecutor for CreateRoleExecutor { 36 | fn execute_ddl_operation( 37 | &self, 38 | _context: &ExecutionContext, 39 | catalog_manager: &mut CatalogManager, 40 | _storage: &StorageManager, 41 | ) -> Result<(String, usize), ExecutionError> { 42 | let role_name = &self.statement.role_name; 43 | 44 | // Create parameters for the role 45 | let mut params = serde_json::Map::new(); 46 | params.insert("name".to_string(), Value::String(role_name.clone())); 47 | 48 | if let Some(description) = &self.statement.description { 49 | params.insert( 50 | "description".to_string(), 51 | Value::String(description.clone()), 52 | ); 53 | } 54 | 55 | // Convert permissions to JSON format if any 56 | if !self.statement.permissions.is_empty() { 57 | let permissions: Vec = self 58 | .statement 59 | .permissions 60 | .iter() 61 | .map(|perm| { 62 | let mut perm_obj = serde_json::Map::new(); 63 | perm_obj.insert( 64 | "resource_type".to_string(), 65 | Value::String(perm.resource_type.clone()), 66 | ); 67 | if let Some(resource_name) = &perm.resource_name { 68 | perm_obj.insert( 69 | "resource_name".to_string(), 70 | Value::String(resource_name.clone()), 71 | ); 72 | } 73 | Value::Object(perm_obj) 74 | }) 75 | .collect(); 76 | params.insert("permissions".to_string(), Value::Array(permissions)); 77 | } 78 | 79 | params.insert( 80 | "if_not_exists".to_string(), 81 | Value::Bool(self.statement.if_not_exists), 82 | ); 83 | 84 | // Create the catalog operation 85 | let operation = CatalogOperation::Create { 86 | entity_type: EntityType::Role, 87 | name: role_name.clone(), 88 | params: Value::Object(params), 89 | }; 90 | 91 | // Execute the operation through the catalog manager 92 | let create_result = catalog_manager.execute("security", operation); 93 | match create_result { 94 | Ok(_response) => { 95 | // Persist the catalog changes transactionally 96 | let persist_result = catalog_manager.persist_catalog("security"); 97 | if let Err(e) = persist_result { 98 | return Err(ExecutionError::RuntimeError(format!( 99 | "Failed to persist role '{}' to storage: {}", 100 | role_name, e 101 | ))); 102 | } 103 | 104 | let message = if self.statement.if_not_exists { 105 | format!("Role '{}' created successfully (if not exists)", role_name) 106 | } else { 107 | format!("Role '{}' created successfully", role_name) 108 | }; 109 | Ok((message, 1)) 110 | } 111 | Err(catalog_error) => Err(ExecutionError::RuntimeError(format!( 112 | "Failed to create role '{}': {}", 113 | role_name, catalog_error 114 | ))), 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /graphlite/src/exec/write_stmt/transaction/commit.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-2025 DeepGraph Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | use crate::ast::CommitStatement; 5 | use crate::exec::write_stmt::{ExecutionContext, StatementExecutor, TransactionStatementExecutor}; 6 | use crate::exec::{ExecutionError, QueryResult, Row}; 7 | use crate::storage::value::Value; 8 | use crate::txn::{log::TransactionLog, state::OperationType}; 9 | use std::collections::HashMap; 10 | use std::sync::RwLock; 11 | 12 | pub struct CommitExecutor { 13 | // Statement data passed via execute_commit parameter, not stored 14 | } 15 | 16 | impl CommitExecutor { 17 | pub fn new(_statement: CommitStatement) -> Self { 18 | Self {} 19 | } 20 | } 21 | 22 | impl StatementExecutor for CommitExecutor { 23 | fn operation_type(&self) -> OperationType { 24 | OperationType::Commit 25 | } 26 | 27 | fn operation_description(&self, _context: &ExecutionContext) -> String { 28 | "COMMIT".to_string() 29 | } 30 | 31 | fn requires_write_permission(&self) -> bool { 32 | false // Transaction control doesn't require graph write permissions 33 | } 34 | } 35 | 36 | impl TransactionStatementExecutor for CommitExecutor { 37 | fn execute_transaction_operation( 38 | &self, 39 | context: &ExecutionContext, 40 | ) -> Result { 41 | // Use the session transaction state to commit the transaction 42 | let transaction_state = context.transaction_state().ok_or_else(|| { 43 | ExecutionError::RuntimeError("No transaction state available".to_string()) 44 | })?; 45 | transaction_state.commit_transaction()?; 46 | 47 | let message = "Transaction committed successfully"; 48 | 49 | Ok(QueryResult { 50 | rows: vec![Row::from_values(HashMap::from([( 51 | "status".to_string(), 52 | Value::String(message.to_string()), 53 | )]))], 54 | variables: vec!["status".to_string()], 55 | execution_time_ms: 0, 56 | rows_affected: 0, 57 | session_result: None, 58 | warnings: Vec::new(), 59 | }) 60 | } 61 | } 62 | 63 | impl CommitExecutor { 64 | #[allow(dead_code)] // ROADMAP v0.5.0 - Explicit transaction commit for ACID guarantees 65 | pub fn execute_commit( 66 | _statement: &CommitStatement, 67 | transaction_manager: &crate::txn::TransactionManager, 68 | current_transaction: &RwLock>, 69 | transaction_logs: &RwLock>, 70 | ) -> Result { 71 | // Check if there's an active transaction 72 | let current_txn = current_transaction.read().map_err(|_| { 73 | ExecutionError::RuntimeError("Failed to acquire transaction lock".to_string()) 74 | })?; 75 | 76 | let txn_id = match *current_txn { 77 | Some(id) => id, 78 | None => { 79 | drop(current_txn); 80 | return Err(ExecutionError::RuntimeError( 81 | "No transaction in progress".to_string(), 82 | )); 83 | } 84 | }; 85 | drop(current_txn); 86 | 87 | // Commit the transaction in the transaction manager 88 | transaction_manager.commit_transaction(txn_id)?; 89 | 90 | // Remove the transaction log since we're committing 91 | { 92 | let mut logs = transaction_logs.write().map_err(|_| { 93 | ExecutionError::RuntimeError("Failed to acquire transaction logs lock".to_string()) 94 | })?; 95 | logs.remove(&txn_id); 96 | } 97 | 98 | // Clear transaction state 99 | { 100 | let mut current_txn = current_transaction.write().map_err(|_| { 101 | ExecutionError::RuntimeError("Failed to acquire transaction lock".to_string()) 102 | })?; 103 | *current_txn = None; 104 | } 105 | 106 | // Persistence is now handled automatically by the unified data modification flow 107 | 108 | let message = "Transaction committed successfully"; 109 | 110 | Ok(QueryResult { 111 | rows: vec![Row::from_values(HashMap::from([( 112 | "status".to_string(), 113 | Value::String(message.to_string()), 114 | )]))], 115 | variables: vec!["status".to_string()], 116 | execution_time_ms: 0, 117 | rows_affected: 0, 118 | session_result: None, 119 | warnings: Vec::new(), 120 | }) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /graphlite/tests/duplicate_edge_warning_test.rs: -------------------------------------------------------------------------------- 1 | // Test to verify that duplicate edge insertion produces a warning 2 | // This test verifies the fix for: https://github.com/GraphLite-AI/GraphLite/issues/XXX 3 | 4 | #![allow(unused_variables)] 5 | 6 | #[path = "testutils/mod.rs"] 7 | mod testutils; 8 | 9 | use testutils::test_fixture::TestFixture; 10 | 11 | #[test] 12 | fn test_duplicate_edge_shows_warning() { 13 | // Create a fresh test fixture 14 | let fixture = TestFixture::new().expect("Failed to create fixture"); 15 | 16 | // Setup graph context 17 | fixture 18 | .setup_graph("duplicate_edge_test") 19 | .expect("Failed to setup graph"); 20 | 21 | // Setup: Create two people 22 | fixture 23 | .query("INSERT (:Person {name: 'Charlie'}), (:Person {name: 'Diana'})") 24 | .expect("Node creation should succeed"); 25 | 26 | // First edge insertion - should succeed without warnings 27 | let result1 = fixture 28 | .query( 29 | "MATCH (c:Person {name: 'Charlie'}), (d:Person {name: 'Diana'}) 30 | INSERT (c)-[:KNOWS {since: 2021, strength: 'weak'}]->(d)", 31 | ) 32 | .expect("First edge insertion should succeed"); 33 | 34 | assert_eq!( 35 | result1.rows_affected, 1, 36 | "First insertion should affect 1 row" 37 | ); 38 | assert!( 39 | result1.warnings.is_empty(), 40 | "First insertion should have no warnings" 41 | ); 42 | 43 | // Second edge insertion - should detect duplicate and show warning 44 | let result2 = fixture 45 | .query( 46 | "MATCH (c:Person {name: 'Charlie'}), (d:Person {name: 'Diana'}) 47 | INSERT (c)-[:KNOWS {since: 2021, strength: 'weak'}]->(d)", 48 | ) 49 | .expect("Second edge insertion should succeed (but skip duplicate)"); 50 | 51 | // Verify warning exists 52 | assert!( 53 | !result2.warnings.is_empty(), 54 | "Expected warning for duplicate edge insertion" 55 | ); 56 | 57 | assert!( 58 | result2.warnings[0].contains("Duplicate edge detected"), 59 | "Warning should mention 'Duplicate edge detected', got: {}", 60 | result2.warnings[0] 61 | ); 62 | 63 | // Verify rows_affected is 0 for duplicate insert 64 | assert_eq!( 65 | result2.rows_affected, 0, 66 | "Duplicate edge insertion should not affect any rows" 67 | ); 68 | 69 | // Verify only one edge exists 70 | let count_result = fixture 71 | .query("MATCH ()-[r:KNOWS]->() RETURN COUNT(r) AS edge_count") 72 | .expect("Count query should succeed"); 73 | 74 | let edge_count = count_result.rows[0].values["edge_count"] 75 | .as_number() 76 | .expect("Should get a number"); 77 | 78 | assert_eq!( 79 | edge_count, 1.0, 80 | "Should have exactly 1 edge after duplicate insertion attempt, found: {}", 81 | edge_count 82 | ); 83 | } 84 | 85 | #[test] 86 | fn test_duplicate_edge_with_regular_insert() { 87 | // Test that duplicate edge detection works with inline node creation 88 | let fixture = TestFixture::new().expect("Failed to create fixture"); 89 | 90 | // Setup graph context 91 | fixture 92 | .setup_graph("regular_insert_test") 93 | .expect("Failed to setup graph"); 94 | 95 | // First insertion with inline nodes - should succeed 96 | let result1 = fixture 97 | .query("INSERT (:User {id: 'u1'})-[:FOLLOWS {since: '2020-01-01'}]->(:User {id: 'u2'})") 98 | .expect("First insertion should succeed"); 99 | 100 | assert!(result1.rows_affected >= 1, "Should create nodes and edge"); 101 | assert!(result1.warnings.is_empty()); 102 | 103 | // Second insertion - nodes already exist, so only edge insertion is attempted 104 | // Since nodes are duplicates, they won't be re-created, but we'll try to create the edge again 105 | let result2 = fixture 106 | .query("INSERT (:User {id: 'u1'})-[:FOLLOWS {since: '2020-01-01'}]->(:User {id: 'u2'})") 107 | .expect("Second insertion should succeed"); 108 | 109 | // Should have warnings - at minimum for duplicate nodes 110 | // Edge warning appears only if nodes already existed and edge is duplicate 111 | assert!( 112 | !result2.warnings.is_empty(), 113 | "Should have warnings for duplicates" 114 | ); 115 | 116 | // Verify we have node duplicate warnings 117 | let has_node_warnings = result2 118 | .warnings 119 | .iter() 120 | .any(|w| w.contains("Duplicate node detected")); 121 | 122 | assert!( 123 | has_node_warnings, 124 | "Should have 'Duplicate node detected' warnings, got: {:?}", 125 | result2.warnings 126 | ); 127 | } 128 | --------------------------------------------------------------------------------