feat(projects): add support for ParadeDB when searching for project

This commit is contained in:
kolaente 2025-06-18 19:25:38 +02:00
parent 1e3a68210a
commit 07d83e67d7
3 changed files with 46 additions and 9 deletions

View File

@ -292,5 +292,19 @@ func CreateParadeDBIndexes() error {
return fmt.Errorf("could not ensure paradedb task index: %w", err)
}
// Create ParadeDB index for projects table
projectIndexSQL := `CREATE INDEX IF NOT EXISTS idx_projects_paradedb ON projects USING bm25 (id, title, description, identifier)
WITH (
key_field='id',
text_fields='{
"title": {"fast": true, "record": "freq"},
"description": {"fast": true, "record": "freq"},
"identifier": {"fast": true, "record": "freq"}
}'
)`
if _, err := x.Exec(projectIndexSQL); err != nil {
return fmt.Errorf("could not ensure paradedb project index: %w", err)
}
return nil
}

View File

@ -30,9 +30,6 @@ import (
// See https://stackoverflow.com/q/7005302/10924593
func ILIKE(column, search string) builder.Cond {
if Type() == schemas.POSTGRES {
if paradedbInstalled {
return builder.Expr(column+" @@@ ?", search)
}
return builder.Expr(column+" ILIKE ?", "%"+search+"%")
}
@ -47,9 +44,14 @@ func ParadeDBAvailable() bool {
// using a single query rather than multiple OR conditions.
// Falls back to individual ILIKE queries for PGroonga and standard PostgreSQL.
func MultiFieldSearch(fields []string, search string) builder.Cond {
return MultiFieldSearchWithTableAlias(fields, search, "")
}
// MultiFieldSearchWithTableAlias performs an optimized search across multiple fields for ParadeDB
// with support for table aliases. When tableAlias is provided, it will be used to prefix field names
// for non-ParadeDB queries and the id field for ParadeDB queries.
func MultiFieldSearchWithTableAlias(fields []string, search, tableAlias string) builder.Cond {
if Type() == schemas.POSTGRES && paradedbInstalled {
// For ParadeDB, use the optimized disjunction_max approach for multi-field search
// This provides better relevance scoring than individual OR conditions
if len(fields) == 1 {
// Single field search - use optimized match function
return builder.Expr("id @@@ paradedb.match(?, ?)", fields[0], search)
@ -62,13 +64,24 @@ func MultiFieldSearch(fields []string, search string) builder.Cond {
args[i*2] = field
args[i*2+1] = search
}
return builder.Expr("id @@@ paradedb.disjunction_max(ARRAY["+strings.Join(fieldMatches, ", ")+"])", args...)
idField := "`id`"
if tableAlias != "" {
idField = "`" + tableAlias + "`.`id`"
}
return builder.Expr(idField+" @@@ paradedb.disjunction_max(ARRAY["+strings.Join(fieldMatches, ", ")+"])", args...)
}
// For non-PostgreSQL databases, use LIKE on all fields
// For non-PostgreSQL databases, use ILIKE on all fields
conditions := make([]builder.Cond, len(fields))
for i, field := range fields {
conditions[i] = ILIKE(field, search)
// Add table alias to field name if provided
fieldName := field
if tableAlias != "" {
fieldName = tableAlias + "." + field
}
conditions[i] = ILIKE(fieldName, search)
}
return builder.Or(conditions...)
}

View File

@ -472,9 +472,19 @@ func getUserProjectsStatement(userID int64, search string, getArchived bool) *bu
ids = append(ids, v)
}
filterCond := db.ILIKE("l.title", search)
var filterCond builder.Cond
if len(ids) > 0 {
filterCond = builder.In("l.id", ids)
} else {
filterCond = db.MultiFieldSearchWithTableAlias(
[]string{
"title",
"description",
"identifier",
},
search,
"l",
)
}
parentCondition := builder.Or(