Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-41393

sql_safe_updates is bypassed by the implicit system versioning condition

    XMLWordPrintable

Details

    • Bug
    • Status: Open (View Workflow)
    • Major
    • Resolution: Unresolved
    • 10.11
    • None
    • Versioned Tables
    • None
    • Can result in unexpected behaviour

    Description

      With sql_safe_updates=1, a single-table DELETE without WHERE is rejected even when it has LIMIT (ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE). For a system-versioned table the same statement is accepted. The user wrote no WHERE, but the server adds a hidden row_end condition that selects current rows, and the safe-updates check counts that condition as a WHERE clause.

      A system-versioned table must behave like a non-versioned one here: the implicit versioning condition is not a restriction written by the user.

      Reproduce

      set sql_safe_updates= 1;
       
      create table plain (x int);
      create table vers (x int) with system versioning;
       
      insert into plain values (1), (2);
      insert into vers values (1), (2);
       
      delete from plain limit 1;
      delete from vers limit 1;
       
      set sql_safe_updates= default;
      drop table plain, vers;
      

      Result

      DELETE FROM plain LIMIT 1 fails, DELETE FROM vers LIMIT 1 succeeds.

      For a table whose history is stored in SYSTEM_TIME partitions, MDEV-17613 replaces the row_end condition with the selection of the current partition, so after MDEV-17613 such a table already behaves like a non-versioned one. Only versioned tables without history partitions are affected.

      Expected

      Both DELETE statements fail with ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE.

      Analysis

      Sql_cmd_delete::delete_from_single_table() (sql/sql_delete.cc) rejects a DELETE under safe updates when its condition is missing or constant, regardless of LIMIT:

      const_cond= (!conds || conds->const_item());
      ...
      if (safe_update && const_cond)
        ... ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE
      

      For a versioned table, SELECT_LEX::vers_setup_conds(), called from Sql_cmd_delete::prepare_inner(), ANDs the row_end condition into select_lex->where before the condition is fixed. So conds is never empty and const_cond is false. Only the later "no index used and no LIMIT" check applies, and LIMIT satisfies it.

      Fix

      Judge the WHERE written by the user, not conds:

      1. In Sql_cmd_delete::prepare_inner(), before vers_setup_conds(), remember whether the user wrote a WHERE clause. Do it only when !skip_setup_conds(thd): on prepared statement re-execution select_lex->where is restored from prep_where, which already contains the versioning condition.
      2. In delete_from_single_table(), use that flag in the safe-updates check only: if (safe_update && (const_cond || no_user_where)). Keep const_cond itself unchanged, since it also controls const_cond_result and delete_all_rows().

      This does not cover a constant WHERE written by the user (e.g. WHERE 1), which is still combined with the versioning condition. Covering it requires the constness of the user's part after fix_fields.

      Related case (to be verified)

      With an index whose first keypart is row_end (e.g. KEY (row_end)), the hidden row_end condition may produce a range access on that index. That satisfies the "no index used and no LIMIT" check of single-table UPDATE and DELETE, and the full-join check of multi-table UPDATE/DELETE. So UPDATE or DELETE without WHERE and without LIMIT could be accepted for such a versioned table, while a non-versioned table with the same index is rejected. This follows from the code but has not been reproduced yet.

      set sql_safe_updates= 1;
      create table t1 (
        x int,
        ts timestamp(6) as row start invisible,
        te timestamp(6) as row end invisible,
        period for system_time (ts, te),
        key (te)
      ) with system versioning;
      insert into t1 values (1), (2);
      update t1 set x= x + 1;
      delete from t1;
      set sql_safe_updates= default;
      drop table t1;
      

      Expected: both statements fail with ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE.

      Attachments

        Issue Links

          Activity

            People

              midenok Aleksey Midenkov
              midenok Aleksey Midenkov
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:

                Git Integration

                  Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.