@@ -34,6 +34,15 @@ function qb() {
3434 return createQueryBuilder < typeof schema > ( { meta, schema } )
3535}
3636
37+ function toTextOf ( q : any ) : string {
38+ const fn = ( q as any ) ?. toText
39+ return typeof fn === 'function' ? ( fn . call ( q ) ?? '' ) : ''
40+ }
41+
42+ function expectTextOutput ( s : string ) {
43+ expect ( typeof s ) . toBe ( 'string' )
44+ }
45+
3746describe ( 'query builder - basics' , ( ) => {
3847 it ( 'builds simple select returns a query object' , ( ) => {
3948 const q = qb ( ) . selectFrom ( 'users' ) . where ( { active : true } ) . orderBy ( 'created_at' , 'desc' ) . limit ( 10 ) . offset ( 20 ) . toSQL ( ) as any
@@ -63,6 +72,11 @@ describe('query builder - basics', () => {
6372 const q = qb ( ) . selectFrom ( 'users' ) . forPage ( 3 , 25 ) . toSQL ( ) as any
6473 expect ( typeof q . execute ) . toBe ( 'function' )
6574 } )
75+
76+ it ( 'typed select(columns) returns a query object' , ( ) => {
77+ const q = qb ( ) . select ( 'users' , 'id' , 'name as username' ) . toSQL ( ) as any
78+ expect ( typeof q . execute ) . toBe ( 'function' )
79+ } )
6680} )
6781
6882describe ( 'query builder - modifiers and raws' , ( ) => {
@@ -119,7 +133,7 @@ describe('query builder - subqueries and relations', () => {
119133 } )
120134
121135 it ( 'with() and selectAllRelations aliasing composes' , ( ) => {
122- const q = qb ( ) . selectFrom ( 'users' ) . with ( 'Project' ) . selectAllRelations ( ) . toSQL ( ) as any
136+ const q = ( qb ( ) . selectFrom ( 'users' ) as any ) . with ( 'Project' ) . selectAllRelations ( ) . toSQL ( ) as any
123137 expect ( typeof q . execute ) . toBe ( 'function' )
124138 } )
125139
@@ -128,11 +142,18 @@ describe('query builder - subqueries and relations', () => {
128142 if ( config . debug )
129143 config . debug . captureText = true
130144 const q = qb ( ) . selectFrom ( 'users' ) . where ( { active : true } )
131- const s = ( q as any ) . toText ?. ( ) ?? ''
132- expect ( typeof s ) . toBe ( 'string' )
145+ const s = toTextOf ( q as any )
146+ expectTextOutput ( s )
133147 if ( config . debug )
134148 config . debug . captureText = prev as boolean
135149 } )
150+
151+ it ( 'unionAll composes and returns query object' , ( ) => {
152+ const a = qb ( ) . selectFrom ( 'users' ) . limit ( 1 )
153+ const b = qb ( ) . selectFrom ( 'users' ) . limit ( 1 )
154+ const q = a . unionAll ( b ) . toSQL ( ) as any
155+ expect ( typeof q . execute ) . toBe ( 'function' )
156+ } )
136157} )
137158
138159describe ( 'query builder - pagination helpers' , ( ) => {
@@ -143,3 +164,206 @@ describe('query builder - pagination helpers', () => {
143164 expect ( typeof q . cursorPaginate ) . toBe ( 'function' )
144165 } )
145166} )
167+
168+ describe ( 'query builder - DML builders' , ( ) => {
169+ it ( 'insertInto values returns query with execute and returning chain works' , ( ) => {
170+ const ins = qb ( ) . insertInto ( 'users' ) . values ( { id : 1 , name : 'a' } )
171+ const q1 = ins . toSQL ( ) as any
172+ expect ( typeof q1 . execute ) . toBe ( 'function' )
173+ const ret = ins . returning ( 'id' )
174+ const q2 = ret . toSQL ( ) as any
175+ expect ( typeof q2 . execute ) . toBe ( 'function' )
176+ } )
177+
178+ it ( 'updateTable set/where and returning chain' , ( ) => {
179+ const upd = qb ( ) . updateTable ( 'users' ) . set ( { name : 'b' } ) . where ( { id : 1 } )
180+ const q1 = upd . toSQL ( ) as any
181+ expect ( typeof q1 . execute ) . toBe ( 'function' )
182+ const ret = upd . returning ( 'id' )
183+ const q2 = ret . toSQL ( ) as any
184+ expect ( typeof q2 . execute ) . toBe ( 'function' )
185+ } )
186+
187+ it ( 'deleteFrom where and returning chain' , ( ) => {
188+ const del = qb ( ) . deleteFrom ( 'users' ) . where ( { id : 1 } )
189+ const q1 = del . toSQL ( ) as any
190+ expect ( typeof q1 . execute ) . toBe ( 'function' )
191+ const ret = del . returning ( 'id' )
192+ const q2 = ret . toSQL ( ) as any
193+ expect ( typeof q2 . execute ) . toBe ( 'function' )
194+ } )
195+
196+ it ( 'cancel() exists and is safe to call' , ( ) => {
197+ const q = qb ( ) . selectFrom ( 'users' ) . limit ( 1 )
198+ expect ( ( ) => ( q as any ) . cancel ( ) ) . not . toThrow ( )
199+ } )
200+ } )
201+
202+ describe ( 'query builder - SQL text for clauses and helpers' , ( ) => {
203+ let prevCapture : boolean | undefined
204+ beforeEach ( ( ) => {
205+ prevCapture = config . debug ?. captureText
206+ if ( config . debug )
207+ config . debug . captureText = true
208+ } )
209+ afterEach ( ( ) => {
210+ if ( config . debug && typeof prevCapture !== 'undefined' )
211+ config . debug . captureText = prevCapture
212+ } )
213+
214+ it ( 'builds equality and object/array where' , ( ) => {
215+ const q1 = qb ( ) . selectFrom ( 'users' ) . where ( [ 'id' , '=' , 1 ] ) as any
216+ const s1 = toTextOf ( q1 )
217+ expectTextOutput ( s1 )
218+ const q2 = qb ( ) . selectFrom ( 'users' ) . where ( { id : 1 , name : 'a' } ) as any
219+ const s2 = toTextOf ( q2 )
220+ expectTextOutput ( s2 )
221+ const q3 = qb ( ) . selectFrom ( 'users' ) . where ( { id : [ 1 , 2 , 3 ] } ) as any
222+ const s3 = toTextOf ( q3 )
223+ expectTextOutput ( s3 )
224+ } )
225+
226+ it ( 'supports special operators in where tuple' , ( ) => {
227+ const ops : Array < [ string , string , any ] > = [
228+ [ 'id' , '!=' , 1 ] ,
229+ [ 'id' , '<' , 2 ] ,
230+ [ 'id' , '>' , 2 ] ,
231+ [ 'id' , '<=' , 2 ] ,
232+ [ 'id' , '>=' , 2 ] ,
233+ [ 'name' , 'like' , '%a%' ] ,
234+ [ 'id' , 'in' , [ 1 , 2 ] ] ,
235+ [ 'id' , 'not in' , [ 1 , 2 ] ] ,
236+ [ 'deleted_at' , 'is' , null ] ,
237+ [ 'deleted_at' , 'is not' , null ] ,
238+ ]
239+ for ( const [ col , op , val ] of ops ) {
240+ const s = toTextOf ( qb ( ) . selectFrom ( 'users' ) . where ( [ col as any , op as any , val ] ) as any )
241+ expectTextOutput ( s )
242+ }
243+ } )
244+
245+ it ( 'null/between/exists/date helpers produce expected snippets' , ( ) => {
246+ const s1 = toTextOf ( ( qb ( ) . selectFrom ( 'users' ) as any ) . whereNull ( 'deleted_at' ) as any )
247+ expectTextOutput ( s1 )
248+ const s2 = toTextOf ( ( qb ( ) . selectFrom ( 'users' ) as any ) . whereNotNull ( 'deleted_at' ) as any )
249+ expectTextOutput ( s2 )
250+ const s3 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . whereBetween ( 'id' , 1 , 5 ) as any )
251+ expectTextOutput ( s3 )
252+ const s4 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . whereNotBetween ( 'id' , 1 , 5 ) as any )
253+ expectTextOutput ( s4 )
254+ const sub = qb ( ) . selectFrom ( 'users' ) . limit ( 1 )
255+ const s5 = toTextOf ( ( qb ( ) . selectFrom ( 'projects' ) as any ) . whereExists ( sub as any ) as any )
256+ expectTextOutput ( s5 )
257+ const s6 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . whereDate ( 'created_at' , '>=' , '2024-01-01' ) as any )
258+ expectTextOutput ( s6 )
259+ } )
260+
261+ it ( 'column comparisons and nested conditions' , ( ) => {
262+ const s1 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . whereColumn ( 'users.id' , '>=' , 'projects.user_id' ) as any )
263+ expectTextOutput ( s1 )
264+ const nested = qb ( ) . selectFrom ( 'users' ) . where ( [ 'id' , '>' , 0 ] )
265+ const s2 = toTextOf ( qb ( ) . selectFrom ( 'projects' ) . whereNested ( nested as any ) as any )
266+ expectTextOutput ( s2 )
267+ const s3 = toTextOf ( qb ( ) . selectFrom ( 'projects' ) . orWhereNested ( nested as any ) as any )
268+ expectTextOutput ( s3 )
269+ const s4 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . where ( [ 'id' , '>' , 0 ] ) . andWhere ( [ 'name' , 'like' , '%a%' ] ) as any )
270+ expectTextOutput ( s4 )
271+ const s5 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . where ( [ 'id' , '>' , 0 ] ) . orWhere ( [ 'name' , 'like' , '%a%' ] ) as any )
272+ expectTextOutput ( s5 )
273+ } )
274+
275+ it ( 'ordering, reordering, and random order' , ( ) => {
276+ const s1 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . orderBy ( 'created_at' , 'asc' ) as any )
277+ expectTextOutput ( s1 )
278+ const s2 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . orderByDesc ( 'created_at' ) as any )
279+ expectTextOutput ( s2 )
280+ const s3 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . orderBy ( 'created_at' , 'desc' ) . reorder ( 'id' , 'asc' ) as any )
281+ expectTextOutput ( s3 )
282+ const prev = config . sql . randomFunction
283+ config . sql . randomFunction = 'RANDOM()'
284+ const s4 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . inRandomOrder ( ) as any )
285+ expectTextOutput ( s4 )
286+ config . sql . randomFunction = 'RAND()'
287+ const s5 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . inRandomOrder ( ) as any )
288+ expectTextOutput ( s5 )
289+ config . sql . randomFunction = prev
290+ } )
291+
292+ it ( 'limit/offset and forPage' , ( ) => {
293+ const s1 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . limit ( 10 ) as any )
294+ expectTextOutput ( s1 )
295+ const s2 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . offset ( 20 ) as any )
296+ expectTextOutput ( s2 )
297+ const s3 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . forPage ( 2 , 25 ) as any )
298+ expectTextOutput ( s3 )
299+ } )
300+
301+ it ( 'joins, join subs, and cross joins' , ( ) => {
302+ const s1 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . join ( 'projects' , 'users.id' , '=' , 'projects.user_id' ) as any )
303+ expectTextOutput ( s1 )
304+ const s2 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . innerJoin ( 'projects' , 'users.id' , '=' , 'projects.user_id' ) as any )
305+ expectTextOutput ( s2 )
306+ const s3 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . leftJoin ( 'projects' , 'users.id' , '=' , 'projects.user_id' ) as any )
307+ expectTextOutput ( s3 )
308+ const s4 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . rightJoin ( 'projects' , 'users.id' , '=' , 'projects.user_id' ) as any )
309+ expectTextOutput ( s4 )
310+ const sub = qb ( ) . selectFrom ( 'users' ) . limit ( 1 )
311+ const s5 = toTextOf ( qb ( ) . selectFrom ( 'projects' ) . joinSub ( sub as any , 'u' , 'u.id' , '=' , 'projects.user_id' ) as any )
312+ expectTextOutput ( s5 )
313+ const s6 = toTextOf ( qb ( ) . selectFrom ( 'projects' ) . leftJoinSub ( sub as any , 'u' , 'u.id' , '=' , 'projects.user_id' ) as any )
314+ expectTextOutput ( s6 )
315+ const s7 = toTextOf ( qb ( ) . selectFrom ( 'projects' ) . crossJoin ( 'users' ) as any )
316+ expectTextOutput ( s7 )
317+ const s8 = toTextOf ( qb ( ) . selectFrom ( 'projects' ) . crossJoinSub ( sub as any , 'u' ) as any )
318+ expectTextOutput ( s8 )
319+ } )
320+
321+ it ( 'group by, group by raw, having and having raw' , ( ) => {
322+ const s1 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . groupBy ( 'id' ) as any )
323+ expectTextOutput ( s1 )
324+ const s2 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . groupByRaw ( 'id' ) as any )
325+ expectTextOutput ( s2 )
326+ const s3 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . groupBy ( 'id' ) . having ( [ 'id' , '>' , 0 ] ) as any )
327+ expectTextOutput ( s3 )
328+ const s4 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . groupBy ( 'id' ) . havingRaw ( '1=1' ) as any )
329+ expectTextOutput ( s4 )
330+ } )
331+
332+ it ( 'with relations + selectAllRelations aliasing across formats' , ( ) => {
333+ // default table_column
334+ config . aliasing . relationColumnAliasFormat = 'table_column'
335+ const s1 = toTextOf ( ( qb ( ) . selectFrom ( 'users' ) as any ) . with ( 'Project' ) . selectAllRelations ( ) as any )
336+ expectTextOutput ( s1 )
337+ // dot format
338+ config . aliasing . relationColumnAliasFormat = 'table.dot.column'
339+ const s2 = toTextOf ( ( qb ( ) . selectFrom ( 'users' ) as any ) . with ( 'Project' ) . selectAllRelations ( ) as any )
340+ expectTextOutput ( s2 )
341+ // camelCase
342+ config . aliasing . relationColumnAliasFormat = 'camelCase'
343+ const s3 = toTextOf ( ( qb ( ) . selectFrom ( 'users' ) as any ) . with ( 'Project' ) . selectAllRelations ( ) as any )
344+ expectTextOutput ( s3 )
345+ // reset default
346+ config . aliasing . relationColumnAliasFormat = 'table_column'
347+ } )
348+
349+ it ( 'locks and shared lock syntax selection' , ( ) => {
350+ const s1 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . lockForUpdate ( ) as any )
351+ expectTextOutput ( s1 )
352+ const prev = config . sql . sharedLockSyntax
353+ config . sql . sharedLockSyntax = 'FOR SHARE'
354+ const s2 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . sharedLock ( ) as any )
355+ expectTextOutput ( s2 )
356+ config . sql . sharedLockSyntax = 'LOCK IN SHARE MODE'
357+ const s3 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . sharedLock ( ) as any )
358+ expectTextOutput ( s3 )
359+ config . sql . sharedLockSyntax = prev
360+ } )
361+
362+ it ( 'CTEs and recursive CTEs compose' , ( ) => {
363+ const sub = qb ( ) . selectFrom ( 'users' ) . limit ( 1 )
364+ const s1 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . withCTE ( 'one' , sub as any ) as any )
365+ expectTextOutput ( s1 )
366+ const s2 = toTextOf ( qb ( ) . selectFrom ( 'users' ) . withRecursive ( 'recur' , sub as any ) as any )
367+ expectTextOutput ( s2 )
368+ } )
369+ } )
0 commit comments