torque-postgresql 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -21,7 +21,7 @@ RSpec.describe 'AuxiliaryStatement' do
21
21
  end
22
22
 
23
23
  result = 'WITH "comments" AS'
24
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
24
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."user_id" FROM "comments")'
25
25
  result << ' SELECT "users".*, "comments"."comment_content" FROM "users"'
26
26
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
27
27
  expect(subject.with(:comments).arel.to_sql).to eql(result)
@@ -34,7 +34,7 @@ RSpec.describe 'AuxiliaryStatement' do
34
34
  end
35
35
 
36
36
  result = 'WITH "comments" AS (SELECT DISTINCT ON ( "comments"."user_id" )'
37
- result << ' "comments"."user_id", "comments"."content" AS last_comment'
37
+ result << ' "comments"."content" AS last_comment, "comments"."user_id"'
38
38
  result << ' FROM "comments" ORDER BY "comments"."user_id" ASC,'
39
39
  result << ' "comments"."id" DESC) SELECT "users".*,'
40
40
  result << ' "comments"."last_comment" FROM "users" INNER JOIN "comments"'
@@ -49,7 +49,7 @@ RSpec.describe 'AuxiliaryStatement' do
49
49
  end
50
50
 
51
51
  result = 'WITH "comments" AS'
52
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content, "comments"."slug" AS comment_slug FROM "comments")'
52
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."slug" AS comment_slug, "comments"."user_id" FROM "comments")'
53
53
  result << ' SELECT "users".*, "comments"."comment_content", "comments"."comment_slug" FROM "users"'
54
54
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
55
55
  expect(subject.with(:comments, select: {slug: :comment_slug}).arel.to_sql).to eql(result)
@@ -62,7 +62,7 @@ RSpec.describe 'AuxiliaryStatement' do
62
62
  end
63
63
 
64
64
  result = 'WITH "comments" AS'
65
- result << ' (SELECT "comments"."user_id", "comments"."active", "comments"."content" AS comment_content FROM "comments")'
65
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."user_id", "comments"."active" FROM "comments")'
66
66
  result << ' SELECT "users".*, "comments"."comment_content" FROM "users"'
67
67
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id" AND "comments"."active" = "users"."active"'
68
68
  expect(subject.with(:comments, join: {active: :active}).arel.to_sql).to eql(result)
@@ -75,7 +75,7 @@ RSpec.describe 'AuxiliaryStatement' do
75
75
  end
76
76
 
77
77
  result = 'WITH "comments" AS'
78
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content'
78
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."user_id"'
79
79
  result << ' FROM "comments" WHERE "comments"."active" = $1)'
80
80
  result << ' SELECT "users".*, "comments"."comment_content" FROM "users"'
81
81
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
@@ -91,7 +91,7 @@ RSpec.describe 'AuxiliaryStatement' do
91
91
  query = subject.where(id: 2).with(:comments)
92
92
 
93
93
  result = 'WITH "comments" AS'
94
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments"'
94
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."user_id" FROM "comments"'
95
95
  result << ' WHERE "comments"."id" = $1)'
96
96
  result << ' SELECT "users".*, "comments"."comment_content" FROM "users"'
97
97
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
@@ -108,7 +108,7 @@ RSpec.describe 'AuxiliaryStatement' do
108
108
  end
109
109
 
110
110
  result = 'WITH "comments" AS'
111
- result << ' (SELECT "comments"."user_id", MAX(id) AS comment_id FROM "comments")'
111
+ result << ' (SELECT MAX(id) AS comment_id, "comments"."user_id" FROM "comments")'
112
112
  result << ' SELECT "users".*, "comments"."comment_id" FROM "users"'
113
113
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
114
114
  expect(subject.with(:comments).arel.to_sql).to eql(result)
@@ -121,7 +121,7 @@ RSpec.describe 'AuxiliaryStatement' do
121
121
  end
122
122
 
123
123
  result = 'WITH "comments" AS'
124
- result << ' (SELECT "comments"."user_id", ROW_NUMBER() OVER (PARTITION BY ORDER BY "comments"."id") AS comment_id FROM "comments")'
124
+ result << ' (SELECT ROW_NUMBER() OVER (PARTITION BY ORDER BY "comments"."id") AS comment_id, "comments"."user_id" FROM "comments")'
125
125
  result << ' SELECT "users".*, "comments"."comment_id" FROM "users"'
126
126
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
127
127
  expect(subject.with(:comments).arel.to_sql).to eql(result)
@@ -134,7 +134,7 @@ RSpec.describe 'AuxiliaryStatement' do
134
134
  end
135
135
 
136
136
  result = 'WITH "comments" AS'
137
- result << ' (SELECT "comments"."user_id", MIN("comments"."id") AS comment_id FROM "comments")'
137
+ result << ' (SELECT MIN("comments"."id") AS comment_id, "comments"."user_id" FROM "comments")'
138
138
  result << ' SELECT "users".*, "comments"."comment_id" FROM "users"'
139
139
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
140
140
  expect(subject.with(:comments).arel.to_sql).to eql(result)
@@ -147,8 +147,8 @@ RSpec.describe 'AuxiliaryStatement' do
147
147
  cte.join name: :id, 'a.col' => :col
148
148
  end
149
149
 
150
- result = 'WITH "comments" AS (SELECT "comments"."id", "comments"."col",'
151
- result << ' "comments"."content" AS comment_content FROM "comments") SELECT "users".*,'
150
+ result = 'WITH "comments" AS (SELECT "comments"."content" AS comment_content,'
151
+ result << ' "comments"."id", "comments"."col" FROM "comments") SELECT "users".*,'
152
152
  result << ' "comments"."comment_content" FROM "users" INNER JOIN "comments"'
153
153
  result << ' ON "comments"."id" = "users"."name" AND "comments"."col" = "a"."col"'
154
154
  expect(subject.with(:comments).arel.to_sql).to eql(result)
@@ -161,8 +161,8 @@ RSpec.describe 'AuxiliaryStatement' do
161
161
  cte.join_type :left
162
162
  end
163
163
 
164
- result = 'WITH "comments" AS (SELECT "comments"."user_id",'
165
- result << ' "comments"."content" AS comment_content FROM "comments") SELECT "users".*,'
164
+ result = 'WITH "comments" AS (SELECT "comments"."content" AS comment_content,'
165
+ result << ' "comments"."user_id" FROM "comments") SELECT "users".*,'
166
166
  result << ' "comments"."comment_content" FROM "users" LEFT OUTER JOIN "comments"'
167
167
  result << ' ON "comments"."user_id" = "users"."id"'
168
168
  expect(subject.with(:comments).arel.to_sql).to eql(result)
@@ -177,7 +177,7 @@ RSpec.describe 'AuxiliaryStatement' do
177
177
  end
178
178
 
179
179
  result = 'WITH "comments" AS'
180
- result << ' (SELECT "comments"."a_user_id", "comments"."content" AS sample_content FROM "comments")'
180
+ result << ' (SELECT "comments"."content" AS sample_content, "comments"."a_user_id" FROM "comments")'
181
181
  result << ' SELECT "users".*, "comments"."sample_content" FROM "users"'
182
182
  result << ' INNER JOIN "comments" ON "comments"."a_user_id" = "users"."id"'
183
183
  expect(subject.with(:comments).arel.to_sql).to eql(result)
@@ -198,8 +198,8 @@ RSpec.describe 'AuxiliaryStatement' do
198
198
  query = subject.where(id: 3).with(:comments2)
199
199
 
200
200
  result = 'WITH '
201
- result << '"comments1" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content1 FROM "comments" WHERE "comments"."id" = $1), '
202
- result << '"comments2" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content2 FROM "comments" WHERE "comments"."id" = $2)'
201
+ result << '"comments1" AS (SELECT "comments"."content" AS comment_content1, "comments"."user_id" FROM "comments" WHERE "comments"."id" = $1), '
202
+ result << '"comments2" AS (SELECT "comments"."content" AS comment_content2, "comments"."user_id" FROM "comments" WHERE "comments"."id" = $2)'
203
203
  result << ' SELECT "users".*, "comments1"."comment_content1", "comments2"."comment_content2" FROM "users"'
204
204
  result << ' INNER JOIN "comments1" ON "comments1"."user_id" = "users"."id"'
205
205
  result << ' INNER JOIN "comments2" ON "comments2"."user_id" = "users"."id"'
@@ -225,8 +225,8 @@ RSpec.describe 'AuxiliaryStatement' do
225
225
 
226
226
  it 'can requires another statement as dependency' do
227
227
  result = 'WITH '
228
- result << '"comments1" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content1 FROM "comments"), '
229
- result << '"comments2" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content2 FROM "comments")'
228
+ result << '"comments1" AS (SELECT "comments"."content" AS comment_content1, "comments"."user_id" FROM "comments"), '
229
+ result << '"comments2" AS (SELECT "comments"."content" AS comment_content2, "comments"."user_id" FROM "comments")'
230
230
  result << ' SELECT "users".*, "comments1"."comment_content1", "comments2"."comment_content2" FROM "users"'
231
231
  result << ' INNER JOIN "comments1" ON "comments1"."user_id" = "users"."id"'
232
232
  result << ' INNER JOIN "comments2" ON "comments2"."user_id" = "users"."id"'
@@ -235,8 +235,8 @@ RSpec.describe 'AuxiliaryStatement' do
235
235
 
236
236
  it 'can uses already already set dependent' do
237
237
  result = 'WITH '
238
- result << '"comments1" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content1 FROM "comments"), '
239
- result << '"comments2" AS (SELECT "comments"."user_id", "comments"."content" AS comment_content2 FROM "comments")'
238
+ result << '"comments1" AS (SELECT "comments"."content" AS comment_content1, "comments"."user_id" FROM "comments"), '
239
+ result << '"comments2" AS (SELECT "comments"."content" AS comment_content2, "comments"."user_id" FROM "comments")'
240
240
  result << ' SELECT "users".*, "comments1"."comment_content1", "comments2"."comment_content2" FROM "users"'
241
241
  result << ' INNER JOIN "comments1" ON "comments1"."user_id" = "users"."id"'
242
242
  result << ' INNER JOIN "comments2" ON "comments2"."user_id" = "users"."id"'
@@ -289,14 +289,14 @@ RSpec.describe 'AuxiliaryStatement' do
289
289
  expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /join columns/)
290
290
  end
291
291
 
292
- it 'raises an error when not given the table name as first argument' do
292
+ it 'not raises an error when not given the table name as first argument' do
293
293
  klass.send(:auxiliary_statement, :comments) do |cte|
294
294
  cte.query 'SELECT * FROM comments'
295
295
  cte.attributes content: :comment
296
296
  cte.join id: :user_id
297
297
  end
298
298
 
299
- expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /table name/)
299
+ expect{ subject.with(:comments).arel.to_sql }.not_to raise_error
300
300
  end
301
301
  end
302
302
 
@@ -309,7 +309,7 @@ RSpec.describe 'AuxiliaryStatement' do
309
309
  end
310
310
 
311
311
  result = 'WITH "comments" AS'
312
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment FROM "comments")'
312
+ result << ' (SELECT "comments"."content" AS comment, "comments"."user_id" FROM "comments")'
313
313
  result << ' SELECT "users".*, "comments"."comment" FROM "users"'
314
314
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
315
315
  expect(subject.with(:comments).arel.to_sql).to eql(result)
@@ -352,7 +352,7 @@ RSpec.describe 'AuxiliaryStatement' do
352
352
  query = subject.with(:comments, args: {id: 1})
353
353
 
354
354
  result = 'WITH "comments" AS'
355
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment'
355
+ result << ' (SELECT "comments"."content" AS comment, "comments"."user_id"'
356
356
  result << ' FROM "comments" WHERE "comments"."id" = $1)'
357
357
  result << ' SELECT "users".*, "comments"."comment" FROM "users"'
358
358
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
@@ -370,14 +370,14 @@ RSpec.describe 'AuxiliaryStatement' do
370
370
  expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /join columns/)
371
371
  end
372
372
 
373
- it 'raises an error when not given the table name as first argument' do
373
+ it 'not raises an error when not given the table name as first argument' do
374
374
  klass.send(:auxiliary_statement, :comments) do |cte|
375
375
  cte.query -> { Comment.all }
376
376
  cte.attributes content: :comment
377
377
  cte.join id: :user_id
378
378
  end
379
379
 
380
- expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /table name/)
380
+ expect{ subject.with(:comments).arel.to_sql }.not_to raise_error
381
381
  end
382
382
 
383
383
  it 'raises an error when the result of the proc is an invalid type' do
@@ -403,7 +403,7 @@ RSpec.describe 'AuxiliaryStatement' do
403
403
  end
404
404
 
405
405
  result = 'WITH "authors" AS'
406
- result << ' (SELECT "authors"."id", "authors"."name" AS author_name FROM "authors")'
406
+ result << ' (SELECT "authors"."name" AS author_name, "authors"."id" FROM "authors")'
407
407
  result << ' SELECT "activity_books".*, "authors"."author_name" FROM "activity_books"'
408
408
  result << ' INNER JOIN "authors" ON "authors"."id" = "activity_books"."author_id"'
409
409
  expect(subject.with(:authors).arel.to_sql).to eql(result)
@@ -423,7 +423,7 @@ RSpec.describe 'AuxiliaryStatement' do
423
423
  end
424
424
 
425
425
  result = 'WITH "authors" AS'
426
- result << ' (SELECT "authors"."id", "authors"."type" AS author_type FROM "authors")'
426
+ result << ' (SELECT "authors"."type" AS author_type, "authors"."id" FROM "authors")'
427
427
  result << ' SELECT "activity_books".*, "authors"."author_type" FROM "activity_books"'
428
428
  result << ' INNER JOIN "authors" ON "authors"."id" = "activity_books"."author_id"'
429
429
  expect(subject.with(:authors).arel.to_sql).to eql(result)
@@ -434,6 +434,233 @@ RSpec.describe 'AuxiliaryStatement' do
434
434
  end
435
435
  end
436
436
 
437
+ context 'recursive' do
438
+ let(:klass) { Course }
439
+
440
+ it 'correctly build a recursive cte' do
441
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
442
+ cte.query Category.all
443
+ cte.join id: :parent_id
444
+ end
445
+
446
+ result = 'WITH RECURSIVE "all_categories" AS ('
447
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
448
+ result << ' FROM "categories"'
449
+ result << ' WHERE "categories"."parent_id" IS NULL'
450
+ result << ' UNION'
451
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
452
+ result << ' FROM "categories", "all_categories"'
453
+ result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
454
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
455
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
456
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
457
+ end
458
+
459
+ it 'allows connect to be set to something different using a single value' do
460
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
461
+ cte.query Category.all
462
+ cte.join id: :parent_id
463
+ cte.connect :name
464
+ end
465
+
466
+ result = 'WITH RECURSIVE "all_categories" AS ('
467
+ result << ' SELECT "categories"."name", "categories"."parent_id"'
468
+ result << ' FROM "categories"'
469
+ result << ' WHERE "categories"."parent_name" IS NULL'
470
+ result << ' UNION'
471
+ result << ' SELECT "categories"."name", "categories"."parent_id"'
472
+ result << ' FROM "categories", "all_categories"'
473
+ result << ' WHERE "categories"."parent_name" = "all_categories"."name"'
474
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
475
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
476
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
477
+ end
478
+
479
+ it 'allows a complete different set of connect' do
480
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
481
+ cte.query Category.all
482
+ cte.join id: :parent_id
483
+ cte.connect left: :right
484
+ end
485
+
486
+ result = 'WITH RECURSIVE "all_categories" AS ('
487
+ result << ' SELECT "categories"."left", "categories"."parent_id"'
488
+ result << ' FROM "categories"'
489
+ result << ' WHERE "categories"."right" IS NULL'
490
+ result << ' UNION'
491
+ result << ' SELECT "categories"."left", "categories"."parent_id"'
492
+ result << ' FROM "categories", "all_categories"'
493
+ result << ' WHERE "categories"."right" = "all_categories"."left"'
494
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
495
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
496
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
497
+ end
498
+
499
+ it 'allows using an union all' do
500
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
501
+ cte.query Category.all
502
+ cte.join id: :parent_id
503
+ cte.union_all!
504
+ end
505
+
506
+ result = 'WITH RECURSIVE "all_categories" AS ('
507
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
508
+ result << ' FROM "categories"'
509
+ result << ' WHERE "categories"."parent_id" IS NULL'
510
+ result << ' UNION ALL'
511
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
512
+ result << ' FROM "categories", "all_categories"'
513
+ result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
514
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
515
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
516
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
517
+ end
518
+
519
+ it 'allows having a complete different initiator' do
520
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
521
+ cte.query Category.where(parent_id: 5)
522
+ cte.join id: :parent_id
523
+ end
524
+
525
+ result = 'WITH RECURSIVE "all_categories" AS ('
526
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
527
+ result << ' FROM "categories"'
528
+ result << ' WHERE "categories"."parent_id" = $1'
529
+ result << ' UNION'
530
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
531
+ result << ' FROM "categories", "all_categories"'
532
+ result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
533
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
534
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
535
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
536
+ end
537
+
538
+ it 'can process the depth of the query' do
539
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
540
+ cte.query Category.all
541
+ cte.join id: :parent_id
542
+ cte.with_depth
543
+ end
544
+
545
+ result = 'WITH RECURSIVE "all_categories" AS ('
546
+ result << ' SELECT "categories"."id", "categories"."parent_id", 0 AS depth'
547
+ result << ' FROM "categories"'
548
+ result << ' WHERE "categories"."parent_id" IS NULL'
549
+ result << ' UNION'
550
+ result << ' SELECT "categories"."id", "categories"."parent_id", ("all_categories"."depth" + 1) AS depth'
551
+ result << ' FROM "categories", "all_categories"'
552
+ result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
553
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
554
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
555
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
556
+ end
557
+
558
+ it 'can process and expose the depth of the query' do
559
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
560
+ cte.query Category.all
561
+ cte.join id: :parent_id
562
+ cte.with_depth 'd', start: 10, as: :category_depth
563
+ end
564
+
565
+ result = 'WITH RECURSIVE "all_categories" AS ('
566
+ result << ' SELECT "categories"."id", "categories"."parent_id", 10 AS d'
567
+ result << ' FROM "categories"'
568
+ result << ' WHERE "categories"."parent_id" IS NULL'
569
+ result << ' UNION'
570
+ result << ' SELECT "categories"."id", "categories"."parent_id", ("all_categories"."d" + 1) AS d'
571
+ result << ' FROM "categories", "all_categories"'
572
+ result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
573
+ result << ' ) SELECT "courses".*, "all_categories"."d" AS category_depth FROM "courses" INNER JOIN "all_categories"'
574
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
575
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
576
+ end
577
+
578
+ it 'can process the path of the query' do
579
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
580
+ cte.query Category.all
581
+ cte.join id: :parent_id
582
+ cte.with_path
583
+ end
584
+
585
+ result = 'WITH RECURSIVE "all_categories" AS ('
586
+ result << ' SELECT "categories"."id", "categories"."parent_id", ARRAY["categories"."id"]::varchar[] AS path'
587
+ result << ' FROM "categories"'
588
+ result << ' WHERE "categories"."parent_id" IS NULL'
589
+ result << ' UNION'
590
+ result << ' SELECT "categories"."id", "categories"."parent_id", array_append("all_categories"."path", "categories"."id"::varchar) AS path'
591
+ result << ' FROM "categories", "all_categories"'
592
+ result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
593
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
594
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
595
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
596
+ end
597
+
598
+ it 'can process and expose the path of the query' do
599
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
600
+ cte.query Category.all
601
+ cte.join id: :parent_id
602
+ cte.with_path 'p', source: :name, as: :category_path
603
+ end
604
+
605
+ result = 'WITH RECURSIVE "all_categories" AS ('
606
+ result << ' SELECT "categories"."id", "categories"."parent_id", ARRAY["categories"."name"]::varchar[] AS p'
607
+ result << ' FROM "categories"'
608
+ result << ' WHERE "categories"."parent_id" IS NULL'
609
+ result << ' UNION'
610
+ result << ' SELECT "categories"."id", "categories"."parent_id", array_append("all_categories"."p", "categories"."name"::varchar) AS p'
611
+ result << ' FROM "categories", "all_categories"'
612
+ result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
613
+ result << ' ) SELECT "courses".*, "all_categories"."p" AS category_path FROM "courses" INNER JOIN "all_categories"'
614
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
615
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
616
+ end
617
+
618
+ it 'works with string queries' do
619
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
620
+ cte.query 'SELECT * FROM categories WHERE a IS NULL'
621
+ cte.sub_query 'SELECT * FROM categories, all_categories WHERE all_categories.a = b'
622
+ cte.join id: :parent_id
623
+ end
624
+
625
+ result = 'WITH RECURSIVE "all_categories" AS ('
626
+ result << 'SELECT * FROM categories WHERE a IS NULL'
627
+ result << ' UNION '
628
+ result << ' SELECT * FROM categories, all_categories WHERE all_categories.a = b'
629
+ result << ') SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
630
+ result << ' ON "all_categories"."parent_id" = "courses"."id"'
631
+ expect(subject.with(:all_categories).arel.to_sql).to eql(result)
632
+ end
633
+
634
+ it 'raises an error when query is a string and there is no sub query' do
635
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
636
+ cte.query 'SELECT * FROM categories WHERE a IS NULL'
637
+ cte.join id: :parent_id
638
+ end
639
+
640
+ expect{ subject.with(:all_categories).arel.to_sql }.to raise_error(ArgumentError, /generate sub query/)
641
+ end
642
+
643
+ it 'raises an error when sub query has an invalid type' do
644
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
645
+ cte.query 'SELECT * FROM categories WHERE a IS NULL'
646
+ cte.sub_query -> { 1 }
647
+ cte.join id: :parent_id
648
+ end
649
+
650
+ expect{ subject.with(:all_categories).arel.to_sql }.to raise_error(ArgumentError, /query and sub query objects/)
651
+ end
652
+
653
+ it 'raises an error when connect can be resolved automatically' do
654
+ allow(klass).to receive(:primary_key).and_return(nil)
655
+ klass.send(:recursive_auxiliary_statement, :all_categories) do |cte|
656
+ cte.query Category.all
657
+ cte.join id: :parent_id
658
+ end
659
+
660
+ expect{ subject.with(:all_categories).arel.to_sql }.to raise_error(ArgumentError, /setting up a proper way to connect/)
661
+ end
662
+ end
663
+
437
664
  it 'works with count and does not add extra columns' do
438
665
  klass.send(:auxiliary_statement, :comments) do |cte|
439
666
  cte.query Comment.all
@@ -441,7 +668,7 @@ RSpec.describe 'AuxiliaryStatement' do
441
668
  end
442
669
 
443
670
  result = 'WITH "comments" AS'
444
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
671
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."user_id" FROM "comments")'
445
672
  result << ' SELECT COUNT(*) FROM "users"'
446
673
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
447
674
 
@@ -456,7 +683,7 @@ RSpec.describe 'AuxiliaryStatement' do
456
683
  end
457
684
 
458
685
  result = 'WITH "comments" AS'
459
- result << ' (SELECT "comments"."user_id", "comments"."id" AS value FROM "comments")'
686
+ result << ' (SELECT "comments"."id" AS value, "comments"."user_id" FROM "comments")'
460
687
  result << ' SELECT SUM("comments"."value") FROM "users"'
461
688
  result << ' INNER JOIN "comments" ON "comments"."user_id" = "users"."id"'
462
689
 
@@ -472,7 +699,7 @@ RSpec.describe 'AuxiliaryStatement' do
472
699
  expect{ subject.with(:comments).arel.to_sql }.to raise_error(ArgumentError, /object types/)
473
700
  end
474
701
 
475
- it 'raises an error when traying to use a statement that is not defined' do
702
+ it 'raises an error when trying to use a statement that is not defined' do
476
703
  expect{ subject.with(:does_not_exist).arel.to_sql }.to raise_error(ArgumentError)
477
704
  end
478
705
 
@@ -495,7 +722,12 @@ RSpec.describe 'AuxiliaryStatement' do
495
722
  expect(subject.protected_methods).to include(:auxiliary_statement)
496
723
  end
497
724
 
498
- it 'allows configurate new auxiliary statements' do
725
+ it 'has the recursive configuration' do
726
+ expect(subject.protected_methods).to include(:recursive_cte)
727
+ expect(subject.protected_methods).to include(:recursive_auxiliary_statement)
728
+ end
729
+
730
+ it 'allows configure new auxiliary statements' do
499
731
  subject.send(:auxiliary_statement, :cte1)
500
732
  expect(subject.auxiliary_statements_list).to include(:cte1)
501
733
  expect(subject.const_defined?('Cte1_AuxiliaryStatement')).to be_truthy
@@ -527,7 +759,7 @@ RSpec.describe 'AuxiliaryStatement' do
527
759
  query = subject.with(sample, select: {content: :comment_content}).arel.to_sql
528
760
 
529
761
  result = 'WITH "comment" AS'
530
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
762
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."user_id" FROM "comments")'
531
763
  result << ' SELECT "users".*, "comment"."comment_content" FROM "users"'
532
764
  result << ' INNER JOIN "comment" ON "comment"."user_id" = "users"."id"'
533
765
  expect(query).to eql(result)
@@ -538,7 +770,7 @@ RSpec.describe 'AuxiliaryStatement' do
538
770
  query = subject.with(sample).arel.to_sql
539
771
 
540
772
  result = 'WITH "comment" AS'
541
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
773
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."user_id" FROM "comments")'
542
774
  result << ' SELECT "users".*, "comment"."comment_content" FROM "users"'
543
775
  result << ' INNER JOIN "comment" ON "comment"."user_id" = "users"."id"'
544
776
  expect(query).to eql(result)
@@ -551,13 +783,120 @@ RSpec.describe 'AuxiliaryStatement' do
551
783
  end
552
784
 
553
785
  result = 'WITH "all_comments" AS'
554
- result << ' (SELECT "comments"."user_id", "comments"."content" AS comment_content FROM "comments")'
786
+ result << ' (SELECT "comments"."content" AS comment_content, "comments"."user_id" FROM "comments")'
555
787
  result << ' SELECT "users".*, "all_comments"."comment_content" FROM "users"'
556
788
  result << ' INNER JOIN "all_comments" ON "all_comments"."user_id" = "users"."id"'
557
789
 
558
790
  query = subject.with(sample).arel.to_sql
559
791
  expect(query).to eql(result)
560
792
  end
793
+
794
+ context 'recursive' do
795
+ let(:klass) { Torque::PostgreSQL::AuxiliaryStatement::Recursive }
796
+ subject { Course }
797
+
798
+ it 'has the external method available' do
799
+ expect(klass).to respond_to(:create)
800
+ end
801
+
802
+ it 'accepts simple recursive auxiliary statement definition' do
803
+ settings = { join: { id: :parent_id } }
804
+ query = subject.with(klass.create(Category.all), **settings).arel.to_sql
805
+
806
+ result = 'WITH RECURSIVE "category" AS ('
807
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
808
+ result << ' FROM "categories"'
809
+ result << ' WHERE "categories"."parent_id" IS NULL'
810
+ result << ' UNION'
811
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
812
+ result << ' FROM "categories", "category"'
813
+ result << ' WHERE "categories"."parent_id" = "category"."id"'
814
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "category"'
815
+ result << ' ON "category"."parent_id" = "courses"."id"'
816
+ expect(query).to eql(result)
817
+ end
818
+
819
+ it 'accepts a connect option' do
820
+ settings = { join: { id: :parent_id }, connect: { a: :b } }
821
+ query = subject.with(klass.create(Category.all), **settings).arel.to_sql
822
+
823
+ result = 'WITH RECURSIVE "category" AS ('
824
+ result << ' SELECT "categories"."a", "categories"."parent_id"'
825
+ result << ' FROM "categories"'
826
+ result << ' WHERE "categories"."b" IS NULL'
827
+ result << ' UNION'
828
+ result << ' SELECT "categories"."a", "categories"."parent_id"'
829
+ result << ' FROM "categories", "category"'
830
+ result << ' WHERE "categories"."b" = "category"."a"'
831
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "category"'
832
+ result << ' ON "category"."parent_id" = "courses"."id"'
833
+ expect(query).to eql(result)
834
+ end
835
+
836
+ it 'accepts an union all option' do
837
+ settings = { join: { id: :parent_id }, union_all: true }
838
+ query = subject.with(klass.create(Category.all), **settings).arel.to_sql
839
+
840
+ result = 'WITH RECURSIVE "category" AS ('
841
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
842
+ result << ' FROM "categories"'
843
+ result << ' WHERE "categories"."parent_id" IS NULL'
844
+ result << ' UNION ALL'
845
+ result << ' SELECT "categories"."id", "categories"."parent_id"'
846
+ result << ' FROM "categories", "category"'
847
+ result << ' WHERE "categories"."parent_id" = "category"."id"'
848
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "category"'
849
+ result << ' ON "category"."parent_id" = "courses"."id"'
850
+ expect(query).to eql(result)
851
+ end
852
+
853
+ it 'accepts a sub query option' do
854
+ settings = { join: { id: :parent_id }, sub_query: Category.where(active: true) }
855
+ query = subject.with(klass.create(Category.all), **settings).arel.to_sql
856
+
857
+ result = 'WITH RECURSIVE "category" AS ('
858
+ result << ' SELECT "categories"."id", "categories"."parent_id" FROM "categories"'
859
+ result << ' UNION'
860
+ result << ' SELECT "categories"."id", "categories"."parent_id" FROM "categories", "category" WHERE "categories"."active" = $1'
861
+ result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "category"'
862
+ result << ' ON "category"."parent_id" = "courses"."id"'
863
+ expect(query).to eql(result)
864
+ end
865
+
866
+ it 'accepts a depth option' do
867
+ settings = { join: { id: :parent_id }, with_depth: { name: 'a', start: 5, as: 'b' } }
868
+ query = subject.with(klass.create(Category.all), **settings).arel.to_sql
869
+
870
+ result = 'WITH RECURSIVE "category" AS ('
871
+ result << ' SELECT "categories"."id", "categories"."parent_id", 5 AS a'
872
+ result << ' FROM "categories"'
873
+ result << ' WHERE "categories"."parent_id" IS NULL'
874
+ result << ' UNION'
875
+ result << ' SELECT "categories"."id", "categories"."parent_id", ("category"."a" + 1) AS a'
876
+ result << ' FROM "categories", "category"'
877
+ result << ' WHERE "categories"."parent_id" = "category"."id"'
878
+ result << ' ) SELECT "courses".*, "category"."a" AS b FROM "courses" INNER JOIN "category"'
879
+ result << ' ON "category"."parent_id" = "courses"."id"'
880
+ expect(query).to eql(result)
881
+ end
882
+
883
+ it 'accepts a path option' do
884
+ settings = { join: { id: :parent_id }, with_path: { name: 'a', source: 'b', as: 'c' } }
885
+ query = subject.with(klass.create(Category.all), **settings).arel.to_sql
886
+
887
+ result = 'WITH RECURSIVE "category" AS ('
888
+ result << ' SELECT "categories"."id", "categories"."parent_id", ARRAY["categories"."b"]::varchar[] AS a'
889
+ result << ' FROM "categories"'
890
+ result << ' WHERE "categories"."parent_id" IS NULL'
891
+ result << ' UNION'
892
+ result << ' SELECT "categories"."id", "categories"."parent_id", array_append("category"."a", "categories"."b"::varchar) AS a'
893
+ result << ' FROM "categories", "category"'
894
+ result << ' WHERE "categories"."parent_id" = "category"."id"'
895
+ result << ' ) SELECT "courses".*, "category"."a" AS c FROM "courses" INNER JOIN "category"'
896
+ result << ' ON "category"."parent_id" = "courses"."id"'
897
+ expect(query).to eql(result)
898
+ end
899
+ end
561
900
  end
562
901
 
563
902
  context 'on settings' do