# LogicTest: local

statement ok
CREATE TABLE kv (
  k INT PRIMARY KEY,
  v INT,
  UNIQUE INDEX foo (v),
  INDEX bar (k, v)
)

statement ok
CREATE TABLE unindexed (
  k INT PRIMARY KEY,
  v INT
)

statement ok
CREATE TABLE indexed (id int primary key, value int, other int, index (value))

statement count 4
INSERT INTO kv VALUES (1, 2), (3, 4), (5, 6), (7, 8)

statement count 2
DELETE FROM kv WHERE k=3 OR v=6

query II rowsort
DELETE FROM kv RETURNING k, v
----
1 2
7 8

statement ok
SET tracing = on,kv,results; SELECT * FROM kv; SET tracing = off

query T
SELECT message FROM [SHOW KV TRACE FOR SESSION] WITH ORDINALITY
 WHERE message LIKE 'fetched:%' OR message LIKE 'output row%'
 ORDER BY message LIKE 'fetched:%' DESC, ordinality ASC
----

statement ok
SET tracing = on,kv,results; SELECT * FROM kv@foo; SET tracing = off

query T
SELECT message FROM [SHOW KV TRACE FOR SESSION] WITH ORDINALITY
 WHERE message LIKE 'fetched:%' OR message LIKE 'output row%'
 ORDER BY message LIKE 'fetched:%' DESC, ordinality ASC
----

statement ok
SET tracing = on,kv,results; SELECT * FROM kv@bar; SET tracing = off

query T
SELECT message FROM [SHOW KV TRACE FOR SESSION] WITH ORDINALITY
 WHERE message LIKE 'fetched:%' OR message LIKE 'output row%'
 ORDER BY message LIKE 'fetched:%' DESC, ordinality ASC
----

# Check that EXPLAIN does not destroy data (#6613)
query T
EXPLAIN DELETE FROM unindexed
----
distribution: local
vectorized: true
·
• delete range
  from: unindexed
  spans: FULL SCAN

query T
EXPLAIN DELETE FROM unindexed WHERE v = 7 ORDER BY v LIMIT 10
----
distribution: local
vectorized: true
·
• delete
│ from: unindexed
│ auto commit
│
└── • limit
    │ count: 10
    │
    └── • filter
        │ filter: v = 7
        │
        └── • scan
              missing stats
              table: unindexed@unindexed_pkey
              spans: FULL SCAN (SOFT LIMIT)

# Check DELETE with LIMIT clause (MySQL extension)
query T
EXPLAIN DELETE FROM unindexed WHERE v = 5 LIMIT 10
----
distribution: local
vectorized: true
·
• delete
│ from: unindexed
│ auto commit
│
└── • limit
    │ count: 10
    │
    └── • filter
        │ filter: v = 5
        │
        └── • scan
              missing stats
              table: unindexed@unindexed_pkey
              spans: FULL SCAN (SOFT LIMIT)

# Check fast DELETE.
query T
EXPLAIN DELETE FROM unindexed WHERE k > 0
----
distribution: local
vectorized: true
·
• delete range
  from: unindexed
  spans: [/1 - ]

# Check fast DELETE with reverse scans (not supported by optimizer).
query error DELETE statement requires LIMIT when ORDER BY is used
EXPLAIN DELETE FROM unindexed WHERE true ORDER BY k DESC

# Check that limits don't permit fast deletes.
query T
EXPLAIN DELETE FROM unindexed WHERE k > 0 LIMIT 1
----
distribution: local
vectorized: true
·
• delete
│ from: unindexed
│ auto commit
│
└── • scan
      missing stats
      table: unindexed@unindexed_pkey
      spans: [/1 - ]
      limit: 1
      locking strength: for update

query T
EXPLAIN DELETE FROM indexed WHERE value = 5 LIMIT 10
----
distribution: local
vectorized: true
·
• delete
│ from: indexed
│ auto commit
│
└── • scan
      missing stats
      table: indexed@indexed_value_idx
      spans: [/5 - /5]
      limit: 10
      locking strength: for update

query T
EXPLAIN DELETE FROM indexed LIMIT 10
----
distribution: local
vectorized: true
·
• delete
│ from: indexed
│ auto commit
│
└── • scan
      missing stats
      table: indexed@indexed_value_idx
      spans: LIMITED SCAN
      limit: 10
      locking strength: for update

# TODO(andyk): Prune columns so that index-join is not necessary.
query T
EXPLAIN DELETE FROM indexed WHERE value = 5 LIMIT 10 RETURNING id
----
distribution: local
vectorized: true
·
• delete
│ from: indexed
│ auto commit
│
└── • scan
      missing stats
      table: indexed@indexed_value_idx
      spans: [/5 - /5]
      limit: 10
      locking strength: for update

# Ensure that index hints in DELETE statements force the choice of a specific index
# as described in #38799.
statement ok
CREATE TABLE t38799 (a INT PRIMARY KEY, b INT, c INT, INDEX foo(b))

query T
EXPLAIN (VERBOSE) DELETE FROM t38799@foo
----
distribution: local
vectorized: true
·
• delete
│ columns: ()
│ estimated row count: 0 (missing stats)
│ from: t38799
│ auto commit
│
└── • scan
      columns: (a, b)
      estimated row count: 1,000 (missing stats)
      table: t38799@foo
      spans: FULL SCAN
      locking strength: for update

# Tracing tests for fast delete.
statement ok
CREATE TABLE a (a INT PRIMARY KEY)

# Delete range operates in chunks of 600 (defined by row.TableTruncateChunkSize).
statement ok
INSERT INTO a SELECT * FROM generate_series(1,1000)

statement ok
SET tracing = on,kv; DELETE FROM a; SET tracing = off

# Ensure that DelRange requests are chunked for DELETE FROM...
query TT nosort
SELECT operation, message FROM [SHOW KV TRACE FOR SESSION]
WHERE message LIKE '%DelRange%' OR message LIKE '%DelRng%'
----
delete range      DelRange /Table/110/1 - /Table/110/2
dist sender send  r74: sending batch 1 DelRng to (n1,s1):1
delete range      DelRange /Table/110/1/601/0 - /Table/110/2
dist sender send  r74: sending batch 1 DelRng to (n1,s1):1

# Ensure that DelRange requests are autocommitted when DELETE FROM happens on a
# chunk of fewer than 600 keys.

statement ok
INSERT INTO a VALUES(5)

statement ok
SET tracing = on,kv; DELETE FROM a WHERE a = 5;

statement ok
SET tracing = off

query TT nosort
SELECT operation, message FROM [SHOW KV TRACE FOR SESSION]
WHERE message LIKE '%Del%' OR message LIKE '%sending batch%'
----
delete range      Del /Table/110/1/5/0
dist sender send  r74: sending batch 1 Del, 1 EndTxn to (n1,s1):1

# Ensure that we send DelRanges when doing a point delete operation on a table
# that has multiple column families.

statement ok
CREATE TABLE multicf (a INT PRIMARY KEY, b INT, FAMILY (a), FAMILY (b))

statement ok
SELECT * FROM multicf

statement ok
SET tracing = on,kv; DELETE FROM multicf WHERE a = 5; SET tracing = off

query TT nosort
SELECT operation, message FROM [SHOW KV TRACE FOR SESSION]
WHERE message LIKE '%Del%' OR message LIKE '%sending batch%'
----
delete range      DelRange /Table/111/1/5 - /Table/111/1/6
dist sender send  r74: sending batch 1 DelRng to (n1,s1):1

statement ok
CREATE TABLE xyz (
  x INT PRIMARY KEY,
  y INT,
  z INT,
  INDEX (y)
)

# Ensure that we can use a hint to avoid a full table scan.

statement ok
SET avoid_full_table_scans_in_mutations = false

# Without the hint, we plan a full table scan.
query T
EXPLAIN (VERBOSE) DELETE FROM xyz WHERE (y > 0 AND y < 1000) OR (y > 2000 AND y < 3000) RETURNING z
----
distribution: local
vectorized: true
·
• project
│ columns: (z)
│
└── • delete
    │ columns: (x, z)
    │ estimated row count: 990 (missing stats)
    │ from: xyz
    │ auto commit
    │
    └── • filter
        │ columns: (x, y, z)
        │ estimated row count: 990 (missing stats)
        │ filter: ((y > 0) AND (y < 1000)) OR ((y > 2000) AND (y < 3000))
        │
        └── • scan
              columns: (x, y, z)
              estimated row count: 1,000 (missing stats)
              table: xyz@xyz_pkey
              spans: FULL SCAN

# With the hint, we use a constrained scan.
query T
EXPLAIN (VERBOSE) DELETE FROM xyz@{NO_FULL_SCAN} WHERE (y > 0 AND y < 1000) OR (y > 2000 AND y < 3000) RETURNING z
----
distribution: local
vectorized: true
·
• project
│ columns: (z)
│
└── • delete
    │ columns: (x, z)
    │ estimated row count: 990 (missing stats)
    │ from: xyz
    │ auto commit
    │
    └── • index join
        │ columns: (x, y, z)
        │ estimated row count: 990 (missing stats)
        │ table: xyz@xyz_pkey
        │ key columns: x
        │ locking strength: for update
        │
        └── • scan
              columns: (x, y)
              estimated row count: 990 (missing stats)
              table: xyz@xyz_y_idx
              spans: /1-/1000 /2001-/3000
              locking strength: for update

# AVOID_FULL_SCAN also works to ensure a constrained scan.
query T
EXPLAIN (VERBOSE) DELETE FROM xyz@{AVOID_FULL_SCAN} WHERE (y > 0 AND y < 1000) OR (y > 2000 AND y < 3000) RETURNING z
----
distribution: local
vectorized: true
·
• project
│ columns: (z)
│
└── • delete
    │ columns: (x, z)
    │ estimated row count: 990 (missing stats)
    │ from: xyz
    │ auto commit
    │
    └── • index join
        │ columns: (x, y, z)
        │ estimated row count: 990 (missing stats)
        │ table: xyz@xyz_pkey
        │ key columns: x
        │ locking strength: for update
        │
        └── • scan
              columns: (x, y)
              estimated row count: 990 (missing stats)
              table: xyz@xyz_y_idx
              spans: /1-/1000 /2001-/3000
              locking strength: for update

# We also avoid the full scan using the session setting
# avoid_full_table_scans_in_mutations.
statement ok
SET avoid_full_table_scans_in_mutations = true

query T
EXPLAIN (VERBOSE) DELETE FROM xyz WHERE (y > 0 AND y < 1000) OR (y > 2000 AND y < 3000) RETURNING z
----
distribution: local
vectorized: true
·
• project
│ columns: (z)
│
└── • delete
    │ columns: (x, z)
    │ estimated row count: 990 (missing stats)
    │ from: xyz
    │ auto commit
    │
    └── • index join
        │ columns: (x, y, z)
        │ estimated row count: 990 (missing stats)
        │ table: xyz@xyz_pkey
        │ key columns: x
        │ locking strength: for update
        │
        └── • scan
              columns: (x, y)
              estimated row count: 990 (missing stats)
              table: xyz@xyz_y_idx
              spans: /1-/1000 /2001-/3000
              locking strength: for update

# Testcase for issue 105803.

statement ok
CREATE TABLE b (b INT PRIMARY KEY)

query T
EXPLAIN (TYPES) DELETE FROM a USING b WHERE b > 1 RETURNING a, b, NULL
----
distribution: local
vectorized: true
·
• render
│ columns: (a int, b int, "?column?" unknown)
│ render ?column?: (NULL)[unknown]
│ render a: (a)[int]
│ render b: (b)[int]
│
└── • delete
    │ columns: (a int, b int)
    │ estimated row count: 1,000 (missing stats)
    │ from: a
    │ auto commit
    │
    └── • distinct
        │ columns: (a int, b int)
        │ estimated row count: 1,000 (missing stats)
        │ distinct on: a
        │
        └── • cross join (inner)
            │ columns: (a int, b int)
            │ estimated row count: 333,333 (missing stats)
            │
            ├── • scan
            │     columns: (a int)
            │     estimated row count: 1,000 (missing stats)
            │     table: a@a_pkey
            │     spans: FULL SCAN
            │
            └── • scan
                  columns: (b int)
                  estimated row count: 333 (missing stats)
                  table: b@b_pkey
                  spans: /2-

# Test that FOR UPDATE locks are applied to reads in generic query plans.

statement ok
CREATE TABLE t137352 (
  k INT PRIMARY KEY,
  a INT,
  b INT,
  INDEX (a),
  INDEX (b)
)

statement ok
SET plan_cache_mode = force_generic_plan

statement ok
PREPARE p AS DELETE FROM t137352 WHERE k = $1

query T
EXPLAIN ANALYZE EXECUTE p(1)
----
planning time: 10µs
execution time: 100µs
distribution: <hidden>
vectorized: <hidden>
plan type: generic, re-optimized
maximum memory usage: <hidden>
network usage: <hidden>
regions: <hidden>
isolation level: serializable
priority: normal
quality of service: regular
·
• delete
│ sql nodes: <hidden>
│ regions: <hidden>
│ actual row count: 1
│ from: t137352
│ auto commit
│
└── • lookup join
    │ sql nodes: <hidden>
    │ kv nodes: <hidden>
    │ regions: <hidden>
    │ actual row count: 0
    │ KV time: 0µs
    │ KV contention time: 0µs
    │ KV rows decoded: 0
    │ KV bytes read: 0 B
    │ KV gRPC calls: 0
    │ estimated max memory allocated: 0 B
    │ table: t137352@t137352_pkey
    │ equality: ($1) = (k)
    │ equality cols are key
    │ locking strength: for update
    │
    └── • values
          sql nodes: <hidden>
          regions: <hidden>
          actual row count: 1
          size: 1 column, 1 row

statement ok
DEALLOCATE p

statement ok
PREPARE p AS DELETE FROM t137352 WHERE a = $1

query T
EXPLAIN ANALYZE EXECUTE p(10)
----
planning time: 10µs
execution time: 100µs
distribution: <hidden>
vectorized: <hidden>
plan type: generic, re-optimized
maximum memory usage: <hidden>
network usage: <hidden>
regions: <hidden>
isolation level: serializable
priority: normal
quality of service: regular
·
• delete
│ sql nodes: <hidden>
│ regions: <hidden>
│ actual row count: 1
│ from: t137352
│ auto commit
│
└── • lookup join
    │ sql nodes: <hidden>
    │ regions: <hidden>
    │ actual row count: 0
    │ KV time: 0µs
    │ KV contention time: 0µs
    │ KV rows decoded: 0
    │ KV bytes read: 0 B
    │ KV gRPC calls: 0
    │ estimated max memory allocated: 0 B
    │ table: t137352@t137352_pkey
    │ equality: (k) = (k)
    │ equality cols are key
    │ locking strength: for update
    │
    └── • lookup join
        │ sql nodes: <hidden>
        │ kv nodes: <hidden>
        │ regions: <hidden>
        │ actual row count: 0
        │ KV time: 0µs
        │ KV contention time: 0µs
        │ KV rows decoded: 0
        │ KV bytes read: 0 B
        │ KV gRPC calls: 0
        │ estimated max memory allocated: 0 B
        │ table: t137352@t137352_a_idx
        │ equality: ($1) = (a)
        │ locking strength: for update
        │
        └── • values
              sql nodes: <hidden>
              regions: <hidden>
              actual row count: 1
              size: 1 column, 1 row
