AI code assistants have a long way to go

2 prmph 0 5/29/2025, 3:25:20 PM
Me>

There's something wrong with this snippet:

    const tagFilters = searchTags.map(tag => tag.cat
        ? ["tags", "contains", JSON.stringify(tag)] as FilterSingle<ContentObjectXtended>
        : ["tags", "contains", tag.val] as FilterSingle<ContentObjectXtended>
    )

Claude>

The issue is in the filter value for categorized tags. You're JSON-stringifying the entire tag object, but the db stores tags as a JSONB array of objects. The correct approach is to use the JSONB containment operator with the proper object structure.

The fix:

    const tagFilters = searchTags.map(tag => tag.cat
        ? ["tags", "contains", { val: tag.val, cat: tag.cat }] as FilterSingle<ContentObjectXtended>
        : ["tags", "contains", { val: tag.val }] as FilterSingle<ContentObjectXtended>
    )
This matches the JSONB array structure in the db where tags are stored as objects with val and optional cat properties. The PostgreSQL JSONB @> operator (which "contains" maps to) will properly match these object structures within the array.

Me>

Is the ternary operator even needed?

Claude>

No, the ternary operator is not needed. Looking at the db, all tags are stored as objects with a val property and an optional cat property.

    const tagFilters = searchTags.map(tag =>
        ["tags", "contains", { val: tag.val, cat: tag.cat }] as FilterSingle<ContentObjectXtended>
    )
This simplified version works because:

- When tag.cat is undefined, the object { val: tag.val, cat: undefined } will still match stored tags that have no category (where cat is null/undefined)

- PostgreSQL's JSONB containment operator handles the undefined/null cat property correctly

Me>

What about the map?

Claude>

You're right. Since each tag creates a separate filter condition and they need to be combined with AND logic, a simple map isn't sufficient. You need to create a compound filter.

    const tagFilter = searchTags.length === 0
        ? undefined
        : searchTags.length === 1
            ? ["tags", "contains", { val: searchTags[0].val, cat: searchTags[0].cat }] as FilterSingle<ContentObjectXtended>
            : searchTags.reduce((acc, tag, index) => {
                const singleFilter: FilterSingle<ContentObjectXtended> = ["tags", "contains", { val: tag.val, cat: tag.cat }]
                return index === 1
                    ? [acc as FilterSingle<ContentObjectXtended>, "AND", singleFilter] as RecordFilter<ContentObjectXtended>
                    : [acc, "AND", singleFilter] as RecordFilter<ContentObjectXtended>
            })
This creates:

- No filter if no search tags

- A single filter if one search tag

- A compound AND filter if multiple search tags

The reduce builds the nested structure: [filter1, "AND", [filter2, "AND", filter3]] for multiple tags, ensuring all specified tags must be present on matching objects.

Me>

No, you don't get it

Claude>

You're absolutely right. The tags column is a JSONB array, and a single "contains" operation can match multiple tags at once. You don't need to map over individual tags or create compound filters.

    const tagFilter = searchTags.length > 0 
        ? ["tags", "contains", searchTags.map(tag => ({ val: tag.val, cat: tag.cat }))] as FilterSingle<ContentObjectXtended>
        : undefined
This passes the entire array of tag objects to the "contains" filter, which will use PostgreSQL's JSONB @> operator to check if the stored tags array contains all the specified tag objects.

Me>

Ok good, but why the map again?

Claude>

You're right again. Since searchTags is already an array of objects that match the db structure, no map is needed at all.

    const tagFilter = searchTags.length > 0 
        ? ["tags", "contains", searchTags] as FilterSingle<ContentObjectXtended>
        : undefined
The searchTags array already has the correct structure to pass directly to the PostgreSQL JSONB containment operator.

Comments (0)

No comments yet