also looking at this
fix(client)!: bind db.raw and db.sql fragment values or refuse them
#1152
16 files
+455
-118
| @@ -48,6 +48,8 @@ const recentOrders = await db | ||
| 48 | 48 | ## Selecting Data |
| 49 | 49 | |
| 50 | 50 | ```ts |
| 51 | import { raw } from 'bun-query-builder' | |
| 52 | ||
| 51 | 53 | // Select all columns from projects |
| 52 | 54 | const allProjects = await db |
| 53 | 55 | .selectFrom('projects') |
| @@ -64,7 +66,7 @@ const userInfo = await db | ||
| 64 | 66 | const userStats = await db |
| 65 | 67 | .selectFrom('users') |
| 66 | 68 | .select('users', 'id', 'name') |
| 67 | .selectRaw(db.sql`COUNT(posts.id) as post_count`) | |
| 69 | .selectRaw(raw`COUNT(posts.id) as post_count`) | |
| 68 | 70 | .leftJoin('posts', 'posts.user_id', '=', 'users.id') |
| 69 | 71 | .groupBy('users.id', 'users.name') |
| 70 | 72 | .execute() |
| @@ -74,7 +76,7 @@ const userProfiles = await db | ||
| 74 | 76 | .selectFrom('users') |
| 75 | 77 | .innerJoin('profiles', 'profiles.user_id', '=', 'users.id') |
| 76 | 78 | .select('users', 'id', 'name') |
| 77 | .selectRaw(db.sql`profiles.bio, profiles.avatar_url`) | |
| 79 | .selectRaw(raw`profiles.bio, profiles.avatar_url`) | |
| 78 | 80 | .where({ 'users.active': true }) |
| 79 | 81 | .execute() |
| 80 | 82 | ``` |
| @@ -82,7 +84,7 @@ const userProfiles = await db | ||
| 82 | 84 | ### Aliases and Expressions |
| 83 | 85 | |
| 84 | 86 | - Use `"column as alias"` syntax in `select(table, ...)` for simple aliases |
| 85 | - Use `selectRaw(db.sql\`...\`)` for complex expressions, functions, and computed columns | |
| 87 | - Use ``selectRaw(raw`...`)`` (the `raw` helper exported by `bun-query-builder`) for complex expressions, functions, and computed columns | |
| 86 | 88 | - Combine `select()` and `selectRaw()` calls to build comprehensive column lists |
| 87 | 89 | - Always prefix column names with table names when joining to avoid ambiguity |
| 88 | 90 | |
| @@ -375,9 +377,9 @@ Use raw sparingly for complex cases not covered by helpers. | ||
| 375 | 377 | ```ts |
| 376 | 378 | await db |
| 377 | 379 | .selectFrom('users') |
| 378 | .whereRaw(db.sql`coalesce(age, 0) > 0`) | |
| 379 | .groupByRaw(db.sql`country`) | |
| 380 | .havingRaw(db.sql`count(_) > 10`) | |
| 380 | .whereRaw(raw`coalesce(age, 0) > 0`) | |
| 381 | .groupByRaw(raw`country`) | |
| 382 | .havingRaw(raw`count(*) > 10`) | |
| 381 | 383 | .execute() |
| 382 | 384 | ``` |
| 383 | 385 | |
| @@ -684,7 +686,7 @@ await db | ||
| 684 | 686 | // Update with complex conditions |
| 685 | 687 | await db |
| 686 | 688 | .updateTable('users') |
| 687 | .set({ last_login: new Date(), login_count: db.sql`login_count + 1` }) | |
| 689 | .set({ last_login: new Date(), login_count: db.raw('login_count + 1') }) | |
| 688 | 690 | .where({ email: 'chris@example.com' }) |
| 689 | 691 | .andWhere(['active', '=', true]) |
| 690 | 692 | .execute() |
| @@ -869,7 +871,7 @@ const users = await db | ||
| 869 | 871 | // Avoid: Excessive raw SQL |
| 870 | 872 | const users = await db |
| 871 | 873 | .selectFrom('users') |
| 872 | .whereRaw(db.sql`active = true ${includeAdmin ? 'OR role = "admin"' : ''}`) | |
| 874 | .whereRaw(raw(`active = true${includeAdmin ? " OR role = 'admin'" : ''}`)) | |
| 873 | 875 | .execute() |
| 874 | 876 | ``` |
| 875 | 877 | |
| @@ -917,7 +919,7 @@ const user = await db | ||
| 917 | 919 | // Dangerous: String interpolation |
| 918 | 920 | const user = await db |
| 919 | 921 | .selectFrom('users') |
| 920 | .whereRaw(db.sql`email = '${userInput.email}'`) // DON'T DO THIS | |
| 922 | .whereRaw(raw(`email = '${userInput.email}'`)) // DON'T DO THIS: the string is built before raw() sees it | |
| 921 | 923 | ``` |
| 922 | 924 | |
| 923 | 925 | ### Error Handling and Monitoring |
renamed throughout · 16 hidden lines
| @@ -1071,14 +1073,14 @@ const searchQuery = 'TypeScript' renamed throughout | ||
| 1071 | 1073 | const articleResults = db |
| 1072 | 1074 | .selectFrom('articles') |
| 1073 | 1075 | .select('articles', 'id', 'title', 'created_at') |
| 1074 | .selectRaw(db.sql`'article' as content_type`) | |
| 1076 | .selectRaw(raw`'article' as content_type`) | |
| 1075 | 1077 | .where(['title', 'like', `%${searchQuery}%`]) |
| 1076 | 1078 | .orWhere(['content', 'like', `%${searchQuery}%`]) |
| 1077 | 1079 | |
| 1078 | 1080 | const projectResults = db |
| 1079 | 1081 | .selectFrom('projects') |
| 1080 | 1082 | .select('projects', 'id', 'name as title', 'created_at') |
| 1081 | .selectRaw(db.sql`'project' as content_type`) | |
| 1083 | .selectRaw(raw`'project' as content_type`) | |
| 1082 | 1084 | .where(['name', 'like', `%${searchQuery}%`]) |
| 1083 | 1085 | .orWhere(['description', 'like', `%${searchQuery}%`]) |
| 1084 | 1086 | |
| @@ -1099,7 +1101,7 @@ const userStats = await db | ||
| 1099 | 1101 | .withCount('posts', 'total_posts') |
| 1100 | 1102 | .withCount('posts', 'published_posts', ['published', '=', true]) |
| 1101 | 1103 | .withCount('comments', 'total_comments') |
| 1102 | .selectRaw(db.sql` | |
| 1104 | .selectRaw(raw` | |
| 1103 | 1105 | CASE |
| 1104 | 1106 | WHEN COUNT(posts.id) > 10 THEN 'high' |
| 1105 | 1107 | WHEN COUNT(posts.id) > 5 THEN 'medium' |
| @@ -1114,13 +1116,13 @@ const userStats = await db | ||
| 1114 | 1116 | // Monthly user registration trends |
| 1115 | 1117 | const registrationTrends = await db |
| 1116 | 1118 | .selectFrom('users') |
| 1117 | .selectRaw(db.sql` | |
| 1119 | .selectRaw(raw` | |
| 1118 | 1120 | DATE_TRUNC('month', created_at) as month, |
| 1119 | COUNT(_) as new_users, | |
| 1121 | COUNT(*) as new_users, | |
| 1120 | 1122 | COUNT(CASE WHEN role = 'admin' THEN 1 END) as new_admins |
| 1121 | 1123 | `) |
| 1122 | 1124 | .where(['created_at', '>=', new Date('2024-01-01')]) |
| 1123 | .groupBy(db.sql`DATE_TRUNC('month', created_at)`) | |
| 1125 | .groupByRaw(raw`DATE_TRUNC('month', created_at)`) | |
| 1124 | 1126 | .orderBy('month', 'desc') |
| 1125 | 1127 | .execute() |
| 1126 | 1128 | ``` |
| @@ -1144,7 +1146,7 @@ const chrisTeamWithStats = await db | ||
| 1144 | 1146 | const projectsWithCollaborators = await db |
| 1145 | 1147 | .selectFrom('projects') |
| 1146 | 1148 | .select('projects', 'id', 'name', 'description') |
| 1147 | .selectRaw(db.sql` | |
| 1149 | .selectRaw(raw` | |
| 1148 | 1150 | STRING_AGG(users.name, ', ') as collaborators, |
| 1149 | 1151 | COUNT(DISTINCT project_users.user_id) as collaborator_count |
| 1150 | 1152 | `) |
| @@ -1226,7 +1228,7 @@ Use `whereColumn(left, op, right)`. | ||
| 1226 | 1228 | |
| 1227 | 1229 | ### How do I add arbitrary fragments |
| 1228 | 1230 | |
| 1229 | Use `selectRaw`, `whereRaw`, `groupByRaw`, or `havingRaw` and pass a Bun `sql` fragment. | |
| 1231 | Use `selectRaw`, `whereRaw`, `orderByRaw`, `groupByRaw`, or `havingRaw` and pass one fragment built with the exported `raw` helper: ``raw`count(*) as c` `` or `raw('age > 18')`. These methods take no bindings, and values interpolated into ``raw`...` `` are inlined as escaped literals. To bind values, pass `db.raw('name = ?', [value])` or `{ sql, parameters }` to `where()` or `set()`. A Bun ``db.sql`...` `` query is not a fragment: it throws on Postgres and MySQL. | |
| 1230 | 1232 | |
| 1231 | 1233 | --- |
| 1232 | 1234 | |