Skip to content

Commit df3ea52

Browse files
authored
Support parsing & serialization of vectors in locales that use comma as decimal separator (#210)
1 parent 0c0f16d commit df3ea52

2 files changed

Lines changed: 27 additions & 24 deletions

File tree

src/Typesense/VectorSearchQuery.cs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Collections.ObjectModel;
4+
using System.Globalization;
45
using System.Linq;
6+
using System.Text;
57
using System.Text.RegularExpressions;
68

79
namespace Typesense;
@@ -93,6 +95,7 @@ public VectorQuery(float[] vector, string vectorFieldName, string? id = null, in
9395
ExtraParams = extraParams ?? new();
9496
}
9597

98+
private static readonly Regex VectorQueryStringRegex = new(@"(.+):\((\[.*?\])(\s*,[^)]+)*\)", RegexOptions.Compiled, TimeSpan.FromSeconds(1));
9699
/// <summary>
97100
/// Parses a query and initializes the related object members.
98101
/// </summary>
@@ -101,9 +104,7 @@ public VectorQuery(float[] vector, string vectorFieldName, string? id = null, in
101104
private void ParseQuery(string query)
102105
{
103106
// First parse the portion of the string inside the vec property - "vec:([0.96826, 0.94, 0.39557, 0.306488], k:100, flat_search_cutoff: 20)"
104-
var pattern = @"(.+):\((\[.*?\])(\s*,[^)]+)*\)";
105-
106-
var match = Regex.Match(query, pattern);
107+
var match = VectorQueryStringRegex.Match(query);
107108
if (!match.Success)
108109
throw new ArgumentException("Malformed vector query string.");
109110

@@ -121,10 +122,10 @@ private void ParseQuery(string query)
121122
{
122123
// Get the float array query portion
123124
_vector = vectorMatch
124-
.Split(",")
125+
.Split(',', StringSplitOptions.TrimEntries)
125126
.Select(x =>
126127
{
127-
if (!float.TryParse(x.Trim(), out float result))
128+
if (!float.TryParse(x, NumberStyles.Float, CultureInfo.InvariantCulture, out float result))
128129
throw new ArgumentException(
129130
"Malformed vector query string: one of the vector values is not a float.");
130131

@@ -134,14 +135,11 @@ private void ParseQuery(string query)
134135

135136
// Commas are always used as a delimiter inside the list of parameters
136137
var qParams = match.Groups[3].Value
137-
.Split(",")
138-
.Select(x => x.Trim())
139-
.Where(x => !string.IsNullOrEmpty(x))
140-
.ToList();
138+
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
141139

142140
foreach (var param in qParams)
143141
{
144-
var kvp = param.Split(':').Select(x => x.Trim()).ToArray();
142+
var kvp = param.Split(':', StringSplitOptions.TrimEntries);
145143

146144
if (kvp.Length != 2)
147145
throw new ArgumentException($"Malformed vector query string at parameter '{param}'");
@@ -186,29 +184,34 @@ private void ParseQuery(string query)
186184
/// <returns>The vector-search query string.</returns>
187185
public virtual string ToQuery()
188186
{
189-
var qParams = new List<string>();
190-
187+
StringBuilder queryStringBuilder = new(VectorFieldName);
188+
queryStringBuilder.Append(":([");
191189
// Float vector is required, even if empty
192-
var qry = string.Join(",", _vector);
193-
qParams.Add($"[{qry}]");
190+
for (var index = 0; index < _vector.Length; index++)
191+
{
192+
if (index != 0)
193+
queryStringBuilder.Append(',');
194+
queryStringBuilder.Append(_vector[index].ToString("R", CultureInfo.InvariantCulture));
195+
}
196+
queryStringBuilder.Append(']');
194197

195198
// All other parameters are optional
199+
// note that the only delimiter for all type/value pairs is a comma and there is no need to surround string values with quotations
196200
if (Id != null)
197-
qParams.Add($"id:{Id}");
201+
queryStringBuilder.Append(",id:").Append(Id);
198202

199203
if (K != null)
200-
qParams.Add($"k:{K}");
204+
queryStringBuilder.Append(",k:").Append(K);
201205

202206
if (FlatSearchCutoff != null)
203-
qParams.Add($"flat_search_cutoff:{FlatSearchCutoff}");
207+
queryStringBuilder.Append(",flat_search_cutoff:").Append(FlatSearchCutoff);
204208

205209
// Allow for additional parameters if provided
206-
qParams.AddRange(ExtraParams.Select(kvp => $"{kvp.Key}:{kvp.Value}"));
210+
foreach (var (key, value) in ExtraParams)
211+
queryStringBuilder.Append(',').Append(key).Append(':').Append(value);
207212

208-
// Build the final query - note that the only delimiter for all type/value pairs is a comma and there is no
209-
// need to surround string values with quotations
210-
var query = string.Join(",", qParams);
213+
queryStringBuilder.Append(')');
211214

212-
return $"{VectorFieldName}:({query})";
215+
return queryStringBuilder.ToString();
213216
}
214217
}

test/Typesense.Tests/TypesenseClientTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,7 +1629,7 @@ await _client.CreateDocument(
16291629
{
16301630
// vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)
16311631
VectorQuery = new(
1632-
vector: new float[] { 0.96826F, 0.94F, 0.39557F, 0.306488F },
1632+
vector: [0.96826F, 0.94F, 0.39557F, 0.306488F],
16331633
vectorFieldName: "vec",
16341634
k: 100)
16351635
};
@@ -1638,7 +1638,7 @@ await _client.CreateDocument(
16381638
var queryUsingQueryString = new MultiSearchParameters(COLLECTION_NAME, "*")
16391639
{
16401640
// vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)
1641-
VectorQuery = new("vec:([0.96826,0.94,0.39557,0.306488],k:100)")
1641+
VectorQuery = new("vec:([ 0.96826 , 0.94 , 0.39557,0.306488], k : 100 )")
16421642
};
16431643

16441644
var queryObjectResponse = await _client.MultiSearch<AddressVectorSearch>(queryUsingQueryObject);

0 commit comments

Comments
 (0)