我目前有一个监听器,我们用它来做一些不同的监控类型的活动(比如在查询超过5秒时记录警告),但它也会监视并杀死"愚蠢的错误&#34 ; - 特别是缺少UPDATE
子句的DELETE
和WHERE
个查询。
过去我们做了以下操作(注意我们正在使用 com.foundationdb.sql ):
/**
* Hook into the query execution lifecycle before rendering queries. We are checking for silly mistakes,
* pure SQL, etc.
*/
@Override
public void renderStart(final @NotNull ExecuteContext ctx) {
if (ctx.type() != ExecuteType.WRITE)
return;
String queryString = ctx.sql();
try (final Query query = ctx.query()) {
// Is our Query object empty? If not, let's run through it
if (!ValidationUtils.isEmpty(query)) {
queryString = query.getSQL(ParamType.INLINED);
final SQLParser parser = new SQLParser();
try {
final StatementNode tokens = parser.parseStatement(query.getSQL());
final Method method = tokens.getClass().getDeclaredMethod("getStatementType");
method.setAccessible(true);
switch (((Integer) method.invoke(tokens)).intValue()) {
case StatementType.UPDATE:
SelectNode snode = ConversionUtils.as(SelectNode.class,
((DMLStatementNode) tokens).getResultSetNode());
// check if we are a mass delete/update (which we don't allow)
if ((Objects.isNull(snode)) || (Objects.isNull(snode.getWhereClause())))
throw new RuntimeException("A mass update has been detected (and prevented): "
+ DatabaseManager.getBuilder().renderInlined(ctx.query()));
break;
case StatementType.DELETE:
snode = ConversionUtils.as(SelectNode.class,
((DMLStatementNode) tokens).getResultSetNode());
// check if we are a mass delete/update (which we don't allow)
if ((Objects.isNull(snode)) || (Objects.isNull(snode.getWhereClause())))
throw new RuntimeException("A mass delete has been detected (and prevented): "
+ DatabaseManager.getBuilder().renderInlined(ctx.query()));
break;
default:
if (__logger.isDebugEnabled()) {
__logger
.debug("Skipping query because we don't need to do anything with it :-): {}", queryString);
}
}
} catch (@NotNull StandardException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException | NoSuchMethodException
| SecurityException e) {
// logger.error(e.getMessage(), e);
}
}
// If the query object is empty AND the SQL string is empty, there's something wrong
else if (ValidationUtils.isEmpty(queryString)) {
__logger.error(
"The ctx.sql and ctx.query.getSQL were empty");
} else
throw new RuntimeException(
"Someone is trying to send pure SQL queries... we don't allow that anymore (use jOOQ): "
+ queryString);
}
}
我真的不想使用其他工具 - 特别是因为大多数SQL解析器无法处理UPSERT
或jOOQ的各种查询可以,所以很多只是被剪掉了 - 并且很想使用jOOQ的结构,但我遇到了麻烦。理想情况下,我可以检查查询类,如果它是更新或删除(或子类),如果它不是UpdateConditionStep或DeleteConditionStep的实例,我只会尖叫,但这不是因为查询以UpdateQueryImpl的形式返回...而且没有疯狂反映,我无法查看是否存在使用条件。
所以...现在我正在做:
/**
* Hook into the query execution lifecycle before rendering queries. We are checking for silly mistakes, pure SQL,
* etc.
*/
@Override
public void renderStart(final @NotNull ExecuteContext ctx) {
if (ctx.type() != ExecuteType.WRITE)
return;
try (final Query query = ctx.query()) {
// Is our Query object empty? If not, let's run through it
if (!ValidationUtils.isEmpty(query)) {
// Get rid of nulls
query.getParams().entrySet().stream().filter(entry -> Objects.nonNull(entry.getValue()))
.filter(entry -> CharSequence.class.isAssignableFrom(entry.getValue().getDataType().getType()))
.filter(entry -> NULL_CHARACTER.matcher((CharSequence) entry.getValue().getValue()).find())
.forEach(entry -> query.bind(entry.getKey(),
NULL_CHARACTER.matcher((CharSequence) entry.getValue().getValue()).replaceAll("")));
if (Update.class.isInstance(query)) {
if (!UpdateConditionStep.class.isInstance(query)) {
if (!WHERE_CLAUSE.matcher(query.getSQL(ParamType.INDEXED)).find()) {
final String queryString = query.getSQL(ParamType.INLINED);
throw new RuntimeException(
"Someone is trying to run an UPDATE query without a WHERE clause: " + queryString);
}
}
} else if (Delete.class.isInstance(query)) {
if (!DeleteConditionStep.class.isInstance(query)) {
if (!WHERE_CLAUSE.matcher(query.getSQL(ParamType.INDEXED)).find()) {
final String queryString = query.getSQL(ParamType.INLINED);
throw new RuntimeException(
"Someone is trying to run a DELETE query without a WHERE clause: " + queryString);
}
}
}
} else
throw new RuntimeException(
"Someone is trying to send pure SQL queries... we don't allow that anymore (use jOOQ): "
+ ctx.sql());
}
}
这让我摆脱了第三方SQL解析器,但现在我在非内联查询中使用正则表达式寻找\\s[wW][hH][eE][rR][eE]\\s
,这不是理想的,或者。
UPDATE
,DELETE
是否有WHERE
条款?UPDATE
还是DELETE
,而是使用ExecuteType
)?答案 0 :(得分:1)
这是一个有趣的想法和方法。我能看到的一个问题是性能。再次呈现SQL字符串,然后再次解析它听起来有点开销。也许,这个ExecuteListener
应仅在开发和集成测试环境中有效,而不是在生产中。
- 有没有办法使用jOOQ告诉我UPDATE,DELETE是否有WHERE子句?
醇>
由于您似乎愿意使用反射来访问第三方库的内部,当然,您可以检查ctx.query()
是org.jooq.impl.UpdateQueryImpl
类型还是{{1} }}。在版本3.10.1中,它们都有一个私有org.jooq.impl.DeleteQueryImpl
成员,您可以检查。
这显然会在内部更改的任何时候中断,但现在可能是一个实用的解决方案。
- 同样,有没有办法让我看到查询正在对哪个表格起作用
醇>
更通用且更健壮的方法是实现VisitListener
,这是在表达式树遍历期间调用的jOOQ的回调。您可以挂钩SQL字符串的生成和绑定变量的集合,并在遇到错误时抛出错误:
condition
或UPDATE
声明DELETE
条款你"只是"必须实现一个堆栈机器,在抛出异常之前记住所有上述内容。这里给出了WHERE
如何实现的示例:
https://blog.jooq.org/2015/06/17/implementing-client-side-row-level-security-with-jooq
此类功能在邮件列表中也已多次讨论过。它本身就是jOOQ支持的低调水果。我为jOOQ 3.11创建了一个功能请求,为此: https://github.com/jOOQ/jOOQ/issues/6771