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
fix(table): drop sql.raw quote-escaping in column-name interpolation
Six call sites in lib/table/service.ts built JSON-key string literals
at runtime via `sql.raw(\`'\${name.replace(/'/g, "''")}'\`)` for use
with PostgreSQL's `data->'key'` / `data->>'key'` operators. Practically
safe (NAME_PATTERN gates column names to alphanumeric+underscore at
insert time) but a smelly pattern that breaks the moment validation
loosens.
Both `data->` and `data->>` accept a parameterized text value as the
key, so the `sql.raw` is unnecessary. Replace each with a normal
`${name}::text` binding. No behavior change; eliminates the manual
quote-escaping surface.
Affected sites: renameColumn (the data-rewrite UPDATE), upsertRow's
match filter, updateColumnType's IS-NOT-NULL gate, updateColumnConstraints'
required-check + unique-duplicate-check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
// All bindings parameterized — `data->` accepts a text parameter for the
2295
+
// key, no need to drop into `sql.raw` with hand-rolled quote escaping.
2298
2296
awaittrx.execute(
2299
-
sql`UPDATE user_table_rows SET data = data - ${actualOldName}::text || jsonb_build_object(${data.newName}::text, data->${sql.raw(`'${actualOldName.replace(/'/g,"''")}'`)}) WHERE table_id = ${data.tableId} AND data ? ${actualOldName}::text`
2297
+
sql`UPDATE user_table_rows SET data = data - ${actualOldName}::text || jsonb_build_object(${data.newName}::text, data->${actualOldName}::text) WHERE table_id = ${data.tableId} AND data ? ${actualOldName}::text`
2300
2298
)
2301
2299
})
2302
2300
@@ -2566,8 +2564,6 @@ export async function updateColumnType(
2566
2564
returntable
2567
2565
}
2568
2566
2569
-
constescapedName=column.name.replace(/'/g,"''")
2570
-
2571
2567
// Validate existing data is compatible with the new type
@@ -2576,7 +2572,7 @@ export async function updateColumnType(
2576
2572
and(
2577
2573
eq(userTableRows.tableId,data.tableId),
2578
2574
sql`${userTableRows.data} ? ${column.name}`,
2579
-
sql`${userTableRows.data}->>${sql.raw(`'${escapedName}'`)} IS NOT NULL`
2575
+
sql`${userTableRows.data}->>${column.name}::text IS NOT NULL`
2580
2576
)
2581
2577
)
2582
2578
@@ -2646,16 +2642,14 @@ export async function updateColumnConstraints(
2646
2642
`Cannot change constraints on workflow-output column "${column.name}". Constraints aren't applicable to columns whose values come from workflow execution.`
2647
2643
)
2648
2644
}
2649
-
constescapedName=column.name.replace(/'/g,"''")
2650
-
2651
2645
if(data.required===true&&!column.required){
2652
2646
const[result]=awaitdb
2653
2647
.select({count: count()})
2654
2648
.from(userTableRows)
2655
2649
.where(
2656
2650
and(
2657
2651
eq(userTableRows.tableId,data.tableId),
2658
-
sql`(NOT (${userTableRows.data} ? ${column.name}) OR ${userTableRows.data}->>${sql.raw(`'${escapedName}'`)} IS NULL)`
2652
+
sql`(NOT (${userTableRows.data} ? ${column.name}) OR ${userTableRows.data}->>${column.name}::text IS NULL)`
2659
2653
)
2660
2654
)
2661
2655
@@ -2668,7 +2662,7 @@ export async function updateColumnConstraints(
2668
2662
2669
2663
if(data.unique===true&&!column.unique){
2670
2664
constduplicates=(awaitdb.execute(
2671
-
sql`SELECT ${userTableRows.data}->>${sql.raw(`'${escapedName}'`)} AS val, count(*) AS cnt FROM ${userTableRows} WHERE table_id = ${data.tableId} AND ${userTableRows.data} ? ${column.name} AND ${userTableRows.data}->>${sql.raw(`'${escapedName}'`)} IS NOT NULL GROUP BY val HAVING count(*) > 1 LIMIT 1`
2665
+
sql`SELECT ${userTableRows.data}->>${column.name}::text AS val, count(*) AS cnt FROM ${userTableRows} WHERE table_id = ${data.tableId} AND ${userTableRows.data} ? ${column.name} AND ${userTableRows.data}->>${column.name}::text IS NOT NULL GROUP BY val HAVING count(*) > 1 LIMIT 1`
0 commit comments