Skip to content

Commit 8da93c4

Browse files
howethomasclaude
andcommitted
fix: extract vcon_id from searchByTags RPC response; fix CROSS JOIN placement in tag analytics
search_by_tags: search_vcons_by_tags RPC returns {vcon_id: uuid} objects, not plain strings. Casting with `as string[]` left them as objects, causing getVCon to receive [object Object] and Postgres to throw 22P02 invalid uuid syntax. get_tag_analytics: CROSS JOIN LATERAL was placed after WHERE, which is invalid SQL (joins must precede the WHERE clause). Refactored all four tag analytics queries (getTagStatistics, getTagFrequencyAnalysis, getTagValueDistribution, getTagTemporalTrends) to filter rows in a subquery/CTE first, then apply the LATERAL join safely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a783c83 commit 8da93c4

2 files changed

Lines changed: 43 additions & 33 deletions

File tree

src/db/database-analytics.ts

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -709,18 +709,19 @@ export class SupabaseDatabaseAnalytics implements IDatabaseAnalytics {
709709

710710
private async getTagStatistics() {
711711
const query = `
712-
WITH tag_data AS (
713-
SELECT
714-
key,
715-
value,
716-
vcon_id
712+
WITH filtered AS (
713+
SELECT body, vcon_id
717714
FROM attachments
718-
WHERE type = 'application/json'
715+
WHERE type = 'application/json'
719716
AND body::jsonb ? 'tags'
720717
AND jsonb_typeof(body::jsonb->'tags') = 'object'
721-
CROSS JOIN LATERAL jsonb_each_text(body::jsonb->'tags')
718+
),
719+
tag_data AS (
720+
SELECT key, value, f.vcon_id
721+
FROM filtered f
722+
CROSS JOIN LATERAL jsonb_each_text(f.body::jsonb->'tags')
722723
)
723-
SELECT
724+
SELECT
724725
COUNT(DISTINCT key) as unique_keys,
725726
COUNT(DISTINCT value) as unique_values,
726727
COUNT(DISTINCT vcon_id) as vcons_with_tags,
@@ -739,21 +740,25 @@ export class SupabaseDatabaseAnalytics implements IDatabaseAnalytics {
739740

740741
private async getTagFrequencyAnalysis(topNKeys: number, minUsageCount: number) {
741742
const query = `
742-
WITH tag_stats AS (
743-
SELECT
744-
key,
745-
COUNT(*) as usage_count,
746-
COUNT(DISTINCT value) as unique_values,
747-
COUNT(DISTINCT vcon_id) as vcons_with_tag
743+
WITH filtered AS (
744+
SELECT body, vcon_id
748745
FROM attachments
749-
WHERE type = 'application/json'
746+
WHERE type = 'application/json'
750747
AND body::jsonb ? 'tags'
751748
AND jsonb_typeof(body::jsonb->'tags') = 'object'
752-
CROSS JOIN LATERAL jsonb_each_text(body::jsonb->'tags')
749+
),
750+
tag_stats AS (
751+
SELECT
752+
key,
753+
COUNT(*) as usage_count,
754+
COUNT(DISTINCT value) as unique_values,
755+
COUNT(DISTINCT f.vcon_id) as vcons_with_tag
756+
FROM filtered f
757+
CROSS JOIN LATERAL jsonb_each_text(f.body::jsonb->'tags')
753758
GROUP BY key
754759
HAVING COUNT(*) >= ${minUsageCount}
755760
)
756-
SELECT
761+
SELECT
757762
key,
758763
usage_count,
759764
unique_values,
@@ -775,28 +780,29 @@ export class SupabaseDatabaseAnalytics implements IDatabaseAnalytics {
775780

776781
private async getTagValueDistribution(topNKeys: number) {
777782
const query = `
778-
WITH top_keys AS (
779-
SELECT key
783+
WITH filtered AS (
784+
SELECT body, vcon_id
780785
FROM attachments
781-
WHERE type = 'application/json'
786+
WHERE type = 'application/json'
782787
AND body::jsonb ? 'tags'
783788
AND jsonb_typeof(body::jsonb->'tags') = 'object'
784-
CROSS JOIN LATERAL jsonb_each_text(body::jsonb->'tags')
789+
),
790+
top_keys AS (
791+
SELECT key
792+
FROM filtered f
793+
CROSS JOIN LATERAL jsonb_each_text(f.body::jsonb->'tags')
785794
GROUP BY key
786795
ORDER BY COUNT(*) DESC
787796
LIMIT ${topNKeys}
788797
),
789798
tag_values AS (
790-
SELECT
799+
SELECT
791800
t.key,
792801
t.value,
793802
COUNT(*) as count
794-
FROM attachments a
803+
FROM filtered a
795804
CROSS JOIN LATERAL jsonb_each_text(a.body::jsonb->'tags') t
796805
INNER JOIN top_keys tk ON t.key = tk.key
797-
WHERE a.type = 'application/json'
798-
AND a.body::jsonb ? 'tags'
799-
AND jsonb_typeof(a.body::jsonb->'tags') = 'object'
800806
GROUP BY t.key, t.value
801807
)
802808
SELECT
@@ -819,16 +825,19 @@ export class SupabaseDatabaseAnalytics implements IDatabaseAnalytics {
819825

820826
private async getTagTemporalTrends() {
821827
const query = `
822-
SELECT
828+
SELECT
823829
DATE_TRUNC('month', a.created_at) as month,
824830
t.key,
825831
COUNT(*) as usage_count
826-
FROM attachments a
832+
FROM (
833+
SELECT body, created_at
834+
FROM attachments
835+
WHERE type = 'application/json'
836+
AND body::jsonb ? 'tags'
837+
AND jsonb_typeof(body::jsonb->'tags') = 'object'
838+
AND created_at >= NOW() - INTERVAL '12 months'
839+
) a
827840
CROSS JOIN LATERAL jsonb_each_text(a.body::jsonb->'tags') t
828-
WHERE a.type = 'application/json'
829-
AND a.body::jsonb ? 'tags'
830-
AND jsonb_typeof(a.body::jsonb->'tags') = 'object'
831-
AND a.created_at >= NOW() - INTERVAL '12 months'
832841
GROUP BY DATE_TRUNC('month', a.created_at), t.key
833842
ORDER BY month, usage_count DESC
834843
`;

src/db/queries.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1336,7 +1336,8 @@ export class SupabaseVConQueries implements IVConQueries {
13361336
}
13371337

13381338
// RPC succeeded and returned results (or no tag attachments exist)
1339-
return (data || []) as string[];
1339+
// The RPC returns [{vcon_id: "uuid"}, ...] — extract the uuid string from each row
1340+
return (data || []).map((row: any) => (typeof row === 'object' && row !== null ? row.vcon_id : row)) as string[];
13401341
}
13411342

13421343
/**

0 commit comments

Comments
 (0)