cypher-graphdb 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cypher_graphdb-0.2.2/src/cypher_graphdb.egg-info → cypher_graphdb-0.2.3}/PKG-INFO +2 -5
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/pyproject.toml +3 -6
- cypher_graphdb-0.2.3/src/cypher_graphdb/backends/age/agebulkwriter.py +253 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/age/agegraphdb.py +71 -33
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/age/agerowfactories.py +9 -8
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/age/agesqlbuilder.py +64 -0
- cypher_graphdb-0.2.3/src/cypher_graphdb/backends/age/agtype.py +24 -0
- cypher_graphdb-0.2.3/src/cypher_graphdb/backends/age/agtype_parser.py +194 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/execute_cypher_command.py +7 -35
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/__init__.py +2 -1
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/column_utils.py +50 -1
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3/src/cypher_graphdb.egg-info}/PKG-INFO +2 -5
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb.egg-info/SOURCES.txt +3 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb.egg-info/requires.txt +1 -5
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/test_indexes_and_bulk.py +148 -0
- cypher_graphdb-0.2.3/tests/unit/test_agtype_parser.py +156 -0
- cypher_graphdb-0.2.3/uv.lock +1716 -0
- cypher_graphdb-0.2.2/src/cypher_graphdb/backends/age/agtype.py +0 -27
- cypher_graphdb-0.2.2/uv.lock +0 -1563
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/.env.age.example +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/.env.example +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/.env.memgraph.example +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/.github/workflows/ci.yml +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/.github/workflows/publish.yml +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/.github/workflows/release.yml +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/.gitignore +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/.pre-commit-config.yaml +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/AGENTS.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/CHANGELOG.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/CONTRIBUTING.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/LICENSE.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/README.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/Taskfile.yml +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/cli +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/TODO.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/changelog.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/css/material.css +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/css/mkdocstrings.css +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/css/style.css +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/design/duckdb-migration.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/design/falkordb-integration.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/design/hierarchical-export-format.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/design/multiple-statement-execution.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/design/parameterized-queries-for-typed-models.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/design/query-result-immutable-design.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/design/read-only-mode.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/design/stateless-multi-graph-support.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/documentation-guide.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/examples/docstring_examples.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/index.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/reference/cypher_graphdb/backends/age/index.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/reference/cypher_graphdb/backends/index.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/reference/cypher_graphdb/cli/index.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/reference/cypher_graphdb/cypher/index.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/reference/cypher_graphdb/index.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/reference/cypher_graphdb/tools/index.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/docs/usage/index.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/mkdocs.yml +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/scripts/gen_ref_nav.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/setup.cfg +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/__main__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/args.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backend.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backendprovider.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/age/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/age/agesearch.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/age/ageserializer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/memgraph/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/memgraph/memgraphdb.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/memgraph/memgraphrowfactories.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cardinality.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/app.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/banner.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/command_manager.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/command_map.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/command_registry.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/add_graph_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/apply_config_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/base_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/clear_graph_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/commit_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/connect_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/create_edge_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/create_graph_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/create_linked_node_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/create_node_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/delete_graphobj_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/disconnect_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/drop_graph_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/dump_backends_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/dump_graphs_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/dump_indexes_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/dump_labels_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/dump_models_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/dump_parsed_query_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/dump_schema_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/dump_statistics_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/execute_file_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/exit_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/export_graph_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/fetch_all_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/fetch_edges_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/fetch_nodes_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/format_output_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/get_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/gid_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/graph_exists_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/graph_op_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/graph_to_tree_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/help_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/import_graph_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/last_result_op_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/load_models_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/resolve_edges_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/rollback_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/search_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/set_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/sql_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/update_graphobj_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/commands/use_graph_command.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/completer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/config.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/exporter.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/file_executor.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/graphdata.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/graphdb.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/help/_overview.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/help/_template.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/help/add_graph.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/help/exit.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/help/export_graph.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/help/import_graph.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/help/last_result.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/help.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/prompt.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/promptparser.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/provider.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/renderer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/runtime.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/schema_cmd.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cli/settings.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/command_reader.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/config.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/.antlr/Cypher.interp +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/.antlr/Cypher.tokens +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/.antlr/CypherBaseListener.java +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/.antlr/CypherLexer.interp +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/.antlr/CypherLexer.java +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/.antlr/CypherLexer.tokens +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/.antlr/CypherListener.java +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/.antlr/CypherParser.java +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/Cypher.g4 +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/Cypher.interp +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/Cypher.tokens +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/CypherLexer.interp +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/CypherLexer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/CypherLexer.tokens +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/CypherListener.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/CypherParser.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypher/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypherbuilder.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/batch.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/bulk_normalize.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/connection.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/criteria.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/cyphergraphdb.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/indexing.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/result.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/schema.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/search.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/sql.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cyphergraphdb/stream_mixin.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypherjson.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/cypherparser.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/dbpool.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/decorators.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/display.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/exceptions.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/graphops.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/main.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/modelinfo.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/modelprovider.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/models.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/options.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/schema/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/schema/converter.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/schema/core.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/schema/generator.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/settings.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/statistics.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/base_exporter.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/base_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/csv_exporter.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/csv_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/csv_source.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/data_flattener.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/excel_exporter.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/excel_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/excel_row_source.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/file_exporter.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/file_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/hierarchical_exporter.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/hierarchical_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/hierarchical_row_source.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/json_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/json_yaml_data_source.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/row_collector.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/row_set.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/row_source.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/tabular_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/tools/yaml_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/collection_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/connection_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/conversion_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/core_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/schema_merge.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/schema_to_llm.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/schema_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/settings_repr.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/utils/string_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb.egg-info/dependency_links.txt +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb.egg-info/entry_points.txt +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb.egg-info/top_level.txt +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/README.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/conftest.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/conftest.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/test_basic_operations.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/test_create_or_merge.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/test_example.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/test_parameters.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/test_read_only_mode.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/integration/test_streaming.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/test_json_schema_loading.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/test_json_schema_vs_decorators.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/README.md +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/cli/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/cli/test_cmd_map.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/cli/test_command_registry.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/mock_backend.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_age_serializer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_age_sqlbuilder.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_backend_capabilities.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_bulk_normalize.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_column_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_command_reader.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_cypherbuilder.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_cypherparser.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_dbpool.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_dict_access_mixin.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_extend_relation_decorator.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_extend_relations.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_graph_id_zero.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_graphops.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_indexing.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_model_inheritance.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_modelinfo.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_modelprovider_loading.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_modelprovider_schemas.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_models.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/test_schema_converter.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/tools/__init__.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/tools/test_data_flattener.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/tools/test_exporters.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/tools/test_hierarchical_importer.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/tools/test_json_yaml_data_source.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/tools/test_resource_management.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/tools/test_tabular_import.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/utils/test_collection_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/utils/test_connection_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/utils/test_conversion_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/utils/test_core_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/utils/test_schema_merge.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/utils/test_schema_utils.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/utils/test_settings_repr.py +0 -0
- {cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/tests/unit/utils/test_string_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cypher_graphdb
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: ORM like library and CLI for cypher query language supporting graph databases.
|
|
5
5
|
Author-email: Wolfgang Miller <wolfgang.miller@petrarca-labs.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -15,7 +15,7 @@ Requires-Dist: art
|
|
|
15
15
|
Requires-Dist: python-dotenv
|
|
16
16
|
Requires-Dist: prompt-toolkit
|
|
17
17
|
Requires-Dist: rich
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: antlr4-python3-runtime==4.11.1
|
|
19
19
|
Requires-Dist: openpyxl
|
|
20
20
|
Requires-Dist: numpy
|
|
21
21
|
Requires-Dist: pydantic
|
|
@@ -57,9 +57,6 @@ Requires-Dist: mkdocs-git-revision-date-localized-plugin; extra == "docs"
|
|
|
57
57
|
Requires-Dist: pymdown-extensions; extra == "docs"
|
|
58
58
|
Requires-Dist: termynal; extra == "docs"
|
|
59
59
|
Requires-Dist: black; extra == "docs"
|
|
60
|
-
Provides-Extra: examples
|
|
61
|
-
Requires-Dist: fastapi; extra == "examples"
|
|
62
|
-
Requires-Dist: uvicorn; extra == "examples"
|
|
63
60
|
Dynamic: license-file
|
|
64
61
|
|
|
65
62
|
# CypherGraphDB Library
|
|
@@ -23,7 +23,7 @@ dependencies = [
|
|
|
23
23
|
"python-dotenv",
|
|
24
24
|
"prompt-toolkit",
|
|
25
25
|
"rich",
|
|
26
|
-
"
|
|
26
|
+
"antlr4-python3-runtime==4.11.1",
|
|
27
27
|
"openpyxl",
|
|
28
28
|
"numpy",
|
|
29
29
|
"pydantic",
|
|
@@ -76,13 +76,10 @@ docs = [
|
|
|
76
76
|
"black",
|
|
77
77
|
]
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
"fastapi",
|
|
81
|
-
"uvicorn",
|
|
82
|
-
]
|
|
79
|
+
|
|
83
80
|
|
|
84
81
|
[tool.uv.sources]
|
|
85
|
-
|
|
82
|
+
|
|
86
83
|
|
|
87
84
|
[project.scripts]
|
|
88
85
|
cgdb-cli = "cypher_graphdb.args:typer_main"
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Direct SQL bulk writer for Apache AGE label tables.
|
|
2
|
+
|
|
3
|
+
Bypasses the Cypher parser by INSERTing directly into AGE's PostgreSQL label
|
|
4
|
+
tables using _graphid() + nextval() for ID generation and ::agtype for
|
|
5
|
+
properties. This is the same pattern AGE's own test suite uses
|
|
6
|
+
(regress/sql/age_global_graph.sql) and is 10-30x faster than Cypher UNWIND
|
|
7
|
+
for large bulk loads.
|
|
8
|
+
|
|
9
|
+
The writer is stateless: it receives a connection and graph_name, performs
|
|
10
|
+
the INSERT, and returns. Transaction control (commit/rollback) is the
|
|
11
|
+
caller's responsibility, with one exception: when a label does not yet exist,
|
|
12
|
+
``_ensure_label`` creates it via DDL inside a savepoint so that the DDL is
|
|
13
|
+
committed immediately (DDL cannot run inside a user transaction in PostgreSQL)
|
|
14
|
+
without affecting any surrounding data already written by the caller.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Literal
|
|
18
|
+
|
|
19
|
+
import psycopg
|
|
20
|
+
from loguru import logger
|
|
21
|
+
from psycopg.sql import SQL, Identifier
|
|
22
|
+
|
|
23
|
+
from cypher_graphdb.utils import chunk_list
|
|
24
|
+
|
|
25
|
+
from .ageserializer import escape_value
|
|
26
|
+
from .agesqlbuilder import SQLBuilder
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AGEBulkWriter:
|
|
30
|
+
"""Direct SQL bulk writer for AGE label tables.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
connection: An open psycopg connection to the AGE database.
|
|
34
|
+
graph_name: The AGE graph name (= PostgreSQL schema name).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, connection: psycopg.Connection, graph_name: str) -> None:
|
|
38
|
+
self._conn = connection
|
|
39
|
+
self._graph_name = graph_name
|
|
40
|
+
|
|
41
|
+
# -- Public API -----------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def bulk_insert_nodes(self, label: str, rows: list[dict], batch_size: int = 200) -> int:
|
|
44
|
+
"""Insert nodes via direct SQL INSERT into the AGE label table.
|
|
45
|
+
|
|
46
|
+
Creates the vlabel if it doesn't exist yet (AGE normally does this
|
|
47
|
+
lazily on first Cypher CREATE).
|
|
48
|
+
|
|
49
|
+
Uses multi-row INSERT with inline agtype literals (not parameterized)
|
|
50
|
+
because AGE's agtype_in text-input function does not interpret JSON
|
|
51
|
+
escape sequences correctly when received as psycopg text parameters.
|
|
52
|
+
The agtype values are built using the same escape_value function as
|
|
53
|
+
the Cypher UNWIND path, ensuring identical storage.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
label: Vertex label name (e.g. "Method").
|
|
57
|
+
rows: List of property dicts, one per node.
|
|
58
|
+
batch_size: Number of rows per multi-row INSERT.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Total number of nodes inserted.
|
|
62
|
+
"""
|
|
63
|
+
if not rows:
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
label_id = self._ensure_label(label, kind="v")
|
|
67
|
+
|
|
68
|
+
total = 0
|
|
69
|
+
with self._conn.cursor() as cursor:
|
|
70
|
+
for batch in chunk_list(rows, batch_size):
|
|
71
|
+
values = ", ".join(
|
|
72
|
+
f"(ag_catalog._graphid({label_id}, nextval('{self._seq_name(label)}'::regclass)), "
|
|
73
|
+
f"{self._to_agtype_literal(row)}::ag_catalog.agtype)"
|
|
74
|
+
for row in batch
|
|
75
|
+
)
|
|
76
|
+
cursor.execute(
|
|
77
|
+
SQL("INSERT INTO {schema}.{table} (id, properties) VALUES {vals}").format(
|
|
78
|
+
schema=Identifier(self._graph_name),
|
|
79
|
+
table=Identifier(label),
|
|
80
|
+
vals=SQL(values),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
total += len(batch)
|
|
84
|
+
|
|
85
|
+
logger.debug("Direct SQL: {} {} nodes inserted", total, label)
|
|
86
|
+
return total
|
|
87
|
+
|
|
88
|
+
def bulk_insert_edges(
|
|
89
|
+
self,
|
|
90
|
+
label: str,
|
|
91
|
+
edges: list[dict],
|
|
92
|
+
src_label: str,
|
|
93
|
+
dst_label: str,
|
|
94
|
+
src_ref_prop: str = "id",
|
|
95
|
+
dst_ref_prop: str = "id",
|
|
96
|
+
batch_size: int = 500,
|
|
97
|
+
) -> int:
|
|
98
|
+
"""Insert edges via direct SQL INSERT into the AGE edge label table.
|
|
99
|
+
|
|
100
|
+
Pre-resolves source and destination graphids via a single SQL query
|
|
101
|
+
per label, then INSERTs edges with known start_id/end_id -- no Cypher
|
|
102
|
+
MATCH at write time.
|
|
103
|
+
|
|
104
|
+
Edges whose src or dst reference cannot be resolved are silently
|
|
105
|
+
skipped (same behaviour as Cypher MATCH -- unmatched patterns produce
|
|
106
|
+
no rows).
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
label: Edge label name (e.g. "CALLS").
|
|
110
|
+
edges: List of dicts with at least "src" and "dst" keys.
|
|
111
|
+
src_label: Vertex label of source nodes.
|
|
112
|
+
dst_label: Vertex label of destination nodes.
|
|
113
|
+
src_ref_prop: Property name on source nodes to match "src" values.
|
|
114
|
+
dst_ref_prop: Property name on destination nodes to match "dst" values.
|
|
115
|
+
batch_size: Number of rows per executemany batch.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Total number of edges inserted (excludes skipped).
|
|
119
|
+
"""
|
|
120
|
+
if not edges:
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
label_id = self._ensure_label(label, kind="e")
|
|
124
|
+
|
|
125
|
+
# Build {ref_value: graphid} maps for source and destination nodes
|
|
126
|
+
src_map = self._build_graphid_index(src_label, src_ref_prop)
|
|
127
|
+
if src_label == dst_label and src_ref_prop == dst_ref_prop:
|
|
128
|
+
dst_map = src_map
|
|
129
|
+
else:
|
|
130
|
+
dst_map = self._build_graphid_index(dst_label, dst_ref_prop)
|
|
131
|
+
|
|
132
|
+
total = 0
|
|
133
|
+
skipped = 0
|
|
134
|
+
with self._conn.cursor() as cursor:
|
|
135
|
+
for batch in chunk_list(edges, batch_size):
|
|
136
|
+
value_parts = []
|
|
137
|
+
for edge in batch:
|
|
138
|
+
src_gid = src_map.get(edge["src"])
|
|
139
|
+
dst_gid = dst_map.get(edge["dst"])
|
|
140
|
+
if src_gid is None or dst_gid is None:
|
|
141
|
+
skipped += 1
|
|
142
|
+
continue
|
|
143
|
+
props = {k: v for k, v in edge.items() if k not in ("src", "dst")}
|
|
144
|
+
value_parts.append(
|
|
145
|
+
f"(ag_catalog._graphid({label_id}, nextval('{self._seq_name(label)}'::regclass)), "
|
|
146
|
+
f"'{src_gid}'::ag_catalog.graphid, '{dst_gid}'::ag_catalog.graphid, "
|
|
147
|
+
f"{self._to_agtype_literal(props)}::ag_catalog.agtype)"
|
|
148
|
+
)
|
|
149
|
+
if value_parts:
|
|
150
|
+
values = ", ".join(value_parts)
|
|
151
|
+
cursor.execute(
|
|
152
|
+
SQL("INSERT INTO {schema}.{table} (id, start_id, end_id, properties) VALUES {vals}").format(
|
|
153
|
+
schema=Identifier(self._graph_name),
|
|
154
|
+
table=Identifier(label),
|
|
155
|
+
vals=SQL(values),
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
total += len(value_parts)
|
|
159
|
+
|
|
160
|
+
if skipped:
|
|
161
|
+
logger.debug("Direct SQL: {} {} edges inserted, {} skipped (unresolved endpoints)", total, label, skipped)
|
|
162
|
+
else:
|
|
163
|
+
logger.debug("Direct SQL: {} {} edges inserted", total, label)
|
|
164
|
+
return total
|
|
165
|
+
|
|
166
|
+
# -- Internal helpers -----------------------------------------------------
|
|
167
|
+
|
|
168
|
+
def _ensure_label(self, label: str, kind: Literal["v", "e"] = "v") -> int:
|
|
169
|
+
"""Look up the numeric label_id, creating the label if needed.
|
|
170
|
+
|
|
171
|
+
When the label does not exist, creates it via a savepoint so that the
|
|
172
|
+
DDL commit is isolated. PostgreSQL DDL (CREATE TABLE, which AGE's
|
|
173
|
+
create_vlabel/create_elabel issue internally) cannot run inside an open
|
|
174
|
+
transaction; committing via savepoint releases the DDL without
|
|
175
|
+
discarding any data already written by the caller in the outer
|
|
176
|
+
transaction.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
label: Label name (e.g. "Method", "CALLS").
|
|
180
|
+
kind: ``"v"`` for vertex label, ``"e"`` for edge label.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
The integer label_id for the label.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ValueError: If the label cannot be created or looked up.
|
|
187
|
+
"""
|
|
188
|
+
lookup_sql = SQLBuilder.lookup_label_id_sql()
|
|
189
|
+
with self._conn.cursor() as cursor:
|
|
190
|
+
cursor.execute(lookup_sql, (self._graph_name, label))
|
|
191
|
+
row = cursor.fetchone()
|
|
192
|
+
if row:
|
|
193
|
+
return row[0]
|
|
194
|
+
|
|
195
|
+
# Label doesn't exist -- create it inside a savepoint so that the
|
|
196
|
+
# DDL commit does not affect the caller's surrounding transaction.
|
|
197
|
+
cursor.execute("SAVEPOINT _age_ensure_label")
|
|
198
|
+
try:
|
|
199
|
+
if kind == "v":
|
|
200
|
+
cursor.execute(SQLBuilder.create_vlabel(self._graph_name, label))
|
|
201
|
+
else:
|
|
202
|
+
cursor.execute(SQLBuilder.create_elabel(self._graph_name, label))
|
|
203
|
+
cursor.execute("RELEASE SAVEPOINT _age_ensure_label")
|
|
204
|
+
self._conn.commit()
|
|
205
|
+
except Exception:
|
|
206
|
+
cursor.execute("ROLLBACK TO SAVEPOINT _age_ensure_label")
|
|
207
|
+
raise
|
|
208
|
+
|
|
209
|
+
cursor.execute(lookup_sql, (self._graph_name, label))
|
|
210
|
+
row = cursor.fetchone()
|
|
211
|
+
|
|
212
|
+
if not row:
|
|
213
|
+
raise ValueError(f"Failed to create label '{label}' in graph '{self._graph_name}'")
|
|
214
|
+
return row[0]
|
|
215
|
+
|
|
216
|
+
def _seq_name(self, label: str) -> str:
|
|
217
|
+
"""Return the sequence name for a label's entry IDs."""
|
|
218
|
+
return f'{self._graph_name}."{label}_id_seq"'
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _to_agtype_literal(props: dict) -> str:
|
|
222
|
+
"""Serialize a dict to a PostgreSQL string literal for ``::agtype`` cast.
|
|
223
|
+
|
|
224
|
+
The agtype text-input parser interprets ``\\"`` as an escaped double
|
|
225
|
+
quote inside strings (same as JSON). When the literal is embedded in a
|
|
226
|
+
standard PostgreSQL ``'...'`` string, backslashes are passed through
|
|
227
|
+
verbatim — so the ``\\"`` from ``escape_value`` reaches the agtype
|
|
228
|
+
parser intact. This produces identical stored values to the Cypher
|
|
229
|
+
UNWIND path.
|
|
230
|
+
|
|
231
|
+
Single quotes inside values are escaped as ``''`` (PostgreSQL standard).
|
|
232
|
+
|
|
233
|
+
Note: standard ``'...'`` strings (not ``E'...'``) are used intentionally.
|
|
234
|
+
``E'...'`` would consume the ``\\`` escape layer before agtype sees it,
|
|
235
|
+
breaking ``\\"`` → ``"`` prematurely.
|
|
236
|
+
"""
|
|
237
|
+
parts = ", ".join(f"{escape_value(k)}: {escape_value(v)}" for k, v in props.items())
|
|
238
|
+
inner = "{" + parts + "}"
|
|
239
|
+
# Escape single quotes for PostgreSQL standard string literal
|
|
240
|
+
inner = inner.replace("'", "''")
|
|
241
|
+
return "'" + inner + "'"
|
|
242
|
+
|
|
243
|
+
def _build_graphid_index(self, label: str, ref_prop: str) -> dict[str, int]:
|
|
244
|
+
"""Load a {ref_prop_value: graphid} mapping for all nodes of a label.
|
|
245
|
+
|
|
246
|
+
Values returned by agtype_access_operator are agtype-quoted strings
|
|
247
|
+
(e.g. '"mth:foo.bar"'), so we strip the surrounding quotes.
|
|
248
|
+
"""
|
|
249
|
+
sql_stmt = SQLBuilder.lookup_node_graphids_sql(self._graph_name, label, ref_prop)
|
|
250
|
+
with self._conn.cursor() as cursor:
|
|
251
|
+
cursor.execute(sql_stmt)
|
|
252
|
+
rows = cursor.fetchall()
|
|
253
|
+
return {str(r[0]).strip('"'): r[1] for r in rows}
|
|
@@ -29,6 +29,7 @@ from cypher_graphdb.models import GraphObject, GraphObjectType, TabularResult
|
|
|
29
29
|
from cypher_graphdb.statistics import IndexInfo, IndexType, LabelStatistics
|
|
30
30
|
from cypher_graphdb.utils import chunk_list, sanitize_connection_params_for_logging, sanitize_connection_string_for_logging
|
|
31
31
|
|
|
32
|
+
from .agebulkwriter import AGEBulkWriter
|
|
32
33
|
from .agerowfactories import age_row_factory
|
|
33
34
|
from .agesearch import convert_to_fts_query
|
|
34
35
|
from .ageserializer import to_cypher_list
|
|
@@ -83,6 +84,17 @@ class AGEGraphDB(CypherBackend):
|
|
|
83
84
|
self._prepared_statements = {} # query_hash -> stmt_name
|
|
84
85
|
self._max_cached_statements = 10
|
|
85
86
|
|
|
87
|
+
# When True, bulk_create_nodes and bulk_create_edges use direct SQL
|
|
88
|
+
# INSERT into AGE label tables instead of Cypher UNWIND. This bypasses
|
|
89
|
+
# the Cypher parser overhead (~3ms per statement) and is 10-30x faster
|
|
90
|
+
# for large bulk loads. The Cypher path stays as the fallback.
|
|
91
|
+
# Set to False to revert to the original Cypher UNWIND path.
|
|
92
|
+
self.direct_bulk_insert: bool = True
|
|
93
|
+
|
|
94
|
+
# Lazy AGEBulkWriter instance -- created on first bulk operation and
|
|
95
|
+
# reused for the lifetime of the connection. Cleared on disconnect/reconnect.
|
|
96
|
+
self._bulk_writer: AGEBulkWriter | None = None
|
|
97
|
+
|
|
86
98
|
if args or kwargs:
|
|
87
99
|
self.connect(*args, **kwargs)
|
|
88
100
|
|
|
@@ -412,12 +424,14 @@ class AGEGraphDB(CypherBackend):
|
|
|
412
424
|
self._connection = None
|
|
413
425
|
self._cinfo = None
|
|
414
426
|
self._ckwargs = None
|
|
427
|
+
self._bulk_writer = None
|
|
415
428
|
|
|
416
429
|
def reconnect(self):
|
|
417
430
|
"""Reconnect to the database using stored connection info."""
|
|
418
431
|
assert self._cinfo is not None, "Can only reconnect if already successfully connected!"
|
|
419
432
|
|
|
420
433
|
logger.debug("Try to reconnect")
|
|
434
|
+
self._bulk_writer = None
|
|
421
435
|
self.connect_to_db()
|
|
422
436
|
|
|
423
437
|
def commit(self):
|
|
@@ -556,17 +570,22 @@ class AGEGraphDB(CypherBackend):
|
|
|
556
570
|
# ── Index management ────────────────────────────────────────────────
|
|
557
571
|
|
|
558
572
|
def create_property_index(self, label: str, *property_names: str) -> None:
|
|
559
|
-
"""Create
|
|
573
|
+
"""Create indexes on a label table for fast property lookups.
|
|
560
574
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
575
|
+
Creates two kinds of indexes:
|
|
576
|
+
|
|
577
|
+
1. **GIN index** on the whole ``properties`` column -- covers the
|
|
578
|
+
``@>`` containment operator used by some internal AGE operations.
|
|
579
|
+
2. **Btree expression indexes** on each specified property -- covers
|
|
580
|
+
the ``agtype_access_operator`` expression that AGE generates for
|
|
581
|
+
Cypher ``WHERE n.prop = ...`` clauses. Without these, every property
|
|
582
|
+
lookup is a sequential scan regardless of the GIN index.
|
|
566
583
|
|
|
567
584
|
Args:
|
|
568
585
|
label: Node label to index (e.g. "Method").
|
|
569
|
-
*property_names:
|
|
586
|
+
*property_names: Property names to create expression indexes for.
|
|
587
|
+
Each gets its own btree index. If empty, only the GIN index
|
|
588
|
+
is created.
|
|
570
589
|
"""
|
|
571
590
|
self._require_connection()
|
|
572
591
|
graph_name = self._graph_name
|
|
@@ -579,17 +598,24 @@ class AGEGraphDB(CypherBackend):
|
|
|
579
598
|
return
|
|
580
599
|
|
|
581
600
|
with self._fetch_cursor(row_factory=None) as cursor:
|
|
601
|
+
# GIN on whole properties column (covers @> containment operator)
|
|
582
602
|
cursor.execute(SQLBuilder.create_gin_index(graph_name, label))
|
|
603
|
+
# Btree expression index per property (covers agtype_access_operator in WHERE)
|
|
604
|
+
for prop in property_names:
|
|
605
|
+
cursor.execute(SQLBuilder.create_expression_index(graph_name, label, prop))
|
|
583
606
|
self._connection.commit()
|
|
584
607
|
|
|
585
|
-
logger.debug("
|
|
608
|
+
logger.debug("Property indexes created for label '{}': GIN + {} expression indexes", label, len(property_names))
|
|
586
609
|
|
|
587
610
|
def drop_index(self, label: str, *property_names: str) -> None:
|
|
588
|
-
"""Drop the GIN
|
|
611
|
+
"""Drop the GIN and expression indexes on a label table.
|
|
612
|
+
|
|
613
|
+
Drops the GIN index on the whole ``properties`` column, plus any
|
|
614
|
+
btree expression indexes for the specified properties.
|
|
589
615
|
|
|
590
616
|
Args:
|
|
591
|
-
label: Node label whose
|
|
592
|
-
*property_names:
|
|
617
|
+
label: Node label whose indexes to drop.
|
|
618
|
+
*property_names: Property names whose expression indexes to drop.
|
|
593
619
|
"""
|
|
594
620
|
self._require_connection()
|
|
595
621
|
graph_name = self._graph_name
|
|
@@ -597,9 +623,11 @@ class AGEGraphDB(CypherBackend):
|
|
|
597
623
|
|
|
598
624
|
with self._fetch_cursor(row_factory=None) as cursor:
|
|
599
625
|
cursor.execute(SQLBuilder.drop_gin_index(graph_name, label))
|
|
626
|
+
for prop in property_names:
|
|
627
|
+
cursor.execute(SQLBuilder.drop_expression_index(graph_name, label, prop))
|
|
600
628
|
self._connection.commit()
|
|
601
629
|
|
|
602
|
-
logger.debug("
|
|
630
|
+
logger.debug("Property indexes dropped for label '{}': GIN + {} expression indexes", label, len(property_names))
|
|
603
631
|
|
|
604
632
|
def list_indexes(self, include_internal: bool = False) -> list[IndexInfo]:
|
|
605
633
|
"""List all indexes on the current graph.
|
|
@@ -649,15 +677,16 @@ class AGEGraphDB(CypherBackend):
|
|
|
649
677
|
# ── Bulk write operations ─────────────────────────────────────────────
|
|
650
678
|
|
|
651
679
|
def bulk_create_nodes(self, label: str, rows: list[dict], batch_size: int = 200) -> int:
|
|
652
|
-
"""Create nodes in batches
|
|
680
|
+
"""Create nodes in batches.
|
|
653
681
|
|
|
654
|
-
|
|
655
|
-
|
|
682
|
+
When ``direct_bulk_insert`` is True (default), uses direct SQL INSERT
|
|
683
|
+
via :class:`AGEBulkWriter` -- bypasses the Cypher parser for 10-30x
|
|
684
|
+
faster bulk loads. Falls back to Cypher UNWIND when disabled.
|
|
656
685
|
|
|
657
686
|
Args:
|
|
658
687
|
label: Node label for all created nodes.
|
|
659
688
|
rows: List of property dicts, one per node.
|
|
660
|
-
batch_size: Number of nodes per
|
|
689
|
+
batch_size: Number of nodes per batch.
|
|
661
690
|
|
|
662
691
|
Returns:
|
|
663
692
|
Total number of nodes created.
|
|
@@ -666,11 +695,13 @@ class AGEGraphDB(CypherBackend):
|
|
|
666
695
|
if not rows:
|
|
667
696
|
return 0
|
|
668
697
|
|
|
698
|
+
if self.direct_bulk_insert:
|
|
699
|
+
return self._get_bulk_writer().bulk_insert_nodes(label, rows, batch_size)
|
|
700
|
+
|
|
669
701
|
total = 0
|
|
670
702
|
for batch in chunk_list(rows, batch_size):
|
|
671
703
|
self._write_node_batch(label, batch)
|
|
672
704
|
total += len(batch)
|
|
673
|
-
|
|
674
705
|
return total
|
|
675
706
|
|
|
676
707
|
def bulk_create_edges(
|
|
@@ -685,8 +716,9 @@ class AGEGraphDB(CypherBackend):
|
|
|
685
716
|
) -> int:
|
|
686
717
|
"""Create edges in batches by matching src/dst nodes on a reference property.
|
|
687
718
|
|
|
688
|
-
|
|
689
|
-
|
|
719
|
+
When ``direct_bulk_insert`` is True and both ``src_label`` and ``dst_label``
|
|
720
|
+
are specified, uses direct SQL INSERT via :class:`AGEBulkWriter` with
|
|
721
|
+
pre-resolved graphids. Falls back to Cypher UNWIND MATCH CREATE otherwise.
|
|
690
722
|
|
|
691
723
|
Args:
|
|
692
724
|
label: Edge label for all created edges.
|
|
@@ -695,7 +727,7 @@ class AGEGraphDB(CypherBackend):
|
|
|
695
727
|
dst_label: Label of destination nodes (empty string for any label).
|
|
696
728
|
src_ref_prop: Property name on source nodes to match against "src".
|
|
697
729
|
dst_ref_prop: Property name on destination nodes to match against "dst".
|
|
698
|
-
batch_size: Number of edges per
|
|
730
|
+
batch_size: Number of edges per batch.
|
|
699
731
|
|
|
700
732
|
Returns:
|
|
701
733
|
Total number of edges created.
|
|
@@ -704,7 +736,12 @@ class AGEGraphDB(CypherBackend):
|
|
|
704
736
|
if not edges:
|
|
705
737
|
return 0
|
|
706
738
|
|
|
707
|
-
|
|
739
|
+
if self.direct_bulk_insert and src_label and dst_label:
|
|
740
|
+
return self._get_bulk_writer().bulk_insert_edges(
|
|
741
|
+
label, edges, src_label, dst_label, src_ref_prop, dst_ref_prop, batch_size
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
# Cypher UNWIND fallback (when labels not specified or direct insert disabled)
|
|
708
745
|
src_pat = f"(a:{src_label} {{{src_ref_prop}: e.src}})" if src_label else f"(a {{{src_ref_prop}: e.src}})"
|
|
709
746
|
dst_pat = f"(b:{dst_label} {{{dst_ref_prop}: e.dst}})" if dst_label else f"(b {{{dst_ref_prop}: e.dst}})"
|
|
710
747
|
|
|
@@ -712,17 +749,22 @@ class AGEGraphDB(CypherBackend):
|
|
|
712
749
|
for batch in chunk_list(edges, batch_size):
|
|
713
750
|
self._write_edge_batch(label, batch, src_pat, dst_pat)
|
|
714
751
|
total += len(batch)
|
|
715
|
-
|
|
716
752
|
return total
|
|
717
753
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
def _write_node_batch(self, label: str, batch: list[dict]) -> None:
|
|
721
|
-
"""Write one batch of node dicts via UNWIND CREATE.
|
|
754
|
+
def _get_bulk_writer(self) -> AGEBulkWriter:
|
|
755
|
+
"""Return the shared AGEBulkWriter, creating it lazily on first call.
|
|
722
756
|
|
|
723
|
-
|
|
724
|
-
|
|
757
|
+
The writer is tied to the current connection and graph. It is cleared
|
|
758
|
+
automatically on disconnect() and reconnect() so it is never stale.
|
|
725
759
|
"""
|
|
760
|
+
if self._bulk_writer is None:
|
|
761
|
+
self._bulk_writer = AGEBulkWriter(self._connection, self._graph_name)
|
|
762
|
+
return self._bulk_writer
|
|
763
|
+
|
|
764
|
+
# ── Cypher UNWIND helpers (fallback path) ─────────────────────────────
|
|
765
|
+
|
|
766
|
+
def _write_node_batch(self, label: str, batch: list[dict]) -> None:
|
|
767
|
+
"""Write one batch of node dicts via Cypher UNWIND CREATE."""
|
|
726
768
|
props_keys = list(batch[0].keys())
|
|
727
769
|
set_clause = ", ".join(f"n.{k} = props.{k}" for k in props_keys)
|
|
728
770
|
cypher = f"UNWIND {to_cypher_list(batch)} AS props CREATE (n:{label}) SET {set_clause}"
|
|
@@ -731,11 +773,7 @@ class AGEGraphDB(CypherBackend):
|
|
|
731
773
|
self.execute_cypher(parsed)
|
|
732
774
|
|
|
733
775
|
def _write_edge_batch(self, label: str, batch: list[dict], src_pat: str, dst_pat: str) -> None:
|
|
734
|
-
"""Write one batch of edges via UNWIND MATCH CREATE.
|
|
735
|
-
|
|
736
|
-
Any keys in batch dicts beyond 'src' and 'dst' are set as edge properties.
|
|
737
|
-
"""
|
|
738
|
-
# Detect extra property keys (beyond src/dst) from first row
|
|
776
|
+
"""Write one batch of edges via Cypher UNWIND MATCH CREATE."""
|
|
739
777
|
edge_prop_keys = [k for k in batch[0] if k not in ("src", "dst")]
|
|
740
778
|
if edge_prop_keys:
|
|
741
779
|
set_clause = " SET " + ", ".join(f"r.{k} = e.{k}" for k in edge_prop_keys)
|
{cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/age/agerowfactories.py
RENAMED
|
@@ -8,7 +8,6 @@ providers.
|
|
|
8
8
|
from collections.abc import Sequence
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
import age
|
|
12
11
|
from psycopg.cursor import BaseCursor
|
|
13
12
|
from psycopg.rows import BaseRowFactory, RowMaker, T
|
|
14
13
|
|
|
@@ -16,15 +15,17 @@ from cypher_graphdb.backend import ExecStatistics
|
|
|
16
15
|
from cypher_graphdb.modelprovider import ModelProvider
|
|
17
16
|
from cypher_graphdb.models import GraphEdge, GraphNode, GraphPath
|
|
18
17
|
|
|
18
|
+
from . import agtype_parser
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
def _create_graph_node(value: agtype_parser.Vertex, stats: ExecStatistics, provider: ModelProvider) -> GraphNode:
|
|
21
22
|
"""Create a GraphNode from an AGE Vertex."""
|
|
22
23
|
node = provider.create_node(value.label, value.properties, id_=value.id)
|
|
23
24
|
stats.track_node(node)
|
|
24
25
|
return node
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
def _create_graph_edge(value:
|
|
28
|
+
def _create_graph_edge(value: agtype_parser.Edge, stats: ExecStatistics, provider: ModelProvider) -> GraphEdge:
|
|
28
29
|
"""Create a GraphEdge from an AGE Edge."""
|
|
29
30
|
edge = provider.create_edge(value.label, value.start_id, value.end_id, value.properties, id_=value.id)
|
|
30
31
|
stats.track_edge(edge)
|
|
@@ -36,11 +37,11 @@ def _create_graph_path(value, stats: ExecStatistics, provider: ModelProvider) ->
|
|
|
36
37
|
path = GraphPath()
|
|
37
38
|
|
|
38
39
|
for entity in value.entities:
|
|
39
|
-
if isinstance(entity,
|
|
40
|
+
if isinstance(entity, agtype_parser.Vertex):
|
|
40
41
|
# Don't update stats here, will be updated when path stats are updated
|
|
41
42
|
node = provider.create_node(entity.label, entity.properties, id_=entity.id)
|
|
42
43
|
path.append(node)
|
|
43
|
-
elif isinstance(entity,
|
|
44
|
+
elif isinstance(entity, agtype_parser.Edge):
|
|
44
45
|
# Don't update stats here, will be updated when path stats are updated
|
|
45
46
|
edge = provider.create_edge(entity.label, entity.start_id, entity.end_id, entity.properties, id_=entity.id)
|
|
46
47
|
path.append(edge)
|
|
@@ -53,11 +54,11 @@ def _create_graph_path(value, stats: ExecStatistics, provider: ModelProvider) ->
|
|
|
53
54
|
|
|
54
55
|
def _map_age_value(value, stats: ExecStatistics, provider: ModelProvider):
|
|
55
56
|
"""Map an AGE value to the appropriate graph object type."""
|
|
56
|
-
if isinstance(value,
|
|
57
|
+
if isinstance(value, agtype_parser.Vertex):
|
|
57
58
|
return _create_graph_node(value, stats, provider)
|
|
58
|
-
elif isinstance(value,
|
|
59
|
+
elif isinstance(value, agtype_parser.Edge):
|
|
59
60
|
return _create_graph_edge(value, stats, provider)
|
|
60
|
-
elif isinstance(value,
|
|
61
|
+
elif isinstance(value, agtype_parser.Path):
|
|
61
62
|
return _create_graph_path(value, stats, provider)
|
|
62
63
|
else:
|
|
63
64
|
# Only count non-null values (matching Memgraph behavior)
|
{cypher_graphdb-0.2.2 → cypher_graphdb-0.2.3}/src/cypher_graphdb/backends/age/agesqlbuilder.py
RENAMED
|
@@ -160,6 +160,31 @@ class SQLBuilder:
|
|
|
160
160
|
table=sql.Identifier(label),
|
|
161
161
|
)
|
|
162
162
|
|
|
163
|
+
@classmethod
|
|
164
|
+
def create_expression_index(cls, graph_name: str, label: str, property_name: str) -> sql.SQL:
|
|
165
|
+
"""Create a btree expression index on a single property of a label table.
|
|
166
|
+
|
|
167
|
+
AGE Cypher uses ``agtype_access_operator`` to extract property values.
|
|
168
|
+
GIN indexes on the whole ``properties`` column do NOT accelerate these
|
|
169
|
+
lookups. A btree expression index on the same access operator expression
|
|
170
|
+
turns sequential scans into index scans (280x measured speedup).
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
graph_name: Graph name (used as schema).
|
|
174
|
+
label: Node label (table name within the schema).
|
|
175
|
+
property_name: Property name to index (e.g. "symbol", "qualified_name").
|
|
176
|
+
"""
|
|
177
|
+
idx_name = f"{graph_name}_{label}_{property_name}_expr"
|
|
178
|
+
# The expression must exactly match what AGE generates in its WHERE clause:
|
|
179
|
+
# agtype_access_operator(VARIADIC ARRAY[properties, '"prop_name"'::agtype])
|
|
180
|
+
expr = f"""ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '"{property_name}"'::agtype])"""
|
|
181
|
+
return sql.SQL("CREATE INDEX IF NOT EXISTS {idx} ON {schema}.{table} USING btree ({expr})").format(
|
|
182
|
+
idx=sql.Identifier(idx_name),
|
|
183
|
+
schema=sql.Identifier(graph_name),
|
|
184
|
+
table=sql.Identifier(label),
|
|
185
|
+
expr=sql.SQL(expr),
|
|
186
|
+
)
|
|
187
|
+
|
|
163
188
|
@classmethod
|
|
164
189
|
def drop_gin_index(cls, graph_name: str, label: str) -> sql.SQL:
|
|
165
190
|
"""Drop a GIN index on the properties column of a label table."""
|
|
@@ -169,6 +194,45 @@ class SQLBuilder:
|
|
|
169
194
|
idx=sql.Identifier(idx_name),
|
|
170
195
|
)
|
|
171
196
|
|
|
197
|
+
@classmethod
|
|
198
|
+
def drop_expression_index(cls, graph_name: str, label: str, property_name: str) -> sql.SQL:
|
|
199
|
+
"""Drop a btree expression index on a single property of a label table."""
|
|
200
|
+
idx_name = f"{graph_name}_{label}_{property_name}_expr"
|
|
201
|
+
return sql.SQL("DROP INDEX IF EXISTS {schema}.{idx}").format(
|
|
202
|
+
schema=sql.Identifier(graph_name),
|
|
203
|
+
idx=sql.Identifier(idx_name),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# ── Direct SQL bulk insert (bypasses Cypher parser) ─────────────────
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def lookup_label_id_sql(cls) -> sql.SQL:
|
|
210
|
+
"""SQL to look up the numeric label_id for a label name within a graph.
|
|
211
|
+
|
|
212
|
+
Returns a parameterised query with two bind positions: (graph_name, label_name).
|
|
213
|
+
"""
|
|
214
|
+
return sql.SQL(
|
|
215
|
+
"SELECT l.id FROM ag_catalog.ag_label l "
|
|
216
|
+
"JOIN ag_catalog.ag_graph g ON g.graphid = l.graph "
|
|
217
|
+
"WHERE g.name = %s AND l.name = %s"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def lookup_node_graphids_sql(cls, graph_name: str, label: str, ref_prop: str) -> sql.SQL:
|
|
222
|
+
"""SQL to look up graphids of nodes by a property value.
|
|
223
|
+
|
|
224
|
+
Returns SQL that takes a list of ref values and returns (ref_value, graphid) pairs.
|
|
225
|
+
Uses the agtype_access_operator for property extraction (covered by expression indexes).
|
|
226
|
+
"""
|
|
227
|
+
return sql.SQL(
|
|
228
|
+
"SELECT ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, {prop}::ag_catalog.agtype])::text, id "
|
|
229
|
+
"FROM {schema}.{table}"
|
|
230
|
+
).format(
|
|
231
|
+
schema=sql.Identifier(graph_name),
|
|
232
|
+
table=sql.Identifier(label),
|
|
233
|
+
prop=sql.Literal(f'"{ref_prop}"'),
|
|
234
|
+
)
|
|
235
|
+
|
|
172
236
|
@classmethod
|
|
173
237
|
def list_indexes(cls, graph_name: str) -> sql.SQL:
|
|
174
238
|
"""List all user-created indexes in the graph schema via pg_indexes."""
|