Skip to content

Commit a29e1c4

Browse files
committed
report-listener: speed up physical brand search path
1 parent 857fb36 commit a29e1c4

File tree

2 files changed

+78
-26
lines changed

2 files changed

+78
-26
lines changed

report-listener/.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
BUILD_VERSION=1.0.107
1+
BUILD_VERSION=1.0.112

report-listener/database/database.go

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -660,18 +660,75 @@ func (d *Database) SearchReports(ctx context.Context, searchQuery string, classi
660660
// Phase 2: Fetch report details only for matching IDs
661661
// This approach is ~5x faster than joining all tables for FULLTEXT match
662662

663-
var seqArgs []interface{}
664-
var seqQuery string
663+
var seqs []int
664+
665+
// Fast path for common single-word brand searches (e.g. "google"):
666+
// use indexed brand columns first and only fall back to FULLTEXT when needed.
667+
term := strings.TrimSpace(searchQuery)
668+
if strings.HasPrefix(term, "+") {
669+
term = strings.TrimPrefix(term, "+")
670+
}
671+
if term != "" && !strings.Contains(term, " ") && !strings.ContainsAny(term, "\"*~()<>") {
672+
prefixTerm := strings.ToLower(term) + "%"
673+
var fastQuery string
674+
var fastArgs []interface{}
675+
if classification != "" {
676+
fastQuery = `
677+
SELECT DISTINCT seq
678+
FROM report_analysis
679+
WHERE is_valid = TRUE
680+
AND classification = ?
681+
AND brand_name LIKE ?
682+
LIMIT 200
683+
`
684+
fastArgs = []interface{}{classification, prefixTerm}
685+
} else {
686+
fastQuery = `
687+
SELECT DISTINCT seq
688+
FROM report_analysis
689+
WHERE is_valid = TRUE
690+
AND brand_name LIKE ?
691+
LIMIT 200
692+
`
693+
fastArgs = []interface{}{prefixTerm}
694+
}
695+
696+
fastRows, err := d.db.QueryContext(ctx, fastQuery, fastArgs...)
697+
if err != nil {
698+
log.Printf("SearchReports brand fast path failed, falling back to FULLTEXT: %v", err)
699+
} else {
700+
defer fastRows.Close()
701+
for fastRows.Next() {
702+
var seq int
703+
if err := fastRows.Scan(&seq); err != nil {
704+
return nil, fmt.Errorf("failed to scan fast-path seq: %w", err)
705+
}
706+
seqs = append(seqs, seq)
707+
}
708+
if err := fastRows.Err(); err != nil {
709+
return nil, fmt.Errorf("error iterating fast-path seqs: %w", err)
710+
}
711+
}
712+
}
713+
714+
if len(seqs) == 0 {
715+
if searchQuery == "" {
716+
// No search query - just return empty
717+
if !full_data {
718+
return []models.ReportWithMinimalAnalysis{}, nil
719+
}
720+
return []models.ReportWithAnalysis{}, nil
721+
}
665722

666-
if searchQuery != "" {
723+
var seqArgs []interface{}
724+
var seqQuery string
667725
if classification != "" {
668726
seqQuery = `
669727
SELECT DISTINCT seq
670728
FROM report_analysis
671729
WHERE is_valid = TRUE
672730
AND classification = ?
673731
AND MATCH(title, description, brand_name, brand_display_name, summary) AGAINST (? IN BOOLEAN MODE)
674-
ORDER BY seq DESC
675732
LIMIT 200
676733
`
677734
seqArgs = []interface{}{classification, searchQuery}
@@ -681,33 +738,28 @@ func (d *Database) SearchReports(ctx context.Context, searchQuery string, classi
681738
FROM report_analysis
682739
WHERE is_valid = TRUE
683740
AND MATCH(title, description, brand_name, brand_display_name, summary) AGAINST (? IN BOOLEAN MODE)
684-
ORDER BY seq DESC
685741
LIMIT 200
686742
`
687743
seqArgs = []interface{}{searchQuery}
688744
}
689-
} else {
690-
// No search query - just return empty
691-
if !full_data {
692-
return []models.ReportWithMinimalAnalysis{}, nil
693-
}
694-
return []models.ReportWithAnalysis{}, nil
695-
}
696745

697-
// Phase 1: Get matching seq IDs (fast - uses FULLTEXT index only)
698-
seqRows, err := d.db.QueryContext(ctx, seqQuery, seqArgs...)
699-
if err != nil {
700-
return nil, fmt.Errorf("failed to query search seq IDs: %w", err)
701-
}
702-
defer seqRows.Close()
746+
// Phase 1 fallback: FULLTEXT search for seq IDs
747+
seqRows, err := d.db.QueryContext(ctx, seqQuery, seqArgs...)
748+
if err != nil {
749+
return nil, fmt.Errorf("failed to query search seq IDs: %w", err)
750+
}
751+
defer seqRows.Close()
703752

704-
var seqs []int
705-
for seqRows.Next() {
706-
var seq int
707-
if err := seqRows.Scan(&seq); err != nil {
708-
return nil, fmt.Errorf("failed to scan seq: %w", err)
753+
for seqRows.Next() {
754+
var seq int
755+
if err := seqRows.Scan(&seq); err != nil {
756+
return nil, fmt.Errorf("failed to scan seq: %w", err)
757+
}
758+
seqs = append(seqs, seq)
759+
}
760+
if err := seqRows.Err(); err != nil {
761+
return nil, fmt.Errorf("error iterating search seq IDs: %w", err)
709762
}
710-
seqs = append(seqs, seq)
711763
}
712764

713765
if len(seqs) == 0 {
@@ -938,7 +990,7 @@ func (d *Database) GetReportBySeq(ctx context.Context, seq int) (*models.ReportW
938990
// Then, get all analyses for this report
939991
analysesQuery := `
940992
SELECT
941-
ra.seq, ra.source, ra.analysis_text, ra.analysis_image,
993+
ra.seq, ra.source, ra.analysis_text, NULL as analysis_image,
942994
ra.title, ra.description, ra.brand_name, ra.brand_display_name,
943995
ra.litter_probability, ra.hazard_probability, ra.digital_bug_probability,
944996
ra.severity_level, ra.summary, ra.language, ra.classification,

0 commit comments

Comments
 (0)