Skip to content

Commit

Permalink
fix(db): handle usage of special characters in searches (#721)
Browse files Browse the repository at this point in the history
* handle full text search for failing cases

* added test

* test getbookmarkcount too

* replaceall, fix getbookmarkcount
  • Loading branch information
fmartingr committed Sep 11, 2023
1 parent f4817cb commit ef1d18d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 36 deletions.
55 changes: 43 additions & 12 deletions internal/database/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ type testDatabaseFactory func(ctx context.Context) (DB, error)
func testDatabase(t *testing.T, dbFactory testDatabaseFactory) {
tests := map[string]databaseTestCase{
// Bookmarks
"testBookmarkAutoIncrement": testBookmarkAutoIncrement,
"testCreateBookmark": testCreateBookmark,
"testCreateBookmarkWithContent": testCreateBookmarkWithContent,
"testCreateBookmarkTwice": testCreateBookmarkTwice,
"testCreateBookmarkWithTag": testCreateBookmarkWithTag,
"testCreateTwoDifferentBookmarks": testCreateTwoDifferentBookmarks,
"testUpdateBookmark": testUpdateBookmark,
"testUpdateBookmarkWithContent": testUpdateBookmarkWithContent,
"testGetBookmark": testGetBookmark,
"testGetBookmarkNotExistant": testGetBookmarkNotExistant,
"testGetBookmarks": testGetBookmarks,
"testGetBookmarksCount": testGetBookmarksCount,
"testBookmarkAutoIncrement": testBookmarkAutoIncrement,
"testCreateBookmark": testCreateBookmark,
"testCreateBookmarkWithContent": testCreateBookmarkWithContent,
"testCreateBookmarkTwice": testCreateBookmarkTwice,
"testCreateBookmarkWithTag": testCreateBookmarkWithTag,
"testCreateTwoDifferentBookmarks": testCreateTwoDifferentBookmarks,
"testUpdateBookmark": testUpdateBookmark,
"testUpdateBookmarkWithContent": testUpdateBookmarkWithContent,
"testGetBookmark": testGetBookmark,
"testGetBookmarkNotExistant": testGetBookmarkNotExistant,
"testGetBookmarks": testGetBookmarks,
"testGetBookmarksWithSQLCharacters": testGetBookmarksWithSQLCharacters,
"testGetBookmarksCount": testGetBookmarksCount,
// Tags
"testCreateTag": testCreateTag,
"testCreateTags": testCreateTags,
Expand Down Expand Up @@ -261,6 +262,36 @@ func testGetBookmarks(t *testing.T, db DB) {
assert.Equal(t, savedBookmark.ID, results[0].ID, "bookmark should be the one saved")
}

func testGetBookmarksWithSQLCharacters(t *testing.T, db DB) {
ctx := context.TODO()

// _ := 0
book := model.Bookmark{
URL: "https://github.com/go-shiori/shiori",
Title: "shiori",
}
_, err := db.SaveBookmarks(ctx, true, book)
assert.NoError(t, err, "Save bookmarks must not fail")

characters := []string{";", "%", "_", "\\", "\"", ":"}

for _, char := range characters {
t.Run("GetBookmarks/"+char, func(t *testing.T) {
_, err := db.GetBookmarks(ctx, GetBookmarksOptions{
Keyword: char,
})
assert.NoError(t, err, "Get bookmarks should not fail")
})

t.Run("GetBookmarksCount/"+char, func(t *testing.T) {
_, err := db.GetBookmarksCount(ctx, GetBookmarksOptions{
Keyword: char,
})
assert.NoError(t, err, "Get bookmarks count should not fail")
})
}
}

func testGetBookmarksCount(t *testing.T, db DB) {
ctx := context.TODO()

Expand Down
17 changes: 8 additions & 9 deletions internal/database/pg.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,12 @@ func (db *PGDatabase) GetBookmarks(ctx context.Context, opts GetBookmarksOptions
// Add where clause for search keyword
if opts.Keyword != "" {
query += ` AND (
url LIKE :lkw OR
title LIKE :kw OR
excerpt LIKE :kw OR
content LIKE :kw
url LIKE '%' || :kw || '%' OR
title LIKE '%' || :kw || '%' OR
excerpt LIKE '%' || :kw || '%' OR
content LIKE '%' || :kw || '%'
)`

arg["lkw"] = "%" + opts.Keyword + "%"
arg["kw"] = opts.Keyword
}

Expand Down Expand Up @@ -372,10 +371,10 @@ func (db *PGDatabase) GetBookmarksCount(ctx context.Context, opts GetBookmarksOp
// Add where clause for search keyword
if opts.Keyword != "" {
query += ` AND (
url LIKE :lurl OR
title LIKE :kw OR
excerpt LIKE :kw OR
content LIKE :kw
url LIKE '%' || :kw || '%' OR
title LIKE '%' || :kw || '%' OR
excerpt LIKE '%' || :kw || '%' OR
content LIKE '%' || :kw || '%'
)`

arg["lurl"] = "%" + opts.Keyword + "%"
Expand Down
36 changes: 21 additions & 15 deletions internal/database/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,19 +270,22 @@ func (db *SQLiteDatabase) GetBookmarks(ctx context.Context, opts GetBookmarksOpt

// Add where clause for search keyword
if opts.Keyword != "" {
query += ` AND (b.url LIKE ? OR b.excerpt LIKE ? OR b.id IN (
query += ` AND (b.url LIKE '%' || ? || '%' OR b.excerpt LIKE '%' || ? || '%' OR b.id IN (
SELECT docid id
FROM bookmark_content
WHERE title MATCH ? OR content MATCH ?))`

args = append(args,
"%"+opts.Keyword+"%",
"%"+opts.Keyword+"%")

// Replace dash with spaces since FTS5 uses `-name` as column identifier
opts.Keyword = strings.Replace(opts.Keyword, "-", " ", -1)
args = append(args, opts.Keyword, opts.Keyword)

// Replace dash with spaces since FTS5 uses `-name` as column identifier and double quote
// since FTS5 uses double quote as string identifier
// Reference: https://sqlite.org/fts5.html#fts5_strings
ftsKeyword := strings.ReplaceAll(opts.Keyword, "-", " ")

// Properly set double quotes for string literals in sqlite's fts
ftsKeyword = strings.ReplaceAll(ftsKeyword, "\"", "\"\"")

args = append(args, "\""+ftsKeyword+"\"", "\""+ftsKeyword+"\"")
}

// Add where clause for tags.
Expand Down Expand Up @@ -461,19 +464,22 @@ func (db *SQLiteDatabase) GetBookmarksCount(ctx context.Context, opts GetBookmar

// Add where clause for search keyword
if opts.Keyword != "" {
query += ` AND (b.url LIKE ? OR b.excerpt LIKE ? OR b.id IN (
query += ` AND (b.url LIKE '%' || ? || '%' OR b.excerpt LIKE '%' || ? || '%' OR b.id IN (
SELECT docid id
FROM bookmark_content
WHERE title MATCH ? OR content MATCH ?))`

args = append(args,
"%"+opts.Keyword+"%",
"%"+opts.Keyword+"%",
)

// Replace dash with spaces since FTS5 uses `-name` as column identifier
opts.Keyword = strings.Replace(opts.Keyword, "-", " ", -1)
args = append(args, opts.Keyword, opts.Keyword)

// Replace dash with spaces since FTS5 uses `-name` as column identifier and double quote
// since FTS5 uses double quote as string identifier
// Reference: https://sqlite.org/fts5.html#fts5_strings
ftsKeyword := strings.ReplaceAll(opts.Keyword, "-", " ")

// Properly set double quotes for string literals in sqlite's fts
ftsKeyword = strings.ReplaceAll(ftsKeyword, "\"", "\"\"")

args = append(args, "\""+ftsKeyword+"\"", "\""+ftsKeyword+"\"")
}

// Add where clause for tags.
Expand Down

0 comments on commit ef1d18d

Please sign in to comment.