forked from synopse/mORMot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SynVirtualDataSet.pas
978 lines (887 loc) · 31.9 KB
/
SynVirtualDataSet.pas
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
/// DB VCL read-only virtual dataset
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynVirtualDataSet;
{
This file is part of Synopse framework.
Synopse framework. Copyright (c) Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse mORMot framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (c)
the Initial Developer. All Rights Reserved.
Contributor(s):
- Alfred Glaenzer (alf)
- Esteban Martin (EMartin)
- mingda
- Murat Ak
- Valentin (StxLog)
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
interface
uses
SysUtils,
Classes,
{$ifndef FPC}
Contnrs,
{$endif}
{$ifndef NOVARIANTS}
Variants,
{$endif}
SynCommons,
SynTable,
{$ifdef ISDELPHIXE2}
System.Generics.Collections,
Data.DB, Data.FMTBcd;
{$else}
DB, FMTBcd;
{$endif}
type
{$ifndef UNICODE} // defined as TRecordBuffer = PByte in newer DB.pas
TRecordBuffer = PChar;
{$endif UNICODE}
PDateTimeRec = ^TDateTimeRec;
/// read-only virtual TDataSet able to access any content
TSynVirtualDataSet = class(TDataSet)
protected
fCurrentRow: integer;
fIsCursorOpen: boolean;
// TDataSet overridden methods
function AllocRecordBuffer: TRecordBuffer; override;
procedure FreeRecordBuffer(var Buffer: TRecordBuffer); override;
procedure InternalInitRecord(Buffer: TRecordBuffer); override;
function GetCanModify: Boolean; override;
procedure GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; override;
function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
function GetRecordSize: Word; override;
procedure InternalClose; override;
procedure InternalFirst; override;
procedure InternalGotoBookmark(Bookmark: Pointer); override;
procedure InternalHandleException; override;
procedure InternalLast; override;
procedure InternalSetToRecord(Buffer: TRecordBuffer); override;
function IsCursorOpen: Boolean; override;
procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); override;
procedure SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
procedure SetRecNo(Value: Integer); override;
function GetRecNo: Integer; override;
// classses should override all those following methods:
// - to read the data e.g. into memory:
procedure InternalOpen; override;
// - to initialize FieldDefs:
// procedure InternalInitFieldDefs; override;
// - to return row count:
// function GetRecordCount: Integer; override;
// - result should point to Int64,Double,Blob,UTF8 data (if ResultLen<>nil)
function GetRowFieldData(Field: TField; RowIndex: integer; out ResultLen: Integer;
OnlyCheckNull: boolean): Pointer; virtual; abstract;
// - to search for a field, returning RecNo (0 = not found by default)
function SearchForField(const aLookupFieldName: RawUTF8; const aLookupValue: variant;
aOptions: TLocateOptions): integer; virtual;
{$ifndef NOVARIANTS}
// used to serialize TBCDVariant as JSON - BcdRead will always fail
class procedure BcdWrite(const aWriter: TTextWriter; const aValue);
//class function BcdRead(P: PUTF8Char; var aValue; out aValid: Boolean): PUTF8Char;
{$endif}
public
/// this overridden constructor will compute an unique Name property
constructor Create(Owner: TComponent); override;
/// get BLOB column data for the current active row
// - handle ftBlob,ftMemo,ftWideMemo via GetRowFieldData()
function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
/// get BLOB column data for a given row (may not the active row)
// - handle ftBlob,ftMemo,ftWideMemo via GetRowFieldData()
function GetBlobStream(Field: TField; RowIndex: integer): TStream;
/// get column data for the current active row
// - handle ftBoolean,ftInteger,ftLargeint,ftFloat,ftCurrency,ftDate,ftTime,
// ftDateTime,ftString,ftWideString kind of fields via GetRowFieldData()
{$ifdef ISDELPHIXE3}
{$ifdef ISDELPHIXE4}
function GetFieldData(Field: TField; var Buffer: TValueBuffer): Boolean; override;
{$else}
function GetFieldData(Field: TField; Buffer: TValueBuffer): Boolean; override;
{$endif}
{$else}
function GetFieldData(Field: TField; Buffer: pointer): Boolean; override;
{$endif}
{$ifndef UNICODE}
function GetFieldData(Field: TField; Buffer: pointer; NativeFormat: Boolean): Boolean; override;
{$endif}
/// searching a dataset for a specified record and making it the active record
// - will call SearchForField protected virtual method for actual lookup
function Locate(const KeyFields: string; const KeyValues: Variant;
Options: TLocateOptions) : boolean; override;
published
property Active;
property BeforeOpen;
property AfterOpen;
property BeforeClose;
property AfterClose;
property BeforeInsert;
property AfterInsert;
property BeforeEdit;
property AfterEdit;
property BeforePost;
property AfterPost;
property BeforeCancel;
property AfterCancel;
property BeforeDelete;
property AfterDelete;
property BeforeScroll;
property AfterScroll;
property OnCalcFields;
property OnDeleteError;
property OnEditError;
property OnFilterRecord;
property OnNewRecord;
property OnPostError;
end;
{$ifndef NOVARIANTS}
/// read-only virtual TDataSet able to access a dynamic array of TDocVariant
// - could be used e.g. from the result of TMongoCollection.FindDocs() to
// avoid most temporary conversion into JSON or TClientDataSet buffers
TDocVariantArrayDataSet = class(TSynVirtualDataSet)
protected
fValues: TVariantDynArray;
fColumns: array of record
Name: RawUTF8;
FieldType: TSQLDBFieldType;
end;
fTemp64: Int64;
fTempUTF8: RawUTF8;
fTempBlob: RawByteString;
procedure InternalInitFieldDefs; override;
function GetRecordCount: Integer; override;
function GetRowFieldData(Field: TField; RowIndex: integer;
out ResultLen: Integer; OnlyCheckNull: boolean): Pointer; override;
function SearchForField(const aLookupFieldName: RawUTF8; const aLookupValue: variant;
aOptions: TLocateOptions): integer; override;
public
/// initialize the virtual TDataSet from a dynamic array of TDocVariant
// - you can set the expected column names and types matching the results
// document layout - if no column information is specified, the first
// TDocVariant will be used as reference
constructor Create(Owner: TComponent; const Data: TVariantDynArray;
const ColumnNames: array of RawUTF8; const ColumnTypes: array of TSQLDBFieldType); reintroduce;
end;
{$endif}
const
/// map the VCL string type, depending on the Delphi compiler version
{$ifdef UNICODE}
ftDefaultVCLString = ftWideString;
{$else}
ftDefaultVCLString = ftString;
{$endif}
/// map the best ft*Memo type available, depending on the Delphi compiler version
{$ifdef ISDELPHI2007ANDUP}
ftDefaultMemo = ftWideMemo;
{$else}
ftDefaultMemo = ftMemo;
{$endif}
/// append a TBcd value as text to the output buffer
// - very optimized for speed
procedure AddBcd(WR: TTextWriter; const AValue: TBcd);
type
/// a string buffer, used by InternalBCDToBuffer to store its output text
TBCDBuffer = array[0..66] of AnsiChar;
/// convert a TBcd value as text to the output buffer
// - buffer is to be array[0..66] of AnsiChar
// - returns the resulting text start in PBeg, and the length as function result
// - does not handle negative sign and 0 value - see AddBcd() function use case
// - very optimized for speed
function InternalBCDToBuffer(const AValue: TBcd; out ADest: TBCDBuffer; var PBeg: PAnsiChar): integer;
/// convert a TBcd value into a currency
// - purepascal version included in latest Delphi versions is slower than this
function BCDToCurr(const AValue: TBcd; var Curr: Currency): boolean;
/// convert a TBcd value into a RawUTF8 text
// - will call fast InternalBCDToBuffer function
procedure BCDToUTF8(const AValue: TBcd; var result: RawUTF8); overload;
/// convert a TBcd value into a RawUTF8 text
// - will call fast InternalBCDToBuffer function
function BCDToUTF8(const AValue: TBcd): RawUTF8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert a TBcd value into a VCL string text
// - will call fast InternalBCDToBuffer function
function BCDToString(const AValue: TBcd): string;
/// export all rows of a TDataSet into JSON
// - will work for any kind of TDataSet
function DataSetToJSON(Data: TDataSet): RawUTF8;
{$ifndef NOVARIANTS}
/// convert a dynamic array of TDocVariant result into a VCL DataSet
// - this function is just a wrapper around TDocVariantArrayDataSet.Create()
// - the TDataSet will be opened once created
function ToDataSet(aOwner: TComponent; const Data: TVariantDynArray;
const ColumnNames: array of RawUTF8; const ColumnTypes: array of TSQLDBFieldType): TDocVariantArrayDataSet; overload;
{$endif}
implementation
function InternalBCDToBuffer(const AValue: TBcd; out ADest: TBCDBuffer; var PBeg: PAnsiChar): integer;
var i,DecimalPos: integer;
P,Frac: PByte;
PEnd: PAnsiChar;
begin
result := 0;
if AValue.Precision=0 then
exit;
DecimalPos := AValue.Precision-(AValue.SignSpecialPlaces and $3F);
P := @ADest;
Frac := @Avalue.Fraction;
for i := 0 to AValue.Precision-1 do begin
if i=DecimalPos then
if i=0 then begin
PWord(P)^ := ord('0')+ord('.')shl 8;
inc(P,2);
end else begin
P^ := ord('.');
inc(P);
end;
if (i and 1)=0 then
P^ := ((Frac^ and $F0) shr 4)+ord('0') else begin
P^ := ((Frac^ and $0F))+ord('0');
inc(Frac);
end;
inc(P);
end;
// remove trailing 0 after decimal
if AValue.Precision>DecimalPos then begin
repeat dec(P) until (P^<>ord('0')) or (P=@ADest);
PEnd := pointer(P);
if PEnd^<>'.' then
inc(PEnd);
end else
PEnd := pointer(P);
PEnd^ := #0;
// remove leading 0
PBeg := @ADest;
while (PBeg[0]='0') and (PBeg[1] in ['0'..'9']) do inc(PBeg);
result := PEnd-PBeg;
end;
procedure AddBcd(WR: TTextWriter; const AValue: TBcd);
var len: integer;
PBeg: PAnsiChar;
tmp: TBCDBuffer;
begin
len := InternalBCDToBuffer(AValue,tmp,PBeg);
if len<=0 then
WR.Add('0') else begin
if AValue.SignSpecialPlaces and $80=$80 then
WR.Add('-');
WR.AddNoJSONEscape(PBeg,len);
end;
end;
function BCDToCurr(const AValue: TBcd; var Curr: Currency): boolean;
var len: integer;
PBeg: PAnsiChar;
tmp: TBCDBuffer;
begin
len := InternalBCDToBuffer(AValue,tmp,PBeg);
if len<=0 then
Curr := 0 else begin
PInt64(@Curr)^ := StrToCurr64(pointer(PBeg));
if AValue.SignSpecialPlaces and $80=$80 then
Curr := -Curr;
end;
result := true;
end;
procedure BCDToUTF8(const AValue: TBcd; var result: RawUTF8);
var len: integer;
PBeg: PAnsiChar;
tmp: TBCDBuffer;
begin
len := InternalBCDToBuffer(AValue,tmp,PBeg);
SetString(result,PBeg,len);
end;
function BCDToUTF8(const AValue: TBcd): RawUTF8;
begin
BCDToUTF8(AValue,result);
end;
function BCDToString(const AValue: TBcd): string;
var len: integer;
PBeg: PAnsiChar;
tmp: TBCDBuffer;
begin
len := InternalBCDToBuffer(AValue,tmp,PBeg);
Ansi7ToString(PWinAnsiChar(PBeg),len,result);
end;
var
GlobalDataSetCount: integer;
type
/// define how a single row is identified
// - for TSynVirtualDataSet, it is just the row index (starting at 0)
TRecInfoIdentifier = integer;
PRecInfoIdentifier = ^TRecInfoIdentifier;
/// pointer to an internal structure used to identify a row position
PRecInfo = ^TRecInfo;
/// internal structure used to identify a row position
TRecInfo = record
/// define how a single row is identified
RowIndentifier: TRecInfoIdentifier;
/// any associated bookmark
Bookmark: TRecInfoIdentifier;
/// any associated bookmark flag
BookmarkFlag: TBookmarkFlag;
end;
{ TSynVirtualDataSet }
function TSynVirtualDataSet.AllocRecordBuffer: TRecordBuffer;
begin
result := AllocMem(sizeof(TRecInfo));
end;
procedure TSynVirtualDataSet.FreeRecordBuffer(var Buffer: TRecordBuffer);
begin
FreeMem(Buffer);
Buffer := nil;
end;
procedure TSynVirtualDataSet.GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
begin
PRecInfoIdentifier(Data)^ := PRecInfo(Buffer)^.Bookmark;
end;
function TSynVirtualDataSet.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
begin
result := PRecInfo(Buffer)^.BookmarkFlag;
end;
function TSynVirtualDataSet.GetCanModify: Boolean;
begin
result := false; // we define a READ-ONLY TDataSet
end;
{$ifndef UNICODE}
function TSynVirtualDataSet.GetFieldData(Field: TField; Buffer: Pointer;
NativeFormat: Boolean): Boolean;
begin
if Field.DataType in [ftWideString] then
NativeFormat := true; // to force Buffer as PWideString
Result := inherited GetFieldData(Field, Buffer, NativeFormat);
end;
{$endif}
{$ifdef ISDELPHIXE3}
{$ifdef ISDELPHIXE4}
function TSynVirtualDataSet.GetFieldData(Field: TField; var Buffer: TValueBuffer): Boolean;
{$else}
function TSynVirtualDataSet.GetFieldData(Field: TField; Buffer: TValueBuffer): Boolean;
{$endif}
{$else}
function TSynVirtualDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
{$endif}
var Data, Dest: pointer;
RowIndex, DataLen, MaxLen: integer;
Temp: RawByteString;
OnlyTestForNull: boolean;
TS: TTimeStamp;
begin
OnlyTestForNull := (Buffer=nil);
RowIndex := PRecInfo(ActiveBuffer).RowIndentifier;
Data := GetRowFieldData(Field,RowIndex,DataLen,OnlyTestForNull);
result := Data<>nil; // null field or out-of-range RowIndex/Field
if OnlyTestForNull or not result then
exit;
Dest := pointer(Buffer); // works also if Buffer is [var] TValueBuffer
case Field.DataType of // Data^ points to Int64,Double,Blob,UTF8
ftBoolean:
PWORDBOOL(Dest)^ := PBoolean(Data)^;
ftInteger:
PInteger(Dest)^ := PInteger(Data)^;
ftLargeint, ftFloat, ftCurrency:
PInt64(Dest)^ := PInt64(Data)^;
ftDate, ftTime, ftDateTime:
if PDateTime(Data)^=0 then // handle 30/12/1899 date as NULL
result := false else begin // inlined DataConvert(Field,Data,Dest,true)
TS := DateTimeToTimeStamp(PDateTime(Data)^);
case Field.DataType of
ftDate: PDateTimeRec(Dest)^.Date := TS.Date;
ftTime: PDateTimeRec(Dest)^.Time := TS.Time;
ftDateTime:
if (TS.Time<0) or (TS.Date<=0) then
result := false else // matches ValidateTimeStamp() expectations
PDateTimeRec(Dest)^.DateTime := TimeStampToMSecs(TS);
end; // see NativeToDateTime/DateTimeToNative in TDataSet.DataConvert
end;
ftString: begin
if DataLen<>0 then begin
CurrentAnsiConvert.UTF8BufferToAnsi(Data,DataLen,Temp);
DataLen := length(Temp);
MaxLen := Field.DataSize-1; // without trailing #0
if DataLen>MaxLen then
DataLen := MaxLen;
move(pointer(Temp)^,Dest^,DataLen);
end;
PAnsiChar(Dest)[DataLen] := #0;
end;
ftWideString: begin
{$ifdef ISDELPHI2007ANDUP} // here Dest = PWideChar[] of DataSize bytes
if DataLen=0 then
PWideChar(Dest)^ := #0 else
UTF8ToWideChar(Dest,Data,(Field.DataSize-2)shr 1,DataLen);
{$else} // here Dest is PWideString
UTF8ToWideString(Data,DataLen,WideString(Dest^));
{$endif}
end;
// ftBlob,ftMemo,ftWideMemo should be retrieved by CreateBlobStream()
else raise EDatabaseError.CreateFmt('%s.GetFieldData unhandled DataType=%s (%d)',
[ClassName,GetEnumName(TypeInfo(TFieldType),ord(Field.DataType))^,ord(Field.DataType)]);
end;
end;
function TSynVirtualDataSet.GetBlobStream(Field: TField; RowIndex: integer): TStream;
var Data: pointer;
DataLen: integer;
begin
Data := GetRowFieldData(Field,RowIndex,DataLen,false);
if Data=nil then // should point to Blob or UTF8 data
result := nil else
case Field.DataType of
ftBlob:
result := TSynMemoryStream.Create(Data,DataLen);
ftMemo, ftString:
result := TRawByteStringStream.Create(CurrentAnsiConvert.UTF8BufferToAnsi(Data,DataLen));
{$ifdef ISDELPHI2007ANDUP} ftWideMemo, {$endif} ftWideString:
result := TRawByteStringStream.Create(Utf8DecodeToRawUnicode(Data,DataLen));
else raise EDatabaseError.CreateFmt('%s.CreateBlobStream DataType=%d',
[ClassName,ord(Field.DataType)]);
end;
end;
function TSynVirtualDataSet.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
begin
if Mode<>bmRead then
raise EDatabaseError.CreateFmt('%s BLOB should be ReadOnly',[ClassName]);
result := GetBlobStream(Field,PRecInfo(ActiveBuffer).RowIndentifier);
if result=nil then
result := TSynMemoryStream.Create; // null BLOB returns a void TStream
end;
function TSynVirtualDataSet.GetRecNo: Integer;
begin
result := fCurrentRow+1;
end;
function TSynVirtualDataSet.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
begin
result := grOK;
case GetMode of
gmPrior:
if fCurrentRow>0 then
dec(fCurrentRow) else
result := grBOF;
gmCurrent:
if fCurrentRow<0 then
result := grBOF else
if fCurrentRow>=GetRecordCount then
result := grEOF;
gmNext:
if fCurrentRow<GetRecordCount-1 then
inc(fCurrentRow) else
result := grEOF;
end;
if result=grOK then
with PRecInfo(Buffer)^ do begin
RowIndentifier := fCurrentRow;
BookmarkFlag := bfCurrent;
Bookmark := fCurrentRow;
end;
end;
function TSynVirtualDataSet.GetRecordSize: Word;
begin
result := SizeOf(TRecInfoIdentifier); // excluding Bookmark information
end;
procedure TSynVirtualDataSet.InternalClose;
begin
BindFields(false);
{$ifdef ISDELPHIXE6}
if not(lcPersistent in Fields.LifeCycles) then
{$else}
if DefaultFields then
{$endif}
DestroyFields;
fIsCursorOpen := False;
end;
procedure TSynVirtualDataSet.InternalFirst;
begin
fCurrentRow := -1;
end;
procedure TSynVirtualDataSet.InternalGotoBookmark(Bookmark: Pointer);
begin
fCurrentRow := PRecInfoIdentifier(Bookmark)^;
end;
procedure TSynVirtualDataSet.InternalHandleException;
begin
if Assigned(Classes.ApplicationHandleException) then
Classes.ApplicationHandleException(ExceptObject) else
SysUtils.ShowException(ExceptObject,ExceptAddr);
end;
procedure TSynVirtualDataSet.InternalInitRecord(Buffer: TRecordBuffer);
begin
FillcharFast(Buffer^,sizeof(TRecInfo),0);
end;
procedure TSynVirtualDataSet.InternalLast;
begin
fCurrentRow := GetRecordCount;
end;
procedure TSynVirtualDataSet.InternalOpen;
begin
BookmarkSize := SizeOf(TRecInfo)-sizeof(TRecInfoIdentifier);
InternalInitFieldDefs;
{$ifdef ISDELPHIXE6}
if not(lcPersistent in Fields.LifeCycles) then
{$else}
if DefaultFields then
{$endif}
CreateFields;
BindFields(true);
fCurrentRow := -1;
fIsCursorOpen := True;
end;
procedure TSynVirtualDataSet.InternalSetToRecord(Buffer: TRecordBuffer);
begin
fCurrentRow := PRecInfo(Buffer).RowIndentifier;
end;
function TSynVirtualDataSet.IsCursorOpen: Boolean;
begin
result := fIsCursorOpen;
end;
procedure TSynVirtualDataSet.SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
begin
PRecInfo(Buffer)^.Bookmark := PRecInfoIdentifier(Data)^;
end;
procedure TSynVirtualDataSet.SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag);
begin
PRecInfo(Buffer)^.BookmarkFlag := Value;
end;
procedure TSynVirtualDataSet.SetRecNo(Value: Integer);
begin
CheckBrowseMode;
if Value<>RecNo then begin
dec(Value);
if cardinal(Value)>=cardinal(GetRecordCount) then
raise ERangeError.CreateFmt('%s.SetRecNo(%d) with Count=%d',
[ClassName,Value+1,GetRecordCount]);
DoBeforeScroll;
fCurrentRow := Value;
Resync([rmCenter]);
DoAfterScroll;
end;
end;
constructor TSynVirtualDataSet.Create(Owner: TComponent);
begin
inherited Create(Owner);
inc(GlobalDataSetCount);
Name := ClassName+IntToStr(GlobalDataSetCount); // force unique name
end;
function TSynVirtualDataSet.SearchForField(const aLookupFieldName: RawUTF8;
const aLookupValue: variant; aOptions: TLocateOptions): integer;
begin
result := 0; // nothing found
end;
function TSynVirtualDataSet.Locate(const KeyFields: string;
const KeyValues: Variant; Options: TLocateOptions) : boolean;
var i, l, h, found: Integer;
{$ifdef ISDELPHIXE4}
FieldList: TList<TField>;
{$else}
FieldList: TList;
{$endif}
begin
CheckActive;
result := true;
if not IsEmpty then
if VarIsArray(KeyValues) then begin
{$ifdef ISDELPHIXE4}
FieldList := TList<TField>.Create;
{$else}
FieldList := TList.Create;
{$endif}
try
GetFieldList(FieldList,KeyFields);
l := VarArrayLowBound(KeyValues,1);
h := VarArrayHighBound(KeyValues,1);
if (FieldList.Count = 1) and (l < h) then begin
found := SearchForField(StringToUTF8(KeyFields),KeyValues,Options);
if found>0 then begin
RecNo := found;
exit;
end;
end
else for i := 0 to FieldList.Count - 1 do begin
found := SearchForField(StringToUTF8(TField(FieldList[i]).FieldName),
KeyValues[l+i],Options);
if found>0 then begin
RecNo := found;
exit;
end;
end;
finally
FieldList.Free;
end;
end else begin
found := SearchForField(StringToUTF8(KeyFields),KeyValues,Options);
if found>0 then begin
RecNo := found;
exit;
end;
end;
result := false;
end;
{$ifndef NOVARIANTS}
type // as in FMTBcd.pas
TFMTBcdData = class(TPersistent)
private
FBcd: TBcd;
end;
TFMTBcdVarData = packed record
VType: TVarType;
Reserved1, Reserved2, Reserved3: Word;
VBcd: TFMTBcdData;
Reserved4: Cardinal;
end;
class procedure TSynVirtualDataSet.BcdWrite(const aWriter: TTextWriter; const aValue);
begin
AddBCD(aWriter,TFMTBcdVarData(aValue).VBcd.FBcd);
end;
{$endif NOVARIANTS}
function DataSetToJSON(Data: TDataSet): RawUTF8;
var W: TJSONWriter;
f: integer;
blob: TRawByteStringStream;
begin
result := 'null';
if Data=nil then
exit;
Data.First;
if Data.Eof then
exit;
W := TJSONWriter.Create(nil,true,false);
try
// get col names and types
SetLength(W.ColNames,Data.FieldCount);
for f := 0 to high(W.ColNames) do
StringToUTF8(Data.FieldDefs[f].Name,W.ColNames[f]);
W.AddColumns;
W.Add('[');
repeat
W.Add('{');
for f := 0 to Data.FieldCount-1 do begin
W.AddString(W.ColNames[f]);
with Data.Fields[f] do
if IsNull then
W.AddShort('null') else
case DataType of
ftBoolean:
W.Add(AsBoolean);
ftSmallint, ftInteger, ftWord, ftAutoInc:
W.Add(AsInteger);
ftLargeint:
W.Add(TLargeintField(Data.Fields[f]).AsLargeInt);
ftFloat, ftCurrency: // TCurrencyField is sadly a TFloatField
W.Add(AsFloat,TFloatField(Data.Fields[f]).Precision);
ftBCD:
W.AddCurr64(AsCurrency);
ftFMTBcd:
AddBcd(W,AsBCD);
ftTimeStamp, ftDate, ftTime, ftDateTime: begin
W.Add('"');
W.AddDateTime(AsDateTime);
W.Add('"');
end;
ftString, ftFixedChar, ftMemo, ftGuid: begin
W.Add('"');
W.AddAnsiString({$ifdef UNICODE}AsAnsiString{$else}AsString{$endif},
twJSONEscape);
W.Add('"');
end;
ftWideString: begin
W.Add('"');
W.AddJSONEscapeW(pointer(TWideStringField(Data.Fields[f]).Value));
W.Add('"');
end;
ftVariant:
W.AddVariant(AsVariant);
ftBytes, ftVarBytes, ftBlob, ftGraphic, ftOraBlob, ftOraClob: begin
blob := TRawByteStringStream.Create;
try
(Data.Fields[f] as TBlobField).SaveToStream(blob);
W.WrBase64(pointer(blob.DataString),length(blob.DataString),true);
finally
blob.Free;
end;
end;
{$ifdef ISDELPHI2007ANDUP}
ftWideMemo, ftFixedWideChar: begin
W.Add('"');
W.AddJSONEscapeW(pointer(AsWideString));
W.Add('"');
end;
{$endif}
{$ifdef UNICODE}
ftShortint, ftByte:
W.Add(AsInteger);
ftLongWord:
W.AddU(TLongWordField(Data.Fields[f]).Value);
ftExtended:
W.AddDouble(AsFloat);
ftSingle:
W.Add(AsFloat,SINGLE_PRECISION);
{$endif}
else W.AddShort('null'); // unhandled field type
end;
W.Add(',');
end;
W.CancelLastComma;
W.Add('}',',');
Data.Next;
until Data.Eof;
W.CancelLastComma;
W.Add(']');
W.SetText(result);
finally
W.Free;
end;
end;
{ TDocVariantArrayDataSet }
constructor TDocVariantArrayDataSet.Create(Owner: TComponent;
const Data: TVariantDynArray; const ColumnNames: array of RawUTF8;
const ColumnTypes: array of TSQLDBFieldType);
var n,ndx,j: PtrInt;
first: PDocVariantData;
begin
fValues := Data;
n := Length(ColumnNames);
if n>0 then begin
if n<>length(ColumnTypes) then
raise ESynException.CreateUTF8('%.Create(ColumnNames<>ColumnTypes)',[self]);
SetLength(fColumns,n);
for ndx := 0 to n-1 do begin
fColumns[ndx].Name := ColumnNames[ndx];
fColumns[ndx].FieldType := ColumnTypes[ndx];
end;
end else
if fValues<>nil then begin
first := _Safe(fValues[0],dvObject);
SetLength(fColumns,first^.Count);
for ndx := 0 to first^.Count-1 do begin
fColumns[ndx].Name := first^.Names[ndx];
fColumns[ndx].FieldType := VariantTypeToSQLDBFieldType(first^.Values[ndx]);
case fColumns[ndx].FieldType of
SynTable.ftNull:
fColumns[ndx].FieldType := SynTable.ftBlob;
SynTable.ftCurrency:
fColumns[ndx].FieldType := SynTable.ftDouble;
SynTable.ftInt64: // ensure type coherency of whole column
for j := 1 to first^.Count-1 do
if j>=Length(fValues) then // check objects are consistent
break else
with _Safe(fValues[j],dvObject)^ do
if (ndx<Length(Names)) and IdemPropNameU(Names[ndx],fColumns[ndx].Name) then
if VariantTypeToSQLDBFieldType(Values[ndx]) in
[SynTable.ftNull,SynTable.ftDouble,SynTable.ftCurrency] then begin
fColumns[ndx].FieldType := SynTable.ftDouble;
break;
end;
end;
end;
end;
inherited Create(Owner);
end;
function TDocVariantArrayDataSet.GetRecordCount: Integer;
begin
result := length(fValues);
end;
function TDocVariantArrayDataSet.GetRowFieldData(Field: TField;
RowIndex: integer; out ResultLen: Integer; OnlyCheckNull: boolean): Pointer;
var F,ndx: integer;
wasString: Boolean;
begin
result := nil;
F := Field.Index;
if (cardinal(RowIndex)<cardinal(length(fValues))) and
(cardinal(F)<cardinal(length(fColumns))) and
not (fColumns[F].FieldType in [ftNull,SynTable.ftUnknown,SynTable.ftCurrency]) then
with _Safe(fValues[RowIndex])^ do
if (Kind=dvObject) and (Count>0) then begin
if IdemPropNameU(fColumns[F].Name,Names[F]) then
ndx := F else // optimistic match
ndx := GetValueIndex(fColumns[F].Name);
if ndx>=0 then
if VarIsEmptyOrNull(Values[ndx]) then
exit else begin
result := @fTemp64;
if not OnlyCheckNull then
case fColumns[F].FieldType of
ftInt64:
VariantToInt64(Values[ndx],fTemp64);
ftDouble,SynTable.ftDate:
VariantToDouble(Values[ndx],unaligned(PDouble(@fTemp64)^));
ftUTF8: begin
VariantToUTF8(Values[ndx],fTempUTF8,wasString);
result := pointer(fTempUTF8);
ResultLen := length(fTempUTF8);
end;
SynTable.ftBlob: begin
VariantToUTF8(Values[ndx],fTempUTF8,wasString);
if Base64MagicCheckAndDecode(pointer(fTempUTF8),length(fTempUTF8),fTempBlob) then begin
result := pointer(fTempBlob);
ResultLen := length(fTempBlob);
end;
end;
end;
end;
end;
end;
procedure TDocVariantArrayDataSet.InternalInitFieldDefs;
const TYPES: array[TSQLDBFieldType] of TFieldType = (
// ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency, ftDate, ftUTF8, ftBlob
ftWideString,ftWideString,ftLargeint,ftFloat,ftFloat,ftDate,ftWideString,ftBlob);
var F,siz: integer;
begin
FieldDefs.Clear;
for F := 0 to high(fColumns) do begin
if fColumns[F].FieldType=ftUTF8 then
siz := 16 else
siz := 0;
FieldDefs.Add(UTF8ToString(fColumns[F].Name),TYPES[fColumns[F].FieldType],siz);
end;
end;
function TDocVariantArrayDataSet.SearchForField(const aLookupFieldName: RawUTF8;
const aLookupValue: variant; aOptions: TLocateOptions): integer;
var f: integer;
begin
f := -1; // allows O(1) field lookup for invariant object columns
for result := 1 to length(fValues) do
with _Safe(fValues[result-1])^ do
if (Kind=dvObject) and (Count>0) then begin
if (cardinal(f)>=cardinal(Count)) or
not IdemPropNameU(aLookupFieldName,Names[f]) then
f := GetValueIndex(aLookupFieldName);
if (f>=0) and (SortDynArrayVariantComp(TVarData(Values[f]),
TVarData(aLookupValue),loCaseInsensitive in aOptions)=0) then
exit;
end;
result := 0;
end;
function ToDataSet(aOwner: TComponent; const Data: TVariantDynArray;
const ColumnNames: array of RawUTF8; const ColumnTypes: array of TSQLDBFieldType): TDocVariantArrayDataSet; overload;
begin
result := TDocVariantArrayDataSet.Create(aOwner,Data,ColumnNames,ColumnTypes);
result.Open;
end;
initialization
{$ifndef NOVARIANTS}
TTextWriter.RegisterCustomJSONSerializerForVariantByType(
VarFMTBcd,nil,TSynVirtualDataSet.BcdWrite);
{$endif}
end.