@an5/orm 1.3.1 → 1.3.2

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.
@@ -968,9 +968,22 @@ namespace An5Orm
968
968
  {
969
969
  try
970
970
  {
971
- if (rawVal is string jsonStr)
971
+ if (rawVal is List<double> dblList)
972
972
  {
973
- rowVector = ParseDoubleArray(jsonStr);
973
+ rowVector = dblList;
974
+ }
975
+ else
976
+ {
977
+ var decoded = DecodeVectorColumn(rawVal);
978
+ if (decoded != null)
979
+ {
980
+ rowVector = new List<double>(decoded.Length);
981
+ for (int i = 0; i < decoded.Length; i++) rowVector.Add(decoded[i]);
982
+ }
983
+ else if (rawVal is string jsonStr)
984
+ {
985
+ rowVector = ParseDoubleArray(jsonStr);
986
+ }
974
987
  }
975
988
  }
976
989
  catch { }
@@ -978,8 +991,31 @@ namespace An5Orm
978
991
 
979
992
  if (rowVector != null && rowVector.Count == vector.Count)
980
993
  {
981
- double sim = CosineSimilarity(vector, rowVector);
982
- double distance = distanceMetric.Equals("cosine", StringComparison.OrdinalIgnoreCase) ? (1.0 - sim) : sim;
994
+ double distance;
995
+ if (distanceMetric.Equals("euclidean", StringComparison.OrdinalIgnoreCase) || distanceMetric.Equals("l2", StringComparison.OrdinalIgnoreCase))
996
+ {
997
+ double sum = 0.0;
998
+ for (int i = 0; i < vector.Count; i++)
999
+ {
1000
+ double diff = vector[i] - rowVector[i];
1001
+ sum += diff * diff;
1002
+ }
1003
+ distance = Math.Sqrt(sum);
1004
+ }
1005
+ else if (distanceMetric.Equals("dot", StringComparison.OrdinalIgnoreCase) || distanceMetric.Equals("inner_product", StringComparison.OrdinalIgnoreCase) || distanceMetric.Equals("ip", StringComparison.OrdinalIgnoreCase))
1006
+ {
1007
+ double dot = 0.0;
1008
+ for (int i = 0; i < vector.Count; i++)
1009
+ {
1010
+ dot += vector[i] * rowVector[i];
1011
+ }
1012
+ distance = -dot;
1013
+ }
1014
+ else
1015
+ {
1016
+ double sim = CosineSimilarity(vector, rowVector);
1017
+ distance = 1.0 - sim;
1018
+ }
983
1019
  results.Add(Tuple.Create(row, distance));
984
1020
  }
985
1021
  }
@@ -1157,37 +1157,39 @@ func (c *TableClient[T]) VectorSearch(ctx context.Context, vector []float64, opt
1157
1157
  \tvecStr := string(vecBytes)
1158
1158
 
1159
1159
  \t// 1. Primary path: dialect-aware native SQL vector query
1160
- \tvar sqlQuery string
1161
- \tqueryArgs := append([]interface{}{vecStr}, whereQueryArgs...)
1162
- \tif c.Dialect == DialectPostgres {
1163
- \t\top := "<=>"
1164
- \t\tif strings.EqualFold(distanceMetric, "euclidean") { op = "<->" }
1165
- \t\tif strings.EqualFold(distanceMetric, "dot") { op = "<#>" }
1166
- \t\tvcol := c.quoteName(vectorField)
1167
- \t\tsqlQuery = fmt.Sprintf("SELECT *, (%s %s $1::vector) AS distance FROM %s", vcol, op, c.quoteTable())
1168
- \t\tif whereCl != "" {
1169
- \t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", vcol, whereCl)
1170
- \t\t} else {
1171
- \t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL", vcol)
1172
- \t\t}
1173
- \t\tsqlQuery += fmt.Sprintf(" ORDER BY distance ASC LIMIT %d", take)
1174
- \t} else {
1175
- \t\tvcol := c.quoteName(vectorField)
1176
- \t\tsqlQuery = fmt.Sprintf(
1177
- \t\t\t"SELECT TOP (%d) *, VECTOR_DISTANCE('%s', CAST(%s AS VECTOR(%d, float32)), CAST(? AS VECTOR(%d, float32))) AS distance FROM %s WITH (NOLOCK)",
1178
- \t\t\ttake, distanceMetric, vcol, dim, dim, c.quoteTable(),
1179
- \t\t)
1180
- \t\tif whereCl != "" {
1181
- \t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", vcol, whereCl)
1160
+ \tif c.Dialect != DialectSqlite {
1161
+ \t\tvar sqlQuery string
1162
+ \t\tqueryArgs := append([]interface{}{vecStr}, whereQueryArgs...)
1163
+ \t\tif c.Dialect == DialectPostgres {
1164
+ \t\t\top := "<=>"
1165
+ \t\t\tif strings.EqualFold(distanceMetric, "euclidean") { op = "<->" }
1166
+ \t\t\tif strings.EqualFold(distanceMetric, "dot") { op = "<#>" }
1167
+ \t\t\tvcol := c.quoteName(vectorField)
1168
+ \t\t\tsqlQuery = fmt.Sprintf("SELECT *, (%s %s $1::vector) AS distance FROM %s", vcol, op, c.quoteTable())
1169
+ \t\t\tif whereCl != "" {
1170
+ \t\t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", vcol, whereCl)
1171
+ \t\t\t} else {
1172
+ \t\t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL", vcol)
1173
+ \t\t\t}
1174
+ \t\t\tsqlQuery += fmt.Sprintf(" ORDER BY distance ASC LIMIT %d", take)
1182
1175
  \t\t} else {
1183
- \t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL", vcol)
1176
+ \t\t\tvcol := c.quoteName(vectorField)
1177
+ \t\t\tsqlQuery = fmt.Sprintf(
1178
+ \t\t\t\t"SELECT TOP (%d) *, VECTOR_DISTANCE('%s', CAST(%s AS VECTOR(%d, float32)), CAST(? AS VECTOR(%d, float32))) AS distance FROM %s WITH (NOLOCK)",
1179
+ \t\t\t\ttake, distanceMetric, vcol, dim, dim, c.quoteTable(),
1180
+ \t\t\t)
1181
+ \t\t\tif whereCl != "" {
1182
+ \t\t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", vcol, whereCl)
1183
+ \t\t\t} else {
1184
+ \t\t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL", vcol)
1185
+ \t\t\t}
1186
+ \t\t\tsqlQuery += " ORDER BY distance ASC"
1184
1187
  \t\t}
1185
- \t\tsqlQuery += " ORDER BY distance ASC"
1186
- \t}
1187
1188
 
1188
- \tnativeRows, nativeErr := c.queryAndScan(ctx, sqlQuery, queryArgs...)
1189
- \tif nativeErr == nil {
1190
- \t\treturn nativeRows, nil
1189
+ \t\tnativeRows, nativeErr := c.queryAndScan(ctx, sqlQuery, queryArgs...)
1190
+ \t\tif nativeErr == nil {
1191
+ \t\t\treturn nativeRows, nil
1192
+ \t\t}
1191
1193
  \t}
1192
1194
 
1193
1195
  \t// 2. Fallback: in-memory similarity
@@ -986,9 +986,22 @@ namespace An5Orm
986
986
  {
987
987
  try
988
988
  {
989
- if (rawVal is string jsonStr)
989
+ if (rawVal is List<double> dblList)
990
990
  {
991
- rowVector = ParseDoubleArray(jsonStr);
991
+ rowVector = dblList;
992
+ }
993
+ else
994
+ {
995
+ var decoded = DecodeVectorColumn(rawVal);
996
+ if (decoded != null)
997
+ {
998
+ rowVector = new List<double>(decoded.Length);
999
+ for (int i = 0; i < decoded.Length; i++) rowVector.Add(decoded[i]);
1000
+ }
1001
+ else if (rawVal is string jsonStr)
1002
+ {
1003
+ rowVector = ParseDoubleArray(jsonStr);
1004
+ }
992
1005
  }
993
1006
  }
994
1007
  catch { }
@@ -996,8 +1009,31 @@ namespace An5Orm
996
1009
 
997
1010
  if (rowVector != null && rowVector.Count == vector.Count)
998
1011
  {
999
- double sim = CosineSimilarity(vector, rowVector);
1000
- double distance = distanceMetric.Equals("cosine", StringComparison.OrdinalIgnoreCase) ? (1.0 - sim) : sim;
1012
+ double distance;
1013
+ if (distanceMetric.Equals("euclidean", StringComparison.OrdinalIgnoreCase) || distanceMetric.Equals("l2", StringComparison.OrdinalIgnoreCase))
1014
+ {
1015
+ double sum = 0.0;
1016
+ for (int i = 0; i < vector.Count; i++)
1017
+ {
1018
+ double diff = vector[i] - rowVector[i];
1019
+ sum += diff * diff;
1020
+ }
1021
+ distance = Math.Sqrt(sum);
1022
+ }
1023
+ else if (distanceMetric.Equals("dot", StringComparison.OrdinalIgnoreCase) || distanceMetric.Equals("inner_product", StringComparison.OrdinalIgnoreCase) || distanceMetric.Equals("ip", StringComparison.OrdinalIgnoreCase))
1024
+ {
1025
+ double dot = 0.0;
1026
+ for (int i = 0; i < vector.Count; i++)
1027
+ {
1028
+ dot += vector[i] * rowVector[i];
1029
+ }
1030
+ distance = -dot;
1031
+ }
1032
+ else
1033
+ {
1034
+ double sim = CosineSimilarity(vector, rowVector);
1035
+ distance = 1.0 - sim;
1036
+ }
1001
1037
  results.Add(Tuple.Create(row, distance));
1002
1038
  }
1003
1039
  }
@@ -1180,37 +1180,39 @@ func (c *TableClient[T]) VectorSearch(ctx context.Context, vector []float64, opt
1180
1180
  \tvecStr := string(vecBytes)
1181
1181
 
1182
1182
  \t// 1. Primary path: dialect-aware native SQL vector query
1183
- \tvar sqlQuery string
1184
- \tqueryArgs := append([]interface{}{vecStr}, whereQueryArgs...)
1185
- \tif c.Dialect == DialectPostgres {
1186
- \t\top := "<=>"
1187
- \t\tif strings.EqualFold(distanceMetric, "euclidean") { op = "<->" }
1188
- \t\tif strings.EqualFold(distanceMetric, "dot") { op = "<#>" }
1189
- \t\tvcol := c.quoteName(vectorField)
1190
- \t\tsqlQuery = fmt.Sprintf("SELECT *, (%s %s $1::vector) AS distance FROM %s", vcol, op, c.quoteTable())
1191
- \t\tif whereCl != "" {
1192
- \t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", vcol, whereCl)
1193
- \t\t} else {
1194
- \t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL", vcol)
1195
- \t\t}
1196
- \t\tsqlQuery += fmt.Sprintf(" ORDER BY distance ASC LIMIT %d", take)
1197
- \t} else {
1198
- \t\tvcol := c.quoteName(vectorField)
1199
- \t\tsqlQuery = fmt.Sprintf(
1200
- \t\t\t"SELECT TOP (%d) *, VECTOR_DISTANCE('%s', CAST(%s AS VECTOR(%d, float32)), CAST(? AS VECTOR(%d, float32))) AS distance FROM %s WITH (NOLOCK)",
1201
- \t\t\ttake, distanceMetric, vcol, dim, dim, c.quoteTable(),
1202
- \t\t)
1203
- \t\tif whereCl != "" {
1204
- \t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", vcol, whereCl)
1183
+ \tif c.Dialect != DialectSqlite {
1184
+ \t\tvar sqlQuery string
1185
+ \t\tqueryArgs := append([]interface{}{vecStr}, whereQueryArgs...)
1186
+ \t\tif c.Dialect == DialectPostgres {
1187
+ \t\t\top := "<=>"
1188
+ \t\t\tif strings.EqualFold(distanceMetric, "euclidean") { op = "<->" }
1189
+ \t\t\tif strings.EqualFold(distanceMetric, "dot") { op = "<#>" }
1190
+ \t\t\tvcol := c.quoteName(vectorField)
1191
+ \t\t\tsqlQuery = fmt.Sprintf("SELECT *, (%s %s $1::vector) AS distance FROM %s", vcol, op, c.quoteTable())
1192
+ \t\t\tif whereCl != "" {
1193
+ \t\t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", vcol, whereCl)
1194
+ \t\t\t} else {
1195
+ \t\t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL", vcol)
1196
+ \t\t\t}
1197
+ \t\t\tsqlQuery += fmt.Sprintf(" ORDER BY distance ASC LIMIT %d", take)
1205
1198
  \t\t} else {
1206
- \t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL", vcol)
1199
+ \t\t\tvcol := c.quoteName(vectorField)
1200
+ \t\t\tsqlQuery = fmt.Sprintf(
1201
+ \t\t\t\t"SELECT TOP (%d) *, VECTOR_DISTANCE('%s', CAST(%s AS VECTOR(%d, float32)), CAST(? AS VECTOR(%d, float32))) AS distance FROM %s WITH (NOLOCK)",
1202
+ \t\t\t\ttake, distanceMetric, vcol, dim, dim, c.quoteTable(),
1203
+ \t\t\t)
1204
+ \t\t\tif whereCl != "" {
1205
+ \t\t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", vcol, whereCl)
1206
+ \t\t\t} else {
1207
+ \t\t\t\tsqlQuery += fmt.Sprintf(" WHERE %s IS NOT NULL", vcol)
1208
+ \t\t\t}
1209
+ \t\t\tsqlQuery += " ORDER BY distance ASC"
1207
1210
  \t\t}
1208
- \t\tsqlQuery += " ORDER BY distance ASC"
1209
- \t}
1210
1211
 
1211
- \tnativeRows, nativeErr := c.queryAndScan(ctx, sqlQuery, queryArgs...)
1212
- \tif nativeErr == nil {
1213
- \t\treturn nativeRows, nil
1212
+ \t\tnativeRows, nativeErr := c.queryAndScan(ctx, sqlQuery, queryArgs...)
1213
+ \t\tif nativeErr == nil {
1214
+ \t\t\treturn nativeRows, nil
1215
+ \t\t}
1214
1216
  \t}
1215
1217
 
1216
1218
  \t// 2. Fallback: in-memory similarity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@an5/orm",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "The lightweight ORM for SQL Server with support for TypeScript, Python, .NET (C#), Go, Rust, Java, Kotlin, and Swift.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",