feat: [CODE-2702]: fix string search by '_' or '%' (#2946)

* fix string search by '_' or '%'
pull/3586/head
Marko Gaćeša 2024-11-07 15:09:35 +00:00 committed by Harness
parent d6f64c8af6
commit 7336cab958
12 changed files with 55 additions and 26 deletions

View File

@ -18,7 +18,6 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/git/sha"
@ -380,7 +379,7 @@ func (s *CheckStore) ResultSummary(ctx context.Context,
func (*CheckStore) applyOpts(stmt squirrel.SelectBuilder, query string) squirrel.SelectBuilder {
if query != "" {
stmt = stmt.Where("LOWER(check_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(query)))
stmt = stmt.Where(PartialMatch("check_uid", query))
}
return stmt

View File

@ -18,7 +18,6 @@ import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/harness/gitness/app/store"
@ -505,7 +504,7 @@ func (s *connectorStore) List(
Where("connector_space_id = ?", fmt.Sprint(parentID))
if filter.Query != "" {
stmt = stmt.Where("LOWER(connector_identifier) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
stmt = stmt.Where(PartialMatch("connector_identifier", filter.Query))
}
stmt = stmt.Limit(database.Limit(filter.Size))
@ -564,7 +563,7 @@ func (s *connectorStore) Count(ctx context.Context, parentID int64, filter types
Where("connector_space_id = ?", parentID)
if filter.Query != "" {
stmt = stmt.Where("LOWER(connector_identifier) LIKE ?", fmt.Sprintf("%%%s%%", filter.Query))
stmt = stmt.Where(PartialMatch("connector_identifier", filter.Query))
}
sql, args, err := stmt.ToSql()

View File

@ -14,7 +14,45 @@
package database
import (
"strings"
)
const (
PostgresDriverName = "postgres"
SqliteDriverName = "sqlite3"
)
// PartialMatch builds a string pair that can be passed as a parameter to squirrel's Where() function
// for a SQL "LIKE" expression. Besides surrounding the input value with '%' wildcard characters for a partial match,
// this function also escapes the '_' and '%' metacharacters supported in SQL "LIKE" expressions.
// The "ESCAPE" clause isn't needed for Postgres, but is necessary for SQLite.
// It will be used only if '_' and '%' are present in the value string.
//
// See:
// https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
// https://www.sqlite.org/lang_expr.html#the_like_glob_regexp_match_and_extract_operators
//
//nolint:goconst
func PartialMatch(column, value string) (string, string) {
var (
n int
escaped bool
)
if n, value = len(value), strings.ReplaceAll(value, `\`, `\\`); n < len(value) {
escaped = true
}
if n, value = len(value), strings.ReplaceAll(value, "_", `\_`); n < len(value) {
escaped = true
}
if n, value = len(value), strings.ReplaceAll(value, "%", `\%`); n < len(value) {
escaped = true
}
if escaped {
return "LOWER(" + column + ") LIKE ? ESCAPE '\\'", "%" + strings.ToLower(value) + "%"
}
return "LOWER(" + column + ") LIKE ?", "%" + strings.ToLower(value) + "%"
}

View File

@ -17,7 +17,6 @@ package database
import (
"context"
"fmt"
"strings"
"time"
"github.com/harness/gitness/app/store"
@ -282,8 +281,7 @@ func applyMembershipUserFilter(
opts types.MembershipUserFilter,
) squirrel.SelectBuilder {
if opts.Query != "" {
searchTerm := "%%" + strings.ToLower(opts.Query) + "%%"
stmt = stmt.Where("LOWER(principal_display_name) LIKE ?", searchTerm)
stmt = stmt.Where(PartialMatch("principal_display_name", opts.Query))
}
return stmt
@ -371,8 +369,7 @@ func applyMembershipSpaceFilter(
opts types.MembershipSpaceFilter,
) squirrel.SelectBuilder {
if opts.Query != "" {
searchTerm := "%%" + strings.ToLower(opts.Query) + "%%"
stmt = stmt.Where("LOWER(space_uid) LIKE ?", searchTerm)
stmt = stmt.Where(PartialMatch("space_uid", opts.Query))
}
return stmt

View File

@ -286,8 +286,7 @@ func (PublicKeyStore) applyQueryFilter(
filter *types.PublicKeyFilter,
) squirrel.SelectBuilder {
if filter.Query != "" {
stmt = stmt.Where("LOWER(public_key_identifier) LIKE ?",
fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
stmt = stmt.Where(PartialMatch("public_key_identifier", filter.Query))
}
return stmt

View File

@ -635,7 +635,7 @@ func (s *PullReqStore) applyFilter(stmt *squirrel.SelectBuilder, opts *types.Pul
}
if opts.Query != "" {
*stmt = stmt.Where("LOWER(pullreq_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query)))
*stmt = stmt.Where(PartialMatch("pullreq_title", opts.Query))
}
if len(opts.CreatedBy) > 0 {

View File

@ -866,7 +866,7 @@ func mapToInternalRepo(in *types.Repository) *repository {
func applyQueryFilter(stmt squirrel.SelectBuilder, filter *types.RepoFilter) squirrel.SelectBuilder {
if filter.Query != "" {
stmt = stmt.Where("LOWER(repo_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
stmt = stmt.Where(PartialMatch("repo_uid", filter.Query))
}
//nolint:gocritic
if filter.DeletedAt != nil {

View File

@ -453,7 +453,7 @@ func (*RuleStore) applyFilter(
}
if filter.Query != "" {
stmt = stmt.Where("LOWER(rule_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
stmt = stmt.Where(PartialMatch("rule_uid", filter.Query))
}
return stmt

View File

@ -17,7 +17,6 @@ package database
import (
"context"
"fmt"
"strings"
"time"
"github.com/harness/gitness/app/store"
@ -203,7 +202,7 @@ func (s *secretStore) List(ctx context.Context, parentID int64, filter types.Lis
Where("secret_space_id = ?", fmt.Sprint(parentID))
if filter.Query != "" {
stmt = stmt.Where("LOWER(secret_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
stmt = stmt.Where(PartialMatch("secret_uid", filter.Query))
}
stmt = stmt.Limit(database.Limit(filter.Size))
@ -286,7 +285,7 @@ func (s *secretStore) Count(ctx context.Context, parentID int64, filter types.Li
Where("secret_space_id = ?", parentID)
if filter.Query != "" {
stmt = stmt.Where("LOWER(secret_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
stmt = stmt.Where(PartialMatch("secret_uid", filter.Query))
}
sql, args, err := stmt.ToSql()

View File

@ -634,7 +634,7 @@ func (s *SpaceStore) count(
Where("space_parent_id = ?", id)
if opts.Query != "" {
stmt = stmt.Where("LOWER(space_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query)))
stmt = stmt.Where(PartialMatch("space_uid", opts.Query))
}
stmt = s.applyQueryFilter(stmt, opts)
@ -781,7 +781,7 @@ func (s *SpaceStore) applyQueryFilter(
opts *types.SpaceFilter,
) squirrel.SelectBuilder {
if opts.Query != "" {
stmt = stmt.Where("LOWER(space_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query)))
stmt = stmt.Where(PartialMatch("space_uid", opts.Query))
}
//nolint:gocritic
if opts.DeletedAt != nil {

View File

@ -17,7 +17,6 @@ package database
import (
"context"
"fmt"
"strings"
"time"
"github.com/harness/gitness/app/store"
@ -218,7 +217,7 @@ func (s *templateStore) List(
Where("template_space_id = ?", fmt.Sprint(parentID))
if filter.Query != "" {
stmt = stmt.Where("LOWER(template_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
stmt = stmt.Where(PartialMatch("template_uid", filter.Query))
}
stmt = stmt.Limit(database.Limit(filter.Size))
@ -282,7 +281,7 @@ func (s *templateStore) Count(ctx context.Context, parentID int64, filter types.
Where("template_space_id = ?", parentID)
if filter.Query != "" {
stmt = stmt.Where("LOWER(template_uid) LIKE ?", fmt.Sprintf("%%%s%%", filter.Query))
stmt = stmt.Where(PartialMatch("template_uid", filter.Query))
}
sql, args, err := stmt.ToSql()

View File

@ -18,7 +18,6 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/harness/gitness/app/store"
@ -280,7 +279,7 @@ func (s *triggerStore) List(
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
if filter.Query != "" {
stmt = stmt.Where("LOWER(trigger_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
stmt = stmt.Where(PartialMatch("trigger_uid", filter.Query))
}
sql, args, err := stmt.ToSql()
@ -331,7 +330,7 @@ func (s *triggerStore) Count(ctx context.Context, pipelineID int64, filter types
Where("trigger_pipeline_id = ?", pipelineID)
if filter.Query != "" {
stmt = stmt.Where("LOWER(trigger_uid) LIKE ?", fmt.Sprintf("%%%s%%", filter.Query))
stmt = stmt.Where(PartialMatch("trigger_uid", filter.Query))
}
sql, args, err := stmt.ToSql()