Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unexpected results when adding optional attributes to a projection expression #464

Closed
anatolzak opened this issue Jan 17, 2025 · 3 comments

Comments

@anatolzak
Copy link
Contributor

anatolzak commented Jan 17, 2025

Describe the bug

If your entity has an optional attribute and you specify this attribute in the attributes selection, ElectroDB behaves as follows: If the item in DynamoDB does not include the optional attribute:

  1. It returns null for the item in a get operation ({ data: null }).
  2. It excludes the item from the results of a query operation ({ data: [], cursor: null }).
  3. It returns an array containing null for a batch get operation, although the TypeScript type does not indicate that the array can contain nulls ({ data: [null], unprocessed: [] }).

Here is an example illustrating the differences in the query operation when specifying optional fields in the attributes (the name attribute is defined as optional at the Entity level):

const queryWithAttributes = await db.entities.User.query
  .byId({ id: '1' })
  .go({ attributes: ['name'] });

const queryWithoutAttributes = await db.entities.User.query.byId({ id: '1' }).go();

console.log({ queryWithAttributes, queryWithoutAttributes });

/*
{
	"queryWithAttributes": {
		"data": [],
		"cursor": null
	},
	"queryWithoutAttributes": {
		"data": [{
			"id": "1"
		}],
		"cursor": null
	}
}
*/

Here is an example illustrating the differences in the get operation when specifying optional fields in the attributes:

const getWithAttributes = await db.entities.User.get({ id: '1' }).go({ attributes: ['name'] });

const getWithoutAttributes = await db.entities.User.get({ id: '1' }).go();

console.log({ getWithAttributes, getWithoutAttributes });

/*
{
	"getWithAttributes": {
		"data": null
	},
	"getWithoutAttributes": {
		"data": {
			"id": "1"
		}
	}
}
*/

Here is an example illustrating the differences in the batchGet operation when specifying optional fields in the attributes:

const batchGetWithAttributes = await db.entities.User.get([{ id: '1' }]).go({ attributes: ['name'] });

const batchGetWithoutAttributes = await db.entities.User.get([{ id: '1' }]).go();

console.log({ batchGetWithAttributes, batchGetWithoutAttributes });

/*
{
	"batchGetWithAttributes": {
		"data": [
			null
		],
		"unprocessed": []
	},
	"batchGetWithoutAttributes": {
		"data": [{
			"id": "1"
		}],
		"unprocessed": []
	}
}
*/

ElectroDB Version
3.0.1

ElectroDB Playground Link
Not relevant, as the issue occurs after ElectroDB receives the response from DynamoDB, and ElectroDB doesn't return all the expected items.

I created a minimal reproducible repository: https://github.com/anatolzak/electrodb-optional-attributes-bug. Just run npm install and ./run.sh.

Entity/Service Definitions

{
    model: {
      entity: 'user',
      version: '1',
      service: 'app',
    },
    attributes: {
      id: { type: 'string', required: true, readOnly: true },
      name: { type: 'string' },
    },
    indexes: {
      byId: {
        pk: { field: 'pk', composite: ['id'] },
        sk: { field: 'sk', composite: [] },
      },
    },
  }

Expected behavior
Items should not be excluded from the results of read operations if they don't contain optional attributes.

@tywalch
Copy link
Owner

tywalch commented Jan 18, 2025

Thanks @anatolzak! I will take a look at this to better understand what's going on under the hood, thanks for sending it over, hope to reply back soon!

@tywalch
Copy link
Owner

tywalch commented Jan 21, 2025

@anatolzak Firstly, thank you very much for the example repo; it helped me quickly jump into this ticket! Is it safe to assume the behavior you're expecting (in the specific case of your example) is to have an empty object returned?

@anatolzak
Copy link
Contributor Author

@tywalch glad the repo was helpful! And yes, I would expect an empty object

tywalch added a commit that referenced this issue Jan 24, 2025
- [Issue #464](#464); When specifing return attributes on retrieval methods, ElectroDB would unexpectly return null or missing values if the options chosen resulted in an empty object being returned. This behavor could be confused with no results being found. ElectroDB now returns the empty object in these cases.

### Added
- ElectroDB Error objects no contain a `params()` method. If your operation resulted in an error thrown by the DynamoDB client, you can call the `params()` method to get the compiled parameters sent to DynamoDB. This can be helpful for debugging. Note, that if the error was thrown prior to parameter creation (validation errors, invalid query errors, etc) then the `params()` method will return the value `null`.
@tywalch tywalch mentioned this issue Jan 24, 2025
tywalch added a commit that referenced this issue Jan 26, 2025
* ### Fixed
- [Issue #464](#464); When specifing return attributes on retrieval methods, ElectroDB would unexpectly return null or missing values if the options chosen resulted in an empty object being returned. This behavor could be confused with no results being found. ElectroDB now returns the empty object in these cases.

### Added
- ElectroDB Error objects no contain a `params()` method. If your operation resulted in an error thrown by the DynamoDB client, you can call the `params()` method to get the compiled parameters sent to DynamoDB. This can be helpful for debugging. Note, that if the error was thrown prior to parameter creation (validation errors, invalid query errors, etc) then the `params()` method will return the value `null`.

* Adds type tests, fixes lacking imports

* Cleans up approach to type test imports
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants