mORMot and Open Source friends
Check-in [aa4b00652c]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:
  • MAX_SQLFIELDS default is still 64, but can now be set to any value (64, 128, and 256 are optimized) so that you can have any number of fields in a Table
  • MAX_SQLTABLES default is now 256, i.e. you can have up to 256 tables in a TSQLModel instance (you can set any other value, on need)
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: aa4b00652c8ee0035935a7770fc17098a6bffff9
User & Date: ab 2011-03-21 09:09:26
Context
2011-03-21
10:09
fixed issue in TSQLRestClientDB.URI: wrong InternalState returned check-in: eac035a226 user: ab tags: trunk
09:09
  • MAX_SQLFIELDS default is still 64, but can now be set to any value (64, 128, and 256 are optimized) so that you can have any number of fields in a Table
  • MAX_SQLTABLES default is now 256, i.e. you can have up to 256 tables in a TSQLModel instance (you can set any other value, on need)
check-in: aa4b00652c user: ab tags: trunk
2011-03-19
08:41
Delphi 2009/2010/XE compatibility fix + UI compilation with TMS check-in: 6834918946 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/SQLite3Commons.pas.

324
325
326
327
328
329
330




331
332
333
334
335
336
337
...
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
....
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
....
6837
6838
6839
6840
6841
6842
6843
6844
6845
6846
6847
6848
6849
6850
6851
....
9083
9084
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095
9096
9097
....
9200
9201
9202
9203
9204
9205
9206
9207
9208
9209
9210
9211
9212
9213
9214
....
9253
9254
9255
9256
9257
9258
9259
9260
9261
9262
9263
9264
9265
9266
9267
.....
10399
10400
10401
10402
10403
10404
10405
10406
10407
10408
10409
10410
10411
10412
10413
.....
10437
10438
10439
10440
10441
10442
10443
10444
10445
10446
10447
10448
10449
10450
10451
.....
12261
12262
12263
12264
12265
12266
12267
12268
12269
12270
12271
12272
12273
12274
12275
.....
12296
12297
12298
12299
12300
12301
12302
12303
12304
12305
12306
12307
12308
12309
12310
.....
14411
14412
14413
14414
14415
14416
14417
14418
14419
14420
14421
14422
14423
14424
14425
14426
14427
.....
14648
14649
14650
14651
14652
14653
14654
14655
14656
14657
14658
14659
14660
14661
14662
    - enhanced TPropInfo.GetLongStrValue/SetLongStrValue methods, now converting
      RawUnicode, WinAnsiString, TSQLRawBlob and AnsiString properties
    - now ensure that no published property named ID or RowID was defined (this
      unique primary key field must be handled directly by TSQLRecord)
    - TSQLModel implementation speed up, in case of a huge number of registered
      TSQLRecord in the database Model
    - added a magic pattern check to ignore broadcasted WM_COPYDATA message






  String usage in the Synopse SQLite3 database framework:
    - RawUTF8 is used for every internal data usage, since both SQLite3 and
      JSON do expect UTF-8 encoding
    - WinAnsiString where WinAnsi-encoded AnsiString (code page 1252) are needed
    - generic string for i18n (in unit SQLite3i18n), i.e. text ready to be
................................................................................

{ ************ classes to access SQLite3 database data }

const
  /// maximum number of Tables in a Database Model
  // - this constant is used internaly to optimize memory usage in the
  // generated asm code
  MAX_SQLTABLES = 64;

type
  /// used to store bit set for all available Tables in a Database Model
  // - with current MAX_SQLTABLES value, uses 8 bytes of memory, therefore is
  // sometimes mapped to an Int64
  TSQLFieldTables = set of 0..MAX_SQLTABLES-1;

  /// a String used to store the BLOB content
  // - equals RawByteString for byte storage, to force no implicit charset
  // conversion, thatever the codepage of the resulting string is
  // - will identify a sftBlob field type, if used to define such a published
  // property
................................................................................

/// special comparaison function for sorting sftDateTime
// UTF-8 encoded values in the SQLite3 database or JSON content
function UTF8CompareISO8601(P1,P2: PUTF8Char): PtrInt;

const
  /// Supervisor Table access right, i.e. alllmighty over all fields
  ALL_ACCESS_RIGHTS = [0..MAX_SQLFIELDS-1];

  /// Supervisor Database access right, i.e. allmighty over all Tables
  SUPERVISOR_ACCESS_RIGHTS: TSQLAccessRights =
    (GET: ALL_ACCESS_RIGHTS; POST: ALL_ACCESS_RIGHTS;
     PUT: ALL_ACCESS_RIGHTS; DELETE: ALL_ACCESS_RIGHTS);

  /// special TSQLFieldBits value containing all field bits set to 1
................................................................................
    end;
    P[-1] := ')';
  end;
  // Assert(P-pointer(result)=length(result));
end;
var F: integer;
    FU: RawUTF8;
    Fields2, Values: array[0..MAX_SQLFIELDS] of RawUTF8;
begin
  result := '';
  if P=nil then
    exit;
  Len := 0;
  if pointer(Fields)=nil then begin
    // get "COL1"="VAL1" pairs, stopping at '}' or ']'
................................................................................
    result := fID;
end;

function TSQLRecord.GetHasBlob: boolean;
begin
  if Self=nil then
    result := false else
    result := Int64(RecordProps.BlobFieldsBits)<>0;
end;

function TSQLRecord.GetSimpleFieldCount: integer;
begin
  if Self=nil then
    result := 0 else
    result := length(RecordProps.SimpleFields);
................................................................................
end;
{$endif}

function TSQLRecord.Filter(const aFields: TSQLFieldBits): boolean;
var f, i: integer;
    Value, Old: RawUTF8;
begin
  result := (Int64(aFields)=0);
  if (self=nil) or result then
    // avoid GPF and handle case if no field was selected
    exit;
  with RecordProps do
  if Filters=nil then
    // no filter set yet -> process OK
    result := true else begin
................................................................................
  aInvalidFieldIndex: PInteger): string;
var f, i: integer;
    Value: RawUTF8;
    Validate: TSynValidate;
    ValidateRest: TSynValidateRest absolute Validate;
begin
  result := '';
  if (self=nil) or (Int64(aFields)=0) then
    // avoid GPF and handle case if no field was selected
    exit;
  with RecordProps do
  for f := 0 to high(Fields) do
  if not(FieldType[f] in [sftUnknown,sftMany]) then begin
    if (Filters<>nil) and (Filters[f]<>nil) then
      for i := 0 to Filters[f].Count-1 do begin
................................................................................
    BlobData: RawByteString;
    i: integer;
begin
  result := false;
  if (Self=nil) or (Value=nil) or (Value.fID<=0) then
    exit;
  with Value.RecordProps do
  if Int64(BlobFieldsBits)<>0 then begin
    URL := FormatUTF8('%/%/%/',[Model.Root,SQLTableName,Value.fID]);
    for i := 0 to high(Fields) do
      if i in BlobFieldsBits then
        // URI is 'ModelRoot/TableName/ID/BlobFieldName' with GET method
        if URI(FormatUTF8('%%',[URL,FieldsName[i]]),'GET',@BlobData).Lo=200 then
          SetLongStrProp(Value,Fields[i],BlobData) else
          exit;
................................................................................
    BlobData: RawByteString;
    i: integer;
begin
  result := false;
  if (Self=nil) or (Value=nil) or (Value.fID<=0) then
    exit;
  with Value.RecordProps do
  if Int64(BlobFieldsBits)<>0 then begin
    URL := FormatUTF8('%/%/%/',[Model.Root,SQLTableName,Value.fID]);
    for i := 0 to high(Fields) do
      if i in BlobFieldsBits then begin
        GetLongStrProp(Value,Fields[i],BlobData);
        // URI is 'ModelRoot/TableName/ID/BlobFieldName' with PUT method
        if URI(FormatUTF8('%%',[URL,FieldsName[i]]),'PUT',nil,nil,@BlobData).Lo<>200 then
          exit;
................................................................................
    exit;
  end;
  if fValue.Count=0 then
    result := 1 else // default ID for a void table
    result := TSQLRecord(fValue.List[fValue.Count-1]).fID+1; // tricky new ID compute
  Rec := fStoredClass.Create;
  Rec.FillFrom(SentData);
  if (Int64(fIsUnique)<>0) and not AreUniqueFieldsOK(Rec) then begin
    result := 0; // mark error
    Rec.Free;
    exit;
  end;
  Rec.fID := result;
  fValue.Add(Rec);
  fModified := true;
................................................................................
     Owner.OnUpdateEvent(self,seAdd,Rec.RecordClass,result);
end;

function TSQLRestServerStaticInMemory.AreUniqueFieldsOK(Rec: TSQLRecord): boolean;
var F, i: integer;
    Val: RawUTF8;
begin
  if Int64(fIsUnique)<>0 then
  // ensure UNIQUE fields correctness
  with fStoredClassProps do begin
    result := false;
    for F := 0 to High(Fields) do
      if F in fIsUnique then begin
        Val := Fields[F]^.GetValue(Rec,false);
        for i := 0 to fValue.Count-1 do
................................................................................
begin
  assert(aTable<>nil); // should not be called directly, but via PropsCreate()
  Table := aTable;
  SQLTableName := GetDisplayNameFromClass(aTable);
  ClassProp := InternalClassProp(aTable);
  assert(ClassProp<>nil);
  nProps := PClassProp(aTable)^.FieldCountWithParents;
  if nProps>=MAX_SQLFIELDS then // for now, we store Fields in an Int64
    raise Exception.CreateFmt('%s has too many fields: %d>%d',
      [aTable.ClassName,nProps,MAX_SQLFIELDS-1]);
  SetLength(FieldType,nProps);
  SetLength(Fields,nProps);
  SetLength(FieldsName,nProps);
  SetLength(ManyFields,nProps);
  SetLength(SimpleFields,nProps);
  MainField[false] := -1;
  MainField[true] := -1;
................................................................................
  inherited;
end;

function TSQLRecordProperties.FieldIndexsFromRawUTF8(const aFields: array of RawUTF8;
  var Bits: TSQLFieldBits): boolean;
var f,ndx: integer;
begin
  Int64(Bits) := 0;
  result := false;
  if self=nil then
    exit;
  for f := 0 to high(aFields) do begin
    ndx := FieldIndexFromRawUTF8(aFields[f]);
    if ndx<0 then
      exit; // invalid field name






>
>
>
>







 







|



<
<







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|

|







 







|







324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
...
418
419
420
421
422
423
424
425
426
427
428


429
430
431
432
433
434
435
....
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
....
6839
6840
6841
6842
6843
6844
6845
6846
6847
6848
6849
6850
6851
6852
6853
....
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095
9096
9097
9098
9099
....
9202
9203
9204
9205
9206
9207
9208
9209
9210
9211
9212
9213
9214
9215
9216
....
9255
9256
9257
9258
9259
9260
9261
9262
9263
9264
9265
9266
9267
9268
9269
.....
10401
10402
10403
10404
10405
10406
10407
10408
10409
10410
10411
10412
10413
10414
10415
.....
10439
10440
10441
10442
10443
10444
10445
10446
10447
10448
10449
10450
10451
10452
10453
.....
12263
12264
12265
12266
12267
12268
12269
12270
12271
12272
12273
12274
12275
12276
12277
.....
12298
12299
12300
12301
12302
12303
12304
12305
12306
12307
12308
12309
12310
12311
12312
.....
14413
14414
14415
14416
14417
14418
14419
14420
14421
14422
14423
14424
14425
14426
14427
14428
14429
.....
14650
14651
14652
14653
14654
14655
14656
14657
14658
14659
14660
14661
14662
14663
14664
    - enhanced TPropInfo.GetLongStrValue/SetLongStrValue methods, now converting
      RawUnicode, WinAnsiString, TSQLRawBlob and AnsiString properties
    - now ensure that no published property named ID or RowID was defined (this
      unique primary key field must be handled directly by TSQLRecord)
    - TSQLModel implementation speed up, in case of a huge number of registered
      TSQLRecord in the database Model
    - added a magic pattern check to ignore broadcasted WM_COPYDATA message
    - MAX_SQLFIELDS default is still 64, but can now be set to any value (64, 128,
      and 256 are optimized) so that you can have any number of fields in a Table
    - MAX_SQLTABLES default is now 256, i.e. you can have up to 256 tables in a
      TSQLModel instance (you can set any other value, on need)


  String usage in the Synopse SQLite3 database framework:
    - RawUTF8 is used for every internal data usage, since both SQLite3 and
      JSON do expect UTF-8 encoding
    - WinAnsiString where WinAnsi-encoded AnsiString (code page 1252) are needed
    - generic string for i18n (in unit SQLite3i18n), i.e. text ready to be
................................................................................

{ ************ classes to access SQLite3 database data }

const
  /// maximum number of Tables in a Database Model
  // - this constant is used internaly to optimize memory usage in the
  // generated asm code
  MAX_SQLTABLES = 256;

type
  /// used to store bit set for all available Tables in a Database Model


  TSQLFieldTables = set of 0..MAX_SQLTABLES-1;

  /// a String used to store the BLOB content
  // - equals RawByteString for byte storage, to force no implicit charset
  // conversion, thatever the codepage of the resulting string is
  // - will identify a sftBlob field type, if used to define such a published
  // property
................................................................................

/// special comparaison function for sorting sftDateTime
// UTF-8 encoded values in the SQLite3 database or JSON content
function UTF8CompareISO8601(P1,P2: PUTF8Char): PtrInt;

const
  /// Supervisor Table access right, i.e. alllmighty over all fields
  ALL_ACCESS_RIGHTS = [0..MAX_SQLTABLES-1];

  /// Supervisor Database access right, i.e. allmighty over all Tables
  SUPERVISOR_ACCESS_RIGHTS: TSQLAccessRights =
    (GET: ALL_ACCESS_RIGHTS; POST: ALL_ACCESS_RIGHTS;
     PUT: ALL_ACCESS_RIGHTS; DELETE: ALL_ACCESS_RIGHTS);

  /// special TSQLFieldBits value containing all field bits set to 1
................................................................................
    end;
    P[-1] := ')';
  end;
  // Assert(P-pointer(result)=length(result));
end;
var F: integer;
    FU: RawUTF8;
    Fields2, Values: array[0..MAX_SQLFIELDS-1] of RawUTF8;
begin
  result := '';
  if P=nil then
    exit;
  Len := 0;
  if pointer(Fields)=nil then begin
    // get "COL1"="VAL1" pairs, stopping at '}' or ']'
................................................................................
    result := fID;
end;

function TSQLRecord.GetHasBlob: boolean;
begin
  if Self=nil then
    result := false else
    result := not IsZero(@RecordProps.BlobFieldsBits);
end;

function TSQLRecord.GetSimpleFieldCount: integer;
begin
  if Self=nil then
    result := 0 else
    result := length(RecordProps.SimpleFields);
................................................................................
end;
{$endif}

function TSQLRecord.Filter(const aFields: TSQLFieldBits): boolean;
var f, i: integer;
    Value, Old: RawUTF8;
begin
  result := IsZero(@aFields);
  if (self=nil) or result then
    // avoid GPF and handle case if no field was selected
    exit;
  with RecordProps do
  if Filters=nil then
    // no filter set yet -> process OK
    result := true else begin
................................................................................
  aInvalidFieldIndex: PInteger): string;
var f, i: integer;
    Value: RawUTF8;
    Validate: TSynValidate;
    ValidateRest: TSynValidateRest absolute Validate;
begin
  result := '';
  if (self=nil) or IsZero(@aFields) then
    // avoid GPF and handle case if no field was selected
    exit;
  with RecordProps do
  for f := 0 to high(Fields) do
  if not(FieldType[f] in [sftUnknown,sftMany]) then begin
    if (Filters<>nil) and (Filters[f]<>nil) then
      for i := 0 to Filters[f].Count-1 do begin
................................................................................
    BlobData: RawByteString;
    i: integer;
begin
  result := false;
  if (Self=nil) or (Value=nil) or (Value.fID<=0) then
    exit;
  with Value.RecordProps do
  if not IsZero(@BlobFieldsBits) then begin
    URL := FormatUTF8('%/%/%/',[Model.Root,SQLTableName,Value.fID]);
    for i := 0 to high(Fields) do
      if i in BlobFieldsBits then
        // URI is 'ModelRoot/TableName/ID/BlobFieldName' with GET method
        if URI(FormatUTF8('%%',[URL,FieldsName[i]]),'GET',@BlobData).Lo=200 then
          SetLongStrProp(Value,Fields[i],BlobData) else
          exit;
................................................................................
    BlobData: RawByteString;
    i: integer;
begin
  result := false;
  if (Self=nil) or (Value=nil) or (Value.fID<=0) then
    exit;
  with Value.RecordProps do
  if not IsZero(@BlobFieldsBits) then begin
    URL := FormatUTF8('%/%/%/',[Model.Root,SQLTableName,Value.fID]);
    for i := 0 to high(Fields) do
      if i in BlobFieldsBits then begin
        GetLongStrProp(Value,Fields[i],BlobData);
        // URI is 'ModelRoot/TableName/ID/BlobFieldName' with PUT method
        if URI(FormatUTF8('%%',[URL,FieldsName[i]]),'PUT',nil,nil,@BlobData).Lo<>200 then
          exit;
................................................................................
    exit;
  end;
  if fValue.Count=0 then
    result := 1 else // default ID for a void table
    result := TSQLRecord(fValue.List[fValue.Count-1]).fID+1; // tricky new ID compute
  Rec := fStoredClass.Create;
  Rec.FillFrom(SentData);
  if not IsZero(@fIsUnique) and not AreUniqueFieldsOK(Rec) then begin
    result := 0; // mark error
    Rec.Free;
    exit;
  end;
  Rec.fID := result;
  fValue.Add(Rec);
  fModified := true;
................................................................................
     Owner.OnUpdateEvent(self,seAdd,Rec.RecordClass,result);
end;

function TSQLRestServerStaticInMemory.AreUniqueFieldsOK(Rec: TSQLRecord): boolean;
var F, i: integer;
    Val: RawUTF8;
begin
  if not IsZero(@fIsUnique) then
  // ensure UNIQUE fields correctness
  with fStoredClassProps do begin
    result := false;
    for F := 0 to High(Fields) do
      if F in fIsUnique then begin
        Val := Fields[F]^.GetValue(Rec,false);
        for i := 0 to fValue.Count-1 do
................................................................................
begin
  assert(aTable<>nil); // should not be called directly, but via PropsCreate()
  Table := aTable;
  SQLTableName := GetDisplayNameFromClass(aTable);
  ClassProp := InternalClassProp(aTable);
  assert(ClassProp<>nil);
  nProps := PClassProp(aTable)^.FieldCountWithParents;
  if nProps>MAX_SQLFIELDS then 
    raise Exception.CreateFmt('%s has too many fields: %d>%d',
      [aTable.ClassName,nProps,MAX_SQLFIELDS]);
  SetLength(FieldType,nProps);
  SetLength(Fields,nProps);
  SetLength(FieldsName,nProps);
  SetLength(ManyFields,nProps);
  SetLength(SimpleFields,nProps);
  MainField[false] := -1;
  MainField[true] := -1;
................................................................................
  inherited;
end;

function TSQLRecordProperties.FieldIndexsFromRawUTF8(const aFields: array of RawUTF8;
  var Bits: TSQLFieldBits): boolean;
var f,ndx: integer;
begin
  fillchar(Bits,sizeof(TSQLFieldBits),0);
  result := false;
  if self=nil then
    exit;
  for f := 0 to high(aFields) do begin
    ndx := FieldIndexFromRawUTF8(aFields[f]);
    if ndx<0 then
      exit; // invalid field name

Changes to SynBigTable.pas.

2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
....
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
    exit; // nothing new
  if Count=0 then begin
    // no data to refresh
    Table.AddedField.Free; // do it once
    Table.AddedField := nil;
  end else begin
    // some data to refresh: guess field added, and process
    Int64(Fields) := 0;
    for F := 0 to Table.FieldCount-1 do
      if Table.AddedField.IndexOf(Table.Field[F])<0 then
        Include(Fields,F);
    Table.AddedField.Free; // do it once
    Table.AddedField := nil;
    RecreateFileContent(Table.UpdateFieldEvent,Fields);
  end;
................................................................................
  if (self=nil) or (Table.AddedField=nil) then
    exit; // nothing new
  if fMetaDataCount=0 then begin // no data to refresh
    Table.AddedField.Free; // do it once
    Table.AddedField:= nil;
    exit;
  end;
  Int64(AvailableFields) := 0;
  for F := 0 to Table.FieldCount-1 do
    if Table.AddedField.IndexOf(Table.Field[F])<0 then
      Include(AvailableFields,F);
  Table.AddedField.Free; // do it once
  Table.AddedField := nil;
  for i := 0 to Count-1 do
    fMetaDataRecords[i] := Table.UpdateFieldRecord(pointer(fMetaDataRecords[i]),AvailableFields);






|







 







|







2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
....
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
    exit; // nothing new
  if Count=0 then begin
    // no data to refresh
    Table.AddedField.Free; // do it once
    Table.AddedField := nil;
  end else begin
    // some data to refresh: guess field added, and process
    fillchar(Fields,sizeof(Fields),0);
    for F := 0 to Table.FieldCount-1 do
      if Table.AddedField.IndexOf(Table.Field[F])<0 then
        Include(Fields,F);
    Table.AddedField.Free; // do it once
    Table.AddedField := nil;
    RecreateFileContent(Table.UpdateFieldEvent,Fields);
  end;
................................................................................
  if (self=nil) or (Table.AddedField=nil) then
    exit; // nothing new
  if fMetaDataCount=0 then begin // no data to refresh
    Table.AddedField.Free; // do it once
    Table.AddedField:= nil;
    exit;
  end;
  fillchar(AvailableFields,sizeof(AvailableFields),0);
  for F := 0 to Table.FieldCount-1 do
    if Table.AddedField.IndexOf(Table.Field[F])<0 then
      Include(AvailableFields,F);
  Table.AddedField.Free; // do it once
  Table.AddedField := nil;
  for i := 0 to Count-1 do
    fMetaDataRecords[i] := Table.UpdateFieldRecord(pointer(fMetaDataRecords[i]),AvailableFields);

Changes to SynCommons.pas.

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
...
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
...
666
667
668
669
670
671
672

673
674
675
676
677
678
679
....
2015
2016
2017
2018
2019
2020
2021

2022
2023
2024
2025
2026
2027
2028
2029
2030
....
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044


2045
2046
2047
2048
2049
2050
2051
....
3034
3035
3036
3037
3038
3039
3040






3041
3042
3043
3044
3045
3046
3047
.....
10538
10539
10540
10541
10542
10543
10544

































10545
10546
10547
10548
10549
10550
10551
.....
16615
16616
16617
16618
16619
16620
16621



16622
16623
16624
16625
16626
16627
16628
.....
18718
18719
18720
18721
18722
18723
18724
18725
18726
18727
18728
18729
18730
18731
18732
.....
18928
18929
18930
18931
18932
18933
18934
18935
18936
18937
18938
18939
18940
18941
18942
.....
20339
20340
20341
20342
20343
20344
20345
20346
20347
20348
20349
20350
20351
20352
20353
  - fixed issue in TSynTableFieldProperties: wrong constraint evaluation and
    index refresh at records update
  - faster implementation of Move() for Delphi versions with no FastCode inside
  - great performance improvement in TSynTableFieldProperties for update process
  - new ConvertCaseUTF8(), UpperCaseU(), LowerCaseU(), Int64ToUInt32(),
    GetCardinalDef(), IsValidEmail, IsValidIP4Address(), PatchCodePtrUInt(),
    GetCaptionFromClass(), GetDisplayNameFromClass(), DateTimeToIso8601Text()
    StrUInt32() and StringBufferToUtf8() procedures or functions (with
    associated tests)
  - new grep-like IsMatch() function for basic pattern matching
  - introducing direct content filtering and validation using
    TSynFilterOrValidate dedicated classes, for both TSQLRecord and Big Table
  - filtering is handled via some TSynFilter classes - TSynFilterUpperCase,
    TSynFilterUpperCaseU, TSynFilterLowerCase, TSynFilterLowerCaseU and
    TSynFilterTrim e.g.
................................................................................
  - dedicated TSynTableFieldProperties.Validate method for validation (e.g. a
    TSynValidateTableUniqueField instance is created if tfoUnique is in Options)
  - dedicated TSynTableFieldProperties.Filter method for filtering (using
    common TSynFilter classes, working at UTF-8 Text content)
  - introducing the GarbageCollector TObjectList for handling a global garbage
    collector for instances which must live during the whole executable process
    (used e.g. to avoid a memory leak for "class var" or such variables)
  - new BinToBase64, Base64ToBin and IsBase64 conversion functions
  - new low-level RTTI functions for handling record types: RecordEquals,
    RecordSave, RecordSaveLength, RecordLoad
  - new TDynArray object, which is a wrapper around any dynamic array: you can
    now access to the dynamic array using TList-like properties and methods,
    e.g. Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort and some
    new methods like LoadFromStream, SaveToStream, LoadFrom and SaveTo which
    allow fast binary serialization of any dynamic array, even containing
    strings or records; a CreateOrderedIndex method is also available to
    create individual index according to the dynamic array content; and any
    dynamic array can be serialized as UTF-8 JSON via TTextWriter.AddDynArrayJSON
    and TDynArray.LoadFromJSON methods 

}


{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER

interface
................................................................................
function Pos(const substr, str: RawUTF8): Integer; overload; inline;
{$endif}

{$endif LVCL}
{$endif PUREPASCAL}

{$endif ENHANCEDRTL}


/// use our fast RawUTF8 version of IntToStr()
// - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009
// - only usefull if our Enhanced Runtime (or LVCL) library is not installed
function Int64ToUtf8(Value: Int64): RawUTF8;

/// use our fast RawUTF8 version of IntToStr()
................................................................................



{ ****************** text buffer and JSON functions and classes ********* }

const
  /// maximum number of fields in a Table

  // - this constant is used internaly to optimize memory usage in the
  // generated asm code, and to map TSQLFieldBits into an Int64
  MAX_SQLFIELDS = 64;

  /// number of entries in the TSynCache, 256 is big enough on practice
  // - code is somewhat faster and easier with a fixed cache size
  // - don't make the cache too big: may consume too much memory
  MAX_SYNCACHE = 256;

................................................................................
  JSON_BASE64_MAGIC_QUOTE = ord('"')+cardinal(JSON_BASE64_MAGIC) shl 8;

  /// '"' + UTF-8 encoded \uFFF0 special code
  JSON_BASE64_MAGIC_QUOTE_VAR: cardinal = JSON_BASE64_MAGIC_QUOTE;

type
  /// used to store bit set for all available fiels in a Table
  // - with current MAX_SQLFIELDS value, uses 8 bytes of memory, therefore is
  // sometimes mapped to an Int64
  TSQLFieldBits = set of 0..MAX_SQLFIELDS-1;



  /// simple writer to a Stream, specialized for the TEXT format
  // - use an internal buffer, faster than string+string
  // - some dedicated methods is able to encode any data with JSON escape
  TTextWriter = class
  protected
    B, BEnd: PUTF8Char;
................................................................................
procedure SetBit64(var Bits: Int64; aIndex: PtrInt);
  {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}

/// unset/clear a particular bit into a Int64 bit array (max aIndex is 63)
procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt);
  {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}









{ ************ fast ISO-8601 types and conversion routines }

/// Date/Time conversion from ISO-8601
// - handle 'YYYYMMDDThhmmss' and 'YYYY-MM-DD hh:mm:ss' format
function Iso8601ToDateTime(const S: RawUTF8): TDateTime;
................................................................................
var tmp: RawUTF8;
    err: integer;
begin
  result := UrlDecodeValue(U, Upper, tmp, Next);
  if result then
    Value := GetExtended(pointer(tmp),err);
end;


































function Hash32(const Text: RawByteString): cardinal;
{$ifdef PUREPASCAL} // this code is quite as fast as the optimized asm below
function SubHash(P: PCardinalArray): cardinal;
{$ifdef HASINLINE}inline;{$endif}
var s1,s2: cardinal;
    i, L: PtrInt;
................................................................................
    Check(UpperCase(U)=RawUTF8(SysUtils.UpperCase(string(U))));
    {$endif}
    Check(StringToUTF8(UTF8ToString(U))=U);
    if U='' then
      continue;
    Check(UnQuoteSQLString(pointer(QuotedStr(U,'"')),res)<>nil);
    Check(res=U);



  end;
  Check(UnQuoteSQLString('"one two"',U)<>nil);
  Check(U='one two');
  Check(UnQuoteSQLString('one two',U)<>nil);
  Check(U='ne tw');
  Check(UnQuoteSQLString('"one "" two"',U)<>nil);
  Check(U='one " two');
................................................................................
  AfterFieldModif;
end;

function TSynTable.CreateJSONWriter(JSON: TStream; Expand, withID: boolean;
  const Fields: TSQLFieldBits): TJSONWriter;
var i, n: integer;
begin
  if (self=nil) or ((Int64(Fields)=0) and not withID) then begin
    result := nil; // no data to retrieve
    exit;
  end;
  // get col max count
  if withID then
    n := 1 else
    n := 0;
................................................................................
function TSynTable.IterateJSONValues(Sender: TObject; Opaque: pointer;
  ID: integer; Data: pointer; DataLen: integer): boolean;
var Statement: TSynTableStatement absolute Opaque;
    F: TSynTableFieldProperties;
    FIndex: cardinal;
begin  // note: we should have handled -2 (=COUNT) case already
  if (self=nil) or (Statement=nil) or (Data=nil) or
     (Statement.WhereValueSBF='') or (Int64(Statement.Fields)=0) then begin
    result := false;
    exit;
  end;
  result := true;
  FIndex := Statement.WhereField;
  if FIndex=0 then begin
    if ID<>Statement.WhereValueInteger then
................................................................................
    GetFieldProp;
    TableName := Prop;
    WhereField := -2;      // mark COUNT(*)
    WhereValue := 'COUNT'; // not void
    exit;
  end else begin
    withID := false;
    Int64(Fields) := 0;
    if not SetFields then
      exit else // we need at least one field name
      if P^=',' then
      repeat
        while P^ in [',',' '] do inc(P); // trim left
      until not SetFields; // add other CSV field names
  end;






|







 







|










|







 







>







 







>

|







 







|
<

>
>







 







>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>







 







|







 







|







 







|







115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
...
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
...
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
....
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
....
2037
2038
2039
2040
2041
2042
2043
2044

2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
....
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
.....
10547
10548
10549
10550
10551
10552
10553
10554
10555
10556
10557
10558
10559
10560
10561
10562
10563
10564
10565
10566
10567
10568
10569
10570
10571
10572
10573
10574
10575
10576
10577
10578
10579
10580
10581
10582
10583
10584
10585
10586
10587
10588
10589
10590
10591
10592
10593
.....
16657
16658
16659
16660
16661
16662
16663
16664
16665
16666
16667
16668
16669
16670
16671
16672
16673
.....
18763
18764
18765
18766
18767
18768
18769
18770
18771
18772
18773
18774
18775
18776
18777
.....
18973
18974
18975
18976
18977
18978
18979
18980
18981
18982
18983
18984
18985
18986
18987
.....
20384
20385
20386
20387
20388
20389
20390
20391
20392
20393
20394
20395
20396
20397
20398
  - fixed issue in TSynTableFieldProperties: wrong constraint evaluation and
    index refresh at records update
  - faster implementation of Move() for Delphi versions with no FastCode inside
  - great performance improvement in TSynTableFieldProperties for update process
  - new ConvertCaseUTF8(), UpperCaseU(), LowerCaseU(), Int64ToUInt32(),
    GetCardinalDef(), IsValidEmail, IsValidIP4Address(), PatchCodePtrUInt(),
    GetCaptionFromClass(), GetDisplayNameFromClass(), DateTimeToIso8601Text()
    StrUInt32(), StringBufferToUtf8() IsZero() procedures or functions (with
    associated tests)
  - new grep-like IsMatch() function for basic pattern matching
  - introducing direct content filtering and validation using
    TSynFilterOrValidate dedicated classes, for both TSQLRecord and Big Table
  - filtering is handled via some TSynFilter classes - TSynFilterUpperCase,
    TSynFilterUpperCaseU, TSynFilterLowerCase, TSynFilterLowerCaseU and
    TSynFilterTrim e.g.
................................................................................
  - dedicated TSynTableFieldProperties.Validate method for validation (e.g. a
    TSynValidateTableUniqueField instance is created if tfoUnique is in Options)
  - dedicated TSynTableFieldProperties.Filter method for filtering (using
    common TSynFilter classes, working at UTF-8 Text content)
  - introducing the GarbageCollector TObjectList for handling a global garbage
    collector for instances which must live during the whole executable process
    (used e.g. to avoid a memory leak for "class var" or such variables)
  - new BinToBase64, Base64ToBin and IsBase64 *fast* conversion functions
  - new low-level RTTI functions for handling record types: RecordEquals,
    RecordSave, RecordSaveLength, RecordLoad
  - new TDynArray object, which is a wrapper around any dynamic array: you can
    now access to the dynamic array using TList-like properties and methods,
    e.g. Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort and some
    new methods like LoadFromStream, SaveToStream, LoadFrom and SaveTo which
    allow fast binary serialization of any dynamic array, even containing
    strings or records; a CreateOrderedIndex method is also available to
    create individual index according to the dynamic array content; and any
    dynamic array can be serialized as UTF-8 JSON via TTextWriter.AddDynArrayJSON
    and TDynArray.LoadFromJSON methods

}


{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER

interface
................................................................................
function Pos(const substr, str: RawUTF8): Integer; overload; inline;
{$endif}

{$endif LVCL}
{$endif PUREPASCAL}

{$endif ENHANCEDRTL}


/// use our fast RawUTF8 version of IntToStr()
// - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009
// - only usefull if our Enhanced Runtime (or LVCL) library is not installed
function Int64ToUtf8(Value: Int64): RawUTF8;

/// use our fast RawUTF8 version of IntToStr()
................................................................................



{ ****************** text buffer and JSON functions and classes ********* }

const
  /// maximum number of fields in a Table
  // - default is 64, but can be set to any value (64, 128 and 256 are optimized)
  // - this constant is used internaly to optimize memory usage in the
  // generated asm code
  MAX_SQLFIELDS = 64;

  /// number of entries in the TSynCache, 256 is big enough on practice
  // - code is somewhat faster and easier with a fixed cache size
  // - don't make the cache too big: may consume too much memory
  MAX_SYNCACHE = 256;

................................................................................
  JSON_BASE64_MAGIC_QUOTE = ord('"')+cardinal(JSON_BASE64_MAGIC) shl 8;

  /// '"' + UTF-8 encoded \uFFF0 special code
  JSON_BASE64_MAGIC_QUOTE_VAR: cardinal = JSON_BASE64_MAGIC_QUOTE;

type
  /// used to store bit set for all available fiels in a Table
  // - with current MAX_SQLFIELDS value, 256 bits uses 64 bytes of memory

  TSQLFieldBits = set of 0..MAX_SQLFIELDS-1;

  PSQLFieldBits = ^TSQLFieldBits;

  /// simple writer to a Stream, specialized for the TEXT format
  // - use an internal buffer, faster than string+string
  // - some dedicated methods is able to encode any data with JSON escape
  TTextWriter = class
  protected
    B, BEnd: PUTF8Char;
................................................................................
procedure SetBit64(var Bits: Int64; aIndex: PtrInt);
  {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}

/// unset/clear a particular bit into a Int64 bit array (max aIndex is 63)
procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt);
  {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}

/// returns TRUE if all bytes equal zero
function IsZero(P: pointer; Length: integer): boolean; overload;

/// returns TRUE if no bit inside this TSQLFieldBits is set
function IsZero(Fields: PSQLFieldBits): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}


{ ************ fast ISO-8601 types and conversion routines }

/// Date/Time conversion from ISO-8601
// - handle 'YYYYMMDDThhmmss' and 'YYYY-MM-DD hh:mm:ss' format
function Iso8601ToDateTime(const S: RawUTF8): TDateTime;
................................................................................
var tmp: RawUTF8;
    err: integer;
begin
  result := UrlDecodeValue(U, Upper, tmp, Next);
  if result then
    Value := GetExtended(pointer(tmp),err);
end;

function IsZero(P: pointer; Length: integer): boolean;
var i: integer;
begin
  result := false;
  for i := 1 to Length shr 4 do // 16 bytes (4 DWORD) by loop - aligned read
    if (PCardinalArray(P)^[0]<>0) or (PCardinalArray(P)^[1]<>0) or
       (PCardinalArray(P)^[2]<>0) or (PCardinalArray(P)^[3]<>0) then
      exit else
      inc(PtrUInt(P),16);
  for i := 1 to Length and 15 do
    if PByte(P)^<>0 then
      exit else
      inc(PtrUInt(P));
  result := true;
end;

{$warnings off}
function IsZero(Fields: PSQLFieldBits): boolean; overload;
begin
  result := (Fields<>nil) and
  {$if MAX_SQLFIELDS=64}
    (PInt64(Fields)^=0)
  {$elseif MAX_SQLFIELDS=128}
    (PInt64Array(Fields)^[0]=0) and (PInt64Array(Fields)^[1]=0)
  {$elseif MAX_SQLFIELDS=256}
    (PInt64Array(Fields)^[0]=0) and (PInt64Array(Fields)^[1]=0) and
    (PInt64Array(Fields)^[2]=0) and (PInt64Array(Fields)^[3]=0)
  {$else}
     IsZero(Fields,sizeof(TSQLFieldBits))
  {$ifend}
end;
{$warnings on}

function Hash32(const Text: RawByteString): cardinal;
{$ifdef PUREPASCAL} // this code is quite as fast as the optimized asm below
function SubHash(P: PCardinalArray): cardinal;
{$ifdef HASINLINE}inline;{$endif}
var s1,s2: cardinal;
    i, L: PtrInt;
................................................................................
    Check(UpperCase(U)=RawUTF8(SysUtils.UpperCase(string(U))));
    {$endif}
    Check(StringToUTF8(UTF8ToString(U))=U);
    if U='' then
      continue;
    Check(UnQuoteSQLString(pointer(QuotedStr(U,'"')),res)<>nil);
    Check(res=U);
    Check(not IsZero(pointer(W),length(W)));
    fillchar(pointer(W)^,length(W),0);
    Check(IsZero(pointer(W),length(W)));
  end;
  Check(UnQuoteSQLString('"one two"',U)<>nil);
  Check(U='one two');
  Check(UnQuoteSQLString('one two',U)<>nil);
  Check(U='ne tw');
  Check(UnQuoteSQLString('"one "" two"',U)<>nil);
  Check(U='one " two');
................................................................................
  AfterFieldModif;
end;

function TSynTable.CreateJSONWriter(JSON: TStream; Expand, withID: boolean;
  const Fields: TSQLFieldBits): TJSONWriter;
var i, n: integer;
begin
  if (self=nil) or (IsZero(@Fields) and not withID) then begin
    result := nil; // no data to retrieve
    exit;
  end;
  // get col max count
  if withID then
    n := 1 else
    n := 0;
................................................................................
function TSynTable.IterateJSONValues(Sender: TObject; Opaque: pointer;
  ID: integer; Data: pointer; DataLen: integer): boolean;
var Statement: TSynTableStatement absolute Opaque;
    F: TSynTableFieldProperties;
    FIndex: cardinal;
begin  // note: we should have handled -2 (=COUNT) case already
  if (self=nil) or (Statement=nil) or (Data=nil) or
     (Statement.WhereValueSBF='') or IsZero(@Statement.Fields) then begin
    result := false;
    exit;
  end;
  result := true;
  FIndex := Statement.WhereField;
  if FIndex=0 then begin
    if ID<>Statement.WhereValueInteger then
................................................................................
    GetFieldProp;
    TableName := Prop;
    WhereField := -2;      // mark COUNT(*)
    WhereValue := 'COUNT'; // not void
    exit;
  end else begin
    withID := false;
    fillchar(Fields,sizeof(Fields),0);
    if not SetFields then
      exit else // we need at least one field name
      if P^=',' then
      repeat
        while P^ in [',',' '] do inc(P); // trim left
      until not SetFields; // add other CSV field names
  end;