[英]How Do You Check For DELETE/UPDATE Without A WHERE Clause
I currently have a listener that we use to do a few different monitoring-type activities (like log a warning if a query takes more than 5 seconds), but it also watches for and kills "silly bugs" -- especially UPDATE
and DELETE
queries that are missing a WHERE
clause.我目前有一个监听器,我们用它来做一些不同的监控类型的活动(比如在查询超过 5 秒时记录警告),但它也会监视并杀死“愚蠢的错误”——尤其是
UPDATE
和DELETE
查询缺少WHERE
子句。
In the past we did the following (note that we are using com.foundationdb.sql ):过去我们做了以下事情(注意我们使用的是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);
}
}
I really don't want to use yet another tool -- especially since most SQL parsers can't handle UPSERT
s or the wide variety of queries that jOOQ can, so a lot just get cut out -- and would love to use jOOQ's constructs, but I'm having trouble.我真的不想再使用另一个工具——尤其是因为大多数 SQL 解析器无法处理
UPSERT
或 jOOQ 可以处理的各种查询,所以很多都被删掉了——并且很想使用 jOOQ 的构造,但我遇到了麻烦。 Ideally I could just check the query class and if it's an Update or Delete (or a subclass), I would just scream if it isn't an instance of UpdateConditionStep or DeleteConditionStep, but that doesn't work because the queries are coming back as UpdateQueryImpl... and without crazy reflection, I can't see if there is a condition in use.理想情况下,我可以检查查询类,如果它是更新或删除(或子类),如果它不是 UpdateConditionStep 或 DeleteConditionStep 的实例,我只会尖叫,但这不起作用,因为查询返回为UpdateQueryImpl... 没有疯狂的反思,我看不出是否有条件在使用。
So... right now I'm doing:所以......现在我正在做:
/**
* 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());
}
}
This let's me get rid of the third party SQL parser, but now I'm using a regular expression on the non-inlined query looking for \\\\s[wW][hH][eE][rR][eE]\\\\s
, which isn't ideal, either.这让我摆脱了第三方 SQL 解析器,但现在我在非内联查询上使用正则表达式寻找
\\\\s[wW][hH][eE][rR][eE]\\\\s
,这也不理想。
UPDATE
, DELETE
, has a WHERE
clause?UPDATE
、 DELETE
是否有WHERE
子句?UPDATE
or DELETE
, instead using the ExecuteType
)?UPDATE
还是DELETE
,而是使用ExecuteType
)?That's an interesting idea and approach.这是一个有趣的想法和方法。 One problem I can see with it is performance.
我可以看到的一个问题是性能。 Rendering the SQL string a second time and then parsing it again sounds like a bit of overhead.
再次呈现 SQL 字符串然后再次解析它听起来有点开销。 Perhaps, this
ExecuteListener
should be active in development and integration test environments only, not in production.也许,这个
ExecuteListener
应该只在开发和集成测试环境中活跃,而不是在生产中。
- Is there a way to use jOOQ to tell me if an UPDATE, DELETE, has a WHERE clause?
有没有办法使用 jOOQ 来告诉我 UPDATE、DELETE 是否有 WHERE 子句?
Since you seem to be open to use reflection to access a third party library's internals, well of course, you could check if the ctx.query()
is of type org.jooq.impl.UpdateQueryImpl
or org.jooq.impl.DeleteQueryImpl
.由于您似乎愿意使用反射来访问第三方库的内部结构,当然,您可以检查
ctx.query()
的类型是org.jooq.impl.UpdateQueryImpl
还是org.jooq.impl.DeleteQueryImpl
。 In version 3.10.1, both of them have a private condition
member, which you could check.在 3.10.1 版本中,它们都有一个私有
condition
成员,您可以检查。
This will obviously break any time the internals are changed, but it might be a pragmatic solution for now.这显然会在内部结构发生变化时中断,但目前它可能是一个务实的解决方案。
- Similarly, is there a way that let's me see what table the query is acting against
同样,有没有办法让我看看查询针对的是哪个表
A more general and more robust approach would be to implement aVisitListener
, which is jOOQ's callback that is called during expression tree traversal.更通用和更健壮的方法是实现
VisitListener
,这是在表达式树遍历期间调用的 jOOQ 回调。 You can hook into the generation of the SQL string and the collection of bind variables, and throw your errors as soon as you encounter:您可以挂钩 SQL 字符串的生成和绑定变量的集合,并在遇到错误时立即抛出:
UPDATE
or DELETE
statement UPDATE
或DELETE
语句WHERE
clause WHERE
子句You "just" have to implement a stack machine that remembers all of the above things prior to throwing the exception.您“只需要”实现一个堆栈机器,在抛出异常之前记住上述所有内容。 An example of how
VisitListener
can be implemented is given here: https://blog.jooq.org/2015/06/17/implementing-client-side-row-level-security-with-jooq此处给出了如何实现
VisitListener
的示例: https : VisitListener
This kind of feature has been discussed a couple of times on the mailing list as well.这种功能也已在邮件列表中讨论过几次。 It's a low hanging fruit to support by jOOQ natively.
jOOQ 本身支持它是一个容易实现的目标。 I've created a feature request for jOOQ 3.11, for this: https://github.com/jOOQ/jOOQ/issues/6771
我为 jOOQ 3.11 创建了一个功能请求,为此: https : //github.com/jOOQ/jOOQ/issues/6771
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.