aerospike / aerospike-client-java

Aerospike Java Client Library
Other
236 stars 212 forks source link

Query method returns RecordSet with userKey set to null #77

Closed Aloren closed 7 years ago

Aloren commented 7 years ago

I have following test.

    @Test
    public void shouldFindByFirstName() throws Exception {
        IndexTask task = client.createIndex(null, info.getNamespace(),
                "my-set", "firstName-index", "firstName", IndexType.STRING);
        task.waitTillComplete();

        client.put(null, new Key(info.getNamespace(), "my-set", "id-0"), new Bin("firstName", "Mykola"));
        client.put(null, new Key(info.getNamespace(), "my-set", "id-1"), new Bin("firstName", "Vasya"));

        Statement statement = new Statement();
        statement.setNamespace(info.getNamespace());
        statement.setSetName("my-set");
        statement.setFilters(Filter.equal("firstName", "Vasya"));

        RecordSet records = client.query(null, statement);

        KeyRecord foundRecord = records.iterator().next();
        Assertions.assertThat(foundRecord).isNotNull();
        Assertions.assertThat(foundRecord.key.namespace).isEqualTo(info.getNamespace());
        Assertions.assertThat(foundRecord.key.setName).isEqualTo("my-set");
        Assertions.assertThat(foundRecord.key.userKey).isNotNull().isEqualTo("id-1");
    }

The last line fails with message:

java.lang.AssertionError: 
Expecting actual not to be null

After debugging I've found MultiCommand method parseKey. For my test it always executes with fieldcount = 3 and this code is never executed:

case FieldType.KEY:
    userKey = Buffer.bytesToKeyValue(dataBuffer[1], dataBuffer, 2, size-1);
    break;
BrianNichols commented 7 years ago

Here is an explanation from our docs:

public Key(String namespace, String setName, int key) throws AerospikeException

Initialize key from namespace, optional set name and user key. The set name and user defined key are converted to a digest before sending to the server. The user key is not used or returned by the server by default. If the user key needs to persist on the server, use one of the following methods:

1) Set "WritePolicy.sendKey" to true. In this case, the key will be sent to the server for storage on writes and retrieved on multi-record scans and queries.

2) Explicitly store and retrieve the key in a bin.

Aloren commented 7 years ago

Thanks. Could you please explain why it was done that way? For me it looks not obvious right now.

BrianNichols commented 7 years ago

Some customers use long URLs for keys. They save bandwidth and storage when the key's hash digest (always 20 bytes) is sent instead of the key. Some applications use the key to uniquely identify a record only.

Aloren commented 7 years ago

Ok. Thanks.

Aloren commented 7 years ago

Hi, I was trying to test how server behaves with multiple filters -- server responded that it now only supports only one filter, so I looked into aerospike-helper-java how multiple filters are implemented there and created following test:

    @Test
    public void shouldFindByFirstNameAndAge() throws Exception {
        IndexTask task = client.createIndex(null, info.getNamespace(),
                "my-set", "firstName-index", "firstName", IndexType.STRING);
        task.waitTillComplete();
        IndexTask task2 = client.createIndex(null, info.getNamespace(),
                "my-set", "age-index", "age", IndexType.NUMERIC);
        task2.waitTillComplete();

        WritePolicy policy = new WritePolicy();
        policy.sendKey = true;
        client.put(policy, new Key(info.getNamespace(), "my-set", "id-0"), new Bin("firstName", "Mykola"), new Bin("age", 45));
        client.put(policy, new Key(info.getNamespace(), "my-set", "id-1"), new Bin("firstName", "Vasya"), new Bin("age", 15));

        Statement statement = new Statement();
        statement.setNamespace(info.getNamespace());
        statement.setSetName("my-set");

        statement.setFilters(Filter.range("age", 10, 20));
        Map<String, Object> originArgs = new HashMap<>();
        originArgs.put("includeAllFields", 1);
        originArgs.put("filterFuncStr", "if rec['firstName'] == 'Vasya' then selectedRec = true end");
        statement.setAggregateFunction(this.getClass().getClassLoader(), "as_utility.lua", "as_utility", "select_records", Value.get(originArgs));

        QueryPolicy queryPolicy = new QueryPolicy();
        queryPolicy.sendKey = true;
        ResultSet records = client.queryAggregate(queryPolicy, statement);

        Object foundRecord = records.iterator().next();
        Assertions.assertThat(foundRecord).isNotNull();
    }

As you can see I set property sendKey to true for all operations, test succeeds, BUT I still am not able to find userKey in foundRecord. What I see in debugger:

screen shot 2017-04-28 at 10 26 37 pm

Is the only option to get userKey is to save it as a bin for such case?

wchu-citrusleaf commented 7 years ago

The result returned is not a Record type, but rather an Object type, most likely constructed by the map() function in the as_utility.lua function. It can be enhanced to include the record key in the object as well, using the record.key() function. See http://www.aerospike.com/docs/udf/api/record.html#functions