feat(falkordb): add production-ready test suite with 180 tests and benchmarks - #123
Conversation
Add FalkorDB as a new target graph database backend. FalkorDB is a Redis-based graph database that supports OpenCypher query language. Changes: - Add falkordb>=1.0.9 and redis>=5.0.0 dependencies - Add FALKORDB to DBFlavor, DBType, ExpressionFlavor enums - Add FalkordbConfig class with docker env support - Implement FalkordbConnection with full Connection interface: - Graph creation/deletion - Batch upsert for nodes and edges - Document fetching with filters, limits, projections - Index creation on nodes and relationships - Aggregation functions (COUNT, MIN, MAX, AVG, SORTED_UNIQUE) - Add Docker infrastructure for testing - Add comprehensive test suite
The REDIS_ARGS environment variable with an empty password caused a configuration error in FalkorDB/Redis.
Ignore limit values <= 0 in fetch_docs and fetch_edges to prevent FalkorDB from rejecting the query with invalid LIMIT values.
Add functional, performance, and edge case tests covering: Functional tests (45 tests): - Data coherence with composite keys and partial updates - Multi-source sync and conflict resolution - Relationship operations (orphans, diamonds, cycles) - ETL scenarios and schema evolution - Query edge cases and traversal patterns Performance tests (18 tests): - Throughput benchmarks (single/batch insert, read, query) - Scalability analysis with increasing data volume - Concurrency testing (parallel reads/writes) - Batch sizing optimization - Sustained load behavior Edge case tests: - Boundary conditions and error handling - Type coercion and null value behavior - Unicode and special character handling Benchmark results (MacBook Pro M1 Pro 16", 16GB RAM): - Single insert: ~1,766 ops/sec - Batch insert: ~2,582 docs/sec (batch size 100) - Read queries: ~423 ops/sec - Cypher queries: ~1,735 ops/sec
|
Hey @JulienDbrt ,
|
Changes: - Pin FalkorDB Docker image to v4.14.10 for reproducibility - Add proper type annotations to FalkordbConnection (client, graph) - Add assertions to prevent operations on closed connections - Fix dynamic method access in test_edge_cases.py using getattr - Remove unused imports (ruff auto-fix) - Re-sort dependencies in pyproject.toml (toml-sort)
|
Thanks for the review @alexander-belikov! I've addressed both points in commit 88959fb: 1. Pre-commit fixes
All pre-commit checks now pass locally: 2. FalkorDB version pinnedDocker image pinned to Let me know if you need any other changes! |
|
That's great, thank you! The style and signature are coherent with the existing codebase. Running the tests =============================================================================== FAILURES ===============================================================================
_______________________________________________________ TestFilterEdgeCases.test_filter_with_special_characters ________________________________________________________
self = <test.db.falkordbs.test_edge_cases.TestFilterEdgeCases object at 0x7961fa566f50>
conn_conf = FalkordbConfig(uri='redis://localhost:6379', username=None, password=None, database='testgraph_0514e5c4', schema_name=None, request_timeout=60.0)
test_graph_name = 'testgraph_0514e5c4', clean_db = None
def test_filter_with_special_characters(self, conn_conf, test_graph_name, clean_db):
"""Filter values containing special characters."""
_ = clean_db
with ConnectionManager(connection_config=conn_conf) as db:
special_values = ["test'quote", 'test"double', "test\\backslash"]
docs = [{"id": str(i), "value": v} for i, v in enumerate(special_values)]
db.upsert_docs_batch(docs, "SpecialFilter", match_keys=["id"])
for v in special_values:
> result = db.fetch_docs("SpecialFilter", filters=["==", v, "value"])
test/db/falkordbs/test_edge_cases.py:1040:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
graflo/db/falkordb/conn.py:829: in fetch_docs
result = self.execute(q)
graflo/db/falkordb/conn.py:229: in execute
result = self.graph.query(query, kwargs if kwargs else None)
.venv/lib/python3.10/site-packages/falkordb/graph.py:115: in query
return self._query(q, params=params, timeout=timeout, read_only=False)
.venv/lib/python3.10/site-packages/falkordb/graph.py:91: in _query
response = self.execute_command(*command)
.venv/lib/python3.10/site-packages/redis/client.py:621: in execute_command
return self._execute_command(*args, **options)
.venv/lib/python3.10/site-packages/redis/client.py:632: in _execute_command
return conn.retry.call_with_retry(
.venv/lib/python3.10/site-packages/redis/retry.py:105: in call_with_retry
return do()
.venv/lib/python3.10/site-packages/redis/client.py:633: in <lambda>
lambda: self._send_command_parse_response(
.venv/lib/python3.10/site-packages/redis/client.py:604: in _send_command_parse_response
return self.parse_response(conn, command_name, **options)
.venv/lib/python3.10/site-packages/redis/client.py:651: in parse_response
response = connection.read_response()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <redis.connection.Connection(host=localhost,port=6379,db=0)>, disable_decoding = False
def read_response(
self,
disable_decoding=False,
*,
disconnect_on_error=True,
push_request=False,
):
"""Read the response from a previously sent command"""
host_error = self._host_error()
try:
if self.protocol in ["3", 3]:
response = self._parser.read_response(
disable_decoding=disable_decoding, push_request=push_request
)
else:
response = self._parser.read_response(disable_decoding=disable_decoding)
except socket.timeout:
if disconnect_on_error:
self.disconnect()
raise TimeoutError(f"Timeout reading from {host_error}")
except OSError as e:
if disconnect_on_error:
self.disconnect()
raise ConnectionError(f"Error while reading from {host_error} : {e.args}")
except BaseException:
# Also by default close in case of BaseException. A lot of code
# relies on this behaviour when doing Command/Response pairs.
# See #1128.
if disconnect_on_error:
self.disconnect()
raise
if self.health_check_interval:
self.next_health_check = time.monotonic() + self.health_check_interval
if isinstance(response, ResponseError):
try:
> raise response
E redis.exceptions.ResponseError: errMsg: Invalid input 'o': expected DETACH DELETE or DELETE line: 3, column: 36, offset: 72 errCtx: WHERE n.value = "test"double" errCtxOffset: 35
.venv/lib/python3.10/site-packages/redis/connection.py:672: ResponseError
=========================================================================== warnings summary ===========================================================================
test/db/falkordbs/test_performance.py:632
/home/alexander/work/codes/python/gg_core/graflo/test/db/falkordbs/test_performance.py:632: PytestUnknownMarkWarning: Unknown pytest.mark.slow - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
@pytest.mark.slow
test/db/falkordbs/test_performance.py:691
/home/alexander/work/codes/python/gg_core/graflo/test/db/falkordbs/test_performance.py:691: PytestUnknownMarkWarning: Unknown pytest.mark.slow - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
@pytest.mark.slow
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================================================================= short test summary info ========================================================================
FAILED test/db/falkordbs/test_edge_cases.py::TestFilterEdgeCases::test_filter_with_special_characters - redis.exceptions.ResponseError: errMsg: Invalid input 'o': expected DETACH DELETE or DELETE line: 3, column: 36, offset: 72 errCtx: WHERE n.value = "te...It would be worthwhile to add an explicit example that uses specifically FalkorDB, but that can also be done in the follow-up. |
Properly escape backslashes and double quotes in string values to prevent Cypher injection and query syntax errors. Before: WHERE n.value = "test"double" (syntax error) After: WHERE n.value = "test\"double" (valid)
|
Fixed in commit 3eeef41! The issue was in Fix: # Before
value = f'"{self.value[0]}"'
# After
escaped = self.value[0].replace("\\", "\\\\").replace('"', '\\"')
value = f'"{escaped}"'All 180 FalkorDB tests pass, including Regarding the |
|
ok, great! Merging this PR. FalkorDB todo's for the near future:
|
Context
This PR adds a comprehensive test suite for the FalkorDB graph database connector introduced in #121. FalkorDB is a high-performance graph database built on Redis, offering Cypher query support and optimized for real-time graph analytics.
The connector enables graflo to leverage FalkorDB as a backend for storing and querying knowledge graphs, providing an alternative to Neo4j with lower operational overhead.
What's Included
Functional Tests (45 tests)
Validate connector behavior in production-like scenarios:
Performance Tests (18 tests)
Benchmark connector throughput and identify bottlenecks:
Edge Case Tests
Cover boundary conditions and error handling for robustness.
Benchmark Results
Measured on MacBook Pro M1 Pro 16", 16GB RAM, FalkorDB v4.0 (Docker):
Test Plan
pytest test/db/falkordbs/ -v)ruff check)ruff format --check)git stashtest)