Files
mem0/tests/vector_stores/test_databricks.py
T
utkarsh240799andClaude Opus 4.6 c6cc8f14b8 fix: prevent SQL injection in Databricks vector store (#4073)
Replace f-string interpolation with Databricks parameterized queries
using StatementParameterListItem in delete(), update(), and insert()
methods. Add column name validation in update() to reject invalid SQL
identifiers from payload keys. Embedding vectors remain inlined as
they are numeric arrays not supported by the parameterization API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:03:10 +05:30

1038 lines
41 KiB
Python

from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
pytest.importorskip("databricks", reason="databricks-sdk package not installed")
from databricks.sdk.service.sql import StatementParameterListItem
from databricks.sdk.service.vectorsearch import VectorIndexType, QueryVectorIndexResponse, ResultManifest, ResultData, ColumnInfo
from mem0.vector_stores.databricks import Databricks
# ---------------------- Fixtures ---------------------- #
def _make_status(state="SUCCEEDED", error=None):
return SimpleNamespace(state=SimpleNamespace(value=state), error=error)
def _make_exec_response(state="SUCCEEDED", error=None):
return SimpleNamespace(status=_make_status(state, error))
@pytest.fixture
def mock_workspace_client():
"""Patch WorkspaceClient and provide a fully mocked client with required sub-clients."""
with patch("mem0.vector_stores.databricks.WorkspaceClient") as mock_wc_cls:
mock_wc = MagicMock(name="WorkspaceClient")
# warehouses.list -> iterable of objects with name/id
warehouse_obj = SimpleNamespace(name="test-warehouse", id="wh-123")
mock_wc.warehouses.list.return_value = [warehouse_obj]
# vector search endpoints
mock_wc.vector_search_endpoints.get_endpoint.side_effect = [Exception("not found"), MagicMock()]
mock_wc.vector_search_endpoints.create_endpoint_and_wait.return_value = None
# tables.exists
exists_obj = SimpleNamespace(table_exists=False)
mock_wc.tables.exists.return_value = exists_obj
mock_wc.tables.create.return_value = None
mock_wc.table_constraints.create.return_value = None
# vector_search_indexes list/create/query/delete
mock_wc.vector_search_indexes.list_indexes.return_value = []
mock_wc.vector_search_indexes.create_index.return_value = SimpleNamespace(name="catalog.schema.mem0")
mock_wc.vector_search_indexes.query_index.return_value = SimpleNamespace(result=SimpleNamespace(data_array=[]))
mock_wc.vector_search_indexes.delete_index.return_value = None
mock_wc.vector_search_indexes.get_index.return_value = SimpleNamespace(name="mem0")
# statement execution
mock_wc.statement_execution.execute_statement.return_value = _make_exec_response()
mock_wc_cls.return_value = mock_wc
yield mock_wc
@pytest.fixture
def db_instance_delta(mock_workspace_client):
return Databricks(
workspace_url="https://test",
access_token="tok",
endpoint_name="vs-endpoint",
catalog="catalog",
schema="schema",
table_name="table",
collection_name="mem0",
warehouse_name="test-warehouse",
index_type=VectorIndexType.DELTA_SYNC,
embedding_model_endpoint_name="embedding-endpoint",
)
@pytest.fixture
def db_instance_direct(mock_workspace_client):
# For DIRECT_ACCESS we want table exists path to skip creation; adjust mock first
mock_workspace_client.tables.exists.return_value = SimpleNamespace(table_exists=True)
return Databricks(
workspace_url="https://test",
access_token="tok",
endpoint_name="vs-endpoint",
catalog="catalog",
schema="schema",
table_name="table",
collection_name="mem0",
warehouse_name="test-warehouse",
index_type=VectorIndexType.DIRECT_ACCESS,
embedding_dimension=4,
embedding_model_endpoint_name="embedding-endpoint",
)
# ---------------------- Initialization Tests ---------------------- #
def test_initialization_delta_sync(db_instance_delta, mock_workspace_client):
# Endpoint ensure called (first attempt get_endpoint fails then create)
mock_workspace_client.vector_search_endpoints.create_endpoint_and_wait.assert_called_once()
# Table creation sequence
mock_workspace_client.tables.create.assert_called_once()
# Index created with expected args
assert (
mock_workspace_client.vector_search_indexes.create_index.call_args.kwargs["index_type"]
== VectorIndexType.DELTA_SYNC
)
assert mock_workspace_client.vector_search_indexes.create_index.call_args.kwargs["primary_key"] == "memory_id"
def test_initialization_direct_access(db_instance_direct, mock_workspace_client):
# DIRECT_ACCESS should include embedding column
assert "embedding" in db_instance_direct.column_names
assert (
mock_workspace_client.vector_search_indexes.create_index.call_args.kwargs["index_type"]
== VectorIndexType.DIRECT_ACCESS
)
def test_create_col_invalid_type(mock_workspace_client):
# Force invalid type by manually constructing and calling create_col after monkeypatching index_type
inst = Databricks(
workspace_url="https://test",
access_token="tok",
endpoint_name="vs-endpoint",
catalog="catalog",
schema="schema",
table_name="table",
collection_name="mem0",
warehouse_name="test-warehouse",
index_type=VectorIndexType.DELTA_SYNC,
)
inst.index_type = "BAD_TYPE"
with pytest.raises(ValueError):
inst.create_col()
# ---------------------- Insert Tests ---------------------- #
def test_insert_generates_sql(db_instance_direct, mock_workspace_client):
vectors = [[0.1, 0.2, 0.3, 0.4]]
payloads = [
{
"data": "hello world",
"user_id": "u1",
"agent_id": "a1",
"run_id": "r1",
"metadata": '{"topic":"greeting"}',
"hash": "h1",
}
]
ids = ["id1"]
db_instance_direct.insert(vectors=vectors, payloads=payloads, ids=ids)
args, kwargs = mock_workspace_client.statement_execution.execute_statement.call_args
sql = kwargs["statement"] if "statement" in kwargs else args[0]
assert "INSERT INTO" in sql
assert "catalog.schema.table" in sql
# Embedding list rendered inline (ARRAY type not supported by parameterized queries)
assert "array(0.1, 0.2, 0.3, 0.4)" in sql
# User-supplied values should use parameterized queries, not inline values
assert ":memory_id_0" in sql
assert "id1" not in sql # id should be in params, not in SQL
params = kwargs["parameters"]
param_names = {p.name for p in params}
assert "memory_id_0" in param_names
id_param = next(p for p in params if p.name == "memory_id_0")
assert id_param.value == "id1"
# ---------------------- Search Tests ---------------------- #
def test_search_delta_sync_text(db_instance_delta, mock_workspace_client):
# Simulate query results
row = [
"id1",
"hash1",
"agent1",
"run1",
"user1",
"memory text",
'{"topic":"greeting"}',
"2024-01-01T00:00:00",
"2024-01-01T00:00:00",
0.42,
]
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(data_array=[row])
)
results = db_instance_delta.search(query="hello", vectors=None, limit=1)
mock_workspace_client.vector_search_indexes.query_index.assert_called_once()
assert len(results) == 1
assert results[0].id == "id1"
assert results[0].score == 0.42
assert results[0].payload["data"] == "memory text"
def test_search_direct_access_vector(db_instance_direct, mock_workspace_client):
row = [
"id2",
"hash2",
"agent2",
"run2",
"user2",
"memory two",
'{"topic":"info"}',
"2024-01-02T00:00:00",
"2024-01-02T00:00:00",
[0.1, 0.2, 0.3, 0.4],
0.77,
]
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(data_array=[row])
)
results = db_instance_direct.search(query="", vectors=[0.1, 0.2, 0.3, 0.4], limit=1)
assert len(results) == 1
assert results[0].id == "id2"
assert results[0].score == 0.77
def test_search_delta_sync_self_managed_vectors(mock_workspace_client):
"""DELTA_SYNC without embedding model endpoint should use query_vector, not query_text."""
mock_workspace_client.tables.exists.return_value = SimpleNamespace(table_exists=True)
# DELTA_SYNC without embedding_model_endpoint_name = self-managed vectors
inst = Databricks(
workspace_url="https://test",
access_token="tok",
endpoint_name="vs-endpoint",
catalog="catalog",
schema="schema",
table_name="table",
warehouse_name="test-warehouse",
index_type=VectorIndexType.DELTA_SYNC,
embedding_dimension=4,
# NOTE: no embedding_model_endpoint_name
)
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(data_array=[])
)
inst.search(query="ignored", vectors=[0.1, 0.2, 0.3, 0.4], limit=5)
call_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_vector" in call_kwargs
assert "query_text" not in call_kwargs
def test_search_missing_params_raises(db_instance_delta):
with pytest.raises(ValueError):
db_instance_delta.search(query="", vectors=[0.1, 0.2]) # DELTA_SYNC with model endpoint requires query text
# ---------------------- Delete Tests ---------------------- #
def test_delete_vector(db_instance_delta, mock_workspace_client):
db_instance_delta.delete("id-delete")
args, kwargs = mock_workspace_client.statement_execution.execute_statement.call_args
sql = kwargs.get("statement") or args[0]
assert "DELETE FROM" in sql
assert ":vector_id" in sql
assert "id-delete" not in sql # value should be in params, not in SQL
params = kwargs["parameters"]
assert len(params) == 1
assert params[0].name == "vector_id"
assert params[0].value == "id-delete"
# ---------------------- Update Tests ---------------------- #
def test_update_vector(db_instance_direct, mock_workspace_client):
db_instance_direct.update(
vector_id="id-upd",
vector=[0.4, 0.5, 0.6, 0.7],
payload={"custom": "val", "user_id": "skip"}, # user_id should be excluded
)
args, kwargs = mock_workspace_client.statement_execution.execute_statement.call_args
sql = kwargs.get("statement") or args[0]
assert "UPDATE" in sql
assert "embedding = [0.4, 0.5, 0.6, 0.7]" in sql
assert "custom = :payload_custom" in sql
assert ":vector_id" in sql
assert "id-upd" not in sql # value should be in params, not in SQL
assert "'val'" not in sql # value should be in params, not in SQL
assert "user_id" not in sql # excluded
params = kwargs["parameters"]
param_map = {p.name: p.value for p in params}
assert param_map["payload_custom"] == "val"
assert param_map["vector_id"] == "id-upd"
# ---------------------- Get Tests ---------------------- #
def test_get_vector(db_instance_delta, mock_workspace_client):
mock_workspace_client.vector_search_indexes.query_index.return_value = QueryVectorIndexResponse(
manifest=ResultManifest(columns=[
ColumnInfo(name="memory_id"),
ColumnInfo(name="hash"),
ColumnInfo(name="agent_id"),
ColumnInfo(name="run_id"),
ColumnInfo(name="user_id"),
ColumnInfo(name="memory"),
ColumnInfo(name="metadata"),
ColumnInfo(name="created_at"),
ColumnInfo(name="updated_at"),
ColumnInfo(name="score"),
]),
result=ResultData(
data_array=[
[
"id-get",
"h",
"a",
"r",
"u",
"some memory",
'{"tag":"x"}',
"2024-01-01T00:00:00",
"2024-01-01T00:00:00",
"0.99",
]
]
)
)
res = db_instance_delta.get("id-get")
assert res.id == "id-get"
assert res.payload["data"] == "some memory"
assert res.payload["tag"] == "x"
# DELTA_SYNC should use query_text, not query_vector
call_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_text" in call_kwargs
assert "query_vector" not in call_kwargs
def test_get_vector_direct_access(db_instance_direct, mock_workspace_client):
"""get() on a DIRECT_ACCESS index must use query_vector instead of query_text."""
mock_workspace_client.vector_search_indexes.query_index.return_value = QueryVectorIndexResponse(
manifest=ResultManifest(columns=[
ColumnInfo(name="memory_id"),
ColumnInfo(name="hash"),
ColumnInfo(name="agent_id"),
ColumnInfo(name="run_id"),
ColumnInfo(name="user_id"),
ColumnInfo(name="memory"),
ColumnInfo(name="metadata"),
ColumnInfo(name="created_at"),
ColumnInfo(name="updated_at"),
ColumnInfo(name="embedding"),
ColumnInfo(name="score"),
]),
result=ResultData(
data_array=[
[
"id-get-da",
"h",
"a",
"r",
"u",
"direct access memory",
'{"tag":"da"}',
"2024-01-01T00:00:00",
"2024-01-01T00:00:00",
[0.1, 0.2, 0.3, 0.4],
"0.88",
]
]
)
)
res = db_instance_direct.get("id-get-da")
assert res.id == "id-get-da"
assert res.payload["data"] == "direct access memory"
# DIRECT_ACCESS should use query_vector, not query_text
call_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_vector" in call_kwargs
assert "query_text" not in call_kwargs
assert call_kwargs["query_vector"] == [0.0] * 4 # embedding_dimension=4
# ---------------------- Collection Info / Listing Tests ---------------------- #
def test_list_cols(db_instance_delta, mock_workspace_client):
mock_workspace_client.vector_search_indexes.list_indexes.return_value = [
SimpleNamespace(name="catalog.schema.mem0"),
SimpleNamespace(name="catalog.schema.other"),
]
cols = db_instance_delta.list_cols()
assert "catalog.schema.mem0" in cols and "catalog.schema.other" in cols
def test_col_info(db_instance_delta):
info = db_instance_delta.col_info()
assert info["name"] == "mem0"
assert any(col.name == "memory_id" for col in info["fields"])
def test_list_memories(db_instance_delta, mock_workspace_client):
mock_workspace_client.vector_search_indexes.query_index.return_value = QueryVectorIndexResponse(
manifest=ResultManifest(columns=[
ColumnInfo(name="memory_id"),
ColumnInfo(name="hash"),
ColumnInfo(name="agent_id"),
ColumnInfo(name="run_id"),
ColumnInfo(name="user_id"),
ColumnInfo(name="memory"),
ColumnInfo(name="metadata"),
ColumnInfo(name="created_at"),
ColumnInfo(name="updated_at"),
ColumnInfo(name="score"),
]),
result=ResultData(
data_array=[
[
"id-get",
"h",
"a",
"r",
"u",
"some memory",
'{"tag":"x"}',
"2024-01-01T00:00:00",
"2024-01-01T00:00:00",
"0.99",
]
]
)
)
res = db_instance_delta.list(limit=1)
assert isinstance(res, list)
assert len(res[0]) == 1
assert res[0][0].id == "id-get"
# DELTA_SYNC should use query_text
call_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_text" in call_kwargs
assert "query_vector" not in call_kwargs
def test_list_memories_direct_access(db_instance_direct, mock_workspace_client):
"""list() on a DIRECT_ACCESS index must use query_vector instead of query_text."""
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(
data_array=[
[
"id-da-list",
"h",
"a",
"r",
"u",
"direct memory",
None,
"2024-01-01T00:00:00",
"2024-01-01T00:00:00",
[0.1, 0.2, 0.3, 0.4],
]
]
)
)
res = db_instance_direct.list(limit=5)
assert isinstance(res, list)
assert len(res[0]) == 1
assert res[0][0].id == "id-da-list"
assert res[0][0].payload["data"] == "direct memory"
# DIRECT_ACCESS should use query_vector
call_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_vector" in call_kwargs
assert "query_text" not in call_kwargs
assert call_kwargs["query_vector"] == [0.0] * 4
def test_get_vector_delta_sync_self_managed(mock_workspace_client):
"""get() on DELTA_SYNC without model endpoint should use query_vector."""
mock_workspace_client.tables.exists.return_value = SimpleNamespace(table_exists=True)
inst = Databricks(
workspace_url="https://test",
access_token="tok",
endpoint_name="vs-endpoint",
catalog="catalog",
schema="schema",
table_name="table",
warehouse_name="test-warehouse",
index_type=VectorIndexType.DELTA_SYNC,
embedding_dimension=4,
# NOTE: no embedding_model_endpoint_name
)
mock_workspace_client.vector_search_indexes.query_index.return_value = QueryVectorIndexResponse(
manifest=ResultManifest(columns=[
ColumnInfo(name="memory_id"), ColumnInfo(name="hash"),
ColumnInfo(name="agent_id"), ColumnInfo(name="run_id"),
ColumnInfo(name="user_id"), ColumnInfo(name="memory"),
ColumnInfo(name="metadata"), ColumnInfo(name="created_at"),
ColumnInfo(name="updated_at"),
]),
result=ResultData(data_array=[["id-sm", "h", None, None, None, "self-managed mem", None, None, None]]),
)
res = inst.get("id-sm")
assert res.id == "id-sm"
call_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_vector" in call_kwargs
assert "query_text" not in call_kwargs
assert call_kwargs["query_vector"] == [0.0] * 4
def test_list_memories_delta_sync_self_managed(mock_workspace_client):
"""list() on DELTA_SYNC without model endpoint should use query_vector."""
mock_workspace_client.tables.exists.return_value = SimpleNamespace(table_exists=True)
inst = Databricks(
workspace_url="https://test",
access_token="tok",
endpoint_name="vs-endpoint",
catalog="catalog",
schema="schema",
table_name="table",
warehouse_name="test-warehouse",
index_type=VectorIndexType.DELTA_SYNC,
embedding_dimension=4,
# NOTE: no embedding_model_endpoint_name
)
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(data_array=[])
)
inst.list(limit=5)
call_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_vector" in call_kwargs
assert "query_text" not in call_kwargs
def test_list_memories_default_limit(db_instance_delta, mock_workspace_client):
"""list() with no limit should default to 100."""
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(data_array=[])
)
db_instance_delta.list(limit=None)
call_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert call_kwargs["num_results"] == 100
# ---------------------- Table Creation Tests ---------------------- #
def test_ensure_source_table_uses_dynamic_names(mock_workspace_client):
"""Verify _ensure_source_table_exists uses self.fully_qualified_table_name and
self.table_name for the PK constraint, not hardcoded values."""
mock_workspace_client.tables.exists.return_value = SimpleNamespace(table_exists=False)
Databricks(
workspace_url="https://test",
access_token="tok",
endpoint_name="vs-endpoint",
catalog="my_catalog",
schema="my_schema",
table_name="my_memories",
collection_name="my_index",
warehouse_name="test-warehouse",
index_type=VectorIndexType.DELTA_SYNC,
embedding_model_endpoint_name="embedding-endpoint",
)
# _ensure_source_table_exists was called during __init__ via create_col
constraint_call = mock_workspace_client.table_constraints.create.call_args
assert constraint_call.kwargs["full_name_arg"] == "my_catalog.my_schema.my_memories"
pk_name = constraint_call.kwargs["constraint"].primary_key_constraint.name
assert pk_name == "pk_my_memories"
# ---------------------- Config Validation Tests ---------------------- #
def test_config_rejects_old_doc_params():
"""Config should reject the old documentation parameter names like index_name and source_table_name."""
from mem0.configs.vector_stores.databricks import DatabricksConfig
with pytest.raises(ValueError, match="Extra fields not allowed"):
DatabricksConfig(
workspace_url="https://test",
access_token="tok",
endpoint_name="ep",
catalog="cat",
schema="sch",
table_name="tbl",
index_name="catalog.schema.index", # old param from docs
)
def test_config_rejects_source_table_name():
"""Config should reject source_table_name which was in old docs."""
from mem0.configs.vector_stores.databricks import DatabricksConfig
with pytest.raises(ValueError, match="Extra fields not allowed"):
DatabricksConfig(
workspace_url="https://test",
access_token="tok",
endpoint_name="ep",
catalog="cat",
schema="sch",
table_name="tbl",
source_table_name="catalog.schema.table", # old param from docs
)
def test_config_accepts_correct_params():
"""Config should accept all the correct parameter names."""
from mem0.configs.vector_stores.databricks import DatabricksConfig
config = DatabricksConfig(
workspace_url="https://test",
access_token="tok",
endpoint_name="ep",
catalog="cat",
schema="sch",
table_name="tbl",
collection_name="my_index",
embedding_dimension=768,
)
assert config.collection_name == "my_index"
assert config.embedding_dimension == 768
# ---------------------- Reset Tests ---------------------- #
def test_reset(db_instance_delta, mock_workspace_client):
# Make delete raise to exercise fallback path then allow recreation
mock_workspace_client.vector_search_indexes.delete_index.side_effect = [Exception("fail fq"), None, None]
with patch.object(db_instance_delta, "create_col", wraps=db_instance_delta.create_col) as create_spy:
db_instance_delta.reset()
assert create_spy.called
# ---------------------- End-to-End Config → Factory → CRUD Tests ---------------------- #
def test_e2e_config_to_factory_delta_sync(mock_workspace_client):
"""End-to-end: VectorStoreConfig validates docs-correct params, factory creates Databricks instance."""
from mem0.vector_stores.configs import VectorStoreConfig
from mem0.utils.factory import VectorStoreFactory
# Step 1: Config validation (simulates what Memory.from_config does)
vs_config = VectorStoreConfig(
provider="databricks",
config={
"workspace_url": "https://my-workspace.databricks.com",
"access_token": "my-token",
"endpoint_name": "my-endpoint",
"catalog": "prod_catalog",
"schema": "ai_schema",
"table_name": "memories_table",
"collection_name": "my_index",
"embedding_dimension": 768,
"warehouse_name": "test-warehouse",
},
)
assert vs_config.config.collection_name == "my_index"
assert vs_config.config.catalog == "prod_catalog"
# Step 2: Factory instantiation (same as MemoryBase.__init__)
instance = VectorStoreFactory.create("databricks", vs_config.config)
assert isinstance(instance, Databricks)
assert instance.fully_qualified_table_name == "prod_catalog.ai_schema.memories_table"
assert instance.fully_qualified_index_name == "prod_catalog.ai_schema.my_index"
assert instance.embedding_dimension == 768
def test_e2e_config_to_factory_direct_access(mock_workspace_client):
"""End-to-end: DIRECT_ACCESS via config → factory creates correct instance."""
from mem0.vector_stores.configs import VectorStoreConfig
from mem0.utils.factory import VectorStoreFactory
mock_workspace_client.tables.exists.return_value = SimpleNamespace(table_exists=True)
vs_config = VectorStoreConfig(
provider="databricks",
config={
"workspace_url": "https://my-workspace.databricks.com",
"access_token": "my-token",
"endpoint_name": "my-endpoint",
"catalog": "cat",
"schema": "sch",
"table_name": "tbl",
"index_type": "DIRECT_ACCESS",
"embedding_dimension": 4,
"warehouse_name": "test-warehouse",
},
)
instance = VectorStoreFactory.create("databricks", vs_config.config)
assert isinstance(instance, Databricks)
assert "embedding" in instance.column_names
def test_e2e_old_docs_config_rejected():
"""End-to-end: Config from old docs (with index_name, source_table_name) is rejected at validation."""
from mem0.vector_stores.configs import VectorStoreConfig
with pytest.raises(ValueError, match="Extra fields not allowed"):
VectorStoreConfig(
provider="databricks",
config={
"workspace_url": "https://my-workspace.databricks.com",
"access_token": "my-token",
"endpoint_name": "my-endpoint",
"index_name": "catalog.schema.index_name",
"source_table_name": "catalog.schema.source_table",
"embedding_dimension": 1536,
},
)
def test_e2e_crud_lifecycle_delta_sync(mock_workspace_client):
"""End-to-end CRUD lifecycle: insert → search → get → list → update → delete."""
from mem0.vector_stores.configs import VectorStoreConfig
from mem0.utils.factory import VectorStoreFactory
vs_config = VectorStoreConfig(
provider="databricks",
config={
"workspace_url": "https://test",
"access_token": "tok",
"endpoint_name": "ep",
"catalog": "cat",
"schema": "sch",
"table_name": "tbl",
"warehouse_name": "test-warehouse",
"embedding_model_endpoint_name": "emb-ep",
},
)
db = VectorStoreFactory.create("databricks", vs_config.config)
# INSERT
db.insert(
vectors=[[0.1, 0.2]],
payloads=[{"data": "test memory", "user_id": "u1", "hash": "h1"}],
ids=["mem-001"],
)
insert_call = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
insert_sql = insert_call["statement"]
assert "INSERT INTO cat.sch.tbl" in insert_sql
assert ":memory_id_0" in insert_sql # parameterized, not inline
insert_params = {p.name: p.value for p in insert_call["parameters"]}
assert insert_params["memory_id_0"] == "mem-001"
# SEARCH
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(
data_array=[["mem-001", "h1", None, None, "u1", "test memory", None, None, None, 0.95]]
)
)
results = db.search(query="test", vectors=None, limit=5)
assert len(results) == 1
assert results[0].id == "mem-001"
assert results[0].payload["data"] == "test memory"
search_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert search_kwargs["query_text"] == "test"
# GET
mock_workspace_client.vector_search_indexes.query_index.return_value = QueryVectorIndexResponse(
manifest=ResultManifest(columns=[
ColumnInfo(name="memory_id"), ColumnInfo(name="hash"),
ColumnInfo(name="agent_id"), ColumnInfo(name="run_id"),
ColumnInfo(name="user_id"), ColumnInfo(name="memory"),
ColumnInfo(name="metadata"), ColumnInfo(name="created_at"),
ColumnInfo(name="updated_at"),
]),
result=ResultData(data_array=[["mem-001", "h1", None, None, "u1", "test memory", None, None, None]]),
)
got = db.get("mem-001")
assert got.id == "mem-001"
assert got.payload["data"] == "test memory"
get_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_text" in get_kwargs
assert "query_vector" not in get_kwargs
# LIST
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(
data_array=[["mem-001", "h1", None, None, "u1", "test memory", None, None, None]]
)
)
listed = db.list(filters={"user_id": "u1"}, limit=10)
assert len(listed[0]) == 1
assert listed[0][0].id == "mem-001"
list_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_text" in list_kwargs
assert list_kwargs["num_results"] == 10
# UPDATE
db.update(vector_id="mem-001", payload={"memory": "updated memory"})
update_call = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
update_sql = update_call["statement"]
assert "UPDATE cat.sch.tbl" in update_sql
assert ":vector_id" in update_sql
assert ":payload_memory" in update_sql
update_params = {p.name: p.value for p in update_call["parameters"]}
assert update_params["vector_id"] == "mem-001"
assert update_params["payload_memory"] == "updated memory"
# DELETE
db.delete("mem-001")
delete_call = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
delete_sql = delete_call["statement"]
assert "DELETE FROM cat.sch.tbl" in delete_sql
assert ":vector_id" in delete_sql
delete_params = {p.name: p.value for p in delete_call["parameters"]}
assert delete_params["vector_id"] == "mem-001"
def test_e2e_crud_lifecycle_direct_access(mock_workspace_client):
"""End-to-end CRUD lifecycle for DIRECT_ACCESS: insert → search → get → list."""
from mem0.vector_stores.configs import VectorStoreConfig
from mem0.utils.factory import VectorStoreFactory
mock_workspace_client.tables.exists.return_value = SimpleNamespace(table_exists=True)
vs_config = VectorStoreConfig(
provider="databricks",
config={
"workspace_url": "https://test",
"access_token": "tok",
"endpoint_name": "ep",
"catalog": "cat",
"schema": "sch",
"table_name": "tbl",
"index_type": "DIRECT_ACCESS",
"embedding_dimension": 4,
"warehouse_name": "test-warehouse",
"embedding_model_endpoint_name": "emb-ep",
},
)
db = VectorStoreFactory.create("databricks", vs_config.config)
assert "embedding" in db.column_names
# INSERT with vector
db.insert(
vectors=[[0.1, 0.2, 0.3, 0.4]],
payloads=[{"data": "direct memory", "user_id": "u1", "hash": "h1"}],
ids=["mem-da-001"],
)
insert_call = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
insert_sql = insert_call["statement"]
assert "array(0.1, 0.2, 0.3, 0.4)" in insert_sql
insert_params = {p.name: p.value for p in insert_call["parameters"]}
assert insert_params["memory_id_0"] == "mem-da-001"
# SEARCH with vector
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(
data_array=[["mem-da-001", "h1", None, None, "u1", "direct memory", None, None, None, [0.1, 0.2, 0.3, 0.4], 0.9]]
)
)
results = db.search(query="", vectors=[0.1, 0.2, 0.3, 0.4], limit=5)
assert len(results) == 1
search_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_vector" in search_kwargs
assert "query_text" not in search_kwargs
# GET — must use query_vector for DIRECT_ACCESS
mock_workspace_client.vector_search_indexes.query_index.return_value = QueryVectorIndexResponse(
manifest=ResultManifest(columns=[
ColumnInfo(name="memory_id"), ColumnInfo(name="hash"),
ColumnInfo(name="agent_id"), ColumnInfo(name="run_id"),
ColumnInfo(name="user_id"), ColumnInfo(name="memory"),
ColumnInfo(name="metadata"), ColumnInfo(name="created_at"),
ColumnInfo(name="updated_at"), ColumnInfo(name="embedding"),
]),
result=ResultData(data_array=[["mem-da-001", "h1", None, None, "u1", "direct memory", None, None, None, [0.1, 0.2, 0.3, 0.4]]]),
)
got = db.get("mem-da-001")
assert got.id == "mem-da-001"
get_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_vector" in get_kwargs
assert get_kwargs["query_vector"] == [0.0] * 4
# LIST — must use query_vector for DIRECT_ACCESS
mock_workspace_client.vector_search_indexes.query_index.return_value = SimpleNamespace(
result=SimpleNamespace(
data_array=[["mem-da-001", "h1", None, None, "u1", "direct memory", None, None, None, [0.1, 0.2, 0.3, 0.4]]]
)
)
db.list(limit=5)
list_kwargs = mock_workspace_client.vector_search_indexes.query_index.call_args.kwargs
assert "query_vector" in list_kwargs
assert "query_text" not in list_kwargs
# ---------------------- SQL Injection Prevention Tests (Issue #4073) ---------------------- #
SQLI_PAYLOADS = [
"'; DELETE FROM table; --",
"' OR '1'='1",
"'; DROP TABLE memories; --",
"1' UNION SELECT * FROM secrets --",
]
@pytest.mark.parametrize("malicious_id", SQLI_PAYLOADS)
def test_delete_prevents_sql_injection(db_instance_delta, mock_workspace_client, malicious_id):
"""Verify delete() uses parameterized queries so SQL injection payloads are never in the SQL string."""
db_instance_delta.delete(malicious_id)
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
# The malicious payload must NOT appear in the SQL statement itself
assert malicious_id not in sql
assert ":vector_id" in sql
# It should be safely passed as a parameter
assert kwargs["parameters"][0].value == malicious_id
@pytest.mark.parametrize("malicious_id", SQLI_PAYLOADS)
def test_update_prevents_sql_injection_in_vector_id(db_instance_delta, mock_workspace_client, malicious_id):
"""Verify update() parameterizes vector_id."""
db_instance_delta.update(vector_id=malicious_id, payload={"memory": "safe value"})
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
assert malicious_id not in sql
assert ":vector_id" in sql
param_map = {p.name: p.value for p in kwargs["parameters"]}
assert param_map["vector_id"] == malicious_id
@pytest.mark.parametrize("malicious_value", SQLI_PAYLOADS)
def test_update_prevents_sql_injection_in_payload(db_instance_delta, mock_workspace_client, malicious_value):
"""Verify update() parameterizes payload values."""
db_instance_delta.update(vector_id="safe-id", payload={"memory": malicious_value})
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
assert malicious_value not in sql
assert ":payload_memory" in sql
param_map = {p.name: p.value for p in kwargs["parameters"]}
assert param_map["payload_memory"] == malicious_value
@pytest.mark.parametrize("malicious_id", SQLI_PAYLOADS)
def test_insert_prevents_sql_injection_in_ids(db_instance_delta, mock_workspace_client, malicious_id):
"""Verify insert() parameterizes memory IDs."""
db_instance_delta.insert(
vectors=[[0.1, 0.2]],
payloads=[{"data": "test", "hash": "h1"}],
ids=[malicious_id],
)
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
assert malicious_id not in sql
assert ":memory_id_0" in sql
param_map = {p.name: p.value for p in kwargs["parameters"]}
assert param_map["memory_id_0"] == malicious_id
@pytest.mark.parametrize("malicious_data", SQLI_PAYLOADS)
def test_insert_prevents_sql_injection_in_payload_data(db_instance_delta, mock_workspace_client, malicious_data):
"""Verify insert() parameterizes payload data values."""
db_instance_delta.insert(
vectors=[[0.1, 0.2]],
payloads=[{"data": malicious_data, "hash": "h1"}],
ids=["safe-id"],
)
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
assert malicious_data not in sql
param_map = {p.name: p.value for p in kwargs["parameters"]}
assert param_map["memory_0"] == malicious_data
@pytest.mark.parametrize("malicious_key", [
"memory; DROP TABLE x--",
"col' OR '1'='1",
"valid_col; DELETE FROM t",
"col\nname",
])
def test_update_rejects_malicious_column_names(db_instance_delta, mock_workspace_client, malicious_key):
"""Verify update() skips payload keys that are not valid SQL identifiers."""
db_instance_delta.update(
vector_id="safe-id",
payload={malicious_key: "some value", "memory": "legit"},
)
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
# The malicious key must NOT appear as a column name in the SQL
assert malicious_key not in sql
# The legitimate key should still be present
assert ":payload_memory" in sql
def test_insert_multi_row(db_instance_delta, mock_workspace_client):
"""Verify multi-row insert generates unique parameter names per row."""
db_instance_delta.insert(
vectors=[[0.1, 0.2], [0.3, 0.4]],
payloads=[
{"data": "first memory", "hash": "h1"},
{"data": "second memory", "hash": "h2"},
],
ids=["id-1", "id-2"],
)
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
params = kwargs["parameters"]
param_map = {p.name: p.value for p in params}
# Each row should have distinct parameter names
assert ":memory_id_0" in sql
assert ":memory_id_1" in sql
assert param_map["memory_id_0"] == "id-1"
assert param_map["memory_id_1"] == "id-2"
assert param_map["memory_0"] == "first memory"
assert param_map["memory_1"] == "second memory"
def test_insert_with_none_values(db_instance_delta, mock_workspace_client):
"""Verify insert() uses NULL literal for None values, not parameters."""
db_instance_delta.insert(
vectors=[[0.1, 0.2]],
payloads=[{"data": "test", "hash": "h1"}], # agent_id, run_id etc will be None
ids=["id-1"],
)
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
assert "NULL" in sql # None values should be literal NULL
# Only non-None values should have parameters
param_names = {p.name for p in kwargs["parameters"]}
assert "memory_id_0" in param_names
assert "memory_0" in param_names
assert "hash_0" in param_names
def test_update_payload_only(db_instance_delta, mock_workspace_client):
"""Verify update() works with payload only (no vector)."""
db_instance_delta.update(vector_id="id-1", payload={"memory": "new text"})
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
assert "UPDATE" in sql
assert "embedding" not in sql
assert ":payload_memory" in sql
assert ":vector_id" in sql
def test_update_vector_only(db_instance_direct, mock_workspace_client):
"""Verify update() works with vector only (no payload)."""
db_instance_direct.update(vector_id="id-1", vector=[0.1, 0.2, 0.3, 0.4])
kwargs = mock_workspace_client.statement_execution.execute_statement.call_args.kwargs
sql = kwargs["statement"]
assert "UPDATE" in sql
assert "embedding = [0.1, 0.2, 0.3, 0.4]" in sql
assert ":vector_id" in sql
param_map = {p.name: p.value for p in kwargs["parameters"]}
assert param_map["vector_id"] == "id-1"
assert len(kwargs["parameters"]) == 1 # only vector_id param