cypher-graphdb 0.7.2__tar.gz → 0.7.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.
Files changed (331) hide show
  1. {cypher_graphdb-0.7.2/src/cypher_graphdb.egg-info → cypher_graphdb-0.7.3}/PKG-INFO +1 -1
  2. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backend.py +10 -5
  3. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/agebulkwriter.py +48 -32
  4. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/agegraphdb.py +11 -8
  5. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/ageserializer.py +8 -4
  6. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/agesqlbuilder.py +62 -36
  7. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/memgraph/memgraphdb.py +19 -7
  8. cypher_graphdb-0.7.3/src/cypher_graphdb/bulk_refs.py +85 -0
  9. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/indexing.py +26 -5
  10. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3/src/cypher_graphdb.egg-info}/PKG-INFO +1 -1
  11. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb.egg-info/SOURCES.txt +2 -0
  12. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb.egg-info/scm_file_list.json +2 -0
  13. cypher_graphdb-0.7.3/src/cypher_graphdb.egg-info/scm_version.json +8 -0
  14. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_indexes_and_bulk.py +323 -0
  15. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_age_serializer.py +10 -3
  16. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_age_sqlbuilder.py +50 -0
  17. cypher_graphdb-0.7.3/tests/unit/test_bulk_refs.py +85 -0
  18. cypher_graphdb-0.7.2/src/cypher_graphdb.egg-info/scm_version.json +0 -8
  19. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.env.age.example +0 -0
  20. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.env.example +0 -0
  21. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.env.memgraph.example +0 -0
  22. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.github/workflows/audit.yml +0 -0
  23. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.github/workflows/ci.yml +0 -0
  24. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.github/workflows/publish.yml +0 -0
  25. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.github/workflows/release.yml +0 -0
  26. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.gitignore +0 -0
  27. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/.pre-commit-config.yaml +0 -0
  28. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/AGENTS.md +0 -0
  29. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/CHANGELOG.md +0 -0
  30. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/CONTRIBUTING.md +0 -0
  31. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/ENGINEERING-STANDARDS.md +0 -0
  32. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/LICENSE.md +0 -0
  33. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/NOTICE +0 -0
  34. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/README.md +0 -0
  35. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/Taskfile.yml +0 -0
  36. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/README.md +0 -0
  37. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/adr/0001-explicit-model-providers.md +0 -0
  38. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/adr/index.md +0 -0
  39. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/cli-prompt-parsing.md +0 -0
  40. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/duckdb-migration.md +0 -0
  41. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/falkordb-integration.md +0 -0
  42. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/fluent-cypher-query-builder.md +0 -0
  43. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/format-registry.md +0 -0
  44. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/hierarchical-export-format.md +0 -0
  45. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/index.md +0 -0
  46. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/model-provider-isolation.md +0 -0
  47. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/model-provider-label-collision.md +0 -0
  48. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/multiple-statement-execution.md +0 -0
  49. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/packaging-and-extras.md +0 -0
  50. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/parameterized-queries-for-typed-models.md +0 -0
  51. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/query-result-immutable-design.md +0 -0
  52. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/read-only-mode.md +0 -0
  53. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/design/stateless-multi-graph-support.md +0 -0
  54. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/usage/bulk-and-indexes.md +0 -0
  55. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/usage/cli.md +0 -0
  56. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/usage/connecting.md +0 -0
  57. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/usage/getting-started.md +0 -0
  58. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/usage/import-export.md +0 -0
  59. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/usage/index.md +0 -0
  60. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/usage/queries.md +0 -0
  61. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/docs/usage/typed-models.md +0 -0
  62. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/pyproject.toml +0 -0
  63. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/scripts/release.config.sh +0 -0
  64. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/scripts/release.sh +0 -0
  65. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/scripts/verify.sh +0 -0
  66. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/setup.cfg +0 -0
  67. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/__init__.py +0 -0
  68. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/__main__.py +0 -0
  69. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/_extras.py +0 -0
  70. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/args.py +0 -0
  71. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backendprovider.py +0 -0
  72. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/__init__.py +0 -0
  73. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/__init__.py +0 -0
  74. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/agerowfactories.py +0 -0
  75. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/agesearch.py +0 -0
  76. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/agtype.py +0 -0
  77. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/age/agtype_parser.py +0 -0
  78. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/memgraph/__init__.py +0 -0
  79. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/backends/memgraph/memgraphrowfactories.py +0 -0
  80. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cardinality.py +0 -0
  81. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/__init__.py +0 -0
  82. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/app.py +0 -0
  83. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/banner.py +0 -0
  84. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/command_manager.py +0 -0
  85. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/command_map.py +0 -0
  86. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/command_registry.py +0 -0
  87. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commandposition.py +0 -0
  88. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/__init__.py +0 -0
  89. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/add_graph_command.py +0 -0
  90. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/apply_config_command.py +0 -0
  91. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/base_command.py +0 -0
  92. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/clear_graph_command.py +0 -0
  93. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/commit_command.py +0 -0
  94. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/connect_command.py +0 -0
  95. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/create_edge_command.py +0 -0
  96. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/create_graph_command.py +0 -0
  97. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/create_linked_node_command.py +0 -0
  98. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/create_node_command.py +0 -0
  99. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/delete_graphobj_command.py +0 -0
  100. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/disconnect_command.py +0 -0
  101. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/drop_graph_command.py +0 -0
  102. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/dump_backends_command.py +0 -0
  103. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/dump_graphs_command.py +0 -0
  104. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/dump_indexes_command.py +0 -0
  105. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/dump_labels_command.py +0 -0
  106. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/dump_models_command.py +0 -0
  107. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/dump_parsed_query_command.py +0 -0
  108. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/dump_schema_command.py +0 -0
  109. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/dump_statistics_command.py +0 -0
  110. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/execute_cypher_command.py +0 -0
  111. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/execute_file_command.py +0 -0
  112. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/exit_command.py +0 -0
  113. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/export_graph_command.py +0 -0
  114. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/fetch_all_command.py +0 -0
  115. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/fetch_edges_command.py +0 -0
  116. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/fetch_nodes_command.py +0 -0
  117. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/format_output_command.py +0 -0
  118. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/get_command.py +0 -0
  119. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/gid_command.py +0 -0
  120. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/graph_exists_command.py +0 -0
  121. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/graph_op_command.py +0 -0
  122. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/graph_to_tree_command.py +0 -0
  123. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/help_command.py +0 -0
  124. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/import_graph_command.py +0 -0
  125. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/last_result_op_command.py +0 -0
  126. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/load_models_command.py +0 -0
  127. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/resolve_edges_command.py +0 -0
  128. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/rollback_command.py +0 -0
  129. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/search_command.py +0 -0
  130. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/set_command.py +0 -0
  131. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/sql_command.py +0 -0
  132. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/update_graphobj_command.py +0 -0
  133. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/commands/use_graph_command.py +0 -0
  134. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/completer.py +0 -0
  135. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/config.py +0 -0
  136. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/exporter.py +0 -0
  137. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/file_executor.py +0 -0
  138. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/graphdata.py +0 -0
  139. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/graphdb.py +0 -0
  140. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/help/_overview.md +0 -0
  141. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/help/_template.md +0 -0
  142. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/help/add_graph.md +0 -0
  143. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/help/exit.md +0 -0
  144. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/help/export_graph.md +0 -0
  145. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/help/import_graph.md +0 -0
  146. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/help/last_result.md +0 -0
  147. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/help.py +0 -0
  148. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/importer.py +0 -0
  149. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/prompt.py +0 -0
  150. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/promptgrammar.py +0 -0
  151. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/promptparser.py +0 -0
  152. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/provider.py +0 -0
  153. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/renderer.py +0 -0
  154. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/runtime.py +0 -0
  155. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/schema_cmd.py +0 -0
  156. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cli/settings.py +0 -0
  157. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/command_reader.py +0 -0
  158. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/config.py +0 -0
  159. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/connection_guard.py +0 -0
  160. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/.antlr/Cypher.interp +0 -0
  161. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/.antlr/Cypher.tokens +0 -0
  162. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/.antlr/CypherBaseListener.java +0 -0
  163. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/.antlr/CypherLexer.interp +0 -0
  164. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/.antlr/CypherLexer.java +0 -0
  165. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/.antlr/CypherLexer.tokens +0 -0
  166. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/.antlr/CypherListener.java +0 -0
  167. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/.antlr/CypherParser.java +0 -0
  168. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/Cypher.g4 +0 -0
  169. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/Cypher.interp +0 -0
  170. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/Cypher.tokens +0 -0
  171. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/CypherLexer.interp +0 -0
  172. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/CypherLexer.py +0 -0
  173. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/CypherLexer.tokens +0 -0
  174. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/CypherListener.py +0 -0
  175. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/CypherParser.py +0 -0
  176. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypher/__init__.py +0 -0
  177. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypherbuilder.py +0 -0
  178. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/__init__.py +0 -0
  179. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/_host.py +0 -0
  180. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/batch.py +0 -0
  181. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/bulk_normalize.py +0 -0
  182. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/connection.py +0 -0
  183. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/criteria.py +0 -0
  184. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/cyphergraphdb.py +0 -0
  185. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/expand.py +0 -0
  186. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/pagination.py +0 -0
  187. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/pagination_mixin.py +0 -0
  188. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/result.py +0 -0
  189. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/schema.py +0 -0
  190. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/search.py +0 -0
  191. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/sql.py +0 -0
  192. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cyphergraphdb/stream_mixin.py +0 -0
  193. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypherjson.py +0 -0
  194. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypherparser.py +0 -0
  195. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypherquery/__init__.py +0 -0
  196. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypherquery/builder.py +0 -0
  197. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypherquery/params.py +0 -0
  198. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/cypherquery/patterns.py +0 -0
  199. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/dbpool.py +0 -0
  200. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/display.py +0 -0
  201. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/exceptions.py +0 -0
  202. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/formatprovider.py +0 -0
  203. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/graphops.py +0 -0
  204. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/main.py +0 -0
  205. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/modelinfo.py +0 -0
  206. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/modelprovider.py +0 -0
  207. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/modelrelations.py +0 -0
  208. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/models.py +0 -0
  209. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/options.py +0 -0
  210. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/py.typed +0 -0
  211. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/schema/__init__.py +0 -0
  212. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/schema/converter.py +0 -0
  213. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/schema/core.py +0 -0
  214. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/schema/generator.py +0 -0
  215. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/settings.py +0 -0
  216. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/statistics.py +0 -0
  217. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/__init__.py +0 -0
  218. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/base_exporter.py +0 -0
  219. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/base_importer.py +0 -0
  220. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/csv_exporter.py +0 -0
  221. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/csv_importer.py +0 -0
  222. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/csv_source.py +0 -0
  223. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/data_flattener.py +0 -0
  224. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/excel_exporter.py +0 -0
  225. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/excel_importer.py +0 -0
  226. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/excel_row_source.py +0 -0
  227. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/file_exporter.py +0 -0
  228. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/file_importer.py +0 -0
  229. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/formats/__init__.py +0 -0
  230. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/formats/csv.py +0 -0
  231. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/formats/excel.py +0 -0
  232. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/formats/json.py +0 -0
  233. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/formats/yaml.py +0 -0
  234. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/hierarchical_exporter.py +0 -0
  235. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/hierarchical_importer.py +0 -0
  236. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/hierarchical_row_source.py +0 -0
  237. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/json_importer.py +0 -0
  238. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/json_yaml_data_source.py +0 -0
  239. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/row_collector.py +0 -0
  240. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/row_set.py +0 -0
  241. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/row_source.py +0 -0
  242. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/tabular_importer.py +0 -0
  243. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/tools/yaml_importer.py +0 -0
  244. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/__init__.py +0 -0
  245. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/collection_utils.py +0 -0
  246. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/column_utils.py +0 -0
  247. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/connection_utils.py +0 -0
  248. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/conversion_utils.py +0 -0
  249. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/core_utils.py +0 -0
  250. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/schema_merge.py +0 -0
  251. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/schema_to_llm.py +0 -0
  252. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/schema_utils.py +0 -0
  253. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/settings_repr.py +0 -0
  254. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb/utils/string_utils.py +0 -0
  255. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb.egg-info/dependency_links.txt +0 -0
  256. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb.egg-info/entry_points.txt +0 -0
  257. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb.egg-info/requires.txt +0 -0
  258. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/src/cypher_graphdb.egg-info/top_level.txt +0 -0
  259. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/README.md +0 -0
  260. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/conftest.py +0 -0
  261. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/__init__.py +0 -0
  262. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/conftest.py +0 -0
  263. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_basic_operations.py +0 -0
  264. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_create_or_merge.py +0 -0
  265. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_cypher_query_builder.py +0 -0
  266. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_cypher_query_writes.py +0 -0
  267. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_example.py +0 -0
  268. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_expand.py +0 -0
  269. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_mass_delete.py +0 -0
  270. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_pagination.py +0 -0
  271. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_parameters.py +0 -0
  272. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_query_execution_error.py +0 -0
  273. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_read_only_mode.py +0 -0
  274. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/integration/test_streaming.py +0 -0
  275. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/README.md +0 -0
  276. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/__init__.py +0 -0
  277. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/cli/__init__.py +0 -0
  278. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/cli/test_cmd_map.py +0 -0
  279. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/cli/test_command_registry.py +0 -0
  280. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/cli/test_promptgrammar.py +0 -0
  281. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/cli/test_promptparser.py +0 -0
  282. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/mock_backend.py +0 -0
  283. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_agtype_parser.py +0 -0
  284. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_backend_capabilities.py +0 -0
  285. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_bulk_normalize.py +0 -0
  286. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_column_utils.py +0 -0
  287. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_command_reader.py +0 -0
  288. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_connection_guard.py +0 -0
  289. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_cypherbuilder.py +0 -0
  290. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_cypherparser.py +0 -0
  291. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_cypherquery.py +0 -0
  292. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_cypherquery_facade.py +0 -0
  293. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_cypherquery_patterns.py +0 -0
  294. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_dbpool.py +0 -0
  295. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_dict_access_mixin.py +0 -0
  296. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_expand.py +0 -0
  297. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_extend_relation_decorator.py +0 -0
  298. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_extend_relations.py +0 -0
  299. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_formatprovider.py +0 -0
  300. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_graph_id_zero.py +0 -0
  301. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_graphops.py +0 -0
  302. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_indexing.py +0 -0
  303. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_json_schema_loading.py +0 -0
  304. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_json_schema_vs_decorators.py +0 -0
  305. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_model_inheritance.py +0 -0
  306. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_modelinfo.py +0 -0
  307. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_modelprovider_loading.py +0 -0
  308. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_modelprovider_schemas.py +0 -0
  309. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_models.py +0 -0
  310. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_pagination.py +0 -0
  311. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_prepared_statement_recovery.py +0 -0
  312. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_query_execution_error.py +0 -0
  313. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_query_timeout.py +0 -0
  314. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_schema_converter.py +0 -0
  315. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/test_settings.py +0 -0
  316. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/tools/__init__.py +0 -0
  317. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/tools/test_data_flattener.py +0 -0
  318. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/tools/test_exporters.py +0 -0
  319. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/tools/test_hierarchical_importer.py +0 -0
  320. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/tools/test_json_yaml_data_source.py +0 -0
  321. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/tools/test_resource_management.py +0 -0
  322. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/tools/test_tabular_import.py +0 -0
  323. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/utils/test_collection_utils.py +0 -0
  324. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/utils/test_connection_utils.py +0 -0
  325. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/utils/test_conversion_utils.py +0 -0
  326. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/utils/test_core_utils.py +0 -0
  327. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/utils/test_schema_merge.py +0 -0
  328. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/utils/test_schema_utils.py +0 -0
  329. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/utils/test_settings_repr.py +0 -0
  330. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/tests/unit/utils/test_string_utils.py +0 -0
  331. {cypher_graphdb-0.7.2 → cypher_graphdb-0.7.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cypher_graphdb
3
- Version: 0.7.2
3
+ Version: 0.7.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
@@ -7,6 +7,7 @@ CypherBackend: Abstract base class for implementing graph database backends.
7
7
 
8
8
  import abc
9
9
  import re
10
+ from collections.abc import Sequence
10
11
  from enum import Enum, auto
11
12
  from typing import Any
12
13
 
@@ -453,23 +454,27 @@ class CypherBackend(abc.ABC):
453
454
  edges: list[dict],
454
455
  src_label: str,
455
456
  dst_label: str,
456
- src_ref_prop: str = "id",
457
- dst_ref_prop: str = "id",
457
+ src_ref_prop: str | Sequence[str] = "id",
458
+ dst_ref_prop: str | Sequence[str] = "id",
458
459
  batch_size: int = 500,
459
460
  ) -> int:
460
- """Create edges in batches by matching src/dst nodes on a reference property.
461
+ """Create edges in batches by matching src/dst nodes on a reference key.
461
462
 
462
463
  Each dict in edges must have "src" and "dst" keys whose values match
463
464
  the src_ref_prop/dst_ref_prop properties on source/destination nodes.
464
465
  Any additional keys are set as properties on the created edge.
465
466
 
467
+ A ref prop may name a single property or an ordered sequence of properties
468
+ (a composite key); for a composite key each "src"/"dst" is a parallel
469
+ sequence of values in the same order.
470
+
466
471
  Args:
467
472
  label: Edge label for all created edges.
468
473
  edges: List of dicts with "src", "dst", and optional edge properties.
469
474
  src_label: Label of source nodes. Required for optimal performance on AGE.
470
475
  dst_label: Label of destination nodes. Required for optimal performance on AGE.
471
- src_ref_prop: Property name on source nodes to match against "src".
472
- dst_ref_prop: Property name on destination nodes to match against "dst".
476
+ src_ref_prop: Property, or ordered sequence of properties, on source nodes to match against "src".
477
+ dst_ref_prop: As ``src_ref_prop``, for destination nodes.
473
478
  batch_size: Number of edges per UNWIND batch.
474
479
 
475
480
  Returns:
@@ -14,6 +14,7 @@ committed immediately (DDL cannot run inside a user transaction in PostgreSQL)
14
14
  without affecting any surrounding data already written by the caller.
15
15
  """
16
16
 
17
+ from collections.abc import Sequence
17
18
  from typing import Literal
18
19
 
19
20
  import psycopg
@@ -21,6 +22,7 @@ from loguru import logger
21
22
  from psycopg.sql import SQL, Composed, Identifier
22
23
  from psycopg.sql import Literal as SQLLiteral
23
24
 
25
+ from cypher_graphdb.bulk_refs import as_ref_props, ref_key
24
26
  from cypher_graphdb.utils import chunk_list
25
27
 
26
28
  from .ageserializer import escape_value
@@ -62,7 +64,7 @@ class AGEBulkWriter:
62
64
  # edges all resolving against Method.qualified_name).
63
65
  # Invalidated by invalidate_graphid_cache() after node inserts that
64
66
  # change the graphid mapping.
65
- self._graphid_cache: dict[tuple[str, str], dict[str, int]] = {}
67
+ self._graphid_cache: dict[tuple[str, tuple[str, ...]], dict[tuple, int]] = {}
66
68
  # Cached list of edge label names for bulk_delete_nodes.
67
69
  # Edge labels are stable within a connection lifetime -- populated on
68
70
  # first call to _list_edge_labels() and reused for all subsequent deletes.
@@ -124,8 +126,8 @@ class AGEBulkWriter:
124
126
  edges: list[dict],
125
127
  src_label: str,
126
128
  dst_label: str,
127
- src_ref_prop: str = "id",
128
- dst_ref_prop: str = "id",
129
+ src_ref_prop: str | Sequence[str] = "id",
130
+ dst_ref_prop: str | Sequence[str] = "id",
129
131
  batch_size: int = 500,
130
132
  ) -> int:
131
133
  """Insert edges via direct SQL INSERT into the AGE edge label table.
@@ -143,8 +145,10 @@ class AGEBulkWriter:
143
145
  edges: List of dicts with at least "src" and "dst" keys.
144
146
  src_label: Vertex label of source nodes.
145
147
  dst_label: Vertex label of destination nodes.
146
- src_ref_prop: Property name on source nodes to match "src" values.
147
- dst_ref_prop: Property name on destination nodes to match "dst" values.
148
+ src_ref_prop: Property (or ordered sequence of properties) on source
149
+ nodes to match "src" values. A sequence forms a composite key;
150
+ each edge's "src" is then a parallel sequence of values.
151
+ dst_ref_prop: As ``src_ref_prop``, for destination nodes.
148
152
  batch_size: Number of rows per executemany batch.
149
153
 
150
154
  Returns:
@@ -155,17 +159,20 @@ class AGEBulkWriter:
155
159
 
156
160
  label_id = self._ensure_label(label, kind="e")
157
161
 
158
- # Collect the unique ref values we actually need -- avoids loading
159
- # the entire label table (which can be 200K+ rows in a shared graph).
160
- src_refs = {e["src"] for e in edges}
161
- dst_refs = {e["dst"] for e in edges}
162
+ src_props = as_ref_props(src_ref_prop)
163
+ dst_props = as_ref_props(dst_ref_prop)
162
164
 
163
- # Build {ref_value: graphid} maps for only the referenced nodes
164
- src_map = self._build_graphid_index(src_label, src_ref_prop, ref_values=src_refs)
165
- if src_label == dst_label and src_ref_prop == dst_ref_prop:
166
- dst_map = self._build_graphid_index(dst_label, dst_ref_prop, ref_values=src_refs | dst_refs)
165
+ # Collect the unique (composite) ref keys we actually need -- avoids
166
+ # loading the entire label table (200K+ rows in a shared graph).
167
+ src_refs = {ref_key(e["src"], len(src_props)) for e in edges}
168
+ dst_refs = {ref_key(e["dst"], len(dst_props)) for e in edges}
169
+
170
+ # Build {ref_key: graphid} maps for only the referenced nodes
171
+ src_map = self._build_graphid_index(src_label, src_props, ref_values=src_refs)
172
+ if src_label == dst_label and src_props == dst_props:
173
+ dst_map = self._build_graphid_index(dst_label, dst_props, ref_values=src_refs | dst_refs)
167
174
  else:
168
- dst_map = self._build_graphid_index(dst_label, dst_ref_prop, ref_values=dst_refs)
175
+ dst_map = self._build_graphid_index(dst_label, dst_props, ref_values=dst_refs)
169
176
 
170
177
  total = 0
171
178
  skipped = 0
@@ -173,8 +180,8 @@ class AGEBulkWriter:
173
180
  for batch in chunk_list(edges, batch_size):
174
181
  value_parts = []
175
182
  for edge in batch:
176
- src_gid = src_map.get(edge["src"])
177
- dst_gid = dst_map.get(edge["dst"])
183
+ src_gid = src_map.get(ref_key(edge["src"], len(src_props)))
184
+ dst_gid = dst_map.get(ref_key(edge["dst"], len(dst_props)))
178
185
  if src_gid is None or dst_gid is None:
179
186
  skipped += 1
180
187
  continue
@@ -479,51 +486,60 @@ class AGEBulkWriter:
479
486
  for k in keys_to_drop:
480
487
  del self._graphid_cache[k]
481
488
 
482
- def _build_graphid_index(self, label: str, ref_prop: str, ref_values: set[str] | None = None) -> dict[str, int]:
483
- """Load a {ref_prop_value: graphid} mapping for nodes of a label.
489
+ def _build_graphid_index(
490
+ self, label: str, ref_props: Sequence[str], ref_values: set[tuple] | None = None
491
+ ) -> dict[tuple, int]:
492
+ """Load a {ref_key_tuple: graphid} mapping for nodes of a label.
493
+
494
+ ``ref_props`` is the ordered key: one property for a simple key, several
495
+ for a composite one. Keys in the returned map are value tuples aligned
496
+ with ``ref_props`` (always a tuple, even for a single prop), so a composite
497
+ key never collapses two nodes that merely share one property value.
484
498
 
485
- When ``ref_values`` is provided, only loads graphids for those specific
486
- property values (using a WHERE IN clause). This is critical for shared
487
- graphs where a label table may contain hundreds of thousands of rows
488
- across all sources, but only a small subset is needed for the current
489
- edge batch.
499
+ When ``ref_values`` is provided (a set of value tuples), only loads
500
+ graphids for those specific keys (WHERE ... IN). Critical for shared
501
+ graphs where a label table may hold hundreds of thousands of rows across
502
+ all sources, but only a small subset is needed for the current edge batch.
490
503
 
491
504
  When ``ref_values`` is None, loads the entire table (used for small
492
505
  labels like structural edges).
493
506
 
494
- Results are cached per (label, ref_prop) and incrementally extended
507
+ Results are cached per (label, ref_props) and incrementally extended
495
508
  when new ref_values are requested.
496
509
 
497
510
  Values returned by agtype_access_operator are agtype-quoted strings
498
511
  (e.g. '"mth:foo.bar"'), so we strip the surrounding quotes.
499
512
  """
500
- cache_key = (label, ref_prop)
513
+ props = tuple(ref_props)
514
+ cache_key = (label, props)
501
515
  cached = self._graphid_cache.get(cache_key)
502
516
 
503
517
  if ref_values is not None and cached is not None:
504
- # Check if all requested values are already in the cache
518
+ # Check if all requested keys are already in the cache
505
519
  missing = ref_values - cached.keys()
506
520
  if not missing:
507
521
  return cached
508
- # Only query the missing values
522
+ # Only query the missing keys
509
523
  ref_values = missing
510
524
 
511
525
  if ref_values is not None and ref_values:
512
- sql_stmt = SQLBuilder.lookup_node_graphids_filtered_sql(self._graph_name, label, ref_prop, ref_values)
526
+ sql_stmt = SQLBuilder.lookup_node_graphids_filtered_sql(self._graph_name, label, props, ref_values)
513
527
  else:
514
- sql_stmt = SQLBuilder.lookup_node_graphids_sql(self._graph_name, label, ref_prop)
528
+ sql_stmt = SQLBuilder.lookup_node_graphids_sql(self._graph_name, label, props)
515
529
 
516
530
  with self._conn.cursor() as cursor:
517
531
  cursor.execute(sql_stmt)
518
532
  rows = cursor.fetchall()
519
- result = {str(r[0]).strip('"'): r[1] for r in rows}
533
+ # Each row is (val_0, ..., val_n, graphid); the key is the tuple of values.
534
+ n = len(props)
535
+ result = {tuple(str(v).strip('"') for v in r[:n]): r[n] for r in rows}
520
536
 
521
537
  # Merge into cache
522
538
  if cached is not None:
523
539
  cached.update(result)
524
- logger.debug("Graphid index extended for {}.{}: +{} entries (total {})", label, ref_prop, len(result), len(cached))
540
+ logger.debug("Graphid index extended for {}.{}: +{} entries (total {})", label, props, len(result), len(cached))
525
541
  return cached
526
542
 
527
543
  self._graphid_cache[cache_key] = result
528
- logger.debug("Graphid index built for {}.{}: {} entries", label, ref_prop, len(result))
544
+ logger.debug("Graphid index built for {}.{}: {} entries", label, props, len(result))
529
545
  return result
@@ -13,7 +13,7 @@ import hashlib
13
13
  import json
14
14
  import re
15
15
  import time
16
- from collections.abc import Iterator
16
+ from collections.abc import Iterator, Sequence
17
17
  from typing import Any
18
18
 
19
19
  import psycopg
@@ -26,6 +26,7 @@ from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_ex
26
26
 
27
27
  # Import directly from the main package
28
28
  from cypher_graphdb.backend import BackendCapability, CypherBackend, ExecStatistics, SqlStatistics
29
+ from cypher_graphdb.bulk_refs import as_ref_props, edge_match_pattern
29
30
 
30
31
  # Deferred at module level to avoid a circular import:
31
32
  # agegraphdb -> pagination (cyphergraphdb package) -> cyphergraphdb -> agegraphdb
@@ -1027,11 +1028,11 @@ class AGEGraphDB(CypherBackend):
1027
1028
  edges: list[dict],
1028
1029
  src_label: str,
1029
1030
  dst_label: str,
1030
- src_ref_prop: str = "id",
1031
- dst_ref_prop: str = "id",
1031
+ src_ref_prop: str | Sequence[str] = "id",
1032
+ dst_ref_prop: str | Sequence[str] = "id",
1032
1033
  batch_size: int = 500,
1033
1034
  ) -> int:
1034
- """Create edges in batches by matching src/dst nodes on a reference property.
1035
+ """Create edges in batches by matching src/dst nodes on a reference key.
1035
1036
 
1036
1037
  When ``direct_bulk_insert`` is True and both ``src_label`` and ``dst_label``
1037
1038
  are specified, uses direct SQL INSERT via :class:`AGEBulkWriter` with
@@ -1042,8 +1043,10 @@ class AGEGraphDB(CypherBackend):
1042
1043
  edges: List of dicts with at least "src" and "dst" keys.
1043
1044
  src_label: Label of source nodes. Required for optimal performance on AGE.
1044
1045
  dst_label: Label of destination nodes. Required for optimal performance on AGE.
1045
- src_ref_prop: Property name on source nodes to match against "src".
1046
- dst_ref_prop: Property name on destination nodes to match against "dst".
1046
+ src_ref_prop: Property (or ordered sequence of properties) on source
1047
+ nodes to match against "src". A sequence forms a composite key and
1048
+ each edge's "src" is then a parallel sequence of values.
1049
+ dst_ref_prop: As ``src_ref_prop``, for destination nodes.
1047
1050
  batch_size: Number of edges per batch.
1048
1051
 
1049
1052
  Returns:
@@ -1059,8 +1062,8 @@ class AGEGraphDB(CypherBackend):
1059
1062
  )
1060
1063
 
1061
1064
  # Cypher UNWIND fallback (direct_bulk_insert disabled).
1062
- src_pat = f"(a:{src_label} {{{src_ref_prop}: e.src}})"
1063
- dst_pat = f"(b:{dst_label} {{{dst_ref_prop}: e.dst}})"
1065
+ src_pat = edge_match_pattern("a", src_label, as_ref_props(src_ref_prop), "src")
1066
+ dst_pat = edge_match_pattern("b", dst_label, as_ref_props(dst_ref_prop), "dst")
1064
1067
 
1065
1068
  total = 0
1066
1069
  for batch in chunk_list(edges, batch_size):
@@ -37,12 +37,14 @@ def escape_string_inner(v: str) -> str:
37
37
  def escape_value(v: object) -> str:
38
38
  """Escape a Python value for inline AGE Cypher literals.
39
39
 
40
- Handles None, bool, int, float, and str-coercible values. Strings are
41
- escaped for double-quoted Cypher literals: backslashes, double quotes,
42
- newlines, carriage returns, tabs, and null bytes are all handled.
40
+ Handles None, bool, int, float, list/tuple (nested), and str-coercible
41
+ values. Strings are escaped for double-quoted Cypher literals: backslashes,
42
+ double quotes, newlines, carriage returns, tabs, and null bytes are all
43
+ handled. A list/tuple becomes a Cypher list literal -- used for composite
44
+ edge-endpoint match keys (``e.src`` / ``e.dst``).
43
45
 
44
46
  Args:
45
- v: Python value (None, bool, int, float, or str-coercible).
47
+ v: Python value (None, bool, int, float, list/tuple, or str-coercible).
46
48
 
47
49
  Returns:
48
50
  Cypher literal string representation.
@@ -53,6 +55,8 @@ def escape_value(v: object) -> str:
53
55
  return "true" if v else "false"
54
56
  if isinstance(v, int | float):
55
57
  return str(v)
58
+ if isinstance(v, (list, tuple)):
59
+ return "[" + ", ".join(escape_value(item) for item in v) + "]"
56
60
  return f'"{escape_string_inner(str(v))}"'
57
61
 
58
62
 
@@ -5,6 +5,7 @@ interface with Apache AGE graph functionality, including graph creation,
5
5
  Cypher query execution, and full-text search operations.
6
6
  """
7
7
 
8
+ from collections.abc import Sequence
8
9
  from dataclasses import dataclass
9
10
 
10
11
  from psycopg import sql
@@ -280,52 +281,77 @@ class SQLBuilder:
280
281
  "WHERE g.name = %s AND l.name = %s"
281
282
  )
282
283
 
283
- @classmethod
284
- def lookup_node_graphids_sql(cls, graph_name: str, label: str, ref_prop: str) -> sql.Composed:
285
- """SQL to look up graphids of all nodes of a label by a property value.
284
+ @staticmethod
285
+ def _prop_access(prop: str) -> sql.Composed:
286
+ """agtype_access_operator expression reading one property, matching the expression index."""
287
+ return sql.SQL("ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, {prop}::ag_catalog.agtype])").format(
288
+ prop=sql.Literal(f'"{prop}"'),
289
+ )
286
290
 
287
- Returns (ref_value, graphid) pairs for every row in the label table.
288
- Use ``lookup_node_graphids_filtered_sql`` when only a subset of values
289
- is needed (avoids scanning hundreds of thousands of rows).
291
+ @classmethod
292
+ def lookup_node_graphids_sql(cls, graph_name: str, label: str, ref_props: Sequence[str]) -> sql.Composed:
293
+ """SQL to look up graphids of all nodes of a label by one or more property values.
294
+
295
+ Returns ``(ref_value_0, ..., ref_value_n, graphid)`` for every row in the
296
+ label table -- one value column per entry in ``ref_props``, in order, so
297
+ the caller can build a composite key. Use
298
+ ``lookup_node_graphids_filtered_sql`` when only a subset is needed
299
+ (avoids scanning hundreds of thousands of rows).
290
300
  """
291
- return sql.SQL(
292
- "SELECT ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, {prop}::ag_catalog.agtype])::text, id "
293
- "FROM {schema}.{table}"
294
- ).format(
301
+ value_cols = sql.SQL(", ").join(cls._prop_access(p) + sql.SQL("::text") for p in ref_props)
302
+ return sql.SQL("SELECT {cols}, id FROM {schema}.{table}").format(
303
+ cols=value_cols,
295
304
  schema=sql.Identifier(graph_name),
296
305
  table=sql.Identifier(label),
297
- prop=sql.Literal(f'"{ref_prop}"'),
298
306
  )
299
307
 
300
308
  @classmethod
301
- def lookup_node_graphids_filtered_sql(cls, graph_name: str, label: str, ref_prop: str, ref_values: set[str]) -> sql.Composed:
302
- """SQL to look up graphids for a specific set of property values.
303
-
304
- Uses a WHERE ... IN (...) clause with the same agtype_access_operator
305
- expression as the expression index, so the btree index is used for
306
- the filter. For 500 ref values against a 200K+ row table this turns
307
- a sequential scan into 500 index lookups.
308
-
309
- The ref values are cast to ``::ag_catalog.agtype`` to match the
310
- expression index type exactly. Values containing double quotes or
311
- backslashes are escaped for the agtype JSON string format before
312
- being wrapped in the PostgreSQL string literal via sql.Literal.
309
+ def lookup_node_graphids_filtered_sql(
310
+ cls, graph_name: str, label: str, ref_props: Sequence[str], ref_values: set
311
+ ) -> sql.Composed:
312
+ """SQL to look up graphids for a specific set of (composite) property values.
313
+
314
+ ``ref_props`` is the ordered list of key properties; ``ref_values`` is a
315
+ set of value tuples (one element per prop, same order).
316
+
317
+ A single prop uses ``WHERE access IN (v, ...)`` -- the fast path that hits
318
+ the expression index. A composite key uses ``WHERE (a0 = v0 AND a1 = v1)
319
+ OR (...) OR ...``: AGE's agtype does not support SQL row-value comparison
320
+ (``(a, b) IN ((x, y))`` raises "cannot cast agtype string to type
321
+ boolean"), so the key is expanded into per-tuple AND groups.
322
+
323
+ Values are cast to ``::ag_catalog.agtype`` to match the expression index
324
+ type. Values containing double quotes or backslashes are escaped for the
325
+ agtype JSON string format before being wrapped via sql.Literal -- identical
326
+ escaping to the Cypher UNWIND path.
313
327
  """
314
- # Build the IN list: each value is a double-quoted agtype string literal.
315
- # escape_string_inner handles backslashes, double quotes, control chars,
316
- # and null bytes -- identical escaping to the Cypher UNWIND path.
317
- in_values = ", ".join(
318
- sql.Literal('"' + escape_string_inner(v) + '"').as_string(None) + "::ag_catalog.agtype" for v in ref_values
319
- )
320
- return sql.SQL(
321
- "SELECT ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, {prop}::ag_catalog.agtype])::text, id "
322
- "FROM {schema}.{table} "
323
- "WHERE ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, {prop}::ag_catalog.agtype]) IN ({in_vals})"
324
- ).format(
328
+
329
+ def agtype_literal(v: str) -> sql.SQL:
330
+ return sql.SQL(sql.Literal('"' + escape_string_inner(v) + '"').as_string(None) + "::ag_catalog.agtype")
331
+
332
+ value_cols = sql.SQL(", ").join(cls._prop_access(p) + sql.SQL("::text") for p in ref_props)
333
+
334
+ # Match each key tuple as an AND of per-prop equalities, OR'd across tuples.
335
+ # agtype equality must use the ag_catalog.= operator -- a plain "=" (or a
336
+ # ::text cast) raises "cannot cast agtype string to type boolean". This
337
+ # uniform form covers both the single-prop and composite cases.
338
+ accessors = [cls._prop_access(p) for p in ref_props]
339
+ groups = [
340
+ sql.SQL("(")
341
+ + sql.SQL(" AND ").join(
342
+ sql.SQL("{a} OPERATOR(ag_catalog.=) {v}").format(a=accessors[i], v=agtype_literal(v))
343
+ for i, v in enumerate(value_tuple)
344
+ )
345
+ + sql.SQL(")")
346
+ for value_tuple in ref_values
347
+ ]
348
+ where = sql.SQL(" OR ").join(groups)
349
+
350
+ return sql.SQL("SELECT {cols}, id FROM {schema}.{table} WHERE {where}").format(
351
+ cols=value_cols,
325
352
  schema=sql.Identifier(graph_name),
326
353
  table=sql.Identifier(label),
327
- prop=sql.Literal(f'"{ref_prop}"'),
328
- in_vals=sql.SQL(in_values),
354
+ where=where,
329
355
  )
330
356
 
331
357
  @classmethod
@@ -8,7 +8,7 @@ Classes:
8
8
  """
9
9
 
10
10
  import contextlib
11
- from collections.abc import Generator
11
+ from collections.abc import Generator, Sequence
12
12
  from typing import Any
13
13
 
14
14
  import mgclient
@@ -17,6 +17,7 @@ from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_ex
17
17
 
18
18
  # Import directly from the main package
19
19
  from cypher_graphdb.backend import BackendCapability, CypherBackend, ExecStatistics
20
+ from cypher_graphdb.bulk_refs import as_ref_props, edge_match_pattern, ref_param
20
21
  from cypher_graphdb.cyphergraphdb.pagination import Page
21
22
  from cypher_graphdb.cyphergraphdb.pagination_mixin import _resolve_col_names
22
23
  from cypher_graphdb.cypherparser import ParsedCypherQuery
@@ -660,8 +661,8 @@ class MemgraphDB(CypherBackend):
660
661
  edges: list[dict],
661
662
  src_label: str,
662
663
  dst_label: str,
663
- src_ref_prop: str = "id",
664
- dst_ref_prop: str = "id",
664
+ src_ref_prop: str | Sequence[str] = "id",
665
+ dst_ref_prop: str | Sequence[str] = "id",
665
666
  batch_size: int = 500,
666
667
  ) -> int:
667
668
  """Create edges in batches using parameterized UNWIND.
@@ -669,13 +670,17 @@ class MemgraphDB(CypherBackend):
669
670
  Each dict in edges must have "src" and "dst" keys. Additional keys
670
671
  are set as edge properties.
671
672
 
673
+ A ref prop may name a single property or an ordered sequence of properties
674
+ (a composite key); for a composite key each "src"/"dst" is a parallel
675
+ sequence of values matched element-wise against the props.
676
+
672
677
  Args:
673
678
  label: Edge label for all created edges.
674
679
  edges: List of dicts with "src", "dst", and optional edge properties.
675
680
  src_label: Label of source nodes. Required for optimal performance on AGE.
676
681
  dst_label: Label of destination nodes. Required for optimal performance on AGE.
677
- src_ref_prop: Property name on source nodes to match against "src".
678
- dst_ref_prop: Property name on destination nodes to match against "dst".
682
+ src_ref_prop: Property, or ordered sequence of properties, on source nodes to match against "src".
683
+ dst_ref_prop: As ``src_ref_prop``, for destination nodes.
679
684
  batch_size: Number of edges per UNWIND batch.
680
685
 
681
686
  Returns:
@@ -685,9 +690,12 @@ class MemgraphDB(CypherBackend):
685
690
  if not edges:
686
691
  return 0
687
692
 
693
+ src_props = as_ref_props(src_ref_prop)
694
+ dst_props = as_ref_props(dst_ref_prop)
695
+
688
696
  # Build MATCH patterns from label/prop names (not user values -- safe to inline)
689
- src_pat = f"(a:{src_label} {{{src_ref_prop}: e.src}})" if src_label else f"(a {{{src_ref_prop}: e.src}})"
690
- dst_pat = f"(b:{dst_label} {{{dst_ref_prop}: e.dst}})" if dst_label else f"(b {{{dst_ref_prop}: e.dst}})"
697
+ src_pat = edge_match_pattern("a", src_label, src_props, "src")
698
+ dst_pat = edge_match_pattern("b", dst_label, dst_props, "dst")
691
699
 
692
700
  # Collect edge property keys (beyond src/dst) from first row
693
701
  edge_prop_keys = [k for k in edges[0] if k not in ("src", "dst")]
@@ -698,6 +706,10 @@ class MemgraphDB(CypherBackend):
698
706
 
699
707
  cypher = f"UNWIND $edges AS e MATCH {src_pat} MATCH {dst_pat} CREATE (a)-[r:{label}]->(b){set_clause}"
700
708
 
709
+ # mgclient rejects tuples as query parameters; coerce each endpoint by its own
710
+ # key arity (source and destination may differ).
711
+ edges = [{**e, "src": ref_param(e["src"], len(src_props)), "dst": ref_param(e["dst"], len(dst_props))} for e in edges]
712
+
701
713
  total = 0
702
714
  for batch in chunk_list(edges, batch_size):
703
715
  cursor = self._conn.cursor()
@@ -0,0 +1,85 @@
1
+ """Composite endpoint keys for bulk edge creation.
2
+
3
+ ``bulk_create_edges`` matches each endpoint node by a *reference key* -- one or
4
+ more properties whose values identify the node. A single property is the common
5
+ case (``"symbol"``), but it is only correct when that property is unique within
6
+ the label. When it is not (e.g. the same ``symbol`` exists in several sources of
7
+ a shared graph), the key must be composite (``("symbol", "source_key")``) so an
8
+ edge binds to the intended node rather than an arbitrary match.
9
+
10
+ These helpers normalise the ``str | Sequence[str]`` ref-prop argument and turn an
11
+ edge's ``src``/``dst`` value (scalar or tuple) into a hashable key aligned with
12
+ the prop order.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Sequence
18
+ from typing import Any
19
+
20
+
21
+ def as_ref_props(ref_prop: str | Sequence[str]) -> tuple[str, ...]:
22
+ """Normalise a ref-prop argument to an ordered tuple of property names.
23
+
24
+ A plain string becomes a one-tuple; a sequence is kept in order. Empty
25
+ sequences are rejected -- an endpoint must be matched on at least one prop.
26
+ """
27
+ if isinstance(ref_prop, str):
28
+ return (ref_prop,)
29
+ props = tuple(ref_prop)
30
+ if not props:
31
+ raise ValueError("ref_prop must name at least one property")
32
+ if not all(isinstance(p, str) for p in props):
33
+ raise TypeError("ref_prop sequence must contain only property-name strings")
34
+ return props
35
+
36
+
37
+ def ref_key(value: Any, n_props: int) -> tuple[Any, ...]:
38
+ """Turn an edge's src/dst match value into a key tuple of length ``n_props``.
39
+
40
+ For a single-prop key the value is a scalar (wrapped into a one-tuple); for a
41
+ composite key the value must be a sequence whose length matches ``n_props``.
42
+ A tuple key is always returned so callers can index a composite map uniformly.
43
+ """
44
+ if n_props == 1:
45
+ # A one-element sequence is accepted too, but a bare scalar is the norm.
46
+ if isinstance(value, (list, tuple)):
47
+ seq = tuple(value)
48
+ if len(seq) != 1:
49
+ raise ValueError(f"expected 1 match value, got {len(seq)}")
50
+ return seq
51
+ return (value,)
52
+ if not isinstance(value, (list, tuple)):
53
+ raise TypeError(f"composite key needs a sequence of {n_props} values, got {type(value).__name__}")
54
+ seq = tuple(value)
55
+ if len(seq) != n_props:
56
+ raise ValueError(f"expected {n_props} match values, got {len(seq)}")
57
+ return seq
58
+
59
+
60
+ def ref_param(value: Any, n_props: int) -> Any:
61
+ """Coerce one endpoint's match value to a driver-friendly query parameter.
62
+
63
+ A single-prop key stays a scalar; a composite key becomes a list (some drivers,
64
+ e.g. mgclient, reject tuples as parameters). Decided per endpoint from that
65
+ endpoint's own key arity: source and destination may differ (a composite caller
66
+ matched to a single-prop target), and coercing both on one endpoint's arity would
67
+ split a scalar string into characters.
68
+ """
69
+ return list(ref_key(value, n_props)) if n_props > 1 else value
70
+
71
+
72
+ def edge_match_pattern(alias: str, node_label: str | None, props: Sequence[str], ref: str) -> str:
73
+ """Build a Cypher node-match pattern for one edge endpoint inside ``UNWIND ... AS e``.
74
+
75
+ ``ref`` is the UNWIND field carrying the match value ("src" or "dst"). A
76
+ single-prop key matches the scalar (``e.src``); a composite key matches each
77
+ property against the corresponding list element (``e.src[0]``, ``e.src[1]``, ...).
78
+ An empty ``node_label`` matches nodes of any label.
79
+ """
80
+ if len(props) == 1:
81
+ body = f"{props[0]}: e.{ref}"
82
+ else:
83
+ body = ", ".join(f"{p}: e.{ref}[{i}]" for i, p in enumerate(props))
84
+ head = f"{alias}:{node_label}" if node_label else alias
85
+ return f"({head} {{{body}}})"
@@ -198,11 +198,11 @@ class IndexingMixin(_Host):
198
198
  src_refs: Sequence[Any] | None = None,
199
199
  dst_refs: Sequence[Any] | None = None,
200
200
  label: str | None = None,
201
- src_ref_prop: str = "id",
202
- dst_ref_prop: str = "id",
201
+ src_ref_prop: str | Sequence[str] = "id",
202
+ dst_ref_prop: str | Sequence[str] = "id",
203
203
  batch_size: int = 500,
204
204
  ) -> int:
205
- """Create edges in batches by matching src/dst nodes on a reference property.
205
+ """Create edges in batches by matching src/dst nodes on a reference key.
206
206
 
207
207
  Accepts either plain dicts (traditional shape) or decorated
208
208
  ``GraphEdge`` instances (typed shape).
@@ -215,6 +215,15 @@ class IndexingMixin(_Host):
215
215
  ``src_refs`` / ``dst_refs`` lists; the edge properties come from
216
216
  ``flatten_properties()`` on each instance.
217
217
 
218
+ A ref prop may name a **single** property (the common case) or an ordered
219
+ **sequence** of properties forming a composite key. A composite key is the
220
+ correct choice when no single property is unique within the label (e.g. the
221
+ same ``symbol`` exists in several sources of a shared graph): matching on
222
+ ``("symbol", "source_key")`` binds each edge to the intended node instead of
223
+ an arbitrary one that happens to share the symbol. For a composite key each
224
+ ``src`` / ``dst`` (or ``src_refs`` / ``dst_refs`` element) is a parallel
225
+ sequence of values, in the same order as the props.
226
+
218
227
  Args:
219
228
  edges: Either a list of dicts (each with ``src``/``dst`` + props)
220
229
  or a list of ``GraphEdge`` instances (all sharing the same
@@ -226,8 +235,9 @@ class IndexingMixin(_Host):
226
235
  label: Edge label. Required for dicts; optional for typed input.
227
236
  src_label: Label of source nodes. Required for optimal performance on AGE.
228
237
  dst_label: Label of destination nodes. Required for optimal performance on AGE.
229
- src_ref_prop: Property name on source nodes to match against src refs.
230
- dst_ref_prop: Property name on destination nodes to match against dst refs.
238
+ src_ref_prop: Property, or ordered sequence of properties (composite
239
+ key), on source nodes to match against src refs.
240
+ dst_ref_prop: As ``src_ref_prop``, for destination nodes.
231
241
  batch_size: Number of edges per batch.
232
242
 
233
243
  Returns:
@@ -254,6 +264,17 @@ class IndexingMixin(_Host):
254
264
  src_ref_prop="qualified_name",
255
265
  dst_ref_prop="qualified_name",
256
266
  )
267
+
268
+ Composite key (disambiguate a non-unique property)::
269
+
270
+ cdb.bulk_create_edges(
271
+ calls,
272
+ src_refs=[("mod.foo.bar", "svc-a"), ("mod.foo.baz", "svc-a")],
273
+ dst_refs=[("mod.target.a", "svc-a"), ("mod.target.b", "svc-a")],
274
+ src_label="Method", dst_label="Method",
275
+ src_ref_prop=("qualified_name", "source_key"),
276
+ dst_ref_prop=("qualified_name", "source_key"),
277
+ )
257
278
  """
258
279
  assert self._backend
259
280
  derived_label, edge_dicts = normalize_edges_input(edges, src_refs, dst_refs, label)