also looking at this
fix(client): updateMany runs with the caller's predicate, or not at all
#1119
2 files
+199
-11
| @@ -8079,23 +8079,54 @@ export function createQueryBuilder<DB extends AnyDatabaseSchema>(state?: Partial | ||
| 8079 | 8079 | |
| 8080 | 8080 | let sql = `UPDATE ${table} SET ${setClauses.join(',')}` |
| 8081 | 8081 | |
| 8082 | // Build WHERE clause | |
| 8082 | // Build WHERE clause. | |
| 8083 | // | |
| 8084 | // Every branch below either appends a predicate or throws. `conditions` | |
| 8085 | // is a required parameter, so there is no shape of it that means "every | |
| 8086 | // row" — passing one is the caller saying they have a filter. Anything | |
| 8087 | // this cannot turn into a predicate used to fall straight through to an | |
| 8088 | // UPDATE with no WHERE, which rewrote the whole table and reported the | |
| 8089 | // full count as success. See #1112, and #1101 for the same defect in | |
| 8090 | // updateTable(). | |
| 8083 | 8091 | if (Array.isArray(conditions)) { |
| 8084 | sql += ` WHERE ${conditions[0]}${conditions[1]}${getPlaceholder(params.length + 1)}` | |
| 8092 | // Separated by spaces: the three parts used to be concatenated bare, | |
| 8093 | // which is fine for `=` and `>=` and produces `namelike?` for every | |
| 8094 | // word operator the allowed set contains. | |
| 8095 | const safeOperator = assertSafeWhereOperator(String(conditions[1]), 'updateMany') | |
| 8096 | sql += ` WHERE ${conditions[0]} ${safeOperator} ${getPlaceholder(params.length + 1)}` | |
| 8085 | 8097 | params.push(conditions[2]) |
| 8086 | 8098 | } |
| 8087 | else if (conditions && typeof conditions === 'object' && !('raw' in conditions)) { | |
| 8099 | // A fragment is inside the declared `WhereExpression` type (through | |
| 8100 | // `WhereRaw`) and `raw` is a public export, so `updateMany(t, raw('id = | |
| 8101 | // 1'), data)` typechecks and reads like the intended way to express a | |
| 8102 | // condition this signature cannot otherwise carry. It matched no branch. | |
| 8103 | else if (isRawExpression(conditions)) { | |
| 8104 | sql += ` WHERE ${conditions.raw}` | |
| 8105 | } | |
| 8106 | else if (isBoundSqlExpression(conditions)) { | |
| 8107 | const rendered = renderBoundSqlExpression(conditions, params.length + 1) | |
| 8108 | sql += ` WHERE ${rendered.text}` | |
| 8109 | params.push(...rendered.parameters) | |
| 8110 | } | |
| 8111 | else if (conditions && typeof conditions === 'object' && Object.keys(conditions).length > 0) { | |
| 8088 | 8112 | const condKeys = Object.keys(conditions) |
| 8089 | 8113 | const condLen = condKeys.length |
| 8090 | if (condLen > 0) { | |
| 8091 | const baseIdx = params.length | |
| 8092 | const whereClauses: string[] = Array.from({ length: condLen }) | |
| 8093 | for (let i = 0; i < condLen; i++) { | |
| 8094 | whereClauses[i] = `${condKeys[i]}=${getPlaceholder(baseIdx + i + 1)}` | |
| 8095 | params.push((conditions as any)[condKeys[i]]) | |
| 8096 | } | |
| 8097 | sql += ` WHERE ${whereClauses.join(' AND ')}` | |
| 8114 | const baseIdx = params.length | |
| 8115 | const whereClauses: string[] = Array.from({ length: condLen }) | |
| 8116 | for (let i = 0; i < condLen; i++) { | |
| 8117 | whereClauses[i] = `${condKeys[i]}=${getPlaceholder(baseIdx + i + 1)}` | |
| 8118 | params.push((conditions as any)[condKeys[i]]) | |
| 8098 | 8119 | } |
| 8120 | sql += ` WHERE ${whereClauses.join(' AND ')}` | |
| 8121 | } | |
| 8122 | // The empty object is the one that bites in production: `conditions` is | |
| 8123 | // usually built from request input, and `updateMany(t, buildFilter(req.query), data)` | |
| 8124 | // rewrote every row the moment the filter came back empty. | |
| 8125 | else if (conditions && typeof conditions === 'object') { | |
| 8126 | throw new TypeError('[query-builder] updateMany(): an empty object is not a filter. Pass a condition, or use updateTable(table).set(data).execute() if updating every row is intended.') | |
| 8127 | } | |
| 8128 | else { | |
| 8129 | throw new TypeError(`[query-builder] updateMany(): expected a condition, got ${conditions === null ? 'null' : typeof conditions}. Refusing to run an UPDATE with no WHERE — use updateTable(table).set(data).execute() if updating every row is intended.`) | |
| 8099 | 8130 | } |
| 8100 | 8131 | |
| 8101 | 8132 | return _sql.unsafe(sql, params).execute() |