You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit e4f2a13
Browse filesBrowse the repository at this point in the historyBrowse files
docs: move row-level DML contracts into TableProvider API docs
Keep the library guide introductory and split the SQL reference changes into a separate PR.
Copy file name to clipboardExpand all lines: docs/source/library-user-guide/custom-table-providers.md
+6-128Lines changed: 6 additions & 128 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -794,138 +794,16 @@ happens later, in the stream produced by `execute()`.
794
794
795
795
## Row-Level DML: DELETE and UPDATE
796
796
797
-
`TableProvider` has two optional hooks for row-level Data Manipulation Language (DML) statements:
797
+
A custom table provider can support `DELETE` and `UPDATE` by implementing the optional [`TableProvider::delete_from()`] and [`TableProvider::update()`] methods. Their default implementations return a "not implemented" error.
798
798
799
-
-`delete_from(state, filters)` serves `DELETE FROM t [WHERE ...]`.
800
-
-`update(state, assignments, filters)` serves `UPDATE t SET ... [WHERE ...]`.
799
+
Each method builds an [ExecutionPlan] that changes the matching rows and returns the number of affected rows in a `count` column. Your provider decides how to apply and persist the changes. Perform the changes when the returned plan executes: these methods run during physical planning, including for `EXPLAIN`.
801
800
802
-
Both hooks have a default implementation that returns a "not implemented" error. A provider that does not override them keeps its previous read-only behaviour, and DataFusion reports the statement as unsupported for that table.
801
+
The method documentation describes the filters, assignments, SQL semantics, and result schema that an implementation must support. [MemTable] provides an implementation of both methods over in-memory batches.
803
802
804
-
The physical planner calls the hook instead of building a plan of its own. Your provider therefore owns the whole row change: which rows change, how to make the change durable, and how many rows the statement affected.
803
+
For SQL syntax and examples, see the [DML section](../user-guide/sql/dml.md) of the user guide.
805
804
806
-
### What the Planner Passes to the Hooks
807
-
808
-
`filters` holds the `WHERE` predicates as logical `Expr` values, after three transformations:
809
-
810
-
- The planner splits `AND` conjunctions into separate elements.
811
-
- The planner strips table qualifiers, so `t.id = 1` arrives as `id = 1` and matches your schema.
812
-
- The planner keeps only the predicates on the target table, and collects them both from `Filter` nodes and from the pushed-down filters of the target `TableScan`.
813
-
814
-
An empty `filters` vector means the statement has no `WHERE` clause. Then the statement applies to every row.
815
-
816
-
`assignments` holds the `SET` clause as `(column_name, Expr)` pairs. The planner removes identity assignments, so the vector contains only the columns that the statement changes.
817
-
818
-
### What the Hooks Must Return
819
-
820
-
Each hook returns an [ExecutionPlan] that produces one row in a single non-null `UInt64` column named `count`. The value is the number of affected rows. `INSERT` uses the same convention, which is why `datafusion-cli` prints the same shape of result for all three statements:
821
-
822
-
```text
823
-
+-------+
824
-
| count |
825
-
+-------+
826
-
| 2 |
827
-
+-------+
828
-
```
829
-
830
-
To evaluate the filters and the assignments, convert each `Expr` with `create_physical_expr()`, then pass your table schema and `state.execution_props()`. Reject an unknown column with a plan error rather than an internal error.
831
-
832
-
Two semantic rules keep your provider consistent with the rest of SQL:
833
-
834
-
- Apply SQL three-valued logic. Change a row only if the predicate is true for that row. A predicate that evaluates to `NULL` must leave the row alone.
835
-
- Evaluate every assignment against the values from before the statement. `SET a = b, b = a` then exchanges the two values.
836
-
837
-
The following example shows the shape of both implementations. The row-level work is in the two private methods. The example counts the rows in the hook, as [MemTable] does; see [When the Work Happens](#when-the-work-happens) for the alternative:
The planner drops some clauses before it calls a hook, so do not expect them:
912
-
913
-
- A `LIMIT` clause on a `DELETE` has no effect. Your hook sees only the filters. This limitation is tracked at [issue #24998](https://github.com/apache/datafusion/issues/24998).
914
-
- A scalar subquery in a `WHERE` clause or in a `SET` clause fails during physical planning, before the hook runs. This limitation is tracked at [issue #24654](https://github.com/apache/datafusion/issues/24654).
915
-
- An `IN` or an `EXISTS` subquery reaches your hook with an empty `filters` vector, because the optimizer rewrites the subquery into a join. Your hook then changes every row, which is the wrong answer. DataFusion does not yet protect a provider against this case. This limitation is tracked at [issue #24654](https://github.com/apache/datafusion/issues/24654).
916
-
-`UPDATE ... FROM`, which reads new values from a second table, returns a "not implemented" error. This limitation is tracked at [issue #19950](https://github.com/apache/datafusion/issues/19950).
917
-
918
-
### When the Work Happens
919
-
920
-
The hooks run during physical planning, like `scan()`. A hook that changes rows before it returns its plan therefore changes them during planning, and `EXPLAIN DELETE` or `EXPLAIN UPDATE` also changes them. [MemTable] works this way. This limitation is tracked at [issue #24656](https://github.com/apache/datafusion/issues/24656).
921
-
922
-
For a provider that writes to durable storage, do the work in the `execute()` method of the plan that you return instead. The hook then stays lightweight, and `EXPLAIN` shows the plan without a side effect.
923
-
924
-
### Reference Implementation
925
-
926
-
[MemTable] implements both hooks over its in-memory batches, and is the best code to read next. It shows how to build the boolean mask for the filters, how to keep the rows where the predicate is false or `NULL`, and how to evaluate an assignment only on the matching rows. That last detail matters: it stops an expression such as `100 / divisor` from failing on rows that the `WHERE` clause excludes.
927
-
928
-
The user-facing behaviour of the two statements is in the [DML section](../user-guide/sql/dml.md) of the user guide.
DELETE FROM <i><b>table_name</b></i> [ WHERE <i><b>condition</b></i> ]
146
-
</pre>
147
-
148
-
`DELETE` returns the number of removed rows in a column named `count`.
149
-
150
-
If you omit the `WHERE` clause, DataFusion removes all rows.
151
-
152
-
DataFusion removes a row only if the condition is true for that row. SQL three-valued logic applies: if the condition evaluates to `NULL`, the row remains. For example, `WHERE value > 15` keeps a row with a `NULL` value, because `NULL > 15` is `NULL`.
153
-
154
-
Not all tables support `DELETE`. See [Table support for DELETE and UPDATE](#table-support-for-delete-and-update).
155
-
156
-
### Examples
157
-
158
-
Remove the rows that match a condition:
159
-
160
-
```sql
161
-
>DELETEFROM target_table WHERE id >1;
162
-
+-------+
163
-
| count |
164
-
+-------+
165
-
| 2 |
166
-
+-------+
167
-
```
168
-
169
-
Remove all rows:
170
-
171
-
```sql
172
-
>DELETEFROM target_table;
173
-
+-------+
174
-
| count |
175
-
+-------+
176
-
| 3 |
177
-
+-------+
178
-
```
179
-
180
-
## UPDATE
181
-
182
-
Changes the values of existing rows.
183
-
184
-
<pre>
185
-
UPDATE <i><b>table_name</b></i> SET <i><b>column</b></i> = <i><b>expression</b></i> [, ...] [ WHERE <i><b>condition</b></i> ]
186
-
</pre>
187
-
188
-
`UPDATE` returns the number of affected rows in a column named `count`.
189
-
190
-
If you omit the `WHERE` clause, DataFusion changes all rows. The three-valued logic of `DELETE` also applies here.
191
-
192
-
Each assignment expression reads the row values from before the statement. `SET a = b, b = a` therefore exchanges the two values.
193
-
194
-
Not all tables support `UPDATE`. See [Table support for DELETE and UPDATE](#table-support-for-delete-and-update).
195
-
196
-
### Examples
197
-
198
-
Set one column in the rows that match a condition:
199
-
200
-
```sql
201
-
>UPDATE target_table SET name ='Baz'WHERE id =2;
202
-
+-------+
203
-
| count |
204
-
+-------+
205
-
| 1 |
206
-
+-------+
207
-
```
208
-
209
-
Set two columns, one from an expression:
210
-
211
-
```sql
212
-
>UPDATE target_table SET value = value *2, name ='Doubled'WHERE id <3;
213
-
+-------+
214
-
| count |
215
-
+-------+
216
-
| 2 |
217
-
+-------+
218
-
```
219
-
220
-
## Table support for DELETE and UPDATE
221
-
222
-
The table provider does the work for `DELETE` and `UPDATE`. Support is therefore a property of each table:
223
-
224
-
-`CREATE TABLE` makes an in-memory table. In-memory tables support both statements.
225
-
-`CREATE EXTERNAL TABLE` makes a file-based table. File-based tables support neither statement.
226
-
- Views support neither statement.
227
-
- A custom table provider supports a statement only if it implements the matching hook. See [Custom Table Provider](../../library-user-guide/custom-table-providers.md#row-level-dml-delete-and-update).
228
-
229
-
A table that gives no support returns an error:
230
-
231
-
```text
232
-
DELETE operation on table 'my_external_table'
233
-
caused by
234
-
This feature is not implemented: DELETE not supported for Base table
235
-
```
236
-
237
-
### Limitations
238
-
239
-
:::{warning}
240
-
Do not use a subquery in the condition of a `DELETE` or an `UPDATE`. A scalar subquery, such as `WHERE id = (SELECT max(id) FROM other)`, returns an error. An `IN` or an `EXISTS` subquery is worse: the statement applies to **all** rows of the table. The optimizer rewrites the subquery into a join, and the condition then no longer reaches the table provider. This limitation is tracked at [issue #24654](https://github.com/apache/datafusion/issues/24654).
241
-
:::
242
-
243
-
:::{warning}
244
-
`EXPLAIN` executes a `DELETE` or an `UPDATE` on an in-memory table. The provider changes the rows while DataFusion plans the statement. Use a copy of the table if you want to read the plan only. This limitation is tracked at [issue #24656](https://github.com/apache/datafusion/issues/24656).
245
-
:::
246
-
247
-
DataFusion ignores a `LIMIT` clause in a `DELETE` statement. The statement removes all rows that match the condition. This limitation is tracked at [issue #24998](https://github.com/apache/datafusion/issues/24998).
248
-
249
-
`UPDATE ... FROM`, which reads the new values from a second table, returns a "not implemented" error. This limitation is tracked at [issue #19950](https://github.com/apache/datafusion/issues/19950).
0 commit comments