sensedeep / dynamodb-onetable

DynamoDB access and management for one table designs with NodeJS
https://doc.onetable.io/
MIT License
689 stars 109 forks source link

`find` ignores index search key expression #530

Closed stabl-gjn closed 3 months ago

stabl-gjn commented 5 months ago

Describe the bug

When using find together with an index and defining a key condition on the SK of the index, onetable will ignore the condition. The operation will succeed and even return items, but when looking at the debug log, no key expression for the SK is applied only the PK.

The same find expression (without the index) does work if the index SK expression (_gs1sk) is used as the normal entity sk and the find is run on it.

Cut/Paste

SCHEMA

const schema = {
    format: "onetable:1.1.0",
    version: "0.0.1",
    params: {
        timestamps: true,
        isoDates: false,
    },
    indexes: {
        primary: { hash: "pk", sort: "sk" },
        gs1: {
            hash: "_gs1pk",
            sk: "_gs1sk",
            project: "keys",
        },
    },
    models: {
        Token: {
            pk: { type: String, value: "registration#${user}" },
            sk: { type: String, value: "token#${key}" },
            user: {
                type: String,
                required: true,
                encode: ["pk", "#", 1],
            },
            key: {
                type: String,
                required: true,
                validate: MATCH.token,
                encode: ["sk", "#", 1],
            },
            expires: { type: Date, required: true, ttl: true },
            updated: { type: Date },
            created: { type: Date },

            // Index Keys
            _gs1pk: {
                type: String,
                value: "token#${key}",
                hidden: true,
            },
            _gs1sk: {
                type: String,
                value: "token#${expires}",
                hidden: true,
            },
        },
    } as const,
} as const;

CODE

const token = await TOKENS.find(
    {
        _gs1pk: "token#ASD12",
        _gs1sk: { ">": "token#1" },
    },
    {
        index: "gs1",
        log: true,
    },
);

LOG

info OneTable "find" "Token" {
    "trace": {
        "model": "Token",
        "cmd": {
            "ExpressionAttributeNames": {
                "#_0": "_gs1pk"
            },
            "ExpressionAttributeValues": {
                ":_0": {
                    "S": "token#ASD12"
                }
            },
            "KeyConditionExpression": "#_0 = :_0",
            "TableName": "asdf",
            "ConsistentRead": false,
            "IndexName": "gs1",
            "ScanIndexForward": true
        },
        "op": "find",
        "properties": {
            "_gs1pk": "token#ASD12"
        }
    }
}
info OneTable result for "find" "Token" {
    "cmd": {
        "ExpressionAttributeNames": {
            "#_0": "_gs1pk"
        },
        "ExpressionAttributeValues": {
            ":_0": {
                "S": "token#ASD12"
            }
        },
        "KeyConditionExpression": "#_0 = :_0",
        "TableName": "asdf",
        "ConsistentRead": false,
        "IndexName": "gs1",
        "ScanIndexForward": true
    },
    "items": [],
    "op": "find",
    "properties": {
        "_gs1pk": "token#ASD12"
    },
    "params": {
        "parse": true,
        "high": true,
        "index": "gs1",
        "log": true,
        "checked": true
    }
}

Expected behavior A clear and concise description of what you expected to happen.

Works when normal entity pk/sk is used:

Details __SCHEMA__ (only delta) ```typescript Token: { pk: { type: String, value: "token#${key}" }, sk: { type: String, value: "token#${expires}" }, } ``` __CODE__ ```typescript const token = await TOKENS.find( { pk: "token#ASD12", sk: { ">": "token#1" }, }, { log: true, }, ); ``` __LOGS__ ```text info OneTable "find" "Token" { "trace": { "model": "Token", "cmd": { "ExpressionAttributeNames": { "#_0": "pk", "#_1": "sk" }, "ExpressionAttributeValues": { ":_0": { "S": "token#ASD12" }, ":_1": { "S": "token#1" } }, "KeyConditionExpression": "#_0 = :_0 and #_1 > :_1", "TableName": "asdf", "ConsistentRead": false, "ScanIndexForward": true }, "op": "find", "properties": { "pk": "token#ASD12", "sk": { ">": "token#1" } } } } info OneTable result for "find" "Token" { "cmd": { "ExpressionAttributeNames": { "#_0": "pk", "#_1": "sk" }, "ExpressionAttributeValues": { ":_0": { "S": "token#ASD12" }, ":_1": { "S": "token#1" } }, "KeyConditionExpression": "#_0 = :_0 and #_1 > :_1", "TableName": "asdf", "ConsistentRead": false, "ScanIndexForward": true }, "items": [], "op": "find", "properties": { "pk": "token#ASD12", "sk": { ">": "token#1" } }, "params": { "parse": true, "high": true, "log": true, "checked": true } } ```

Screenshots If applicable, add screenshots to help explain your problem.

Environment (please complete the following information):

mobsense commented 4 months ago

Thanks for raising, we'll check this and get back to you.

mobsense commented 4 months ago

A couple of issues with your test code and the issue seems fixed.

In your index:

    indexes: {
        primary: { hash: "pk", sort: "sk" },
        gs1: {
            hash: "_gs1pk",
            sk: "_gs1sk",
            project: "keys",
        },

Your gs1 "sk" property should be "sort".

In your test code:

const token = await TOKENS.find({
    _gs1pk: "token#ASD12",
    _gs1sk: { ">": "token#1" },
}, {
        index: "gs1",
});

Your gs1pk and gs1sk values are wrong. They should be "token#1" and "token#EXPIRES_VALUE".

Can I please also ask one and all that a complete debug.ts is much better than fragements of code. Despite this case being really well documented -- a debug.ts saves us a lot of time just to replicate the issue. It takes about 30 minutes to construct a test case for such issues. Doesn't sound like much, but we do this for free and those 30 minutes really add up over time.

stabl-gjn commented 4 months ago

@mobsense thanks for taking the time and look into this and doing the write up. Also sorry for not providing a full example, will remember to do so next time.

I missed the sk => sort and will try with that again. As for the values i don't think the are mixed up, the intention is to query for a specific token key and a expiry where the first digit is bigger than 1.