如何在没有WHERE子句的情况下检查DELETE / UPDATE

时间:2017-10-25 16:02:00

标签: java sql jooq

我目前有一个监听器,我们用它来做一些不同的监控类型的活动(比如在查询超过5秒时记录警告),但它也会监视并杀死"愚蠢的错误&#34 ; - 特别是缺少UPDATE子句的DELETEWHERE个查询。

过去我们做了以下操作(注意我们正在使用 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,这不是理想的,或者。

  1. 有没有办法使用jOOQ告诉我UPDATEDELETE是否有WHERE条款?
  2. 同样,有没有办法可以让我看到查询所针对的表格(这样我就可以限制某些人可以执行可变行动的表格 - 显然,我不会检查它是UPDATE还是DELETE,而是使用ExecuteType)?

1 个答案:

答案 0 :(得分:1)

这是一个有趣的想法和方法。我能看到的一个问题是性能。再次呈现SQL字符串,然后再次解析它听起来有点开销。也许,这个ExecuteListener应仅在开发和集成测试环境中有效,而不是在生产中。

关于您的问题

  
      
  1. 有没有办法使用jOOQ告诉我UPDATE,DELETE是否有WHERE子句?
  2.   

由于您似乎愿意使用反射来访问第三方库的内部,当然,您可以检查ctx.query()org.jooq.impl.UpdateQueryImpl类型还是{{1} }}。在版本3.10.1中,它们都有一个私有org.jooq.impl.DeleteQueryImpl成员,您可以检查。

这显然会在内部更改的任何时候中断,但现在可能是一个实用的解决方案。

  
      
  1. 同样,有没有办法让我看到查询正在对哪个表格起作用
  2.   

更通用且更健壮的方法是实现VisitListener,这是在表达式树遍历期间调用的jOOQ的回调。您可以挂钩SQL字符串的生成和绑定变量的集合,并在遇到错误时抛出错误:

  • conditionUPDATE声明
  • ...没有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