feat(data-access): auto-normalize enum values in PostgREST query filters#1392
feat(data-access): auto-normalize enum values in PostgREST query filters#1392
Conversation
Postgres enum comparisons are case-sensitive, so passing 'new' when the DB stores 'NEW' silently returns no results. This adds automatic case-insensitive matching for all array-typed (enum) attributes in #applyKeyFilters and the batchGetByKeys bulk .in() path. When the attribute type is an array of known values, the input is matched case-insensitively and replaced with the correctly-cased enum value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
This PR will trigger a minor release when merged. |
There was a problem hiding this comment.
Hey @ekremney
Verdict: Approve (conditional) - right approach, needs a rebase before merge.
Clean, well-scoped fix at the right layer. The core implementation is sound - 11 lines that solve a real class of bugs. One critical branch divergence issue to resolve.
Must fix before merge
| # | Issue | Severity |
|---|---|---|
| 1 | caseInsensitive flag regression - The branch predates the caseInsensitive / ilike feature in #applyKeyFilters. The PR's version of #applyKeyFilters drops the if (attr?.caseInsensitive) check entirely, breaking Site.baseURL lookups (which rely on ilike). Rebase on main and merge both behaviors: caseInsensitive -> ilike, else -> eq with #normalizeEnumValue. |
Critical |
| 2 | Remove incorrect eslint-disable - // eslint-disable-next-line class-methods-use-this on #normalizeEnumValue is wrong. The method uses this.schema.getAttribute(key). Remove it. |
Minor (quick fix) |
| 3 | Revert package-lock.json noise - 186 lines of unrelated "peer": true additions and sibling package version bumps. Split out or drop. |
Minor (hygiene) |
Should track as follow-ups
| # | Issue | Severity |
|---|---|---|
| 4 | applyWhere gap - The where clause path (postgrest.utils.js) does op.eq() without normalization. where: (attr, op) => op.eq(attr.status, 'new') silently returns empty results. No callers currently use where with enum fields, so this is latent. Document or fix in follow-up. |
Important (latent) |
| 5 | Write-side asymmetry - Reads now accept 'new' for 'NEW', but #validateItem on the write path uses strict includes() and will reject 'new'. Not a bug per se - reads are forgiving, writes enforce canonical values - but should be documented to avoid confusion. |
Low |
| 6 | removeByIndexKeys bulk .in() path - Not normalized (unlike batchGetByKeys which is). Low severity since bulk deletes typically use UUIDs, not enum values. |
Low |
| 7 | Direct PostgREST consumers - Mystique queries PostgREST directly and won't benefit from this fix. If Mystique hits the same case regression, a DB-level solution (citext) will still be needed. | Informational |
No concerns
- Security - No vulnerabilities. Bounded match set from schema definitions, safe fallback, parameterized queries. No injection or authorization risk.
- Performance -
.find()with.toLowerCase()over 2-8 enum values per key is negligible against PostgREST round-trip latency. - Schema alignment - All model constants are consistently uppercase and match the schema
typearrays. - FK / ID resolution -
#normalizeEnumValuecorrectly skips non-enum attributes, so UUIDs and FK attributes are untouched.
Recommended merged #applyKeyFilters
After rebase, the method should look like:
#applyKeyFilters(query, keys) {
if (!isNonEmptyObject(keys)) return query;
let filtered = query;
Object.entries(keys).forEach(([key, value]) => {
const dbField = this.#toDbField(key);
const attr = this.schema.getAttribute(key);
if (attr?.caseInsensitive && typeof value === 'string') {
const escaped = value.replace(/[%_]/g, '\\$&');
filtered = filtered.ilike(dbField, escaped);
} else {
filtered = filtered.eq(dbField, this.#normalizeEnumValue(key, value));
}
});
return filtered;
}
Correction to review item #1The That said, the two features ( Updated verdict: items #2 and #3 remain (eslint-disable fix, package-lock cleanup). Item #1 is not a blocker. |
- Remove incorrect eslint-disable on #normalizeEnumValue (uses this) - Normalize enum values in removeByIndexKeys bulk .in() path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This reverts commit 905c5cc.
|
Thanks for the thorough review @solaris007! Addressed the feedback: Fixed:
Acknowledged follow-ups:
And confirmed — |
This reverts commit 2f9fde6.
|
|
# Conflicts: # package-lock.json
## [@adobe/spacecat-shared-data-access-v3.5.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.4.2...@adobe/spacecat-shared-data-access-v3.5.0) (2026-03-02) ### Features * **data-access:** auto-normalize enum values in PostgREST query filters ([#1392](#1392)) ([f44d0d2](f44d0d2))
|
🎉 This PR is included in version @adobe/spacecat-shared-data-access-v3.5.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
…ries (#1890) ## Summary - Adds 9 integration tests verifying that `by-status` and `by-delivery-type` API endpoints work with lowercase and mixed-case enum values (e.g. `new`, `New` instead of `NEW`) - Exercises the auto-normalize enum feature added to `BaseCollection` in spacecat-shared-data-access 3.5.0 ([spacecat-shared#1392](adobe/spacecat-shared#1392)) end-to-end through the full HTTP → controller → data-access → PostgREST stack ### New tests by entity | File | Tests | What they verify | |---|---|---| | `opportunities.js` | 2 | `/by-status/new` and `/by-status/New` return same results as `/by-status/NEW` | | `suggestions.js` | 3 | `/by-status/new`, `/by-status/New`, and `/by-status/new/paged/10` | | `fixes.js` | 2 | `/by-status/pending` and `/by-status/Pending` | | `sites.js` | 2 | `/by-delivery-type/AEM_EDGE` and `/by-delivery-type/Aem_Edge` | ## Test plan - [ ] PostgreSQL IT suite passes: `npx mocha --require test/it/postgres/harness.js --timeout 30000 'test/it/postgres/**/*.test.js'` - [ ] DynamoDB IT suite passes: `npx mocha --require test/it/dynamo/harness.js --timeout 30000 'test/it/dynamo/**/*.test.js'` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
'new'vs'NEW') no longer silently return empty results#normalizeEnumValuehelper toBaseCollectionthat matches input strings case-insensitively against the known enum values array (attribute.type)#applyKeyFilters(covers all synthetic accessors,allByIndexKeys,findByIndexKeys,updateByKeys,removeByIndexKeys) and thebatchGetByKeysbulk.in()pathtype: Object.values(...)(array)Test plan
npm run test:it🤖 Generated with Claude Code