|
19 | 19 | * <p> |
20 | 20 | * Adapted from the DQE oracle, state is observed with an auxiliary column ({@link EETDMLGenerator#ROW_ID_COLUMN}) which |
21 | 21 | * uniquely identifies each row. The rows are stamped with identifiers once, before both executions of the statement run |
22 | | - * (each in a rolled-back transaction), so both executions observe the same identifiers regardless of how they are |
23 | | - * produced. The resulting state is compared as a full post-image (each surviving row's identifier and content column |
24 | | - * values), which covers every DML statement: a DELETE removes rows from it, an UPDATE changes values in it. |
| 22 | + * (each in a rolled-back transaction), so both executions observe the same identifiers. The resulting state is compared |
| 23 | + * as a full post-image (each surviving row's identifier and content column values), which covers any of the three DML |
| 24 | + * statements (DELETE, UPDATE, INSERT). |
25 | 25 | * |
26 | 26 | * <p> |
27 | 27 | * Most of these statements are standard SQL, likely common to most DBMSs, so are provided as {@code default} methods. |
@@ -66,6 +66,15 @@ public interface EETDMLGenerator<E extends Expression<C>, T extends AbstractTabl |
66 | 66 | */ |
67 | 67 | List<Map.Entry<C, E>> generateSetAssignments(); |
68 | 68 |
|
| 69 | + /** |
| 70 | + * Generates a fresh value expression for each content column of the current table, used as an INSERT statement's |
| 71 | + * inserted values. The returned expressions are positionally aligned with {@link AbstractTable#getColumns()}, and |
| 72 | + * each is transformed by the oracle. |
| 73 | + * |
| 74 | + * @return one fresh random value expression per content column, in {@link AbstractTable#getColumns()} order |
| 75 | + */ |
| 76 | + List<E> generateInsertValues(); |
| 77 | + |
69 | 78 | /** |
70 | 79 | * Creates a DBMS-specific {@link EETTransformer} backed by this generator, used to rewrite the statement's |
71 | 80 | * expressions into semantically equivalent ones. |
@@ -106,6 +115,17 @@ public interface EETDMLGenerator<E extends Expression<C>, T extends AbstractTabl |
106 | 115 | */ |
107 | 116 | String rowIdColumnType(); |
108 | 117 |
|
| 118 | + /** |
| 119 | + * A SQL expression, evaluated once per source row of an {@code INSERT ... SELECT}, that derives the inserted row's |
| 120 | + * {@link #ROW_ID_COLUMN} value from the source row's identifier. It must be deterministic (so both the original and |
| 121 | + * transformed statements assign the same identifiers), unique per source row, and distinct from every existing |
| 122 | + * identifier (so an inserted row never collides with the source row it was derived from in the post-image). DBMS- |
| 123 | + * specific because it names a suitable derivation function (e.g. a hash of the source identifier). |
| 124 | + * |
| 125 | + * @return the SQL expression deriving an inserted row's identifier from the source row's {@link #ROW_ID_COLUMN} |
| 126 | + */ |
| 127 | + String insertedRowIdExpression(); |
| 128 | + |
109 | 129 | // --- Standard-SQL statements (override only where the DBMS's dialect differs) --- |
110 | 130 |
|
111 | 131 | /** |
@@ -139,8 +159,9 @@ default String dropRowIdColumnStatement(T table) { |
139 | 159 | * |
140 | 160 | * <p> |
141 | 161 | * This single value-level snapshot is the comparison surface for all DML statements: a DELETE removes rows from it, |
142 | | - * an UPDATE changes column values in it. Row identity alone (which the identifier already captures) would suffice |
143 | | - * for DELETE, but not for UPDATE, where the two runs could touch the same rows yet write different values. |
| 162 | + * an UPDATE changes column values in it, an INSERT adds rows to it. Row identity alone (which the identifier |
| 163 | + * already captures) would suffice for DELETE, but not for UPDATE, where the two runs could touch the same rows yet |
| 164 | + * write different values. |
144 | 165 | * |
145 | 166 | * @param table |
146 | 167 | * the table to snapshot |
@@ -223,8 +244,55 @@ default String updateStatement(T table, List<Map.Entry<C, E>> assignments, E pre |
223 | 244 | } |
224 | 245 |
|
225 | 246 | /** |
226 | | - * Renders the trailing {@code ORDER BY ... LIMIT n} clause shared by {@link #deleteStatement} and |
227 | | - * {@link #updateStatement}, or the empty string when {@code limit} is null. |
| 247 | + * SQL that inserts a new row into {@code table} for each source row (optionally filtered by {@code predicate}), |
| 248 | + * setting each content column to its corresponding value in {@code values}, optionally limited to the first |
| 249 | + * {@code limit} source rows (see {@link #orderByLimitClause}). |
| 250 | + * |
| 251 | + * <p> |
| 252 | + * The {@code INSERT ... SELECT} form is used rather than {@code INSERT ... VALUES} because the transformed value |
| 253 | + * expressions reference the table's columns (the transformer injects column references into its equivalent |
| 254 | + * sub-expressions), which are legal in a {@code SELECT} but not in a {@code VALUES} clause. Each inserted row's |
| 255 | + * {@link #ROW_ID_COLUMN} is derived from its source row via {@link #insertedRowIdExpression()}, giving it a |
| 256 | + * deterministic identifier that is unique and distinct from every existing one, so the two statements' post-images |
| 257 | + * align (and inserted rows never collide with their source rows). |
| 258 | + * |
| 259 | + * @param table |
| 260 | + * the table to insert into |
| 261 | + * @param values |
| 262 | + * one value expression per content column, positionally aligned with {@link AbstractTable#getColumns()}; |
| 263 | + * each is rendered via {@link #asString} |
| 264 | + * @param predicate |
| 265 | + * the WHERE predicate filtering the source rows, or {@code null} to insert from every source row; |
| 266 | + * rendered via {@link #asString} |
| 267 | + * @param orderByColumns |
| 268 | + * the columns to order the source rows by before the row-id tiebreaker (may be empty); only used when |
| 269 | + * {@code limit} is non-null |
| 270 | + * @param limit |
| 271 | + * the maximum number of source rows to insert from, or {@code null} for no limit |
| 272 | + * |
| 273 | + * @return the SQL statement |
| 274 | + */ |
| 275 | + default String insertStatement(T table, List<E> values, E predicate, List<C> orderByColumns, Integer limit) { |
| 276 | + List<String> columnNames = new ArrayList<>(); |
| 277 | + columnNames.add(ROW_ID_COLUMN); |
| 278 | + List<String> selectItems = new ArrayList<>(); |
| 279 | + selectItems.add(insertedRowIdExpression()); |
| 280 | + List<C> columns = table.getColumns(); |
| 281 | + for (int i = 0; i < columns.size(); i++) { |
| 282 | + columnNames.add(columns.get(i).getName()); |
| 283 | + selectItems.add(asString(values.get(i))); |
| 284 | + } |
| 285 | + String statement = "INSERT INTO " + table.getName() + " (" + String.join(", ", columnNames) + ") SELECT " |
| 286 | + + String.join(", ", selectItems) + " FROM " + table.getName(); |
| 287 | + if (predicate != null) { |
| 288 | + statement += " WHERE " + asString(predicate); |
| 289 | + } |
| 290 | + return statement + orderByLimitClause(orderByColumns, limit); |
| 291 | + } |
| 292 | + |
| 293 | + /** |
| 294 | + * Renders the trailing {@code ORDER BY ... LIMIT n} clause shared by {@link #deleteStatement}, |
| 295 | + * {@link #updateStatement} and {@link #insertStatement}, or the empty string when {@code limit} is null. |
228 | 296 | * |
229 | 297 | * <p> |
230 | 298 | * The rows are ordered by {@code orderByColumns} followed by {@link #ROW_ID_COLUMN} as a tiebreaker. Because the |
|
0 commit comments