dynamoose: [BUG] No error thrown for invalid condition

Summary:

Dynamoose does not throw/log an error when we create an invalid Dynamoose#Condition, using the DynamoDB condition syntax. This causes silent logic bugs!

Code sample:

Schema

const definition = new dynamoose.Schema(
    {
        id: {
            type: String,
            required: true,
        },
        value: {
            type: String,
            required: true,
        },
        randomNumber: {
            type: Number,
            required: true,
        },
    },
    {
        /**
         * Let Dynamoose manage the `createdAt` and `updatedAt` timestamps,
         * Unix epoch in seconds (not milliseconds)
         */
        timestamps: true,
    },
);

Model

const tableName = 'dipack_dynamoose_bug_repro';
const Model = dynamoose.model(tableName, definition, { create: true });

General

// ========== ALL CODE HERE INCLUDING SCHEMA AND MODEL =============

const dynamoose = require('dynamoose');

const ddb = new dynamoose.aws.sdk.DynamoDB({
    region: 'eu-west-1',
    version: 'latest',
});

dynamoose.aws.ddb.set(ddb);

const tableName = 'dipack_dynamoose_bug_repro';

const definition = new dynamoose.Schema(
    {
        id: {
            type: String,
            required: true,
        },
        value: {
            type: String,
            required: true,
        },
        randomNumber: {
            type: Number,
            required: true,
        },
    },
    {
        /**
         * Let Dynamoose manage the `createdAt` and `updatedAt` timestamps,
         * Unix epoch in seconds (not milliseconds)
         */
        timestamps: true,
    },
);

const DELETION_THRESHOLD_MS = (1000 * 60);
const now = Date.now();

const buggyQuery = new dynamoose.Condition({
    FilterExpression: '#createdAtCol <= :createdAtValue OR #updatedAtCol <= :updatedAtValue',
    ExpressionAttributeValues: {
        ':createdAtValue': {
            N: now - DELETION_THRESHOLD_MS,
        },
        ':updatedAtValue': {
            N: now - DELETION_THRESHOLD_MS,
        },
    },
    ExpressionAttributeNames: {
        '#createdAtCol': 'createdAt',
        '#updatedAtCol': 'updatedAt',
    },
});

const workingQueryDyn = new dynamoose.Condition().where('createdAt').le(now - DELETION_THRESHOLD_MS).or().where('updatedAt').le(now - DELETION_THRESHOLD_MS);

(async () => {
    const Model = dynamoose.model(tableName, definition, { create: true });
    const existingRows = await Model.scan().exec();
    console.log(`Found ${existingRows.length} rows.`);
    const newRows = await Promise.all(
        new Array(5).fill(0).map(async (_, idx) => {
            const id = Math.floor(Math.random() * 10 ** 10);
            const row = await Model.create({
                id: String(id),
                value: `${id}-${idx}`,
                randomNumber: Math.random(),
            });
            return row;
        })
    ).catch(err => console.error('Error while creating new rows', err));
    console.log(`Created ${newRows.length} new rows.`);
    const filteredRows = await Model.scan(buggyQuery).exec();
    console.log(`Filtered ${filteredRows.length} rows using DynamoDB condition API`);
    const allRows = await Model.scan().exec();
    if (allRows.every(row => filteredRows.find(fRow => fRow.id === row.id) !== undefined)) {
        console.warn('No rows were filtered!');
    }
    const filteredRowsDyn = await Model.scan(workingQueryDyn).exec();
    console.log(`Filtered ${filteredRowsDyn.length} rows using Dynamoose condition API`);
    if (allRows.every(row => filteredRowsDyn.find(fRow => fRow.id === row.id) !== undefined)) {
        console.warn('No rows were filtered, using the Dynamoose API too!');
    }
    console.log('DynamoDB API condition object', buggyQuery.requestObject());
    console.log('Dynamoose API condition object', workingQueryDyn.requestObject());
})();

Current output and behavior (including stack trace):

The DynamoDB based Condition is currently shown as an empty object.

Found 105 rows.
Created 5 new rows.
Filtered 110 rows using DynamoDB condition API
Filtered 100 rows using Dynamoose condition API
DynamoDB API condition object {}
Dynamoose API condition object {
  ConditionExpression: '#a0 <= :v0 OR #a1 <= :v1',
  ExpressionAttributeNames: { '#a0': 'createdAt', '#a1': 'updatedAt' },
  ExpressionAttributeValues: { ':v0': { N: '1640063029486' }, ':v1': { N: '1640063029486' } }
}

Expected output and behavior:

Expect the condition objects built using the DynamoDB syntax, and the Dynamoose API to be the same. If the condition generated is invalid, then we should throw an error.

Environment:

Operating System: MacOS Operating System Version: 12.1 Monterey Node.js version (node -v): 14.17.3 NPM version: (npm -v): 6.14.13 Dynamoose version: 2.8.3

Other information (if applicable):

Other:

  • I have read through the Dynamoose documentation before posting this issue
  • I have searched through the GitHub issues (including closed issues) and pull requests to ensure this issue has not already been raised before
  • I have searched the internet and Stack Overflow to ensure this issue hasn’t been raised or answered before
  • I have tested the code provided and am confident it doesn’t work as intended
  • I have filled out all fields above
  • I am running the latest version of Dynamoose

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 16 (8 by maintainers)

Commits related to this issue

Most upvoted comments

@fishcharlie It worked! 🥳

@dipack95 Forgot to push 😆. Check again now.

@dipack95 Please do me a favor and dig into this a bit further yourself. I have limited resources and insight into your code and items in your table.

Maybe try running “dynamoose.logger.providers.set([console]);” to see what that outputs.

And please work on trying to create an MCVE.

As far as I can tell everything is working fine. I need more context here about what specifically isn’t working as opposed to just a bunch of code (most of which isn’t relevant to this issue).