[Server] Fix FTS search
This commit is contained in:
@@ -4,13 +4,15 @@ import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/dataext"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/mathext"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/sq"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MessageFilter struct {
|
||||
@@ -219,7 +221,7 @@ func (f MessageFilter) SQL() (string, string, sq.PP, error) {
|
||||
if f.SearchStringFTS != nil {
|
||||
filter := make([]string, 0)
|
||||
for _, v := range *f.SearchStringFTS {
|
||||
filter = append(filter, fmt.Sprintf("(messages_fts match :%s)", params.Add(v)))
|
||||
filter = append(filter, fmt.Sprintf("(messages_fts match :%s)", params.Add(sanitizeFTSMatchQuery(v))))
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")")
|
||||
}
|
||||
@@ -246,6 +248,43 @@ func (f MessageFilter) SQL() (string, string, sq.PP, error) {
|
||||
return sqlClause, joinClause, params, nil
|
||||
}
|
||||
|
||||
// sanitizeFTSMatchQuery converts an arbitrary user-provided search string into a valid FTS5 MATCH query.
|
||||
// It preserves explicit "quoted phrases" typed by the user and wraps every other whitespace-separated word
|
||||
// in double-quotes, so that FTS5 special characters ( - : * ^ ( ) AND OR NOT NEAR ... ) inside plain search
|
||||
// terms are treated as literal text instead of query operators.
|
||||
func sanitizeFTSMatchQuery(s string) string {
|
||||
out := make([]string, 0)
|
||||
buf := strings.Builder{}
|
||||
inQuote := false
|
||||
|
||||
flush := func() {
|
||||
if buf.Len() > 0 {
|
||||
out = append(out, `"`+strings.ReplaceAll(buf.String(), `"`, `""`)+`"`)
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r == '"':
|
||||
// a double-quote opens or closes an explicit phrase; flush the pending word either way
|
||||
flush()
|
||||
inQuote = !inQuote
|
||||
case !inQuote && unicode.IsSpace(r):
|
||||
flush()
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
flush() // trailing word or an unterminated phrase
|
||||
|
||||
if len(out) == 0 {
|
||||
return `""` // valid FTS5 query that simply matches nothing
|
||||
}
|
||||
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
func (f MessageFilter) Hash() string {
|
||||
bh, err := dataext.StructHash(f, dataext.StructHashOptions{HashAlgo: sha512.New()})
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user