torque-postgresql 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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