diff --git a/dotnet/DotNetStandardClasses.sln b/dotnet/DotNetStandardClasses.sln index 94c5499b3..212c0e507 100644 --- a/dotnet/DotNetStandardClasses.sln +++ b/dotnet/DotNetStandardClasses.sln @@ -69,9 +69,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXGoogleCloud", "src\dotnet EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dynservice", "dynservice", "{A14C2C2C-ACE3-4712-A527-E4E5F02729FA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynService.Core", "src\dotnetframework\DynService.Core\DynService.Core.csproj", "{B0DEB9ED-9E0A-42E3-BBE1-9F70DD78DC93}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynService.Core", "src\dotnetcommon\DynService.Core\DynService.Core.csproj", "{B0DEB9ED-9E0A-42E3-BBE1-9F70DD78DC93}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynService.DynamoDB", "src\dotnetframework\DynService.Dynamo\DynService.DynamoDB.csproj", "{4DC1C813-E7C1-442E-B7D6-C9B932A55759}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynService.DynamoDB", "src\dotnetcommon\DynService.Dynamo\DynService.DynamoDB.csproj", "{4DC1C813-E7C1-442E-B7D6-C9B932A55759}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynService.Fabric", "src\dotnetframework\DynServiceFabric\DynService.Fabric.csproj", "{42D048EC-29C5-4553-B301-A6A5A76A7FB8}" EndProject @@ -119,8 +119,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetUnitTest", "test\DotN EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dynservice", "dynservice", "{79C9ECC6-2935-4C43-BF32-94698547F584}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynService.Core", "src\dotnetcore\DynService\Core\DynService.Core.csproj", "{E2FB8843-503A-4A22-923E-B12510233127}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynServiceOData", "src\dotnetcore\DynService\OData\DynServiceOData.csproj", "{2238FCB1-3A83-46C2-873E-82DA4266069D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "azurefunctionhandlers", "azurefunctionhandlers", "{BD804A75-9F3F-416C-BF6B-D3DF6C4A8DC0}" @@ -186,6 +184,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\.editorconfig = ..\.editorconfig EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dynservice", "dynservice", "{C264F34E-2CE0-4DCA-B22D-4155821BE611}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GxXsl", "src\dotnetcore\GxXsl\GxXsl.csproj", "{30E7E437-F9B0-42B8-9144-A8E8F972B462}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mocking", "mocking", "{5045873B-E7CF-4317-94C1-0EF8623D23FA}" @@ -394,10 +394,6 @@ Global {251CCD53-21B3-4EBE-8627-49885C4E98B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {251CCD53-21B3-4EBE-8627-49885C4E98B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {251CCD53-21B3-4EBE-8627-49885C4E98B9}.Release|Any CPU.Build.0 = Release|Any CPU - {E2FB8843-503A-4A22-923E-B12510233127}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2FB8843-503A-4A22-923E-B12510233127}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2FB8843-503A-4A22-923E-B12510233127}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2FB8843-503A-4A22-923E-B12510233127}.Release|Any CPU.Build.0 = Release|Any CPU {2238FCB1-3A83-46C2-873E-82DA4266069D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2238FCB1-3A83-46C2-873E-82DA4266069D}.Debug|Any CPU.Build.0 = Debug|Any CPU {2238FCB1-3A83-46C2-873E-82DA4266069D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -533,8 +529,8 @@ Global {F6372249-AF37-4455-B572-5BDF8DE2ACC8} = {F82842DA-F15E-49C5-993E-4C269818FF1F} {E9072D95-D116-4D4B-B981-46146BCDE052} = {F82842DA-F15E-49C5-993E-4C269818FF1F} {A14C2C2C-ACE3-4712-A527-E4E5F02729FA} = {F900A4AD-7249-41B4-B918-CB9E8C73747C} - {B0DEB9ED-9E0A-42E3-BBE1-9F70DD78DC93} = {A14C2C2C-ACE3-4712-A527-E4E5F02729FA} - {4DC1C813-E7C1-442E-B7D6-C9B932A55759} = {A14C2C2C-ACE3-4712-A527-E4E5F02729FA} + {B0DEB9ED-9E0A-42E3-BBE1-9F70DD78DC93} = {C264F34E-2CE0-4DCA-B22D-4155821BE611} + {4DC1C813-E7C1-442E-B7D6-C9B932A55759} = {C264F34E-2CE0-4DCA-B22D-4155821BE611} {42D048EC-29C5-4553-B301-A6A5A76A7FB8} = {A14C2C2C-ACE3-4712-A527-E4E5F02729FA} {9C521E63-7B6F-4AD3-ACB5-132175D9413A} = {A14C2C2C-ACE3-4712-A527-E4E5F02729FA} {277490D7-8834-4722-9084-E894F804C66A} = {2261B65E-3757-4E5B-9DCD-EAE8D1E236A3} @@ -557,7 +553,6 @@ Global {55297803-2DE8-48C4-AB65-9D19E77614E1} = {1D6F1776-FF4B-46C2-9B3D-BC46CCF049DC} {251CCD53-21B3-4EBE-8627-49885C4E98B9} = {1D6F1776-FF4B-46C2-9B3D-BC46CCF049DC} {79C9ECC6-2935-4C43-BF32-94698547F584} = {2261B65E-3757-4E5B-9DCD-EAE8D1E236A3} - {E2FB8843-503A-4A22-923E-B12510233127} = {79C9ECC6-2935-4C43-BF32-94698547F584} {2238FCB1-3A83-46C2-873E-82DA4266069D} = {79C9ECC6-2935-4C43-BF32-94698547F584} {BD804A75-9F3F-416C-BF6B-D3DF6C4A8DC0} = {C6AFB6A3-FF0B-4970-B1F1-10BCD3D932B2} {41E1D031-799F-484F-85DE-7A30AF1A6FBA} = {BD804A75-9F3F-416C-BF6B-D3DF6C4A8DC0} @@ -587,6 +582,7 @@ Global {911BC7CC-4A07-408F-BA20-8BAB5355BE84} = {C8590651-46B6-4681-A6A0-0850B5A9A697} {B59F76D8-FDB2-4F51-89DB-F15E9BDFF1DC} = {420E8A4A-11D9-42E9-BFB7-4325EA7330B8} {D97E17A4-C945-4BF3-957E-F73142C4C6D0} = {947E032A-C385-4586-96E3-FC7D2767F082} + {C264F34E-2CE0-4DCA-B22D-4155821BE611} = {F1E13DF4-9F50-41A2-9DC3-04B673B21032} {30E7E437-F9B0-42B8-9144-A8E8F972B462} = {2261B65E-3757-4E5B-9DCD-EAE8D1E236A3} {5045873B-E7CF-4317-94C1-0EF8623D23FA} = {C6AFB6A3-FF0B-4970-B1F1-10BCD3D932B2} {8E5A25F9-2D64-4742-8227-2A3C5816AFEC} = {5045873B-E7CF-4317-94C1-0EF8623D23FA} diff --git a/dotnet/src/dotnetcommon/DynService.Core/DynService.Core.csproj b/dotnet/src/dotnetcommon/DynService.Core/DynService.Core.csproj new file mode 100644 index 000000000..6d8118577 --- /dev/null +++ b/dotnet/src/dotnetcommon/DynService.Core/DynService.Core.csproj @@ -0,0 +1,21 @@ + + + net462;net6.0 + GeneXus.Data.NTier + GeneXus.Data.DynService.Core + CA1812 + Dynservice + GeneXus.DynService.Core + + + + NETCORE + + + + + + + + + \ No newline at end of file diff --git a/dotnet/src/dotnetframework/DynService.Core/DynServiceCommon.cs b/dotnet/src/dotnetcommon/DynService.Core/DynServiceCommon.cs similarity index 83% rename from dotnet/src/dotnetframework/DynService.Core/DynServiceCommon.cs rename to dotnet/src/dotnetcommon/DynService.Core/DynServiceCommon.cs index 77cf83643..695113439 100644 --- a/dotnet/src/dotnetframework/DynService.Core/DynServiceCommon.cs +++ b/dotnet/src/dotnetcommon/DynService.Core/DynServiceCommon.cs @@ -16,21 +16,20 @@ namespace GeneXus.Data.NTier { public class Query { - private object mDataStoreHelper; - public string TableName { get; set; } = String.Empty; - public string[] Projection { get; set; } = Array.Empty(); - public string[] OrderBys { get; set; } = Array.Empty(); - public string[] Filters { get; set; } = Array.Empty(); - public string[] AssignAtts { get; set; } = Array.Empty(); - public IODataMap2[] SelectList { get; set; } = Array.Empty(); - public VarValue[] Vars { get; set; } = Array.Empty(); + public IEnumerable Projection { get; set; } = Array.Empty(); + public IEnumerable OrderBys { get; set; } = Array.Empty(); + public IEnumerable Filters { get; set; } = Array.Empty(); + private List> mAssignAtts; + public IEnumerable> AssignAtts { get { return mAssignAtts ?? Array.Empty>() as IEnumerable>; } } + public IEnumerable SelectList { get; set; } = Array.Empty(); + + private List mVarValues; + public IEnumerable Vars { get { return (mVarValues ?? Array.Empty() as IEnumerable); } } public CursorType CursorType { get; set; } = CursorType.Select; - public Query(object dataStoreHelper) - { - mDataStoreHelper = dataStoreHelper; - } + public Query(object dataStoreHelper) { } + public Query For(string v) { TableName = v; @@ -54,9 +53,10 @@ public Query Filter(string[] filters) return this; } - public Query Set(string[] assignAtts) + public Query Set(string name, string value) { - AssignAtts = assignAtts; + mAssignAtts = mAssignAtts ?? new List>(); + mAssignAtts.Add(new KeyValuePair(name, value)); return this; } @@ -66,11 +66,6 @@ public Query SetMaps(IODataMap2[] iODataMap) return this; } - public Query SetVars(VarValue[] vars) - { - Vars = vars; - return this; - } public Query SetType(CursorType cType) { @@ -78,6 +73,12 @@ public Query SetType(CursorType cType) return this; } + public Query AddConst(GXType gxType, object parm) + { + mVarValues = mVarValues ?? new List(); + mVarValues.Add(new VarValue($":const{ mVarValues.Count + 1 }", gxType, parm)); + return this; + } } public class VarValue @@ -89,24 +90,18 @@ public class VarValue public VarValue(string name, GXType type, object value) { Name = name; + Type = type; Value = value; } } - public class QueryExpression - { - public string For { get; set; } - internal string[] Select { get; set; } - } - - public interface IODataMap2 { object GetValue(IOServiceContext serviceContext, RecordEntryRow currentEntry); string GetName(IOServiceContext serviceContext); - void SetValue(IOServiceContext serviceContext, RecordEntryRow currentEntry, object value); + void SetValue(RecordEntryRow currentEntry, object value); } @@ -129,7 +124,7 @@ public virtual object GetValue(IOServiceContext context, RecordEntryRow currentE throw new NotImplementedException(); } - public virtual void SetValue(IOServiceContext context, RecordEntryRow currentEntry, object value) + public virtual void SetValue(RecordEntryRow currentEntry, object value) { throw new NotImplementedException(); } @@ -157,7 +152,15 @@ public interface IODataMap void SetValue(IDictionary currentEntry, object value); } - public abstract class DynServiceDataStoreHelper : DataStoreHelperBase + public abstract class DynServiceDataStoreHelperBase : DataStoreHelperBase + { + public static object InvalidQuery(string msg) + { + throw new ServiceException(msg); + } + } + + public abstract class DynServiceDataStoreHelper : DynServiceDataStoreHelperBase { public abstract Guid GetParmGuid(IDataParameterCollection parms, string parm); public abstract string GetParmStr(IDataParameterCollection parms, string parm); diff --git a/dotnet/src/dotnetcommon/DynService.Core/GlobalSuppressions.cs b/dotnet/src/dotnetcommon/DynService.Core/GlobalSuppressions.cs new file mode 100644 index 000000000..3dc76b4ab --- /dev/null +++ b/dotnet/src/dotnetcommon/DynService.Core/GlobalSuppressions.cs @@ -0,0 +1,5 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + diff --git a/dotnet/src/dotnetframework/DynService.Core/Properties/AssemblyInfo.cs b/dotnet/src/dotnetcommon/DynService.Core/Properties/AssemblyInfo.cs similarity index 100% rename from dotnet/src/dotnetframework/DynService.Core/Properties/AssemblyInfo.cs rename to dotnet/src/dotnetcommon/DynService.Core/Properties/AssemblyInfo.cs diff --git a/dotnet/src/dotnetcommon/DynService.Dynamo/DynService.DynamoDB.csproj b/dotnet/src/dotnetcommon/DynService.Dynamo/DynService.DynamoDB.csproj new file mode 100644 index 000000000..cafcef4b2 --- /dev/null +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynService.DynamoDB.csproj @@ -0,0 +1,31 @@ + + + net462;net6.0 + GeneXus.Data.NTier + GeneXus.Data.DynService.DynamoDB + false + DynamoDB + GeneXus.DynService.DynamoDB + + + + NETCORE + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBConnection.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBConnection.cs new file mode 100644 index 000000000..f3b2acf9d --- /dev/null +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBConnection.cs @@ -0,0 +1,340 @@ +using Amazon; +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.Runtime; +using GeneXus.Data.Dynamo; +using GeneXus.Data.NTier.DynamoDB; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text.RegularExpressions; +using System.Linq; +using System.Net; +using System.Text; +using GeneXus.Cache; + +namespace GeneXus.Data.NTier +{ + + public class DynamoDBService: GxService + { + public DynamoDBService(string id, string providerId): base(id, providerId, typeof(DynamoDBConnection)) + { + + } + + public override IDataReader GetCacheDataReader(CacheItem item, bool computeSize, string keyCache) + { + return new GxDynamoDBCacheDataReader(item, computeSize, keyCache); + } + } + + public class DynamoDBConnection : ServiceConnection + { + private readonly string CLIENT_ID = "User Id"; + private readonly string CLIENT_SECRET = "password"; + private readonly string REGION = "region"; + private readonly string LOCAL_URL = "LocalUrl"; + private readonly char[] SHARP_CHARS = new char[] { '#' }; + private AmazonDynamoDBClient mDynamoDB; + private AmazonDynamoDBConfig mConfig; + private AWSCredentials mCredentials; + private RegionEndpoint mRegion = RegionEndpoint.USEast1; + + private void InitializeDBConnection() + { + DbConnectionStringBuilder builder = new DbConnectionStringBuilder(false); + builder.ConnectionString = this.ConnectionString; + mConfig = new AmazonDynamoDBConfig(); + string mLocalUrl = null; + if (builder.TryGetValue(LOCAL_URL, out object localUrl)) + { + mLocalUrl = localUrl.ToString(); + } + if (builder.TryGetValue(CLIENT_ID, out object clientId) && builder.TryGetValue(CLIENT_SECRET, out object clientSecret)) + { + mCredentials = new BasicAWSCredentials(clientId.ToString(), clientSecret.ToString()); + } + if (builder.TryGetValue(REGION, out object region)) + { + mRegion = RegionEndpoint.GetBySystemName(region.ToString()); + } + + if (localUrl != null) + { + mConfig.ServiceURL = mLocalUrl; + if (region != null) + mConfig.AuthenticationRegion = region as string; + } + else + mConfig.RegionEndpoint = mRegion; + } + + private void Initialize() + { + InitializeDBConnection(); + State = ConnectionState.Executing; + + mDynamoDB = new AmazonDynamoDBClient(mCredentials, mConfig); + } + + private const string FILTER_PATTERN = @"\((.*) = :(.*)\)"; + + public override int ExecuteNonQuery(ServiceCursorDef cursorDef, IDataParameterCollection parms, CommandBehavior behavior) + { + Initialize(); + DynamoQuery query = cursorDef.Query as DynamoQuery; + bool isInsert = query.CursorType == ServiceCursorDef.CursorType.Insert; + + Dictionary values = new Dictionary(); + Dictionary expressionAttributeNames = null; + HashSet mappedNames = null; + string keyItemForUpd = query.PartitionKey; + if (keyItemForUpd != null && keyItemForUpd.StartsWith("#")) + { + expressionAttributeNames = new Dictionary(); + mappedNames = mappedNames ?? new HashSet(); + string keyName = keyItemForUpd.Substring(1); + expressionAttributeNames.Add(keyItemForUpd, keyName); + mappedNames.Add(keyName); + } + foreach (KeyValuePair asg in query.AssignAtts) + { + string name = asg.Key; + if (name.StartsWith("#")) + { + if (!isInsert) + { + expressionAttributeNames = new Dictionary(); + mappedNames = mappedNames ?? new HashSet(); + string keyName = name.Substring(1); + expressionAttributeNames.Add(name, keyName); + mappedNames.Add(keyName); + } + name = name.Substring(1); + } + string parmName = asg.Value.Substring(1); + DynamoDBHelper.AddAttributeValue(isInsert ? name : $":{ name }", parmName, values, parms, query.Vars); + } + + Dictionary keyCondition = new Dictionary(); + + foreach (string keyFilter in query.KeyFilters.Concat(query.Filters)) + { + Match match = Regex.Match(keyFilter, FILTER_PATTERN); + if (match.Groups.Count > 1) + { + string varName = match.Groups[2].Value; + string name = match.Groups[1].Value.TrimStart(SHARP_CHARS); + VarValue varValue = query.Vars.FirstOrDefault(v => v.Name == $":{varName}"); + if (varValue != null) + keyCondition[name] = DynamoDBHelper.ToAttributeValue(varValue); + else + { + if (parms[varName] is ServiceParameter serviceParm) + keyCondition[name] = DynamoDBHelper.ToAttributeValue(serviceParm.DbType, serviceParm.Value); + } + } + } + AmazonDynamoDBRequest request; + AmazonWebServiceResponse response = null; + + switch (query.CursorType) + { + case ServiceCursorDef.CursorType.Select: + throw new NotImplementedException(); + + case ServiceCursorDef.CursorType.Delete: + request = new DeleteItemRequest() + { + TableName = query.TableName, + Key = keyCondition, + ConditionExpression = $"attribute_exists({ keyItemForUpd })", + ExpressionAttributeNames = expressionAttributeNames + }; + try + { +#if NETCORE + response = DynamoDBHelper.RunSync(() => mDynamoDB.DeleteItemAsync((DeleteItemRequest)request)); +#else + response = mDynamoDB.DeleteItem((DeleteItemRequest)request); +#endif + } + catch (ConditionalCheckFailedException recordNotFound) + { + throw new ServiceException(ServiceError.RecordNotFound, recordNotFound); + } + break; + case ServiceCursorDef.CursorType.Insert: + request = new PutItemRequest + { + TableName = query.TableName, + Item = values, + ConditionExpression = $"attribute_not_exists({ keyItemForUpd })", + ExpressionAttributeNames = expressionAttributeNames + }; + try + { +#if NETCORE + response = DynamoDBHelper.RunSync(() => mDynamoDB.PutItemAsync((PutItemRequest)request)); +#else + response = mDynamoDB.PutItem((PutItemRequest)request); +#endif + } + catch (ConditionalCheckFailedException recordAlreadyExists) + { + throw new ServiceException(ServiceError.RecordAlreadyExists, recordAlreadyExists); + } + break; + case ServiceCursorDef.CursorType.Update: + request = new UpdateItemRequest + { + TableName = query.TableName, + Key = keyCondition, + UpdateExpression = ToAttributeUpdates(keyCondition, values, mappedNames), + ConditionExpression = $"attribute_exists({ keyItemForUpd })", + ExpressionAttributeValues = values, + ExpressionAttributeNames = expressionAttributeNames + }; + try + { +#if NETCORE + response = DynamoDBHelper.RunSync(() => mDynamoDB.UpdateItemAsync((UpdateItemRequest)request)); +#else + response = mDynamoDB.UpdateItem((UpdateItemRequest)request); +#endif + } + catch (ConditionalCheckFailedException recordNotFound) + { + throw new ServiceException(ServiceError.RecordNotFound, recordNotFound); + } + break; + } + + return response?.HttpStatusCode == HttpStatusCode.OK ? 1 : 0; + } + + private string ToAttributeUpdates(Dictionary keyConditions, Dictionary values, HashSet mappedNames) + { + StringBuilder updateExpression = new StringBuilder(); + foreach (var item in values) + { + string keyName = item.Key.Substring(1); + if (!keyConditions.ContainsKey(keyName) && !keyName.StartsWith("AV", StringComparison.InvariantCulture)) + { + if (mappedNames?.Contains(keyName) == true) + keyName = $"#{keyName}"; + updateExpression.Append(updateExpression.Length == 0 ? "SET " : ", "); + updateExpression.Append($"{ keyName } = { item.Key }"); + } + } + return updateExpression.ToString(); + } + + public override IDataReader ExecuteReader(ServiceCursorDef cursorDef, IDataParameterCollection parms, CommandBehavior behavior) + { + + Initialize(); + DynamoQuery query = cursorDef.Query as DynamoQuery; + + try + { + CreateDynamoQuery(query, GetQueryValues(query, parms), out DynamoDBDataReader dataReader, out AmazonDynamoDBRequest req); + RequestWrapper reqWrapper = new RequestWrapper(mDynamoDB, req); + dataReader = new DynamoDBDataReader(cursorDef, reqWrapper); + return dataReader; + } + catch (AmazonDynamoDBException e) + { + if (e.ErrorCode == DynamoDBErrors.ValidationException && + e.Message.Contains(DynamoDBErrors.ValidationExceptionMessageKey)) + throw new ServiceException(ServiceError.RecordNotFound); // Handles special case where a string key attribute is filtered with an empty value which is not supported on DynamoDB but should yield a not record found in GX + throw e; + } + catch (AmazonServiceException e) { throw e; } + catch (Exception e) { throw e; } + } + + private Dictionary GetQueryValues(DynamoQuery query, IDataParameterCollection parms) + { + Dictionary values = new Dictionary(); + foreach (object parm in parms) + DynamoDBHelper.GXToDynamoQueryParameter(values, parm as ServiceParameter); + foreach (VarValue item in query.Vars) + values.Add(item.Name, DynamoDBHelper.ToAttributeValue(item)); + return values; + } + + private static void CreateDynamoQuery(DynamoQuery query, Dictionary values, out DynamoDBDataReader dataReader, out AmazonDynamoDBRequest req) + { + dataReader = null; + req = null; + Dictionary expressionAttributeNames = null; + foreach (string mappedName in query.SelectList.Where(selItem => (selItem as DynamoDBMap)?.NeedsAttributeMap == true).Select(selItem => selItem.GetName(NewServiceContext()))) + { + expressionAttributeNames = expressionAttributeNames ?? new Dictionary(); + string key = $"#{ mappedName }"; + string value = mappedName; + expressionAttributeNames.Add(key, value); + } + + bool issueScan = query is DynamoScan; + if (!issueScan) + { // Check whether a query has to be demoted to scan due to empty parameters + foreach (string keyFilter in query.KeyFilters) + { + Match match = Regex.Match(keyFilter, @".*(:.*)\).*"); + if (match.Groups.Count > 0) + { + string varName = match.Groups[1].Value; + if(values.TryGetValue(varName, out AttributeValue value) && value.S?.Length == 0) + { + issueScan = true; + break; + } + } + } + } + + if (issueScan) + { + ScanRequest scanReq; + IEnumerable allFilters = query.KeyFilters.Concat(query.Filters); + req = scanReq = new ScanRequest + { + TableName = query.TableName, + ProjectionExpression = String.Join(",", query.Projection), + FilterExpression = allFilters.Any() ? String.Join(" AND ", allFilters) : null, + ExpressionAttributeValues = values, + }; + if (expressionAttributeNames != null) + scanReq.ExpressionAttributeNames = expressionAttributeNames; + } + else + { + QueryRequest queryReq; + req = queryReq = new QueryRequest + { + TableName = query.TableName, + KeyConditionExpression = String.Join(" AND ", query.KeyFilters), + FilterExpression = query.Filters.Any() ? String.Join(" AND ", query.Filters) : null, + ExpressionAttributeValues = values, + ProjectionExpression = String.Join(",", query.Projection), + IndexName = query.Index, + ScanIndexForward = query.ScanIndexForward, + }; + if (expressionAttributeNames != null) + queryReq.ExpressionAttributeNames = expressionAttributeNames; + } + } + internal static IOServiceContext NewServiceContext() => null; + + } + + public class DynamoDBErrors + { + public const string ValidationException = "ValidationException"; + public const string ValidationExceptionMessageKey = "The AttributeValue for a key attribute cannot contain an empty string value."; + } +} diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBDataReader.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBDataReader.cs similarity index 62% rename from dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBDataReader.cs rename to dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBDataReader.cs index 641fea832..cb659a2e4 100644 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBDataReader.cs +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBDataReader.cs @@ -1,24 +1,23 @@ using System; using System.Collections.Generic; using System.Data; +using System.Globalization; +using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Amazon.DynamoDBv2.Model; using GeneXus.Cache; -using GeneXus.Data.Dynamo; using GeneXus.Data.NTier; using GeneXus.Data.NTier.DynamoDB; -using log4net; namespace GeneXus.Data.Dynamo { public class DynamoDBDataReader : IDataReader { - private RequestWrapper mRequest; + private readonly RequestWrapper mRequest; private ResponseWrapper mResponse; - private int mCurrentPosition = -1; - private IODataMap2[] selectList; + private int mCurrentPosition; + private readonly IODataMap2[] selectList; + private DynamoDBRecordEntry currentEntry; private int ItemCount { @@ -36,24 +35,26 @@ private List> Items } } - public DynamoDBDataReader(ServiceCursorDef cursorDef, RequestWrapper request, IDataParameterCollection parameters) + private void CheckCurrentPosition() + { + if (currentEntry == null) + throw new ServiceException(ServiceError.RecordNotFound); + } + + public DynamoDBDataReader(ServiceCursorDef cursorDef, RequestWrapper request) { Query query = cursorDef.Query as Query; - selectList = query.SelectList; + selectList = query.SelectList.ToArray(); mRequest = request; mResponse = mRequest.Read(); + mCurrentPosition = -1; } public object this[string name] { get { - if (mCurrentPosition >= 0 && mCurrentPosition < ItemCount) - { - return Items[mCurrentPosition][name].S; - } - throw new ArgumentOutOfRangeException(nameof(name)); - + throw new NotImplementedException(); } } @@ -61,16 +62,7 @@ public object this[int i] { get { - if (mCurrentPosition >= 0 && mCurrentPosition < ItemCount) - { - int j = 0; - foreach (var col in Items[mCurrentPosition]) - { - if (j == i) - return col.Value.S; - } - } - throw new ArgumentOutOfRangeException(nameof(i)); + throw new NotImplementedException(); } } @@ -120,12 +112,13 @@ public void Dispose() public long getLong(int i) { - return Convert.ToInt64(GetAttValue(i).N); + return Convert.ToInt64(GetAttValueN(i)); } public bool GetBoolean(int i) { - return GetAttValue(i).BOOL; + AttributeValue value = GetAttValue(i); + return value.IsBOOLSet && value.BOOL; } public byte GetByte(int i) @@ -135,7 +128,11 @@ public byte GetByte(int i) public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) { - throw new NotImplementedException(); + MemoryStream ms = GetAttValue(i).B; + if (ms == null) + return 0; + ms.Seek(fieldOffset, SeekOrigin.Begin); + return ms.Read(buffer, bufferoffset, length); } public char GetChar(int i) @@ -145,8 +142,7 @@ public char GetChar(int i) public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) { - buffer = GetAttValue(i).S.ToCharArray(); - return buffer.Length; + throw new NotImplementedException(); } public IDataReader GetData(int i) @@ -161,29 +157,28 @@ public string GetDataTypeName(int i) public DateTime GetDateTime(int i) { - DateTime dt = new DateTime(); - DateTime.TryParse(GetAttValue(i).S, out dt); + DateTime.TryParse(GetAttValue(i).S, null, DateTimeStyles.AdjustToUniversal, out DateTime dt); return dt; } public decimal GetDecimal(int i) { - return decimal.Parse(GetAttValue(i).N); + return decimal.Parse(GetAttValueN(i)); } public double GetDouble(int i) { - return double.Parse(GetAttValue(i).N); + return double.Parse(GetAttValueN(i)); } public Type GetFieldType(int i) { - throw new NotImplementedException(); + return selectList[i].GetValue(DynamoDBConnection.NewServiceContext(), currentEntry).GetType(); } public float GetFloat(int i) { - return float.Parse(GetAttValue(i).N); + return float.Parse(GetAttValueN(i)); } public Guid GetGuid(int i) @@ -193,18 +188,18 @@ public Guid GetGuid(int i) public short GetInt16(int i) { - return short.Parse(GetAttValue(i).N); + return short.Parse(GetAttValueN(i)); } public int GetInt32(int i) { - return int.Parse(GetAttValue(i).N); + return int.Parse(GetAttValueN(i)); } public long GetInt64(int i) { - return long.Parse(GetAttValue(i).N); + return long.Parse(GetAttValueN(i)); } public string GetName(int i) @@ -214,14 +209,11 @@ public string GetName(int i) public int GetOrdinal(string name) { - int j = 0; - foreach (var col in Items[mCurrentPosition]) - { - if (col.Key.ToLower() == name.ToLower()) - return j; - j++; - } - throw new ArgumentOutOfRangeException(nameof(name)); + CheckCurrentPosition(); + int ordinal = currentEntry.CurrentRow.ToList().FindIndex(col => col.Key.ToLower() == name.ToLower()); + if (ordinal == -1) + throw new ArgumentOutOfRangeException(nameof(name)); + else return ordinal; } public DataTable GetSchemaTable() @@ -231,7 +223,7 @@ public DataTable GetSchemaTable() public string GetString(int i) { - return GetAttValue(i).S; + return DynamoDBHelper.GetString(GetAttValue(i)); } public object GetValue(int i) @@ -242,16 +234,13 @@ public object GetValue(int i) if (attValue.IsBOOLSet) { value = attValue.BOOL; - } - if (attValue.S != null) + }else if (attValue.S != null || attValue.IsLSet || attValue.IsMSet) { - value = attValue.S; - } - if (attValue.N != null) + value = DynamoDBHelper.GetString(attValue); + }else if (attValue.N != null) { value = attValue.N; - } - if (attValue.B != null) + }else if (attValue.B != null) { value = attValue.B; } @@ -260,9 +249,11 @@ public object GetValue(int i) private AttributeValue GetAttValue(int i) { - return (AttributeValue)selectList[i].GetValue(NewServiceContext(), new DynamoDBRecordEntry(Items[mCurrentPosition])); + return (AttributeValue)selectList[i].GetValue(DynamoDBConnection.NewServiceContext(), currentEntry); } + private string GetAttValueN(int i) => GetAttValue(i)?.N ?? "0"; + public int GetValues(object[] values) { System.Diagnostics.Debug.Assert(selectList.Length == values.Length, "Values mismatch"); @@ -281,12 +272,15 @@ public bool IsDBNull(int i) public bool NextResult() { mCurrentPosition++; - return (mCurrentPosition < ItemCount); + currentEntry = (mCurrentPosition < ItemCount) ? new DynamoDBRecordEntry(Items[mCurrentPosition]) : null; + return currentEntry != null; } public bool Read() - { - if (mCurrentPosition == ItemCount && mCurrentPosition > 0) + { + if (NextResult()) + return true; + else if (mCurrentPosition > 0 && mResponse.LastEvaluatedKey?.Count > 0) { mResponse = mRequest.Read(mResponse.LastEvaluatedKey); /* @@ -295,15 +289,11 @@ public bool Read() * The result set contains the last_evaluated_key field. If more data is available for the operation, * this key contains information about the last evaluated key. Otherwise, the key remains empty. * */ + mCurrentPosition = -1; + return NextResult(); } - mCurrentPosition++; - return (mCurrentPosition < ItemCount); + return false; } - internal IOServiceContext NewServiceContext() - { - return null; - } - } public class GxDynamoDBCacheDataReader : GxCacheDataReader @@ -316,8 +306,7 @@ public GxDynamoDBCacheDataReader(CacheItem cacheItem, bool computeSize, string k public override DateTime GetDateTime(int i) { - DateTime dt = new DateTime(); - DateTime.TryParse(GetAttValue(i).S, out dt); + DateTime.TryParse(GetAttValue(i).S, null, DateTimeStyles.AdjustToUniversal, out DateTime dt); return dt; } @@ -325,42 +314,46 @@ private AttributeValue GetAttValue(int i) { return (AttributeValue)block.Item(pos, i); } - + private string GetAttValueN(int i) => GetAttValue(i)?.N ?? "0"; public override decimal GetDecimal(int i) { - return decimal.Parse(GetAttValue(i).N); + return decimal.Parse(GetAttValueN(i)); } - public override short GetInt16(int i) { - return short.Parse(GetAttValue(i).N); + return short.Parse(GetAttValueN(i)); } public override int GetInt32(int i) { - return int.Parse(GetAttValue(i).N); + return int.Parse(GetAttValueN(i)); } public override long GetInt64(int i) { - return long.Parse(GetAttValue(i).N); + return long.Parse(GetAttValueN(i)); } public override string GetString(int i) { - return GetAttValue(i).S; + return DynamoDBHelper.GetString(GetAttValue(i)); } public override bool GetBoolean(int i) { - return GetAttValue(i).IsBOOLSet; + AttributeValue value = GetAttValue(i); + return value.IsBOOLSet && value.BOOL; } public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) { - throw new NotImplementedException(); + MemoryStream ms = GetAttValue(i).B; + if (ms == null) + return 0; + ms.Seek(fieldOffset, SeekOrigin.Begin); + return ms.Read(buffer, bufferoffset, length); } } } diff --git a/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBDataStoreHelper.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBDataStoreHelper.cs new file mode 100644 index 000000000..0c3cb20b8 --- /dev/null +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBDataStoreHelper.cs @@ -0,0 +1,109 @@ +using GeneXus.Data.Dynamo; +using GeneXus.Utils; +using System; +using System.Collections.Generic; + +namespace GeneXus.Data.NTier +{ + public class DynamoDBDataStoreHelper: DynServiceDataStoreHelperBase + { + + public DynamoQuery NewQuery() => new DynamoQuery(this); + public DynamoQuery NewScan() => new DynamoScan(this); + + public DynamoDBMap Map(string name) + { + return new DynamoDBMap(name); + } + + public object empty(GXType gxtype) + { + switch(gxtype) + { + case GXType.Number: + case GXType.Int16: + case GXType.Int32: + case GXType.Int64: return 0; + case GXType.Date: + case GXType.DateTime: + case GXType.DateTime2: return DateTimeUtil.NullDate(); + case GXType.Byte: + case GXType.NChar: + case GXType.NClob: + case GXType.NVarChar: + case GXType.Char: + case GXType.LongVarChar: + case GXType.Clob: + case GXType.VarChar: + case GXType.Raw: + case GXType.Blob: return string.Empty; + case GXType.Boolean: return false; + case GXType.Decimal: return 0f; + case GXType.NText: + case GXType.Text: + case GXType.Image: + case GXType.UniqueIdentifier: + case GXType.Xml: return string.Empty; + case GXType.Geography: + case GXType.Geopoint: + case GXType.Geoline: + case GXType.Geopolygon: return new Geospatial(); + case GXType.DateAsChar: return string.Empty; + case GXType.Undefined: + default: return null; + + } + } + } + + public static class DynamoFluentExtensions + { + public static Query OrderBy(this Query dynamoQuery, string index) + { + return (dynamoQuery as DynamoQuery)?.OrderBy(index); + } + public static Query SetKey(this Query dynamoQuery, string partitionKey) + { + return (dynamoQuery as DynamoQuery)?.SetKey(partitionKey); + } + public static Query KeyFilter(this Query dynamoQuery, string[] filters) + { + return (dynamoQuery as DynamoQuery)?.KeyFilter(filters); + } + } + + public class DynamoQuery : Query + { + public string Index { get; set; } + public bool ScanIndexForward = true; + private const string RANGE_KEY_INDEX = "RangeKey"; + private static readonly char[] indexTrimChars = new char[] { '(', ')' }; + + public DynamoQuery OrderBy(string index) + { + if(index.StartsWith("(", StringComparison.InvariantCulture)) + { + ScanIndexForward = false; + index = index.Trim(indexTrimChars); + } + if (index != RANGE_KEY_INDEX) + Index = index; + return this; + } + + public string PartitionKey { get; private set; } + public DynamoQuery SetKey(string partitionKey) + { + PartitionKey = partitionKey; + return this; + } + internal IEnumerable KeyFilters { get; set; } = Array.Empty(); + public DynamoQuery KeyFilter(string[] filters) + { + KeyFilters = filters; + return this; + } + + public DynamoQuery(DynamoDBDataStoreHelper dataStoreHelper) : base(dataStoreHelper) { } + } +} diff --git a/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBHelper.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBHelper.cs new file mode 100644 index 000000000..4aa4789ce --- /dev/null +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBHelper.cs @@ -0,0 +1,153 @@ +using Amazon.DynamoDBv2.Model; +using GeneXus.Data.NTier; +using GeneXus.Utils; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +#if NETCORE +using System.Threading; +using System.Threading.Tasks; +#endif + +namespace GeneXus.Data.Dynamo +{ + internal class DynamoDBHelper + { +#if NETCORE + private static readonly TaskFactory mTaskFactory = new + TaskFactory(CancellationToken.None, + TaskCreationOptions.None, + TaskContinuationOptions.None, + TaskScheduler.Default); + internal static TResult RunSync(Func> func) => mTaskFactory.StartNew>(func).Unwrap().GetAwaiter().GetResult(); +#endif + + internal static AttributeValue ToAttributeValue(VarValue var) + { + return ToAttributeValue(GxService.GXTypeToDbType(var.Type), var.Value); + } + + internal static void GXToDynamoQueryParameter(Dictionary dynParm, ServiceParameter parm) => AddAttributeValue($":{ parm.ParameterName }", dynParm, parm); + + internal static bool AddAttributeValue(string parmName, string fromName, Dictionary values, IDataParameterCollection parms, IEnumerable queryVars) + { + if(!AddAttributeValue(parmName, values, parms[fromName] as ServiceParameter)) + { + VarValue varValue = queryVars.FirstOrDefault(v => v.Name == $":{fromName}"); + if (varValue != null) + values[parmName] = DynamoDBHelper.ToAttributeValue(varValue); + return varValue != null; + } + return true; + } + + internal static bool AddAttributeValue(string parmName, Dictionary dynParm, ServiceParameter parm) + { + if (parm == null) + return false; + AttributeValue value = ToAttributeValue(parm.DbType, parm.Value); + if (value != null) + { + dynParm[parmName] = value; + return true; + } + return false; + } + + internal static AttributeValue ToAttributeValue(DbType dbType, Object value) + { + AttributeValue attValue; + switch (dbType) + { + case DbType.Binary: + if (value is byte[] valueArr) + { + attValue = new AttributeValue + { + B = new MemoryStream(valueArr) + }; + break; + } + else throw new ArgumentException("Required value not found"); + case DbType.Boolean: + case DbType.Byte: + attValue = new AttributeValue + { + BOOL = (bool)value + }; + break; + case DbType.Time: + case DbType.Date: + attValue = new AttributeValue + { + S = DateTime.SpecifyKind((DateTime)value, DateTimeKind.Utc).ToString("yyyy-MM-dd") + }; + break; + case DbType.DateTime2: + case DbType.DateTime: + attValue = new AttributeValue + { + S = DateTime.SpecifyKind((DateTime)value, DateTimeKind.Utc).ToString("yyyy-MM-ddTHH:mm:ssK") + }; + break; + + + case DbType.UInt16: + case DbType.UInt32: + case DbType.UInt64: + case DbType.VarNumeric: + case DbType.Decimal: + case DbType.Double: + case DbType.Int16: + case DbType.Int32: + case DbType.Int64: + attValue = new AttributeValue + { + N = value.ToString() + }; + break; + default: + string valueS = value.ToString().Replace("%", string.Empty); + attValue = new AttributeValue + { + S = valueS + }; + break; + } + return attValue; + } + + public static string GetString(AttributeValue attValue) + { + string value = attValue.S; + if (value != null) + return value; + else if (attValue.NS.Count > 0) + return SetToString(attValue.NS); + else if (attValue.SS.Count > 0) + return SetToString(attValue.SS); + else if (attValue.IsBOOLSet) + return $"{ attValue.BOOL }"; + else if (attValue.IsMSet) + return JSONHelper.Serialize(ConvertToDictionary(attValue.M), Encoding.UTF8); + else if (attValue.IsLSet) + return JSONHelper.Serialize>(attValue.L.Select(item => GetString(item)).ToList()); + return null; + } + + private static Dictionary ConvertToDictionary(Dictionary m) + { + Dictionary dict = new Dictionary(); + foreach (KeyValuePair keyValues in m) + { + dict.Add(keyValues.Key, GetString(keyValues.Value)); + } + return dict; + } + + private static string SetToString(List nS) => $"[ { string.Join(", ", nS) } ]"; + } +} diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBMaps.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBMaps.cs similarity index 58% rename from dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBMaps.cs rename to dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBMaps.cs index 63497aaf9..4c01e9d14 100644 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBMaps.cs +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBMaps.cs @@ -2,30 +2,27 @@ using GeneXus.Data.NTier; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace GeneXus.Data.Dynamo { public class DynamoDBMap : Map { - - public DynamoDBMap(string name): base(name) - { + internal bool NeedsAttributeMap { get; } + public DynamoDBMap(string name): base(RemoveSharp(name)) + { + NeedsAttributeMap = name.StartsWith("#", StringComparison.InvariantCulture); } + private static string RemoveSharp(string name) => name.StartsWith("#", StringComparison.InvariantCulture) ? name.Substring(1) : name; public override object GetValue(IOServiceContext context, RecordEntryRow currentEntry) { Dictionary values = ((DynamoDBRecordEntry)currentEntry).CurrentRow; - AttributeValue val = null; - values.TryGetValue(GetName(context), out val); + values.TryGetValue(GetName(context), out AttributeValue val); return val; } - public override void SetValue(IOServiceContext context, RecordEntryRow currentEntry, object value) + public override void SetValue(RecordEntryRow currentEntry, object value) { throw new NotImplementedException(); } diff --git a/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBRequestWrapper.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBRequestWrapper.cs new file mode 100644 index 000000000..220b42b50 --- /dev/null +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBRequestWrapper.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +#if NETCORE +using GeneXus.Data.Dynamo; +#endif + +namespace GeneXus.Data.NTier.DynamoDB +{ + public class RequestWrapper + { + private readonly AmazonDynamoDBClient mDynamoDB; + private readonly AmazonDynamoDBRequest mReq; + + public RequestWrapper(AmazonDynamoDBClient mDynamoDB, AmazonDynamoDBRequest req) + { + this.mDynamoDB = mDynamoDB; + this.mReq = req; + } + + public ResponseWrapper Read() + { + return Read(null); + } + + public ResponseWrapper Read(Dictionary lastEvaluatedKey) + { + if (mReq is ScanRequest scanReq) + { + scanReq.ExclusiveStartKey = lastEvaluatedKey; +#if NETCORE + ScanResponse scanResponse = DynamoDBHelper.RunSync(() => mDynamoDB.ScanAsync(scanReq)); +#else + ScanResponse scanResponse = mDynamoDB.Scan(scanReq); +#endif + return new ResponseWrapper(scanResponse); + } + if (mReq is QueryRequest queryReq) + { + queryReq.ExclusiveStartKey = lastEvaluatedKey; +#if NETCORE + QueryResponse queryResponse = DynamoDBHelper.RunSync(() => mDynamoDB.QueryAsync(queryReq)); +#else + QueryResponse queryResponse = mDynamoDB.Query(queryReq); +#endif + return new ResponseWrapper(queryResponse); + } + throw new NotImplementedException(); + } + + internal void Close() + { + mDynamoDB.Dispose(); + } + } +} diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBResponseWrapper.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBResponseWrapper.cs similarity index 89% rename from dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBResponseWrapper.cs rename to dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBResponseWrapper.cs index 59024cea7..1aacb128e 100644 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBResponseWrapper.cs +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBResponseWrapper.cs @@ -1,9 +1,5 @@ using Amazon.DynamoDBv2.Model; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace GeneXus.Data.NTier.DynamoDB { diff --git a/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBScan.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBScan.cs new file mode 100644 index 000000000..080802b72 --- /dev/null +++ b/dotnet/src/dotnetcommon/DynService.Dynamo/DynamoDBScan.cs @@ -0,0 +1,10 @@ +namespace GeneXus.Data.NTier +{ + public class DynamoScan : DynamoQuery + { + public DynamoScan(DynamoDBDataStoreHelper dataStoreHelper):base(dataStoreHelper) + { + + } + } +} diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/Properties/AssemblyInfo.cs b/dotnet/src/dotnetcommon/DynService.Dynamo/Properties/AssemblyInfo.cs similarity index 100% rename from dotnet/src/dotnetframework/DynService.Dynamo/Properties/AssemblyInfo.cs rename to dotnet/src/dotnetcommon/DynService.Dynamo/Properties/AssemblyInfo.cs diff --git a/dotnet/src/dotnetcore/DynService/Dynamo/DynService.DynamoDB.csproj b/dotnet/src/dotnetcore/DynService/Dynamo/DynService.DynamoDB.csproj new file mode 100644 index 000000000..7756884cb --- /dev/null +++ b/dotnet/src/dotnetcore/DynService/Dynamo/DynService.DynamoDB.csproj @@ -0,0 +1,34 @@ + + + net5.0;net6.0 + NETCORE + GeneXus.Data.NTier + GeneXus.Data.DynService.DynamoDB + false + DynamoDB + GeneXus.DynService.DynamoDB + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/dotnet/src/dotnetcore/DynService/OData/DynServiceOData.csproj b/dotnet/src/dotnetcore/DynService/OData/DynServiceOData.csproj index 9e144b035..0f3e48a31 100644 --- a/dotnet/src/dotnetcore/DynService/OData/DynServiceOData.csproj +++ b/dotnet/src/dotnetcore/DynService/OData/DynServiceOData.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/src/dotnetframework/DynService.Core/DynService.Core.csproj b/dotnet/src/dotnetframework/DynService.Core/DynService.Core.csproj deleted file mode 100644 index 72410d78f..000000000 --- a/dotnet/src/dotnetframework/DynService.Core/DynService.Core.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - net462 - GeneXus.Data.NTier - GeneXus.Data.DynService.Core - CA1812 - Dynservice - GeneXus.DynService.Core - - - - - \ No newline at end of file diff --git a/dotnet/src/dotnetframework/DynService.Core/GlobalSuppressions.cs b/dotnet/src/dotnetframework/DynService.Core/GlobalSuppressions.cs deleted file mode 100644 index 292ccd6ae..000000000 --- a/dotnet/src/dotnetframework/DynService.Core/GlobalSuppressions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Data.NTier.Query.AssignAtts")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Data.NTier.Query.Filters")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Data.NTier.Query.OrderBys")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Data.NTier.Query.Projection")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Data.NTier.Query.SelectList")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Data.NTier.Query.Vars")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Data.NTier.QueryExpression.Select")] diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynService.DynamoDB.csproj b/dotnet/src/dotnetframework/DynService.Dynamo/DynService.DynamoDB.csproj deleted file mode 100644 index 41d1c00ce..000000000 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynService.DynamoDB.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - net462 - GeneXus.Data.NTier - GeneXus.Data.DynService.DynamoDB - false - DynamoDB - GeneXus.DynService.DynamoDB - - - - - - - - - \ No newline at end of file diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBConnection.cs b/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBConnection.cs deleted file mode 100644 index 72247b237..000000000 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBConnection.cs +++ /dev/null @@ -1,226 +0,0 @@ -using Amazon; -using Amazon.DynamoDBv2; -using Amazon.DynamoDBv2.Model; -using Amazon.Runtime; -using GeneXus.Data.Dynamo; -using GeneXus.Data.NTier.DynamoDB; -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Text.RegularExpressions; -using System.Linq; -using GeneXus.Cache; - -namespace GeneXus.Data.NTier -{ - - public class DynamoDBService: GxService - { - public DynamoDBService(string id, string providerId): base(id, providerId, typeof(DynamoDBConnection)) - { - - } - - public override IDataReader GetCacheDataReader(CacheItem item, bool computeSize, string keyCache) - { - return new GxDynamoDBCacheDataReader(item, computeSize, keyCache); - } - } - - public class DynamoDBConnection : ServiceConnection - { - private readonly string CLIENT_ID = "User Id"; - private readonly string CLIENT_SECRET = "password"; - private readonly string REGION = "region"; - private AmazonDynamoDBClient mDynamoDB; - private AWSCredentials mCredentials; - private RegionEndpoint mRegion = RegionEndpoint.USEast1; - - public override string ConnectionString - { - get { return base.ConnectionString; } - - set - { - base.ConnectionString = value; - } - } - - private void InitializeDBConnection() - { - - DbConnectionStringBuilder builder = new DbConnectionStringBuilder(false); - builder.ConnectionString = this.ConnectionString; - if (builder.TryGetValue(CLIENT_ID, out object clientId) && builder.TryGetValue(CLIENT_SECRET, out object clientSecret)) - { - mCredentials = new BasicAWSCredentials(clientId.ToString(), clientSecret.ToString()); - } - if (builder.TryGetValue(REGION, out object region)) - { - mRegion = RegionEndpoint.GetBySystemName(region.ToString()); - } - } - private bool Initialize() - { - InitializeDBConnection(); - State = ConnectionState.Executing; - mDynamoDB = new Amazon.DynamoDBv2.AmazonDynamoDBClient(mCredentials, mRegion); - return true; - } - - public override int ExecuteNonQuery(ServiceCursorDef cursorDef, IDataParameterCollection parms, CommandBehavior behavior) - { - Initialize(); - Query query = cursorDef.Query as Query; - - Dictionary values = new Dictionary(); - if (parms.Count > 0) - { - for (int i = 0; i < parms.Count; i++) - { - ServiceParameter parm = parms[i] as ServiceParameter; - DynamoDBHelper.GXToDynamoQueryParameter("", values, parm); - } - } - var pattern = @"\((.*) = :(.*)\)"; - Dictionary keyCondition = new Dictionary(); - List filters = new List(); - - foreach (var keyFilter in query.Filters) - { - var match = Regex.Match(keyFilter, pattern); - String varName = match.Groups[2].Value; - if (match.Groups.Count > 1) - { - keyCondition[match.Groups[1].Value] = values[varName]; - } - } - AmazonDynamoDBRequest request = null; - - switch (query.CursorType) - { - case ServiceCursorDef.CursorType.Select: - throw new NotImplementedException(); - - case ServiceCursorDef.CursorType.Delete: - request = new DeleteItemRequest() - { - TableName = query.TableName, - Key = keyCondition - }; - mDynamoDB.DeleteItem((DeleteItemRequest)request); - - break; - case ServiceCursorDef.CursorType.Insert: - request = new PutItemRequest - { - TableName = query.TableName, - Item = values - }; - mDynamoDB.PutItem((PutItemRequest)request); - break; - case ServiceCursorDef.CursorType.Update: - request = new UpdateItemRequest - { - TableName = query.TableName, - Key = keyCondition, - AttributeUpdates = ToAttributeUpdates(keyCondition, values) - - }; - mDynamoDB.UpdateItem((UpdateItemRequest)request); - break; - - default: - break; - } - - return 0; - } - - private Dictionary ToAttributeUpdates(Dictionary keyConditions, Dictionary values) - { - Dictionary updates = new Dictionary(); - foreach (var item in values) - { - if (!keyConditions.ContainsKey(item.Key) && !item.Key.StartsWith("AV")) - { - updates[item.Key] = new AttributeValueUpdate(item.Value, AttributeAction.PUT); - } - } - return updates; - } - - public override IDataReader ExecuteReader(ServiceCursorDef cursorDef, IDataParameterCollection parms, CommandBehavior behavior) - { - - Initialize(); - Query query = cursorDef.Query as Query; - Dictionary valuesAux = new Dictionary(); - Dictionary values = new Dictionary(); - if (parms.Count > 0) - { - for (int i = 0; i < parms.Count; i++) - { - ServiceParameter parm = parms[i] as ServiceParameter; - DynamoDBHelper.GXToDynamoQueryParameter(":", values, parm); - } - } - - List filtersAux = new List(); - List filters = new List(); - - filters.AddRange(query.Filters); - - foreach (var item in query.Vars) - { - values.Add(item.Name, DynamoDBHelper.ToAttributeValue(item)); - } - - try - { - DynamoDBDataReader dataReader; - AmazonDynamoDBRequest req; - CreateDynamoQuery(query, values, filters.ToArray(), out dataReader, out req); - RequestWrapper reqWrapper = new RequestWrapper(mDynamoDB, req); - dataReader = new DynamoDBDataReader(cursorDef, reqWrapper, parms); - return dataReader; - } - catch (AmazonDynamoDBException e) { throw e; } - catch (AmazonServiceException e) { throw e; } - catch (Exception e) { throw e; } - } - - private static void CreateDynamoQuery(Query query, Dictionary values, String[] queryFilters, out DynamoDBDataReader dataReader, out AmazonDynamoDBRequest req) - { - dataReader = null; - req = null; - if (query is Scan) - { - req = new ScanRequest - { - TableName = query.TableName, - ProjectionExpression = String.Join(",", query.Projection), - - }; - if (queryFilters.Length > 0) - { - ((ScanRequest)req).FilterExpression = String.Join(" AND ", queryFilters); - ((ScanRequest)req).ExpressionAttributeValues = values; - } - } - else - { - req = new QueryRequest - { - TableName = query.TableName, - KeyConditionExpression = String.Join(" AND ", query.Filters), - ExpressionAttributeValues = values, - ProjectionExpression = String.Join(",", query.Projection), - - }; - } - } - - } -} diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBDataStoreHelper.cs b/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBDataStoreHelper.cs deleted file mode 100644 index 86d97da9f..000000000 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBDataStoreHelper.cs +++ /dev/null @@ -1,20 +0,0 @@ -using GeneXus.Data.Dynamo; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GeneXus.Data.NTier -{ - public class DynamoDBDataStoreHelper: DataStoreHelperBase - { - - public DynamoDBMap Map(string name) - { - return new DynamoDBMap(name); - } - } - - -} diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBHelper.cs b/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBHelper.cs deleted file mode 100644 index 6a66c2fea..000000000 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBHelper.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Amazon.DynamoDBv2.Model; -using GeneXus.Data.NTier; -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GeneXus.Data.Dynamo -{ - internal class DynamoDBHelper - { - internal static AttributeValue ToAttributeValue(VarValue var) - { - return ToAttributeValue(GxService.GXTypeToDbType(var.Type), var.Value, false); - } - - internal static void GXToDynamoQueryParameter(string prefix, Dictionary dynParm, ServiceParameter parm) - { - AttributeValue v = ToAttributeValue(parm.DbType, parm.Value, true); - if (v != null) - dynParm[$"{prefix}{parm.ParameterName}"] = v; - } - - private static AttributeValue ToAttributeValue(DbType dbType, Object value, bool skipEmpty) - { - AttributeValue attValue = null; - switch (dbType) - { - case System.Data.DbType.Binary: - throw new NotImplementedException("Binary column not implemented yet"); - case System.Data.DbType.Boolean: - case System.Data.DbType.Byte: - attValue = new AttributeValue - { - BOOL = (bool)value - }; - break; - case System.Data.DbType.Time: - case System.Data.DbType.Date: - case System.Data.DbType.DateTime2: - case System.Data.DbType.DateTime: - attValue = new AttributeValue - { - S = value.ToString() - }; - break; - - - case System.Data.DbType.UInt16: - case System.Data.DbType.UInt32: - case System.Data.DbType.UInt64: - case System.Data.DbType.VarNumeric: - case System.Data.DbType.Decimal: - case System.Data.DbType.Double: - case System.Data.DbType.Int16: - case System.Data.DbType.Int32: - case System.Data.DbType.Int64: - attValue = new AttributeValue - { - N = value.ToString() - }; - break; - default: - //DynamoDB does not support empty values - string valueS = value.ToString().Replace("%", string.Empty); - if (!string.IsNullOrEmpty(valueS)) - { - attValue = new AttributeValue - { - S = valueS - }; - } - break; - } - return attValue; - } - } -} diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBRequestWrapper.cs b/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBRequestWrapper.cs deleted file mode 100644 index d123627b5..000000000 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBRequestWrapper.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Amazon.DynamoDBv2; -using Amazon.DynamoDBv2.Model; - -namespace GeneXus.Data.NTier.DynamoDB -{ - public class RequestWrapper - { - private AmazonDynamoDBClient mDynamoDB; - private AmazonDynamoDBRequest mReq; - - public RequestWrapper() - { - - } - - public RequestWrapper(AmazonDynamoDBClient mDynamoDB, AmazonDynamoDBRequest req) - { - this.mDynamoDB = mDynamoDB; - this.mReq = req; - } - - public ResponseWrapper Read() - { - return Read(null); - } - - public ResponseWrapper Read(Dictionary lastEvaluatedKey) - { - if (mReq is ScanRequest) - { - ((ScanRequest)mReq).ExclusiveStartKey = lastEvaluatedKey; - return new ResponseWrapper(mDynamoDB.Scan((ScanRequest)mReq)); - } - if (mReq is QueryRequest) - { - ((QueryRequest)mReq).ExclusiveStartKey = lastEvaluatedKey; - return new ResponseWrapper(mDynamoDB.Query((QueryRequest)mReq)); - } - throw new NotImplementedException(); - } - - internal void Close() - { - mDynamoDB.Dispose(); - } - } -} diff --git a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBScan.cs b/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBScan.cs deleted file mode 100644 index 9c1442355..000000000 --- a/dotnet/src/dotnetframework/DynService.Dynamo/DynamoDBScan.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GeneXus.Data.NTier -{ - public class Scan: Query - { - public Scan(object dataStoreHelper):base(dataStoreHelper) - { - - } - } -} diff --git a/dotnet/src/dotnetframework/DynServiceFabric/DynService.Fabric.csproj b/dotnet/src/dotnetframework/DynServiceFabric/DynService.Fabric.csproj index cce2c76e6..e85f57197 100644 --- a/dotnet/src/dotnetframework/DynServiceFabric/DynService.Fabric.csproj +++ b/dotnet/src/dotnetframework/DynServiceFabric/DynService.Fabric.csproj @@ -1,4 +1,4 @@ - + net462 GeneXus.Data.DynService.Fabric @@ -18,7 +18,7 @@ - + diff --git a/dotnet/src/dotnetframework/DynServiceFabric/FabricDataReader.cs b/dotnet/src/dotnetframework/DynServiceFabric/FabricDataReader.cs index ebf7c4360..6025ad3e3 100644 --- a/dotnet/src/dotnetframework/DynServiceFabric/FabricDataReader.cs +++ b/dotnet/src/dotnetframework/DynServiceFabric/FabricDataReader.cs @@ -232,7 +232,7 @@ public int GetValues(object[] values) { System.Diagnostics.Debug.Assert(mResponse[mCurrentPosition].Values.Count == values.Length, "Values mismatch"); int i = 0; - foreach (var attName in mProjection) + foreach (string attName in mProjection) { values[i] = mResponse[mCurrentPosition][attName]; i++; diff --git a/dotnet/src/dotnetframework/DynServiceOData/DynServiceOData.csproj b/dotnet/src/dotnetframework/DynServiceOData/DynServiceOData.csproj index 906d0183c..ec1a4c6de 100644 --- a/dotnet/src/dotnetframework/DynServiceOData/DynServiceOData.csproj +++ b/dotnet/src/dotnetframework/DynServiceOData/DynServiceOData.csproj @@ -26,7 +26,7 @@ - + diff --git a/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierADO.cs b/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierADO.cs index f593de63c..79d2d3cce 100644 --- a/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierADO.cs +++ b/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierADO.cs @@ -586,7 +586,7 @@ public void SetParameterMultimedia(int id, string parm, string multimediaParm) //Second execution of Dataprovider that updates images, CategoryImage = calendar.Link(), image_gxi=https://chatteatest.s3.amazonaws.com/TestPGXReleased/Category/CategoryImage/calendar_dc0ca2d9335a484cbdc2d21fc7568af7.png, copy falla, multimediaUri = image_gxi; #pragma warning disable SCS0018 // Path traversal: injection possible in {1} argument passed to '{0}' public void SetParameterMultimedia(int id, string image_gxi, string image, string tableName, string fieldName) -{ + { GXLogging.Debug(log, "SetParameterMultimedia image_gxi:", image_gxi + " image:" + image); bool storageServiceEnabled = !string.IsNullOrEmpty(tableName) && !string.IsNullOrEmpty(fieldName) && (GXServices.Instance != null && GXServices.Instance.Get(GXServices.STORAGE_SERVICE) != null); string imageUploadName=image; diff --git a/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierService.cs b/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierService.cs index b8a1ff5d5..4df83cb39 100644 --- a/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierService.cs +++ b/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierService.cs @@ -186,6 +186,8 @@ public static DbType GXTypeToDbType(GXType type) return DbType.Object; case GXType.Blob: return DbType.Binary; + case GXType.Boolean: + return DbType.Boolean; case GXType.Undefined: default: return DbType.Object; diff --git a/dotnet/src/dotnetframework/GxClasses/View/GXWebControls.cs b/dotnet/src/dotnetframework/GxClasses/View/GXWebControls.cs index 5beefb333..16b54116b 100644 --- a/dotnet/src/dotnetframework/GxClasses/View/GXWebControls.cs +++ b/dotnet/src/dotnetframework/GxClasses/View/GXWebControls.cs @@ -41,7 +41,7 @@ private static Type GetWebStdType(GeneXus.Application.IGxContext context) } } #else - var cname = "GxWebStd"; + string cname = "GxWebStd"; string mainNamespace, className; if (Config.GetValueOf("AppMainNamespace", out mainNamespace)) className = mainNamespace + "." + cname; @@ -1658,7 +1658,7 @@ public override void FromJSONObject(dynamic Obj) JArray jsonArrValues = (JArray)jsonObj["v"]; if (jsonArrValues != null) { - var idx = 1; + int idx = 1; for (int i = 0; i < jsonArrValues.Count; i++) { JArray jsonItem = jsonArrValues.GetArray(i);