我试图编写一个MySQL搜索函数来构建动态sql值并通过预准备语句执行它。显然我想通过参数传递用户输入(搜索词)以确保安全性,但是我不知道如何将一个参数匹配到多个?查询中的标记。可能最能表明我的意思:
CREATE DEFINER=`admin`@`localhost` PROCEDURE `WEBSITE_mainSearch`(
IN searchWordIn VARCHAR(128)
)
BEGIN
DECLARE articlesModule BIT;
SET @query = '';
SET @searchWordIn = searchWordIn;
SELECT articlesModuleEnabled INTO articlesModule FROM sys_options WHERE ID = 1;
SET @query = CONCAT(@query, 'SELECT blockName AS itemName, blockPath AS seoName, blockID AS itemID, MATCH(blockName, blockBody) AGAINST (?) AS relevance, \'block\' AS itemType FROM content_blocks WHERE MATCH(blockName, blockBody) AGAINST (?)') ;
IF articlesModule = 1 THEN
SET @query = CONCAT(@query, 'UNION SELECT articleName AS itemName, seoName, articleID AS itemID, MATCH(articleName, articleBody) AGAINST (?) AS relevance, \'article\' AS itemType FROM news_articles WHERE MATCH(articleName, articleBody) AGAINST (?)') ;
END IF;
PREPARE stmt FROM @query;
EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
DEALLOCATE PREPARE stmt;
END
由于将根据启用的模块动态确定?s的数量,我如何知道在此声明中将searchWordIn作为参数发送多少时间 EXECUTE stmt USING searchWordIn; ?
谢谢!
答案 0 :(得分:5)
必须为EXECUTE
语句提供一个固定的参数列表,因此您必须准备和在IF/THEN/ELSE
块中执行该语句。
IF articlesModule = 1 THEN
SET @query = ... UNION ...
PREPARE stmt FROM @query;
EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
ELSE
SET @query = ...; /* no UNION */
PREPARE stmt FROM @query;
EXECUTE stmt USING @searchWordIn, @searchWordIn;
END IF;
我不知道在MySQL存储过程语言的有限范围内解决这个问题的方法。对我而言,这是在存储过程中不使用动态SQL的另一个好理由。
重新评论:
我不能做上面的建议 - 我使用的系统有大约7个模块。
我明白了......您可以使用CASE
statement代替IF/THEN/ELSE
,但实际上您有2个 7 = 128个不同的查询字符串案例,因为我假设这7个模块中的任何一个都可以被搜索到。
允许您使用查询参数的替代方法是忘记使用UNION
,而是以最多运行7个单独的SELECT
查询并返回所有查询的方式编写过程作为多个结果集。这就是存储过程的目的。但是您必须在PHP层中编写代码以依次获取每个结果集。也就是说,循环遍历结果集,并在该循环内循环遍历当前结果集的行。请参阅PDO::nextRowset()或mysqli::next_result()上的示例。
我认为我很安全,只需将搜索词汇入动态SQL
即可
不,如果你这样做就不安全了!使用PHP中的查询参数将字符串传递给CALL WEBSITE_mainSearch(?)
对于防止SQL注入是没有用的,如果你那么将该参数值连接到过程内的另一个字符串,并执行动态SQL解析和执行。使用查询参数不会使参数值“安全”,它们只是将这些值与SQL分析阶段分开。
如果在连接字符串时使用MySQL的内置函数QUOTE(),则会更安全。 QUOTE()
可以转义特殊字符,就像mysql_real_escape_string()
一样。除了它略有不同,因为它也会产生分隔字符串的单引号,就像PDO::quote()那样。
SET @query = CONCAT(@query, 'SELECT blockName AS itemName, blockPath AS seoName,
blockID AS itemID, MATCH(blockName, blockBody) AGAINST (',
QUOTE(searchWordIn), ') AS relevance, \'block\' AS itemType
FROM content_blocks WHERE MATCH(blockName, blockBody) AGAINST (',
QUOTE(searchWordIn),')') ;
更新:还有一个选择:使用UNION
添加更多子查询,并保留模块的计数。然后使用CASE
根据累计计数使用不同数量的参数执行准备好的查询。
SET @n = 0;
IF articlesModule = 1 THEN
SET @query = ... UNION ...
SET @n = @n+1;
END IF;
IF newsModule = 1 THEN
SET @query = ... UNION ...
SET @n = @n+1;
END IF;
... and similar for the other 5 modules ...
PREPARE stmt FROM @query;
CASE @n
WHEN 1:
EXECUTE stmt USING @searchWordIn, @searchWordIn;
WHEN 2:
EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
WHEN 3:
EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn;
WHEN 4:
EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
WHEN 5:
EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn;
WHEN 6:
EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
WHEN 7:
EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
@searchWordIn, @searchWordIn;
END;