Repository navigation
Expand file tree
/
Copy pathuIterSort.pas
More file actions
executable file
·1496 lines (1368 loc) · 48.2 KB
/
Copy pathuIterSort.pas
File metadata and controls
executable file
·1496 lines (1368 loc) · 48.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
unit uIterSort;
{ ThinkSQL Relational Database Management System
Copyright © 2000-2012 Greg Gaughan
See LICENCE.txt for details
}
{$DEFINE DEBUGDETAIL}
//{$DEFINE DEBUGDETAIL2}
//{$DEFINE DEBUGDETAIL3} //duplicate removal
//todo tidy!
interface
uses uIterator, uSyntax, uTransaction, uStmt, uAlgebra, uTuple,
uFile, uTempTape, uGlobal;
const
nTempFiles=7; //number of temporary files (tapes) //todo make flexible?
//Note: need to extend FNAME template if max digits changes
nNodes=20; //number of nodes for selection tree //todo make flexible?
FNAME='sort%4.4d_%4.4d_%1.1d'; //temporary filename template (stmt,plan-node,file#) //todo make unique to server etc.
MaxKeyCol=MaxCol; //maximum number of sort columns
type
{Temporary file (tape) wrappers}
TtFile=class //hide? - but need to keep in sort class to assist multi-user memory handling
fp:TTempTape; //temporary tape file
fpBuf:array [0..MaxRecSize-1] of char; //read buffer area
fpBufLen:integer; //read buffer length of current record
dummy:integer; //number of dummy runs D[]
fib:integer; //ideal fibonacci number A[]
eof:boolean; //end of file flag
eor:boolean; //end of run flag
valid:boolean; //true if tuple is valid
end; {TtFile}
{Selection tree nodes}
TiNode=class; //forward
TeNode=class //external node (4+4+4+4+1=17 bytes = 20 bytes)
parent:TiNode; //parent of external node
rec:PChar; //pointer to dynamic tuple record data buffer
recLen:integer;
run:integer; //run number
valid:boolean; //input tuple is valid
end; {TeNode}
TiNode=class //internal node (4+4=8 bytes)
parent:TiNode; //parent of internal node
loser:TeNode; //external loser
end; {TiNode}
TNode=class //(4+4=8 bytes) e.g. 1000 nodes = 8000 + 8000 + 20000 + rec buffer = 36000+ (e.g. 76000 for 40 char rec buffers)
i:TiNode; //internal node
e:TeNode; //external node
end; {TNode}
TIterSort=class(TIterator)
private
tFile:array [0..nTempFiles-1] of TtFile;
level:integer; //level of runs
sorted:boolean; //first call = sort & materialise
node:array [0..nNodes] of TNode; //array of selection tree nodes
win:TeNode; //new winner
eof:boolean; //end of file, input
maxrun:integer; //maximum run number
currun:integer; //current run number
lastKeyValid:boolean; //true if last key is valid
lastKey:TTuple; //buffer to store last key comparison for readTuple
noMoreData:boolean; //input tuple noMore flag (needed to be static by IterNestedLoop)
tempTuple1,tempTuple2:TTuple;
distinct:boolean; //remove duplicates?
function CompareTupleKeysLT(tl,tr:TTuple;var res:boolean):integer;
function CompareTupleKeysEQ(tl,tr:TTuple;var res:boolean):integer;
function CompareTupleKeysGT(tl,tr:TTuple;var res:boolean):integer;
function initTempFiles:integer;
function deleteTempFiles:integer;
function termTempFiles:integer;
function rewindFile(f:integer):integer;
function readTuple(var noMore:boolean):integer;
function makeRuns:integer;
function doMergeSort:integer;
function mergeSort:integer;
public
//TODO: use keyColMap:array [0..MaxKeyCol] of TKeyColMap;
//todo use same array/structure as TindexFile...
//Note: made public so parent iterSet can override them
keyCol:array [0..MaxKeyCol-1] of record col:colRef; direction:TsortDirection; end; //array of sort columns
keyColCount:integer;
function description:string; override;
function status:string; override;
constructor create(S:TStmt;itemExprRef:TAlgebraNodePtr;distinctFlag:boolean);
destructor destroy; override;
function prePlan(outerRef:TIterator):integer; override;
function optimise(var SARGlist:TSyntaxNodePtr;var newChildParent:TIterator):integer; override;
function start:integer; override; //begin
function stop:integer ;override; //end
function next(var noMore:boolean):integer; override; //loop
function GetPosition:cardinal;
function FindPosition(p:cardinal):integer;
end; {TIterSort}
implementation
uses
{$IFDEF Debug_Log}
uLog,
{$ENDIF}
sysUtils, uEvalCondExpr, uHeapFile, uMarshalGlobal;
const
where='uIterSort';
fileT=nTempFiles-1; //last file
fileP=fileT-1; //next to last file (P-way merging)
constructor TIterSort.create(S:TStmt;itemExprRef:TAlgebraNodePtr;distinctFlag:boolean);
const routine=':create';
var
i:integer;
begin
inherited create(s);
aNodeRef:=itemExprRef;
distinct:=distinctFlag;
sorted:=false;
for i:=0 to nTempFiles-1 do
begin
tFile[i]:=TtFile.Create; //todo: faster if use records & new(TtFilePtr)?
tFile[i].fp:=TTempTape.Create;
end;
for i:=0 to nNodes-1 do //todo check for memory full...
begin
node[i]:=TNode.Create;
node[i].i:=TiNode.Create;
node[i].e:=TeNode.Create;
end;
lastKey:=TTuple.Create(nil);
tempTuple1:=TTuple.Create(nil);
tempTuple2:=TTuple.Create(nil);
end; {create}
destructor TIterSort.destroy;
const routine=':destroy';
var i:integer;
begin
tempTuple2.free;
tempTuple1.free;
lastKey.free;
for i:=nNodes-1 downto 0 do
begin
if node[i].e.rec<>nil then
begin
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('buffer for external node %d was not released',[i]),vAssertion);
{$ENDIF}
//continue //abort?
end;
node[i].e.free;
node[i].i.free;
node[i].free;
end;
for i:=nTempFiles-1 downto 0 do
begin
tFile[i].fp.Free;
tFile[i].free;
end;
inherited destroy;
end; {destroy}
function TIterSort.description:string;
{Return a user-friendly description of this node
}
begin
result:=inherited description;
if distinct then
result:=result+' (distinct)';
end; {description}
function TIterSort.status:string;
begin
{$IFDEF DEBUG_LOG}
if distinct then
result:=format('TIterSort %d (distinct)',[keyColCount])
else
result:=format('TIterSort %d',[keyColCount]);
if anodeRef<>nil then result:=result+' '+anodeRef.rangeName; //not set for merge-join children
{$ELSE}
result:='';
{$ENDIF}
end; {status}
function TIterSort.prePlan(outerRef:TIterator):integer;
{PrePlans the sort process
RETURNS: ok, else fail
NOTE:
if the sort is used before an iterSet, the iterSet may
push down the keyCol array settings after this prePlan
i.e. once the iterSet preplan has found out the 'corresponding' columns
}
const routine=':prePlan';
var
nhead:TSyntaxNodePtr;
i:colRef;
colName:string;
cTuple:TTuple; //todo make global?
cId:TColId;
cRef:ColRef;
begin
result:=inherited prePlan(outerRef);
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('%s preplanning',[self.status]),vDebugLow);
{$ENDIF}
if assigned(leftChild) then
begin
result:=leftChild.prePlan(outer); //recurse down tree
correlated:=correlated OR leftChild.correlated;
end;
if result<>ok then exit; //aborted by child
{Define this ituple from leftChild.ituple}
iTuple.CopyTupleDef(leftChild.iTuple);
{Define the temporary tuples to be identical to the input tuple}
lastKey.CopyTupleDef(leftChild.iTuple);
tempTuple1.CopyTupleDef(leftChild.iTuple);
tempTuple2.CopyTupleDef(leftChild.iTuple);
{Set up the sort key from the column list passed}
//in future may need to handle expressions here...?
// - (only for GROUP-BY sort?) although the standard grammar I have only allows column-refs!!!!
// could do SELECT exp as E ... GROUP BY E = legal
keyColCount:=0;
if anodeRef=nil then
nhead:=nil //e.g. sort for joinMerge - no syntax nodes passed at this stage...
else
begin
nhead:=anodeRef.nodeRef;
//todo remove the following line: overkill, since iterSet will push down the sort-order afterwards... no harm? better to do it this way? = one set of code...
if (nhead<>nil) and (nhead.nType in [ntCorresponding,ntCorrespondingBy]) then nhead:=nhead.leftChild;
end;
i:=0;
while nhead<>nil do
begin
//todo impossible, but double check nhead not nil!
//todo use new code that relies on complete* routines... although overkill here maybe?
{todo: take name from column name (pass up?)
ntSelectItem:
begin
n:=n.rightChild; //optional
}
//Note: the expression calculators will re-define these if needed anyway - ok?
colName:=intToStr(i); //default column name
case nhead.nType of
ntColumnRef: //todo note: this is from group-by! = no ASC/DESC
begin
//todo: if n.leftChild.ntype=ntTable then check if its leftChild is a schema & if so restrict find...
//assumes we have a right child! -assert!
result:=leftchild.iTuple.FindCol(nhead,nhead.rightChild.idval,'',leftchild.outer,cTuple,cRef,cid);
if result<>ok then
begin
if result=-2 then
begin
stmt.addError(seSyntaxAmbiguousColumn,format(seSyntaxAmbiguousColumnText,[nhead.rightChild.idval]));
end
else
begin
stmt.addError(seSyntaxLookupFailed,format(seSyntaxLookupFailedText,['column '+nhead.rightChild.idval]));
end;
exit; //abort if child aborts
end;
if cid=InvalidColId then
begin
//shouldn't this have been caught before now!?
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Unknown column reference (%s)',[nhead.rightChild.idVal]),vError);
{$ENDIF}
stmt.addError(seSyntaxUnknownColumn,format(seSyntaxUnknownColumnText,[nhead.rightChild.idVal]));
result:=Fail;
exit; //abort, no point continuing?
end;
if cTuple<>leftChild.iTuple then //todo relax later when using expressions? if so remember to pull up correlated from any Complete... call
begin
//shouldn't this have been caught before now!?
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Column reference (%s) must be in this sub-select',[nhead.rightChild.idVal]),vError);
{$ENDIF}
result:=Fail;
exit; //abort, no point continuing?
end;
if keyColCount>=MaxKeyCol-1 then
begin
//shouldn't this have been caught before now!?
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Too many sort columns %d',[keyColCount]),vError);
{$ENDIF}
result:=Fail;
exit; //abort, no point continuing?
end;
inc(keyColCount);
keyCol[keyColCount-1].col:=cRef;
keyCol[keyColCount-1].direction:=sdASC;
inc(i); //single column added
end; {ntColumnRef}
ntOrderItem:
begin
//assumes we have a left child! -assert!
if (nhead.leftChild.idval='') and (nhead.leftChild.dtype=ctNumeric) then //assume integer //todo check dtype as well/instead?
begin
{Note: this is deprecated in SQL-92}
cRef:=trunc(nhead.leftChild.numVal); //user subscript
if (cRef<1) or (cRef>iTuple.ColCount) then
begin
//shouldn't this have been caught before now!?
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Unknown column subscript (%d)',[trunc(nhead.leftChild.numVal)]),vError);
{$ENDIF}
stmt.addError(seSyntaxUnknownColumn,format(seSyntaxUnknownColumnText,[intToStr(cRef)]));
result:=Fail;
exit; //abort, no point continuing?
end;
cRef:=cRef-1; //todo check ok to map directly to subscript..
end
else
begin //column name
//assumes we have a right child! -assert!
result:=leftchild.iTuple.FindCol(nhead.leftChild,nhead.leftChild.rightChild.idval,'',outer,cTuple,cRef,cid);
if result<>ok then
begin
if result=-2 then
begin
stmt.addError(seSyntaxAmbiguousColumn,format(seSyntaxAmbiguousColumnText,[nhead.leftChild.rightChild.idval]));
end
else
begin
stmt.addError(seSyntaxLookupFailed,format(seSyntaxLookupFailedText,['column '+nhead.leftChild.rightChild.idval]));
end;
exit; //abort if child aborts
end;
if cid=InvalidColId then
begin
//shouldn't this have been caught before now!?
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Unknown column (%s)',[nhead.leftChild.idVal]),vError);
{$ENDIF}
stmt.addError(seSyntaxUnknownColumn,format(seSyntaxUnknownColumnText,[nhead.leftChild.idVal]));
result:=Fail;
exit; //abort, no point continuing?
end;
if cTuple<>leftChild.iTuple then
begin
//shouldn't this have been caught before now!?
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Column (%s) must be in this sub-select',[nhead.leftChild.rightChild.idVal]),vError);
{$ENDIF}
result:=Fail;
exit; //abort, no point continuing?
end;
if keyColCount>=MaxKeyCol-1 then
begin
//shouldn't this have been caught before now!?
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Too many sort columns %d',[keyColCount]),vError);
{$ENDIF}
result:=Fail;
exit; //abort, no point continuing?
end;
end;
inc(keyColCount);
keyCol[keyColCount-1].col:=cRef;
keyCol[keyColCount-1].direction:=sdASC; //default
if nhead.rightChild<>nil then //Retrieve ASC/DESC from nhead.rightChild
case nhead.rightChild.nType of
ntASC: keyCol[keyColCount-1].direction:=sdASC;
ntDESC: keyCol[keyColCount-1].direction:=sdDESC;
else
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Unknown column sort direction',[nil]),vError);
{$ENDIF}
//ignore it! ok?
end; {case}
inc(i); //single column added
end; {ntOrderItem}
else
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Only columns/column references allowed in sort',[nil]),vError);
{$ENDIF}
//ignore it! ok?
end; {case}
nhead:=nhead.NextNode;
end; {while}
if keyColCount=0 then
begin
if distinct then //duplicate removal, so sort on all columns
begin
for i:=0 to iTuple.ColCount-1 do
begin
inc(keyColCount);
keyCol[keyColCount-1].col:=i;
keyCol[keyColCount-1].direction:=sdASC;
end;
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Sorting on all %d columns (for distinct)',[keyColCount]),vDebugLow);
{$ENDIF}
end
else
begin //assume sort on all columns anyway (was needed for initial pre-iterSet - now pushed down)
for i:=0 to iTuple.ColCount-1 do
begin
inc(keyColCount);
keyCol[keyColCount-1].col:=i;
keyCol[keyColCount-1].direction:=sdASC;
end;
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('Sorting on all %d columns',[keyColCount]),vDebugLow);
{$ENDIF}
end;
end //todo instead assume all columns?
else
begin
{$IFDEF DEBUGDETAIL2}
{$IFDEF DEBUG_LOG}
for i:=0 to keyColCount-1 do
log.add(stmt.who,where+routine,format('Sorting on column %d',[keyCol[i].col]),vDebugLow);
{$ENDIF}
{$ENDIF}
end;
end; {prePlan}
function TIterSort.optimise(var SARGlist:TSyntaxNodePtr;var newChildParent:TIterator):integer;
{Optimise the prepared plan from a local perspective
IN: SARGlist list of SARGs
OUT: newChildParent child inserted new parent, so caller should relink to it
RETURNS: ok, else fail
}
const routine=':optimise';
begin
result:=inherited optimise(SARGlist,newChildParent);
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('%s optimising',[self.status]),vDebugLow);
{$ENDIF}
//todo: optimise sort
// ensure projections have been pushed down below here at least
// if small result set (expected) then quicksort in memory! - use buffers or keep separate? how to limit...
if assigned(leftChild) then
begin
result:=leftChild.optimise(SARGlist,newChildParent); //recurse down tree
end;
if result<>ok then exit; //aborted by child
if newChildParent<>nil then
begin
{Child has inserted an intermediate node - re-link to new child for execution calls}
{$IFDEF DEBUGDETAIL2}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('linking to new leftChild: %s',[newChildParent.Status]),vDebugLow);
{$ENDIF}
{$ENDIF}
leftChild:=newChildParent;
newChildParent:=nil; //don't continue passing up!
end;
//todo: same for rightChild if we could have one
//check if any of our SARGs are now marked 'pushed' & remove them from ourself if so
end; {optimise}
function TIterSort.start:integer;
{Start the sort process
RETURNS: ok, else fail
}
const routine=':start';
var
i:integer;
begin
result:=inherited start;
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('%s starting',[self.status]),vDebugLow);
{$ENDIF}
if assigned(leftChild) then result:=leftChild.start; //recurse down tree
if result<>ok then exit; //aborted by child
{Clear - mainly needed to point scratch data buffer pointer for all columns}
iTuple.clear(stmt);
lastKey.clear(stmt);
tempTuple1.clear(stmt);
tempTuple2.clear(stmt);
sorted:=false; //13/05/00 fix: nested iteration of groups called re-start but wasn't re-sorting on 1st call to next!
{18/11/02 fix: re-execute prepared caused win top errors (moved here from create)}
for i:=0 to nNodes-1 do
begin
node[i].i.loser:=node[i].e;
node[i].i.parent:=node[i div 2].i;
node[i].e.parent:=node[(nNodes+i) div 2].i;
node[i].e.run:=0;
node[i].e.valid:=False;
if node[i].e.rec<>nil then
begin
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('buffer for external node %d was not released',[i]),vAssertion);
{$ENDIF}
freemem(node[i].e.rec,node[i].e.recLen); //Note: we don't have to specify the length
node[i].e.rec:=nil;
node[i].e.recLen:=0; //todo remove - no need?
end;
end;
{29/03/03 fix: re-execute prepared caused empty results set (not resetting valid)}
for i:=0 to nTempFiles-1 do
begin
tFile[i].dummy:=0;
tFile[i].fib:=0;
tFile[i].eof:=false;
tFile[i].eor:=false;
tFile[i].valid:=false;
end;
win:=node[0].e;
eof:=False;
maxrun:=0; //29/03/03 fix: re-execute prepared caused empty results set
currun:=0; //29/03/03 fix: re-execute prepared caused empty results set
lastKeyValid:=False;
noMoreData:=false; //29/03/03 fix: re-execute prepared caused empty results set
end; {start}
function TIterSort.stop:integer;
{Stop the sort process
RETURNS: ok, else fail
}
const routine=':stop';
begin
result:=inherited stop;
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('%s stopping',[self.status]),vDebugLow);
{$ENDIF}
if assigned(leftChild) then result:=leftChild.stop; //recurse down tree
//todo ok/better to continue?.... if result<>ok then exit; //aborted by child
result:=tFile[fileT].fp.close; //stop materialised scan
result:=tFile[fileT].fp.delete; //delete materialised scan
end; {stop}
function TIterSort.next(var noMore:boolean):integer;
{Get the next tuple in sort-order
If this is the first call to next() then we perform the sort & materialise
the sorted relation, then return the next=first tuple
(although for now, just in a temp-file)
RETURNS: ok, else fail
}
const routine=':next';
begin
// inherited next;
result:=ok;
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('%s next',[self.status]),vDebugLow);
{$ENDIF}
if stmt.status=ssCancelled then
begin
result:=Cancelled;
exit;
end;
if not sorted then
begin //first call = sort input & materialise
sorted:=True;
//todo: when we can determine that the child iterator is already in sorted order, then skip this step & just shallow read child-tuple! -speed!
result:=mergeSort; //todo need to allow to be interrupted by caller somehow...
if result<>ok then exit; //abort
end;
if not tFile[fileT].fp.noMore then
begin
{already sorted, so get next from materialised relation}
result:=tFile[fileT].fp.readRecord(tFile[fileT].fpBuf,tFile[fileT].fpBufLen);
if result<>ok then exit; //abort
result:=iTuple.CopyBufferToData(tFile[fileT].fpBuf,tFile[fileT].fpBufLen);
noMore:=False; //todo: bug? we should not need this. Similar problem in materialise.next was because .stop was called but should have delayed closing children until 'really' done
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('%s',[iTuple.Show(stmt)]),vDebugLow);
{$ENDIF}
{$ENDIF}
end
else //end of tape
noMore:=True;
end; {next}
{Expose materialised positioning from final tape
- initially used by join merge for bookmarking a block
}
function TIterSort.GetPosition:cardinal;
begin
if not sorted then
result:=0
else
result:=tFile[fileT].fp.GetPosition;
end; {GetPosition}
function TIterSort.FindPosition(p:cardinal):integer;
begin
if not sorted then
result:=fail
else
begin
result:=tFile[fileT].fp.FindPosition(p);
end;
end; {FindPosition}
//combine these two routines - or use the ones from EvalCondExpr
//eventually we will call EvalExpr to allow non-column orderings... eg. ORDER BY tot*2
function TIterSort.CompareTupleKeysLT(tl,tr:TTuple;var res:boolean):integer;
{Compare 2 tuple keys for l < r
Assumes:
keyCol array has been defined
both tuples have same column definitions
Note:
in future, may need to call EvalCondExpr() with 'a.key1<b.key1 and a.key2<b.key2...'
- this would allow sorts such as Order by name||'z'
Takes account of sort direction for each column (reverses test result for DESCending)
}
const routine=':compareTupleKeysLT';
var
cl:colRef;
resComp:shortint;
resNull:boolean;
begin
result:=ok;
res:=False;
cl:=0;
//todo make into an assertion: if tl.ColCount<>tr.ColCount then res:=isFalse; //done! mismatch column valency
//todo speed logic
resComp:=0;
while (resComp=0) and (cl<keyColCount) do
begin
result:=tl.CompareCol(stmt,keyCol[cl].col,keyCol[cl].col,tr,resComp,resNull);
if result<>ok then exit; //abort if compare fails
if resNull then
begin
result:=tl.ColIsNull(keyCol[cl].col,resNull);
if result<>ok then exit; //abort
if resNull then
begin
result:=tr.ColIsNull(keyCol[cl].col,resNull);
if result<>ok then exit; //abort
if resNull then
resComp:=0 //both are null so treat as equal (SQL anomaly!)
else
resComp:=NullSortOthers;
end
else
resComp:=-NullSortOthers;
end;
if keyCol[cl].direction=sdDESC then resComp:=-resComp; //reverse direction
inc(cl);
end;
if resComp<0 then res:=True else res:=False;
end; {CompareTupleKeysLT}
function TIterSort.CompareTupleKeysEQ(tl,tr:TTuple;var res:boolean):integer;
{Compare 2 tuple keys for l = r
Assumes:
keyCol array has been defined
both tuples have same column definitions
Note:
in future, may need to call EvalCondExpr() with 'a.key1<b.key1 and a.key2<b.key2...'
- this would allow sorts such as Order by name||'z'
Takes account of sort direction for each column (reverses test result for DESCending)
}
const routine=':compareTupleKeysEQ';
var
cl:colRef;
resComp:shortint;
resNull:boolean;
begin
result:=ok;
res:=False;
cl:=0;
//todo make into an assertion: if tl.ColCount<>tr.ColCount then res:=isFalse; //done! mismatch column valency
//todo speed logic
resComp:=0;
while (resComp=0) and (cl<keyColCount) do
begin
result:=tl.CompareCol(stmt,keyCol[cl].col,keyCol[cl].col,tr,resComp,resNull);
if result<>ok then exit; //abort if compare fails
if resNull then
begin
result:=tl.ColIsNull(keyCol[cl].col,resNull);
if result<>ok then exit; //abort
if resNull then
begin
result:=tr.ColIsNull(keyCol[cl].col,resNull);
if result<>ok then exit; //abort
if resNull then
resComp:=0 //both are null so treat as equal (SQL anomaly!)
else
resComp:=NullSortOthers;
end
else
resComp:=-NullSortOthers;
end;
if keyCol[cl].direction=sdDESC then resComp:=-resComp; //reverse direction
inc(cl);
end;
if resComp=0 then res:=True else res:=False;
end; {CompareTupleKeysEQ}
function TIterSort.CompareTupleKeysGT(tl,tr:TTuple;var res:boolean):integer;
{Compare 2 tuple keys for l > r
Assumes:
keyCol array has been defined
both tuples have same column definitions
Note:
in future, may need to call EvalCondExpr() with 'a.key1>b.key1 and a.key2>b.key2...'
- this would allow sorts such as Order by name||'z'
Takes account of sort direction for each column (reverses test result for DESCending)
}
const routine=':compareTupleKeysGT';
var
cl:colRef;
resComp:shortint;
resNull:boolean;
begin
result:=ok;
res:=False;
cl:=0;
//todo make into an assertion: if tl.ColCount<>tr.ColCount then res:=isFalse; //done! mismatch column valency
//todo speed logic
resComp:=0;
while (resComp=0) and (cl<keyColCount) do
begin
result:=tl.CompareCol(stmt,keyCol[cl].col,keyCol[cl].col,tr,resComp,resNull);
if result<>ok then exit; //abort if compare fails
if resNull then
begin
result:=tl.ColIsNull(keyCol[cl].col,resNull);
if result<>ok then exit; //abort
if resNull then
begin
result:=tr.ColIsNull(keyCol[cl].col,resNull);
if result<>ok then exit; //abort
if resNull then
resComp:=0 //both are null so treat as equal (SQL anomaly!)
else
resComp:=NullSortOthers;
end
else
resComp:=-NullSortOthers;
end;
if keyCol[cl].direction=sdDESC then resComp:=-resComp; //reverse direction
inc(cl);
end;
if resComp>0 then res:=True else res:=False;
end; {CompareTupleKeysGT}
function TIterSort.initTempFiles:integer;
{Initialise the temp files
RETURNS: ok, else fail
}
const routine=':initTempFiles';
var
i:integer;
r:integer;
begin
result:=ok;
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,'initialising temp files',vDebugLow);
{$ENDIF}
{$ENDIF}
if nTempFiles<3 then
begin
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('must have more than 3 temp files (%d)',[nTempFiles]),vAssertion);
{$ENDIF}
result:=fail;
exit; //abort
end;
for i:=0 to nTempFiles-1 do
begin
{Open each new relation ready for writing to}
r:=trunc(random(9999)); //todo DEBUG ONLY - REMOVE!! need to make file unique to trans+node! i.e. SYS-getNextFilename!
result:=tFile[i].fp.CreateNew(format(FNAME,[stmt.Rt.tranId,r,i]));
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,'initialising temp file: '+format(FNAME,[stmt.Rt.tranId,r,i]),vDebugLow);
{$ENDIF}
{$ENDIF}
if result<>ok then exit; //abort
end;
end; {initTempFiles}
function TIterSort.deleteTempFiles:integer;
{Delete temp files, except final relation
RETURNS: ok, else fail
}
const routine=':deleteTempFiles';
var
i:integer;
begin
result:=ok;
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,'deleting temp files:',vDebugLow);
{$ENDIF}
{$ENDIF}
for i:=0 to fileT-1 do
begin
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format(' %s',[tFile[i].fp.filename]),vDebugLow);
{$ENDIF}
{$ENDIF}
result:=tFile[i].fp.close;
result:=tFile[i].fp.delete;
//we don't clean up allocations until Iter is finished
end;
end; {deleteTempFiles}
function TIterSort.termTempFiles:integer;
{Clean up files & restart scan on final output relation
RETURNS: ok, else fail
}
const routine=':termTempFiles';
begin
result:=ok;
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,'finalising temp files',vDebugLow);
{$ENDIF}
{$ENDIF}
{file[T] contains results}
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('re-opening results file %d',[fileT]),vDebugLow);
{$ENDIF}
{$ENDIF}
result:=tFile[fileT].fp.rewind;
//todo check result...
result:=deleteTempFiles;
end; {termTempFiles}
function TIterSort.rewindFile(f:integer):integer;
{Rewinds the temp file ready for a pass.
The file scan is (re-)started and the first tuple is read.
The tFile[] end-of-file flag is set appropriately.
IN: f - the file number
RETURNS: ok, else fail
}
const routine=':rewindFile';
begin
result:=ok;
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('rewinding file %d',[f]),vDebugLow);
{$ENDIF}
{$ENDIF}
tFile[f].eor:=False;
tFile[f].eof:=False;
tFile[f].fp.rewind;
//todo check result...
if tFile[f].fp.noMore then
begin
if result<>ok then exit; //abort
tFile[f].eor:=True;
tFile[f].eof:=True;
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('initial record read = eof from file %d',[f]),vDebugLow);
{$ENDIF}
{$ENDIF}
end
else
begin
result:=tFile[f].fp.readRecord(tFile[f].fpBuf,tFile[f].fpBufLen);
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format('initial record read from file %d: %s',[f,tFile[f].fpBuf]),vDebugLow);
{$ENDIF}
{$ENDIF}
end;
end; {rewindFile}
function TIterSort.readTuple(var noMore:boolean):integer;
{Read next tuple using replacement selection
Algorithm from Knuth volume 3.
OUT: noMore - no more tuples left (end of input)
iTuple - the next tuple
RETURNS: ok, else fail
Note:
we (over-)use iTuple to return the next tuple
//todo return in temp buffer area
//todo replace with fixed heap array?
//also - use buffer pages to store such dynamic memory
// to save the Delphi heap from being ragged
}
const routine=':readTuple';
var
p:TiNode; //pointer to internal nodes
t:TeNode; //pointer for swapping
swap:boolean;
res:boolean;
begin
result:=ok;
while True do
begin
{replace previous winner with new tuple}
if not eof then
begin
if assigned(leftChild) then
begin
result:=leftChild.next(noMoreData); //recurse down tree (this input will be buffered)
if result<>ok then exit; //abort
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.add(stmt.who,where+routine,format(' reading %s',[leftChild.iTuple.show(stmt)]),vDebugLow);
{$ENDIF}
{$ENDIF}
if not noMoreData then
begin
{copy leftChild.iTuple data to new win.rec buffer}
//todo: note this copy routine is probably relatively slow... (we need to account for multiple versions of buffers from child)
{$IFDEF DEBUG_LOG}
// log.quick('tuple= '+leftChild.iTuple.show+'');
{$ENDIF}
{$IFDEF DEBUG_LOG}
// log.quick('tuple= ['+leftChild.iTuple.showMap+']');
{$ENDIF}
result:=leftChild.iTuple.CopyDataToBuffer(win.rec,win.recLen);
{$IFDEF DEBUGDETAIL}
{$IFDEF DEBUG_LOG}
log.quick('win.rec ['+intToStr(win.recLen)+']='+win.rec+']');
{$ENDIF}
{$ENDIF}
if lastKeyValid then
result:=CompareTupleKeysLT(leftChild.iTuple,lastKey,res)
else
res:=False; //speed?
if result<>ok then exit;
if not(lastKeyValid) or res then
begin
inc(win.run);
if win.run>maxRun then maxRun:=win.run;
end;
win.valid:=True;
end
else
begin
//todo: maybe leftChild.stop now? - may as well!
eof:=true;
win.valid:=False;
win.run:=maxRun+1;
end;
end;
end