Skip to content

Commit 2b5d79c

Browse files
authored
Support double values as partition keys in CosmosDB (#797)
* Support double values as partition keys in CosmosDB * Fix dates evaluated at the server * Add GXCacheDataReader implementation for cosmosDB
1 parent b1a353d commit 2b5d79c

File tree

3 files changed

+165
-16
lines changed

3 files changed

+165
-16
lines changed

dotnet/src/dotnetcore/DynService/Cosmos/CosmosDBConnection.cs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
using System.Collections.Generic;
33
using System.Data;
44
using System.Data.Common;
5+
using System.Globalization;
56
using System.IO;
67
using System.Linq;
78
using System.Text;
89
using System.Text.Json.Nodes;
910
using System.Text.RegularExpressions;
1011
using System.Threading.Tasks;
12+
using GeneXus.Cache;
1113
using GeneXus.Data.Cosmos;
1214
using GeneXus.Data.NTier.CosmosDB;
1315
using log4net;
@@ -19,6 +21,10 @@ namespace GeneXus.Data.NTier
1921
public class CosmosDBService : GxService
2022
{
2123
public CosmosDBService(string id, string providerId) : base(id, providerId, typeof(CosmosDBConnection)){}
24+
public override IDataReader GetCacheDataReader(CacheItem item, bool computeSize, string keyCache)
25+
{
26+
return new GxCosmosDBCacheDataReader(item, computeSize, keyCache);
27+
}
2228
}
2329

2430
public class CosmosDBConnection : ServiceConnection
@@ -358,7 +364,6 @@ public override int ExecuteNonQuery(ServiceCursorDef cursorDef, IDataParameterCo
358364
}
359365
public override IDataReader ExecuteReader(ServiceCursorDef cursorDef, IDataParameterCollection parms, CommandBehavior behavior)
360366
{
361-
362367
Initialize();
363368
CosmosDBQuery query = cursorDef.Query as CosmosDBQuery;
364369
Container container = GetContainer(query?.TableName);
@@ -484,9 +489,25 @@ private void CreateCosmosQuery(CosmosDBQuery query,ServiceCursorDef cursorDef, I
484489
varValuestr = '"' + $"{item.Value.ToString()}" + '"';
485490
else
486491
{
487-
varValuestr = item.Value.ToString();
488-
varValuestr = varValuestr.Equals("True") ? "true" : varValuestr;
489-
varValuestr = varValuestr.Equals("False") ? "false" : varValuestr;
492+
if (GeneXus.Data.Cosmos.CosmosDBHelper.FormattedAsStringDateGXType(item.Type))
493+
{
494+
DateTime dt = DateTime.SpecifyKind((DateTime)item.Value, DateTimeKind.Utc);
495+
varValuestr = '"' + $"{dt.ToString(CosmosDBHelper.ISO_DATE_FORMAT)}" + '"';
496+
}
497+
else
498+
{
499+
if (item.Value is double)
500+
{
501+
double dValue = (double)item.Value;
502+
varValuestr = dValue.ToString(CultureInfo.InvariantCulture.NumberFormat);
503+
}
504+
else
505+
{
506+
varValuestr = item.Value.ToString();
507+
varValuestr = varValuestr.Equals("True") ? "true" : varValuestr;
508+
varValuestr = varValuestr.Equals("False") ? "false" : varValuestr;
509+
}
510+
}
490511
}
491512
filterProcess = filterProcess.Replace(string.Format($"{item.Name}:"), varValuestr);
492513
}
@@ -502,8 +523,24 @@ private void CreateCosmosQuery(CosmosDBQuery query,ServiceCursorDef cursorDef, I
502523
if (GeneXus.Data.Cosmos.CosmosDBHelper.FormattedAsStringDbType(p1.DbType))
503524
varValuestr = '"' + $"{p1.Value.ToString()}" + '"';
504525
else
505-
varValuestr = p1.Value.ToString();
506-
526+
if (GeneXus.Data.Cosmos.CosmosDBHelper.FormattedAsStringDateDbType(p1.DbType))
527+
{
528+
DateTime dt = DateTime.SpecifyKind((DateTime)p1.Value, DateTimeKind.Utc);
529+
varValuestr = '"' + $"{dt.ToString(CosmosDBHelper.ISO_DATE_FORMAT)}" + '"';
530+
}
531+
else
532+
if (p1.Value is double)
533+
{
534+
double dValue = (double)p1.Value;
535+
varValuestr = dValue.ToString(CultureInfo.InvariantCulture.NumberFormat);
536+
}
537+
else
538+
{
539+
varValuestr = p1.Value.ToString();
540+
varValuestr = varValuestr.Equals("True") ? "true" : varValuestr;
541+
varValuestr = varValuestr.Equals("False") ? "false" : varValuestr;
542+
}
543+
507544
filterProcess = filterProcess.Replace(string.Format($":{p1.ParameterName}:"), varValuestr);
508545
}
509546
}

dotnet/src/dotnetcore/DynService/Cosmos/CosmosDBDataReader.cs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Text.Json;
88
using System.Threading.Tasks;
9+
using GeneXus.Cache;
910
using GeneXus.Data.NTier;
1011
using GeneXus.Data.NTier.CosmosDB;
1112
using log4net;
@@ -189,9 +190,9 @@ public string GetDataTypeName(int i)
189190

190191
public DateTime GetDateTime(int i)
191192
{
192-
if (GetAttValue(i) is DateTime value)
193-
return value;
194-
return default(DateTime);
193+
DateTime.TryParseExact(GetAttValue(i).ToString(), CosmosDBHelper.ISO_DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out DateTime dt);
194+
return dt;
195+
195196
}
196197
public decimal GetDecimal(int i)
197198
{
@@ -344,5 +345,60 @@ public object GetValue(int i)
344345
return GetAttValue(i);
345346
}
346347
}
348+
public class GxCosmosDBCacheDataReader : GxCacheDataReader
349+
{
350+
public GxCosmosDBCacheDataReader(CacheItem cacheItem, bool computeSize, string keyCache)
351+
: base(cacheItem, computeSize, keyCache)
352+
{ }
353+
354+
public override DateTime GetDateTime(int i)
355+
{
356+
DateTime.TryParseExact(GetAttValue(i).ToString(), CosmosDBHelper.ISO_DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out DateTime dt);
357+
return dt;
358+
}
347359

360+
public override decimal GetDecimal(int i)
361+
{
362+
return Convert.ToDecimal(GetAttValue(i), CultureInfo.InvariantCulture);
363+
}
364+
365+
public override short GetInt16(int i)
366+
{
367+
return Convert.ToInt16(GetAttValue(i));
368+
}
369+
370+
public override int GetInt32(int i)
371+
{
372+
return Convert.ToInt32(GetAttValue(i));
373+
}
374+
375+
public override long GetInt64(int i)
376+
{
377+
return Convert.ToInt64(GetAttValue(i)); ;
378+
}
379+
public override bool GetBoolean(int i)
380+
{
381+
if (GetAttValue(i) is bool value)
382+
{
383+
return value;
384+
}
385+
return false;
386+
}
387+
public override string GetString(int i)
388+
{
389+
return GetAttValue(i).ToString();
390+
}
391+
public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
392+
{
393+
MemoryStream ms = (MemoryStream)GetAttValue(i);
394+
if (ms == null)
395+
return 0;
396+
ms.Seek(fieldOffset, SeekOrigin.Begin);
397+
return ms.Read(buffer, bufferoffset, length);
398+
}
399+
private object GetAttValue(int i)
400+
{
401+
return block.Item(pos, i);
402+
}
403+
}
348404
}

dotnet/src/dotnetcore/DynService/Cosmos/CosmosDBHelper.cs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Data;
4+
using System.Globalization;
45
using System.Linq;
56
using System.Text.Json.Nodes;
67
using GeneXus.Data.NTier;
@@ -10,19 +11,52 @@ namespace GeneXus.Data.Cosmos
1011
{
1112
internal class CosmosDBHelper
1213
{
14+
internal const string ISO_DATE_FORMAT = "yyyy-MM-ddTHH:mm:ss:fffK";
1315
internal static PartitionKey ToPartitionKey(object value)
1416
{
1517
if (value is double)
1618
return new PartitionKey((double)value);
1719
if (value is bool)
1820
return new PartitionKey((bool)value);
1921
if (value is string)
20-
return new PartitionKey((string)value);
22+
return new PartitionKey((string)value);
23+
if (value is decimal)
24+
{
25+
decimal valueDecimal = (decimal)value;
26+
try
27+
{
28+
double convertedDouble = (double)value;
29+
decimal convertedDecimal = (decimal)convertedDouble;
30+
31+
//Possible loss of precision when converting from decimal to double
32+
33+
if (valueDecimal != convertedDecimal)
34+
throw new Exception("Loss of precision converting from decimal to double. Partitionkey should be double.");
35+
else
36+
return new PartitionKey(convertedDouble);
37+
}
38+
catch
39+
{
40+
try
41+
{
42+
double convertedDouble = Convert.ToDouble(value);
43+
decimal convertedDecimal = Convert.ToDecimal(convertedDouble);
44+
if (valueDecimal != convertedDecimal)
45+
throw new Exception("Loss of precision converting from decimal to double. Partitionkey should be double.");
46+
else
47+
return new PartitionKey(convertedDouble);
48+
}
49+
catch
50+
{
51+
throw new Exception("Loss of precision converting from decimal to double. Partitionkey should be double.");
52+
}
53+
}
54+
}
2155
else
2256
throw new Exception("Partitionkey can be double, bool or string.");
2357
}
2458
internal static bool AddItemValue(string parmName, string fromName, Dictionary<string, object> values, IDataParameterCollection parms, IEnumerable<VarValue> queryVars, ref JsonObject jsonObject)
25-
{
59+
{
2660
if (!AddItemValue(parmName, values, parms[fromName] as ServiceParameter, ref jsonObject))
2761
{
2862
VarValue varValue = queryVars.FirstOrDefault(v => v.Name == $":{fromName}");
@@ -34,20 +68,36 @@ internal static bool AddItemValue(string parmName, string fromName, Dictionary<s
3468
jsonObject.Add(keyvalue);
3569
}
3670
else
37-
jsonObject.Add(parmName, JsonValue.Create(varValue.Value));
71+
if (FormattedAsStringDateGXType(varValue.Type))
72+
{
73+
DateTime dt = DateTime.SpecifyKind((DateTime)varValue.Value, DateTimeKind.Utc);
74+
jsonObject.Add(parmName, dt.ToString(ISO_DATE_FORMAT));
75+
}
76+
else
77+
jsonObject.Add(parmName, JsonValue.Create(varValue.Value));
3878
values[parmName] = varValue.Value;
3979
}
4080
return varValue != null;
4181
}
4282
return true;
4383
}
44-
public static bool FormattedAsStringGXType(GXType gXType)
84+
85+
internal static bool FormattedAsStringDateGXType(GXType gXType)
86+
{
87+
return (gXType == GXType.Date || gXType == GXType.DateTime || gXType == GXType.DateTime2);
88+
}
89+
internal static bool FormattedAsStringGXType(GXType gXType)
4590
{
46-
return (gXType == GXType.Date || gXType == GXType.DateTime || gXType == GXType.DateTime2 || gXType == GXType.VarChar || gXType == GXType.DateAsChar || gXType == GXType.NVarChar || gXType == GXType.LongVarChar || gXType == GXType.NChar || gXType == GXType.Char || gXType == GXType.Text || gXType == GXType.NText);
91+
return (gXType == GXType.VarChar || gXType == GXType.DateAsChar || gXType == GXType.NVarChar || gXType == GXType.LongVarChar || gXType == GXType.NChar || gXType == GXType.Char || gXType == GXType.Text || gXType == GXType.NText);
92+
}
93+
94+
internal static bool FormattedAsStringDateDbType(DbType dbType)
95+
{
96+
return (dbType == DbType.Date || dbType == DbType.DateTime || dbType == DbType.DateTime2 || dbType == DbType.DateTimeOffset || dbType == DbType.Time);
4797
}
4898
internal static bool FormattedAsStringDbType(DbType dbType)
4999
{
50-
return (dbType == DbType.String || dbType == DbType.Date || dbType == DbType.DateTime || dbType == DbType.DateTime2 || dbType == DbType.DateTimeOffset || dbType == DbType.StringFixedLength || dbType == DbType.AnsiString || dbType == DbType.AnsiStringFixedLength || dbType == DbType.Guid || dbType == DbType.Time);
100+
return (dbType == DbType.String || dbType == DbType.StringFixedLength || dbType == DbType.AnsiString || dbType == DbType.AnsiStringFixedLength || dbType == DbType.Guid);
51101
}
52102
internal static string FormatExceptionMessage(string statusCode, string message)
53103
{
@@ -65,7 +115,13 @@ internal static bool AddItemValue(string parmName, Dictionary<string, object> dy
65115
jsonObject.Add(keyvalue);
66116
}
67117
else
68-
jsonObject.Add(parmName, JsonValue.Create(parm.Value));
118+
if (FormattedAsStringDateDbType(parm.DbType))
119+
{
120+
DateTime dt = DateTime.SpecifyKind((DateTime)parm.Value, DateTimeKind.Utc);
121+
jsonObject.Add(parmName, dt.ToString(ISO_DATE_FORMAT));
122+
}
123+
else
124+
jsonObject.Add(parmName, JsonValue.Create(parm.Value));
69125
dynParm[parmName] = parm.Value;
70126
return true;
71127
}

0 commit comments

Comments
 (0)