Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
a1f4ae5
start on WRONGTYPE tests for Vector Sets and Vector Set commands
kevin-montrose Jul 10, 2026
aa87e1d
tiny bit of cleanup
kevin-montrose Jul 14, 2026
527e673
copilot generate most of the remaining tests; generally they all fail…
kevin-montrose Jul 14, 2026
3f879ef
stopgap commit; many more tests passing with minor changes; MGET is g…
kevin-montrose Jul 14, 2026
d72e8cd
MGET refactor to handle wrongtypes appropriately
kevin-montrose Jul 14, 2026
0471c6f
more tests passing
kevin-montrose Jul 14, 2026
ee48cc6
the relatively easy WRONGTYPEs are all working now
kevin-montrose Jul 15, 2026
cbda153
implement VCARD, as that's pretty easy and fixes a test
kevin-montrose Jul 15, 2026
b9259ee
implement VISMEMBER, likewise easy and fixes a test
kevin-montrose Jul 15, 2026
13775b2
sketch out VLINKS, but don't actually implement all the way - needs D…
kevin-montrose Jul 15, 2026
34f1442
sketch out VRANDMEMBER; same thing, not actually implemented as we ne…
kevin-montrose Jul 15, 2026
87deafd
similarly sketch out VSETATTR
kevin-montrose Jul 15, 2026
8146e5c
test and fix the various Garnet extension commands for Vector Set WRO…
kevin-montrose Jul 15, 2026
2ca0550
formatting
kevin-montrose Jul 15, 2026
65ff551
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 16, 2026
893b003
lay some groundwork for overwrite tests
kevin-montrose Jul 16, 2026
6bf29b6
have MSET do a transactional DEL+SET if blind SET fails with WRONGTYPE
kevin-montrose Jul 16, 2026
e324bcd
similar change to (P)SETEX
kevin-montrose Jul 16, 2026
42a4607
handle ETAG commands
kevin-montrose Jul 16, 2026
dbe040b
run overwriting tests in transactional contexts as well, since we nee…
kevin-montrose Jul 16, 2026
81928a3
correct local function naming
kevin-montrose Jul 16, 2026
d752d0d
formatting
kevin-montrose Jul 16, 2026
6f5462a
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 16, 2026
d4eb9de
rework BITFIELD and BITFIELD_RO to properly handle WRONGTYPE and (att…
kevin-montrose Jul 16, 2026
913d87e
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 16, 2026
0d4e6e5
update ACL test for newly implemented Vector Set instructions
kevin-montrose Jul 16, 2026
420301d
RI.Create should be tested for WRONGTYPE, and should WRONGTYPE on an …
kevin-montrose Jul 16, 2026
0958ba7
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 16, 2026
b6c9ab6
fix more tests
kevin-montrose Jul 17, 2026
b2f3ced
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 17, 2026
53285b1
expand overwrite tests to include post-eviction; all of those tests a…
kevin-montrose Jul 17, 2026
9b79589
test still fail, but force a checkpoint so we get truncate callbacks …
kevin-montrose Jul 17, 2026
f4eb597
hook up compaction callbacks, handle compaction triggering Vector Set…
kevin-montrose Jul 17, 2026
8524051
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 17, 2026
8b4258e
SET_Conditional promotes to transactional if needed
kevin-montrose Jul 20, 2026
99e72aa
correct wrongtype returns in SET_Conditional
kevin-montrose Jul 20, 2026
e139d68
MSET_Conditional is only called from MSETNX, and NX means we can neve…
kevin-montrose Jul 20, 2026
5b7ea19
BITOP also needs transactional promotion - note that half these tests…
kevin-montrose Jul 20, 2026
e3e6cea
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 20, 2026
8b46bf6
address feedback
kevin-montrose Jul 20, 2026
2b49be1
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 21, 2026
b14ffc0
hmm, seeing some failures in Tsavorite asserts; commenting out my add…
kevin-montrose Jul 21, 2026
5689a22
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 21, 2026
ac69268
comment out most of the IsDeleted callback too
kevin-montrose Jul 21, 2026
0d2d21a
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 22, 2026
cb18ca8
restore code in IsDeleted path
kevin-montrose Jul 22, 2026
0db9653
this is actually implemented
kevin-montrose Jul 22, 2026
d4c8296
these commands are fine on Vector Sets
kevin-montrose Jul 22, 2026
be1d732
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 22, 2026
b8a8696
cleanup
kevin-montrose Jul 22, 2026
bb23285
Merge branch 'users/kmontrose/vectorSetWrongTypeTests' of https://git…
kevin-montrose Jul 22, 2026
449fa5b
move compact callback session work onto task
kevin-montrose Jul 22, 2026
cf59190
shrink dummy data some to speed up tests - still needs to be pretty l…
kevin-montrose Jul 22, 2026
1f98056
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 22, 2026
6e72792
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 22, 2026
8806e66
restore BITOP in transaction tests for Vector Set overwrites
kevin-montrose Jul 22, 2026
a1da6bf
address feedback
kevin-montrose Jul 22, 2026
00c0566
Merge branch 'main' into users/kmontrose/vectorSetWrongTypeTests
kevin-montrose Jul 22, 2026
4de4733
formatting
kevin-montrose Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions libs/server/API/GarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@ public GarnetStatus IncrementByFloat(PinnedSpanByte key, ref StringOutput output
SessionParseState parseState = default;

var input = new StringInput(RespCommand.INCRBYFLOAT, ref parseState, BitConverter.DoubleToInt64Bits(val));
_ = Increment(key, ref input, ref output);
return GarnetStatus.OK;
return Increment(key, ref input, ref output);
}

/// <inheritdoc />
Expand All @@ -205,7 +204,13 @@ public GarnetStatus IncrementByFloat(PinnedSpanByte key, out double output, doub
Span<byte> outputBuffer = stackalloc byte[NumUtils.MaximumFormatDoubleLength + 1];
var stringOutput = StringOutput.FromPinnedSpan(outputBuffer);

_ = IncrementByFloat(key, ref stringOutput, val);
var res = IncrementByFloat(key, ref stringOutput, val);

if (res == GarnetStatus.WRONGTYPE)
{
output = 0;
return res;
}

if (!stringOutput.HasError)
{
Expand Down Expand Up @@ -372,6 +377,21 @@ public void ResetScratchBuffer()

#region VectorSet commands

/// <inheritdoc/>
public GarnetStatus VectorSetCardinality(PinnedSpanByte key, out long card)
=> storageSession.VectorSetCardinality(key, out card);

/// <inheritdoc/>
public GarnetStatus VectorSetIsMember(PinnedSpanByte key, PinnedSpanByte element)
=> storageSession.VectorSetIsMember(key, element);

/// <inheritdoc/>
public GarnetStatus VectorSetLinks(PinnedSpanByte key, PinnedSpanByte element, bool withScores, ref SpanByteAndMemory idResults, ref SpanByteAndMemory distanceResults)
=> storageSession.VectorSetLinks(key, element, withScores, ref idResults, ref distanceResults);

/// <inheritdoc/>
public GarnetStatus VectorSetRandomMembers(PinnedSpanByte key, int count, ref SpanByteAndMemory idResults)
=> storageSession.VectorSetRandomMembers(key, count, ref idResults);
/// <inheritdoc />
public unsafe GarnetStatus VectorSetAdd(PinnedSpanByte key, int reduceDims, VectorValueType valueType, PinnedSpanByte values, PinnedSpanByte element, VectorQuantType quantizer, int buildExplorationFactor, PinnedSpanByte attributes, int numLinks, VectorDistanceMetricType distanceMetric, out VectorManagerResult result, out ReadOnlySpan<byte> errorMsg)
=> storageSession.VectorSetAdd(key, reduceDims, valueType, values, element, quantizer, buildExplorationFactor, attributes, numLinks, distanceMetric, out result, out errorMsg);
Expand All @@ -380,6 +400,10 @@ public unsafe GarnetStatus VectorSetAdd(PinnedSpanByte key, int reduceDims, Vect
public unsafe GarnetStatus VectorSetRemove(PinnedSpanByte key, PinnedSpanByte element)
=> storageSession.VectorSetRemove(key, element);

/// <inheritdoc />
public GarnetStatus VectorSetSetAttribute(PinnedSpanByte key, PinnedSpanByte element, PinnedSpanByte attribute)
=> storageSession.VectorSetSetAttribute(key, element, attribute);

/// <inheritdoc />
public unsafe GarnetStatus VectorSetValueSimilarity(PinnedSpanByte key, VectorValueType valueType, PinnedSpanByte values, int count, float delta, int searchExplorationFactor, PinnedSpanByte filter, int maxFilteringEffort, bool includeAttributes, ref SpanByteAndMemory outputIds, out VectorIdFormat outputIdFormat, out ReadOnlySpan<byte> errorMessage, ref SpanByteAndMemory outputDistances, ref SpanByteAndMemory outputAttributes, out VectorManagerResult result, ref SpanByteAndMemory filterBitmap)
=> storageSession.VectorSetValueSimilarity(key, valueType, values, count, delta, searchExplorationFactor, filter.ReadOnlySpan, maxFilteringEffort, includeAttributes, ref outputIds, out outputIdFormat, out errorMessage, ref outputDistances, ref outputAttributes, out result, ref filterBitmap);
Expand Down
29 changes: 29 additions & 0 deletions libs/server/API/GarnetWatchApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,35 @@ public void ResetScratchBuffer()
#endregion

#region Vector Sets

/// <inheritdoc/>
public GarnetStatus VectorSetCardinality(PinnedSpanByte key, out long card)
{
garnetApi.WATCH(key, StoreType.Main);
return garnetApi.VectorSetCardinality(key, out card);
}

/// <inheritdoc/>
public GarnetStatus VectorSetIsMember(PinnedSpanByte key, PinnedSpanByte element)
{
garnetApi.WATCH(key, StoreType.Main);
return garnetApi.VectorSetIsMember(key, element);
}

/// <inheritdoc/>
public GarnetStatus VectorSetLinks(PinnedSpanByte key, PinnedSpanByte element, bool withScores, ref SpanByteAndMemory idResults, ref SpanByteAndMemory distanceResults)
{
garnetApi.WATCH(key, StoreType.Main);
return garnetApi.VectorSetLinks(key, element, withScores, ref idResults, ref distanceResults);
}

/// <inheritdoc/>
public GarnetStatus VectorSetRandomMembers(PinnedSpanByte key, int count, ref SpanByteAndMemory idResults)
{
garnetApi.WATCH(key, StoreType.Main);
return garnetApi.VectorSetRandomMembers(key, count, ref idResults);
}

/// <inheritdoc/>
public GarnetStatus VectorSetValueSimilarity(PinnedSpanByte key, VectorValueType valueType, PinnedSpanByte value, int count, float delta, int searchExplorationFactor, PinnedSpanByte filter, int maxFilteringEffort, bool includeAttributes, ref SpanByteAndMemory outputIds, out VectorIdFormat outputIdFormat, out ReadOnlySpan<byte> errorMessage, ref SpanByteAndMemory outputDistances, ref SpanByteAndMemory outputAttributes, out VectorManagerResult result, ref SpanByteAndMemory filterBitmap)
{
Expand Down
37 changes: 37 additions & 0 deletions libs/server/API/IGarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1340,6 +1340,11 @@ GarnetStatus RangeIndexMetrics(PinnedSpanByte key,
/// Remove a member from a vector set, if it is present and the key exists.
/// </summary>
GarnetStatus VectorSetRemove(PinnedSpanByte key, PinnedSpanByte element);

/// <summary>
/// Update attribute for element in a Vector Set.
/// </summary>
GarnetStatus VectorSetSetAttribute(PinnedSpanByte key, PinnedSpanByte element, PinnedSpanByte attribute);
#endregion
}

Expand Down Expand Up @@ -2120,6 +2125,38 @@ public bool IterateStore<TScanFunctions>(ref TScanFunctions scanFunctions, ref l

#region Vector Sets

/// <summary>
/// Count the number of vectors in a Vector Set.
/// </summary>
GarnetStatus VectorSetCardinality(PinnedSpanByte key, out long card);

/// <summary>
/// Returns <see cref="GarnetStatus.OK"/> if the given element exists in the given Vector Set.
///
/// Returns <see cref="GarnetStatus.NOTFOUND"/> if the given element is not in the Vector Set, or the Vector Set does not exist.
///
/// Returns <see cref="GarnetStatus.WRONGTYPE"/> if the given key exists, but is not a Vector set.
/// </summary>
GarnetStatus VectorSetIsMember(PinnedSpanByte key, PinnedSpanByte element);

/// <summary>
/// For a given element, find all neighbors and (optionally) the distance to those neighbors.
///
/// On success, <paramref name="idResults"/> has length prefixed element names, and <paramref name="distanceResults"/> (if <paramref name="withScores"/> is true) has a float for each of those elements.
/// </summary>
GarnetStatus VectorSetLinks(PinnedSpanByte key, PinnedSpanByte element, bool withScores, ref SpanByteAndMemory idResults, ref SpanByteAndMemory distanceResults);

/// <summary>
/// Fetch random elements from the given Vector Set.
///
/// If <paramref name="count"/> is &lt; 0 we allow duplicates, if <paramref name="count"/> &gt; 0 we remove duplicates.
///
/// It is OK to fetch fewer than the requested number of elements.
///
/// On success, <paramref name="idResults"/> has length prefixed element names.
/// </summary>
GarnetStatus VectorSetRandomMembers(PinnedSpanByte key, int count, ref SpanByteAndMemory idResults);

/// <summary>
/// Perform a similarity search given a vector and these parameters.
///
Expand Down
4 changes: 2 additions & 2 deletions libs/server/Databases/DatabaseManagerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ private async ValueTask DoCompactionAsync(GarnetDatabase db, int mainStoreMaxSeg
break;

case LogCompactionType.Scan:
storeLog.Compact<PinnedSpanByte, Empty, Empty>(untilAddress, CompactionType.Scan);
storeLog.Compact<PinnedSpanByte, Empty, Empty, GarnetRecordTriggers>(db.Store.StoreFunctions.recordTriggers, untilAddress, CompactionType.Scan);
if (compactionForceDelete)
{
await CompactionCommitAofAsync(db).ConfigureAwait(false);
Expand All @@ -454,7 +454,7 @@ private async ValueTask DoCompactionAsync(GarnetDatabase db, int mainStoreMaxSeg
break;

case LogCompactionType.Lookup:
storeLog.Compact<PinnedSpanByte, Empty, Empty>(untilAddress, CompactionType.Lookup);
storeLog.Compact<PinnedSpanByte, Empty, Empty, GarnetRecordTriggers>(db.Store.StoreFunctions.recordTriggers, untilAddress, CompactionType.Lookup);
if (compactionForceDelete)
{
await CompactionCommitAofAsync(db).ConfigureAwait(false);
Expand Down
4 changes: 2 additions & 2 deletions libs/server/Objects/ItemBroker/CollectionItemBroker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp
return false;

// Check for type match between the observer and the source object type
if ((GarnetObjectType)osObject.GarnetObject.Type != objectType)
if (statusOp == GarnetStatus.WRONGTYPE || (GarnetObjectType)osObject.GarnetObject.Type != objectType)
{
// Return a type mismatch result if we should fail on source object type mismatch
if (failOnSrcTypeMismatch)
Expand All @@ -570,7 +570,7 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp
dstObj = osDstObject.GarnetObject;

// If there is a destination object type mismatch, we should always return a type mismatch result
if ((GarnetObjectType)dstObj.Type != objectType)
if (dstStatusOp == GarnetStatus.WRONGTYPE || (GarnetObjectType)dstObj.Type != objectType)
{
result = CollectionItemResult.TypeMismatch;
return true;
Expand Down
6 changes: 6 additions & 0 deletions libs/server/Resp/AdminCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,9 @@ private bool NetworkHCOLLECT<TGarnetApi>(ref TGarnetApi storageApi)
while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend))
SendAndReset();
break;
case GarnetStatus.WRONGTYPE:
WriteError(CmdStrings.RESP_ERR_WRONG_TYPE);
break;
default:
while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_HCOLLECT_ALREADY_IN_PROGRESS, ref dcurr, dend))
SendAndReset();
Expand Down Expand Up @@ -700,6 +703,9 @@ private bool NetworkZCOLLECT<TGarnetApi>(ref TGarnetApi storageApi)
while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend))
SendAndReset();
break;
case GarnetStatus.WRONGTYPE:
WriteError(CmdStrings.RESP_ERR_WRONG_TYPE);
break;
default:
while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_ZCOLLECT_ALREADY_IN_PROGRESS, ref dcurr, dend))
SendAndReset();
Expand Down
42 changes: 39 additions & 3 deletions libs/server/Resp/ArrayCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,43 @@ private bool NetworkMSET<TGarnetApi>(ref TGarnetApi storageApi)
return AbortWithWrongNumberOfArguments(nameof(RespCommand.MSET));
}

for (int c = 0; c < parseState.Count; c += 2)
for (var c = 0; c < parseState.Count; c += 2)
{
var key = parseState.GetArgSliceByRef(c);
var val = parseState.GetArgSliceByRef(c + 1);
_ = storageApi.SET(key, val);

var res = storageApi.SET(key, val);

// Type already stored in key which requires an explicit delete
if (res == GarnetStatus.WRONGTYPE)
{
var createTransaction = false;
if (txnManager.state != TxnState.Running)
{
createTransaction = true;
txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Main | TransactionStoreTypes.Object);
txnManager.SaveKeyEntryToLock(key, LockType.Exclusive);
_ = txnManager.Run(true);
}

try
{
_ = storageSession.DELETE(key, ref storageSession.unifiedTransactionalContext);
_ = storageSession.SET(key, val, ref storageSession.stringTransactionalContext);
}
finally
{
if (createTransaction)
{
txnManager.Commit(true);
}
}
}
}

while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend))
SendAndReset();

return true;
}

Expand Down Expand Up @@ -452,7 +481,14 @@ private bool NetworkLCS<TGarnetApi>(ref TGarnetApi storageApi)
var output = GetStringOutput();
var status = storageApi.LCS(key1, key2, ref output, lenOnly, withIndices, withMatchLen, minMatchLen);

ProcessOutput(output.SpanByteAndMemory);
if (status == GarnetStatus.WRONGTYPE)
{
WriteError(CmdStrings.RESP_ERR_WRONG_TYPE);
}
else
{
ProcessOutput(output.SpanByteAndMemory);
}

return true;
}
Expand Down
Loading
Loading