Exp,And() , Exp.NE cause Aerospike.Client.AerospikeException : Error 4, Parameter error; Then bin is empty and op has no create flag(s)

Here is the failed unit test, it ** throws Aerospike.Client.AerospikeException : Error 4, Parameter error** when client.Operate( …) is called.

If the condition is Exp.BinExists(binName) alone, it works. As soon as put it in Exp.And(), even just the only parameter, it begins to fail.

Exp.NE line alone as condition will also throw the same exception.

Thanks for the help in advance.

[Fact]
   public void TestNestedMap()
   {
       AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);
       Key key = new Key("test", "NestedMapOps", "key1");
       string binName = "NestedMap";
       string outKey = "Out";
       string innerKey = "Inner";
       Value value = Value.Get(99);

       try
       {
           var writeOps = GetNestedWriteOperationsForField<int>(binName, outKey, innerKey, value).ToArray();
           var recordWrite = client.Operate(null, key, writeOps);
           
       }
       catch (AerospikeException ae) when (ae.Result == ResultCode.KEY_EXISTS_ERROR)
       {
           //ignore
       }

       var recordRead = client.Operate(null, key, GetNestedReadOperationsForField(binName, outKey, innerKey)); // throws  Aerospike.Client.AerospikeException : Error 4, Parameter error

       Assert.NotNull(recordRead);
       Assert.Equal(99, recordRead.GetInt(binName));
   }

   private static IEnumerable<Operation> GetNestedWriteOperationsForField<TValue>(string binName, string outerKey, string innerKey, Value value)
   {
       //create an empty innder map first as needed, otherwise AE exception
       yield return MapOperation.Put(new MapPolicy(MapOrder.UNORDERED, MapWriteMode.CREATE_ONLY), binName,
           Value.Get(outerKey), Value.Get(new Dictionary<string, TValue> { }));

       yield return MapOperation.Increment(MapPolicy.Default, binName, new StringValue(innerKey), value, CTX.MapKey(Value.Get(outerKey)));
   }

   private static Operation GetNestedReadOperationsForField(string binName, string outerKey, string innerKey)
   {
       Exp condition = Exp.And(
           Exp.BinExists(binName),
           Exp.NE(MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.MAP, Exp.Val(outerKey), Exp.Bin(binName, Exp.Type.MAP)), Exp.Nil())
        );

       Exp work = MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.INT, Exp.Val(innerKey), Exp.Bin(binName, Exp.Type.MAP), CTX.MapKey(Value.Get(outerKey))); //Good

       Exp conditionedWork = Exp.Cond(condition, work, Exp.Val(0)); 
       return ExpOperation.Read(binName, Exp.Build(conditionedWork), ExpReadFlags.EVAL_NO_FAIL);         
   }

Looks like you are getting a map from a map and trying to compare that with NIL.

In the server logs you should be getting a warning from the exp context - likely something like:

build_compare - error %u mismatched arg types ltype %u (%s) rtype %u (%s)"

Expressions have strict typing, you aren’t allowed to compare across types.

Thanks Kporter. I made progress.

With following update code, now I got WARNING (particle): (cdt.c:1222) cdt_process_state_context_eval() bin is empty and op has no create flag(s) error. But I do have RecordExistsAction.UPDATE and MapWriteMode.CREATE_ONLY specified in few places.

How to fix that?

What I am trying to do is to increment an entry in “map in map” bin. Based on my test, outer map must be there first, so I create outer map only when it is not there using Expression, and that is where trouble comes from.

If I can solve this problem, or I create increase inner map entry from a non-existing bin, both maps will be crated automatically, that will be great.

And ocz, I never want either inner or outer map be replaced and lost data.

Thanks!

public void TestNestedMap()
 {
     AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);
     Key key = new Key("test", "NestedMapOps", "key1");
     string binName = "NestedMap";
     string outKey = "Out";
     string innerKey = "Inner";
     Value value = Value.Get(99);

     try
     {
         WritePolicy wp = new WritePolicy()
         {
             expiration = 3600,
             recordExistsAction = RecordExistsAction.UPDATE,  //create or update
             respondAllOps = true,
             sendKey = true                    
         };
         var writeOps = GetNestedWriteOperationsForField<int>(binName, outKey, innerKey, value).ToArray();
         var recordWrite = client.Operate(wp, key, writeOps);

     }
     catch (AerospikeException ae) when (ae.Result == ResultCode.KEY_EXISTS_ERROR)
     {
         //ignore
     }

     var recordRead = client.Operate(null, key, GetNestedReadOperationsForField(binName, outKey, innerKey));

     Assert.NotNull(recordRead);
     Assert.Equal(99, recordRead.GetInt(binName));
 }

 private static IEnumerable<Operation> GetNestedWriteOperationsForField<TValue>(string binName, string outerKey, string innerKey, Value value)
 {            
     
     Exp outerKeyExp = MapExp.GetByKey(MapReturnType.KEY, Exp.Type.STRING, Exp.Val(outerKey), Exp.Bin(binName, Exp.Type.MAP));

     Exp outKeyNotExist = Exp.EQ(outerKeyExp, Exp.Val((string)null));

     IDictionary innerEmptyMap = new Dictionary<string, TValue> { };

     Exp insertMap = MapExp.Put(new MapPolicy(MapOrder.UNORDERED, MapWriteMode.CREATE_ONLY), 
         Exp.Val(outerKey), Exp.Val(innerEmptyMap, MapOrder.UNORDERED), Exp.Bin(binName, Exp.Type.MAP));
     
     Exp conditioned = Exp.Cond(outKeyNotExist, insertMap, Exp.Unknown());
     yield return ExpOperation.Write(binName, Exp.Build(conditioned), ExpWriteFlags.CREATE_ONLY | ExpWriteFlags.EVAL_NO_FAIL | ExpWriteFlags.POLICY_NO_FAIL);

     yield return MapOperation.Increment(MapPolicy.Default, binName, new StringValue(innerKey), value, CTX.MapKey(Value.Get(outerKey)));
 }

 private static Operation GetNestedReadOperationsForField(string binName, string outerKey, string innerKey)
 {
     Exp condition = Exp.And(                
         Exp.BinExists(binName), 
         Exp.NE(MapExp.GetByKey(MapReturnType.KEY, Exp.Type.STRING, Exp.Val(outerKey), Exp.Bin(binName, Exp.Type.MAP)), Exp.Val((string?)null))
      );
   
     Exp work = MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.INT, Exp.Val(innerKey), Exp.Bin(binName, Exp.Type.MAP), CTX.MapKey(Value.Get(outerKey))); 

     Exp conditionedWork = Exp.Cond(condition, work, Exp.Val(0)); // works
     return ExpOperation.Read(binName, Exp.Build(conditionedWork), ExpReadFlags.EVAL_NO_FAIL);         
 }

The return type for the GetByKey should have an EXISTS option - this will result in a Boolean.

1 Like

Hi, apologies for the time lag, C# isn’t my usual language, so it took some time to look things up.

  1. For MapExp.GetByKey(), MapReturnType.EXIST (instead of MapReturnType.KEY) will return bool so you don’t even need outKeyNotExist. Kevin has already mentioned this.
  2. You can use the CTX.MapKeyCreate (instead of MapKey) to create the outer map and it will be more efficient and simpler.
return MapOperation.Increment(MapPolicy.Default, binName, new StringValue(innerKey), value, CTX.MapKeyCreate(Value.Get(outerKey), MapOrder.UNORDERED));
  1. Your problem really is because increment doesn’t work if the value didn’t exist. You can’t increment if it wasn’t a number to begin with. You can get around this by priming an initial value:
IDictionary innerEmptyMap = new Dictionary<string, TValue> { innerKey: 0 }; // I don't know C# but I mean to have an inner map with the pair (key = innerKey, value = 0)
  1. I suggest using KEY_ORDERED maps in almost all use cases. It’s just better.
  2. Putting it all together and simplifying – We can do this without expressions by doing two operations (need testing):
private static IEnumerable<Operation> GetNestedWriteOperationsForField<TValue>(string binName, string outerKey, string innerKey, Value value)
{
  // 1st Operation.
  yield return MapOperation.Put(MapPolicy(MapOrder.KEY_ORDERED, MapWriteFlags.CREATE_ONLY | MapWriteFlags.NO_FAIL), binName, Value.Get(innerKey), Value.Get(0), CTX.MapKeyCreate(Value.Get(outerKey), MapOrder.KEY_ORDERED));
  // This will create the outer Map if it doesn't exist, and insert an entry with key outerKey.
  // It will next check if the inner Map has innerKey.
  // If no innerKey, it will create an entry (innerKey, 0). Initial value 0.
  // If innerKey exists, it will fail. Fortunately, we specified MapWriteFlags.NO_FAIL so the fail is converted into a no-op.

  // 2nd Op.
  yield return MapOperation.Increment(MapPolicy.Default, binName, new StringValue(innerKey), value, CTX.MapKey(Value.Get(outerKey)));
  // If 1st Op no-op, everything should be fine and we can just increment.
  // Otherwise 1st Op would set us up for success at 2nd op.
}

Please check my work as C# is not my usual language so I’m a complete noob at it. Those are my best guesses at C# correctness.