diff --git a/CHANGELOG.md b/CHANGELOG.md index 860b701..87edee5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add shared SQL LIKE literal escaping for crawler search queries. - Add a safe tokenized FTS5 query helper for arbitrary user search input. - Preserve unrelated tracked mirror edits across write synchronization rebases. - Retry atomic branch-and-tag snapshot pushes after rebasing and retargeting unpublished tags. diff --git a/store/query.go b/store/query.go new file mode 100644 index 0000000..4afe05a --- /dev/null +++ b/store/query.go @@ -0,0 +1,16 @@ +package store + +import "strings" + +// EscapeLike escapes a literal value for a LIKE expression using backslash as +// the SQL ESCAPE character. Callers must add `ESCAPE '\'` to the expression. +func EscapeLike(value string) string { + var escaped strings.Builder + for _, r := range value { + if r == '\\' || r == '%' || r == '_' { + escaped.WriteRune('\\') + } + escaped.WriteRune(r) + } + return escaped.String() +} diff --git a/store/query_test.go b/store/query_test.go new file mode 100644 index 0000000..af91349 --- /dev/null +++ b/store/query_test.go @@ -0,0 +1,16 @@ +package store + +import "testing" + +func TestEscapeLike(t *testing.T) { + tests := map[string]string{ + "plain": "plain", + `100%_ready`: `100\%\_ready`, + `path\value%`: `path\\value\%`, + } + for input, want := range tests { + if got := EscapeLike(input); got != want { + t.Errorf("EscapeLike(%q) = %q, want %q", input, got, want) + } + } +}