简体   繁体   English

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

[英]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 秒时记录警告),但它也会监视并杀死“愚蠢的错误”——尤其是UPDATEDELETE查询缺少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 ,这也不理想。

  1. Is there a way to use jOOQ to tell me if an UPDATE , DELETE , has a WHERE clause?有没有办法使用 jOOQ 来告诉我UPDATEDELETE是否有WHERE子句?
  2. Similarly, is there a way that let's me see what table the query is acting against (so that I can limit the tables someone can perform mutable actions against -- obviously that one wouldn't check if it's 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应该只在开发和集成测试环境中活跃,而不是在生产中。

Regarding your questions关于你的问题

  1. 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.这显然会在内部结构发生变化时中断,但目前它可能是一个务实的解决方案。

  1. 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 字符串的生成和绑定变量的集合,并在遇到错误时立即抛出:

  • An UPDATE or DELETE statement UPDATEDELETE语句
  • ... without a WHERE clause ...没有WHERE子句
  • ... updating a table from a specific set of tables ...从一组特定的表更新表

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

New feature in the future未来的新功能

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.

相关问题 如何在不使用select子句的情况下使用apache cayenne更新记录 - How do you update a record with apache cayenne without using select clause Java在哪里存储记住的HTTP基本身份验证密码,以及如何删除它们? - Where does Java store remembered HTTP basic auth passwords and how do you delete them? 使用 HQL 中的 where 子句删除查询 - delete query with where clause in HQL SQLite 删除 where 子句参数 - SQLite delete where clause arguments 如何使用Where子句从多个表中删除多行? - How to delete multiple rows from multiple tables using Where clause? 如何在where子句中使用枚举作为JPA中的常量检查 - How to use enum in where clause as constant check in jpa 如何使用JOOQ检查WHERE子句条件是否为VALID? - How to check if a WHERE clause condition is VALID or not using JOOQ? 如何在不创建新数组或使用 ArrayLists 的情况下从数组中删除元素? java - How do you delete an element from an array without creating a new array or using ArrayLists? java MySQL:如何将值更新为多行并同时使用where子句? - MySQL: How to Update values into multiple rows and use where clause as well? 如何使用where子句更新spring数据JPA中的表 - how to update a table in spring data JPA using where clause
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM