为小巧的查询清理和准备文本搜索字符串
Sanitizing and preparation of text search string for dapper query
我们在 postgresql
中有一个 table 以下结构(简化):
CREATE TABLE items(
id bigint NOT NULL DEFAULT nextval('item_id_seq') PRIMARY KEY,
name varchar(40) not null,
name_search tsvector GENERATED ALWAYS AS (to_tsvector('simple', name)) STORED
);
使用 C#
作为编程语言我想对 name_search
字段进行全文搜索。没有像 EntifyFramework
.
这样的完整 ORM
使用 Dapper
,当前的 query/logic 如下所示:
public async Task<IEnumerable<FeedItemInDb>> GetItems(string searchTerm)
{
string searchFilter = "";
if (!string.IsNullOrEmpty(searchTerm))
{
string searchTermProcessed = $"{searchTerm}:*";
searchTermProcessed = searchTermProcessed.Replace(" ", " & ");
searchFilter = $"AND i.name_search @@ to_tsquery('simple', '{searchTermProcessed}')";
}
var results = await uow.Connection.QueryAsync<FeedItemInDb>($@"select i.* FROM items i WHERE 1=1 {searchFilter}", new
{
// params here
});
return results;
}
它适用于非常琐碎的情况。 例如搜索字符串 my test
被清理为 my & test:*
这种方法有一个主要缺陷 - 您必须提前知道查询所需的所有清理规则!例如,以下原始输入 my :*test
以异常结束:
Npgsql.PostgresException: 42601: syntax error in tsquery: "my & :test:"
我想知道 Dapper
中是否有某种包或规则或代码可以为我完成所有必需的清理工作?类似于我们如何在查询中参数化其他值...
通常,使用 Dapper
,我希望代码看起来像这样:
public async Task<IEnumerable<FeedItemInDb>> GetItems(string searchTerm)
{
var results = await uow.Connection.QueryAsync<FeedItemInDb>($@"select i.* FROM items i
WHERE i.name_search @@ to_tsquery('simple', '@SearchParam:*')", new
{
SearchParam = searchTerm
});
return results;
}
但不幸的是,它没有 return 任何结果。也不与 to_tsquery('simple', '@SearchParam')
.
总的来说我只是想知道如何解决这个问题。如何使用 Dapper
清理全文搜索的字符串。如果用户开始在查询中传递 :,.%&*
,如果保持原样,我希望我的代码开始失败。我是否应该 prohibit/filter 从用户输入中删除所有特殊字符?问题是我不知道我应该过滤掉什么..
编辑:
to_tsquery('simple', @SearchParam)
似乎确实有效,如果我在搜索之前手动格式化搜索字符串。所以基本上我遇到了和以前一样的问题。如果字符串格式不正确,则会抛出 sql
异常。因此您必须提前了解并应用所有 formatting/sanitization 规则,这样查询才不会失败。所以我对如何处理这种情况仍然有同样的疑问。
虽然没有答案,但它发现用像这样的正则表达式替换 non-supported 个字符“有点可行”:
private static string CorrectInputString(string input)
{
string result = input?.Trim();
if (!string.IsNullOrEmpty(result))
{
// remove newlines and tabs
result = Regex.Replace(result, @"\t|\n|\r", "");
// remove not-supported characters (supported are: numbers, regular letters, hyphens, spaces)
result = Regex.Replace(result, "[^\p{L}0-9- ]", "");
// remove double spaces (also trims)
result = string.Join(" ", result.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
return result;
}
然后
public static string PrepareSearchString(string input)
{
string result = input?.Trim();
if (!string.IsNullOrEmpty(result))
{
// prepare query #1
result = $"{result}:*";
// prepare query #2
result = result.Replace(" ", "&");
}
return result;
}
我在助手中按顺序使用了其中的 2 个方法 class。
这当然会从初始输入字符串中删除相当多的信息,这正是我想避免做的事情。似乎还有另一种 full-text 搜索解决方案,使用 GIST
和 GIN
索引在这里解释 (Postgresql prefix wildcard for full text)。对于给定的 use-case.
它可能会更好
我决定保留解决方案原样(如果没有更好的答案)。
p.s。这是涵盖所有类型转换的 unit-test
:
[TestMethod]
public async Task Sanitize_DisplayName()
{
var sourceExpected = new List<(string source, string expected)>()
{
("1", "1"),
("test", "test"),
("test1", "test1"),
("test 1", "test 1"),
("test-1", "test-1"),
("test-test", "test-test"),
("test-test test", "test-test test"),
("1 test-TEST test", "1 test-TEST test"),
("тест", "тест"),
(" тест", "тест"),
(@"
тест", "тест"), // ENTER here!
("test*/!#%^()[]{}", "test"),
("te st*/", "te st"),
("te st*/", "te st"),
("te st*/", "te st"),
("\t te st */", "te st"),
(" te st */", "te st"),
("test ? &", "test"),
("test ? &-", "test -"), // !
};
foreach (var item in sourceExpected)
{
var res = SqlHelper.CorrectInputString(item.source);
Assert.AreEqual(item.expected, res);
}
}
我们在 postgresql
中有一个 table 以下结构(简化):
CREATE TABLE items(
id bigint NOT NULL DEFAULT nextval('item_id_seq') PRIMARY KEY,
name varchar(40) not null,
name_search tsvector GENERATED ALWAYS AS (to_tsvector('simple', name)) STORED
);
使用 C#
作为编程语言我想对 name_search
字段进行全文搜索。没有像 EntifyFramework
.
Dapper
,当前的 query/logic 如下所示:
public async Task<IEnumerable<FeedItemInDb>> GetItems(string searchTerm)
{
string searchFilter = "";
if (!string.IsNullOrEmpty(searchTerm))
{
string searchTermProcessed = $"{searchTerm}:*";
searchTermProcessed = searchTermProcessed.Replace(" ", " & ");
searchFilter = $"AND i.name_search @@ to_tsquery('simple', '{searchTermProcessed}')";
}
var results = await uow.Connection.QueryAsync<FeedItemInDb>($@"select i.* FROM items i WHERE 1=1 {searchFilter}", new
{
// params here
});
return results;
}
它适用于非常琐碎的情况。 例如搜索字符串 my test
被清理为 my & test:*
这种方法有一个主要缺陷 - 您必须提前知道查询所需的所有清理规则!例如,以下原始输入 my :*test
以异常结束:
Npgsql.PostgresException: 42601: syntax error in tsquery: "my & :test:"
我想知道 Dapper
中是否有某种包或规则或代码可以为我完成所有必需的清理工作?类似于我们如何在查询中参数化其他值...
通常,使用 Dapper
,我希望代码看起来像这样:
public async Task<IEnumerable<FeedItemInDb>> GetItems(string searchTerm)
{
var results = await uow.Connection.QueryAsync<FeedItemInDb>($@"select i.* FROM items i
WHERE i.name_search @@ to_tsquery('simple', '@SearchParam:*')", new
{
SearchParam = searchTerm
});
return results;
}
但不幸的是,它没有 return 任何结果。也不与 to_tsquery('simple', '@SearchParam')
.
总的来说我只是想知道如何解决这个问题。如何使用 Dapper
清理全文搜索的字符串。如果用户开始在查询中传递 :,.%&*
,如果保持原样,我希望我的代码开始失败。我是否应该 prohibit/filter 从用户输入中删除所有特殊字符?问题是我不知道我应该过滤掉什么..
编辑:
to_tsquery('simple', @SearchParam)
似乎确实有效,如果我在搜索之前手动格式化搜索字符串。所以基本上我遇到了和以前一样的问题。如果字符串格式不正确,则会抛出 sql
异常。因此您必须提前了解并应用所有 formatting/sanitization 规则,这样查询才不会失败。所以我对如何处理这种情况仍然有同样的疑问。
虽然没有答案,但它发现用像这样的正则表达式替换 non-supported 个字符“有点可行”:
private static string CorrectInputString(string input)
{
string result = input?.Trim();
if (!string.IsNullOrEmpty(result))
{
// remove newlines and tabs
result = Regex.Replace(result, @"\t|\n|\r", "");
// remove not-supported characters (supported are: numbers, regular letters, hyphens, spaces)
result = Regex.Replace(result, "[^\p{L}0-9- ]", "");
// remove double spaces (also trims)
result = string.Join(" ", result.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
return result;
}
然后
public static string PrepareSearchString(string input)
{
string result = input?.Trim();
if (!string.IsNullOrEmpty(result))
{
// prepare query #1
result = $"{result}:*";
// prepare query #2
result = result.Replace(" ", "&");
}
return result;
}
我在助手中按顺序使用了其中的 2 个方法 class。
这当然会从初始输入字符串中删除相当多的信息,这正是我想避免做的事情。似乎还有另一种 full-text 搜索解决方案,使用 GIST
和 GIN
索引在这里解释 (Postgresql prefix wildcard for full text)。对于给定的 use-case.
我决定保留解决方案原样(如果没有更好的答案)。
p.s。这是涵盖所有类型转换的 unit-test
:
[TestMethod]
public async Task Sanitize_DisplayName()
{
var sourceExpected = new List<(string source, string expected)>()
{
("1", "1"),
("test", "test"),
("test1", "test1"),
("test 1", "test 1"),
("test-1", "test-1"),
("test-test", "test-test"),
("test-test test", "test-test test"),
("1 test-TEST test", "1 test-TEST test"),
("тест", "тест"),
(" тест", "тест"),
(@"
тест", "тест"), // ENTER here!
("test*/!#%^()[]{}", "test"),
("te st*/", "te st"),
("te st*/", "te st"),
("te st*/", "te st"),
("\t te st */", "te st"),
(" te st */", "te st"),
("test ? &", "test"),
("test ? &-", "test -"), // !
};
foreach (var item in sourceExpected)
{
var res = SqlHelper.CorrectInputString(item.source);
Assert.AreEqual(item.expected, res);
}
}