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

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

Overview
Comment:{4164} several fixes and enhancements to TSQLTable
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 58246461d316bf4184984fb808e7bc9af2998d36
User & Date: ab 2018-01-24 11:50:39
Context
2018-01-24
15:48
{4165} minor TSQLTable fixes - no new feature check-in: 1187eb2d68 user: ab tags: trunk
11:50
{4164} several fixes and enhancements to TSQLTable check-in: 58246461d3 user: ab tags: trunk
11:49
{4163} fixed some compilation errors introducing by latest commit check-in: 60e6ff1900 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/mORMot.pas.

8146
8147
8148
8149
8150
8151
8152



8153
8154
8155
8156
8157
8158
8159

8160
8161
8162
8163
8164
8165
8166
....
8567
8568
8569
8570
8571
8572
8573
8574
8575
8576
8577
8578
8579
8580
8581
....
8618
8619
8620
8621
8622
8623
8624


8625
8626
8627
8628
8629
8630
8631
.....
25286
25287
25288
25289
25290
25291
25292

25293
25294
25295
25296
25297
25298
25299
25300
25301













25302
25303
25304
25305
25306
25307
25308
.....
25310
25311
25312
25313
25314
25315
25316

25317
25318
25319
25320
25321
25322
25323
25324
25325
25326
25327
25328

25329
25330
25331
25332
25333
25334
25335
.....
25337
25338
25339
25340
25341
25342
25343
25344

25345
25346
25347
25348
25349
25350
25351
25352
25353
25354
25355
25356
25357
25358
25359
25360
25361
25362
25363
25364
25365
25366
25367
25368
25369
25370
25371
25372
25373
25374
25375

25376


25377
25378
25379
25380
25381
25382
25383
25384
25385
25386
25387
25388
25389
25390
25391
25392
25393
25394
25395
25396
25397

25398

25399
25400
25401
25402
25403
25404
25405
.....
25770
25771
25772
25773
25774
25775
25776
25777
25778

25779
25780
25781
25782
25783
25784
25785
25786
25787
.....
25792
25793
25794
25795
25796
25797
25798
25799
25800
25801
25802
25803
25804
25805
25806
25807
25808
25809
25810
25811
25812
25813
25814
25815
25816
25817
25818
25819
25820
25821
25822
25823
25824
25825
25826
25827
25828
25829
25830
25831
25832
25833
25834
25835

25836
25837
25838




25839
25840
25841
25842
25843
25844
25845
25846
25847
25848
25849
25850
25851
25852
25853
.....
25924
25925
25926
25927
25928
25929
25930
25931
25932
25933
25934
25935
25936
25937
25938
25939
25940
25941
25942
25943
25944
25945
25946
25947
25948
25949
25950
25951
25952
25953
25954
.....
25956
25957
25958
25959
25960
25961
25962
25963
25964
25965
25966
25967
25968
25969
25970
25971
25972
25973
25974
25975
25976
25977
25978
25979
25980
25981
25982
25983
25984
25985
25986
25987
25988
25989
25990
25991
25992
25993
.....
26027
26028
26029
26030
26031
26032
26033
26034
26035
26036
26037
26038
26039
26040
26041
26042
.....
26052
26053
26054
26055
26056
26057
26058
26059
26060
26061
26062
26063
26064
26065
26066
26067
26068
26069
26070
26071
26072
26073
26074
26075
26076
26077
26078
26079
26080
26081
26082
26083
26084
26085
26086
26087
26088
26089
26090
26091
26092
26093
26094
26095
26096
26097
26098
26099
26100
26101
26102
26103
26104
26105
26106
.....
27222
27223
27224
27225
27226
27227
27228
27229
27230
27231
27232
27233
27234
27235
27236
.....
27417
27418
27419
27420
27421
27422
27423
27424
27425
27426
27427
27428
27429
27430
27431
.....
27443
27444
27445
27446
27447
27448
27449
27450
27451
27452
27453
27454
27455
27456
27457
.....
27508
27509
27510
27511
27512
27513
27514
27515

27516
27517
27518
27519
27520
27521
27522
27523
27524
27525
.....
28581
28582
28583
28584
28585
28586
28587
28588
28589
28590
28591
28592
28593
28594
28595
.....
28605
28606
28607
28608
28609
28610
28611
28612
28613
28614
28615
28616
28617
28618
28619
.....
50504
50505
50506
50507
50508
50509
50510
50511
50512
50513
50514
50515
50516
50517
50518
50519
50520
50521
  TOnSQLTableGetValue = function(Sender: TSQLTable; Row, Field: integer;
    HumanFriendly: boolean): RawJSON of object;

  /// store TSQLFieldType and RTTI for a given TSQLTable field
  TSQLTableFieldType = record
    /// the field kind, as in JSON (match TSQLPropInfo.SQLFieldTypeStored)
    ContentType: TSQLFieldType;



    /// the field size in bytes; -1 means not computed yet
    ContentSize: integer;
    /// used for sftEnumerate, sftSet and sftBlobDynArray fields
    ContentTypeInfo: pointer;
    /// the corresponding index in fQueryTables[]
    TableIndex: integer;
  end;


  /// wrapper to an ORM result table, staticaly stored as UTF-8 text
  // - contain all result in memory, until destroyed
  // - first row contains the field names
  // - following rows contains the data itself
  // - GetString() can be used in a TDrawString
  // - will be implemented as TSQLTableJSON for remote access through optimized
................................................................................
    /// guess the field type from first non null data row
    // - if QueryTables[] are set, exact field type and (enumerate) TypeInfo() is
    // retrieved from the Delphi RTTI; otherwise, get from the cells content
    // - return sftUnknown is all data fields are null
    // - sftBlob is returned if the field is encoded as SQLite3 BLOB literals
    // (X'53514C697465' e.g.)
    // - since TSQLTable data is PUTF8Char, string type is sftUTF8Text only
    function FieldType(Field: integer; out FieldTypeInfo: TSQLTableFieldType): TSQLFieldType; overload;
    /// get the appropriate Sort comparaison function for a field,
    // nil if not available (bad field index or field is blob)
    // - field type is guessed from first data row
    function SortCompare(Field: integer): TUTF8Compare;
    /// get the mean of characters length of all fields
    // - the character length is for the first line of text only (stop counting
    // at every newline character, i.e. #10 or #13 char)
................................................................................
    // - you can define a specific type for a given column, and optionally
    // a maximum column size
    // - FieldTypeInfo can be specified for sets or enumerations, as such:
    // ! aTable.SetFieldType('Sample',sftEnumerate,TypeInfo(TEnumSample));
    // ! aTable.SetFieldType('Samples',sftSet,TypeInfo(TSetSamples));
    procedure SetFieldType(const FieldName: RawUTF8; FieldType: TSQLFieldType;
      FieldTypeInfo: pointer=nil; FieldSize: integer=-1); overload;


    /// increase a particular Field Length Mean value
    // - to be used to customize the field appareance (e.g. for adding of left
    // checkbox for Marked[] fields)
    procedure FieldLengthMeanIncrease(aField, aIncrease: integer);

    /// copy the parameters of a TSQLTable into this instance
    // - the fResults remain in the source TSQLTable: source TSQLTable has not to
................................................................................
        ContentTypeInfo := FieldTypeInfo;
        ContentType := NullableTypeToSQLFieldType(FieldTypeInfo);
        if ContentType=sftUnknown then
          ContentType := sftNullable;
      end;
      {$endif}
      end;

    TableIndex := FieldTableIndex;
  end;
end;

procedure TSQLTable.SetFieldType(const FieldName: RawUTF8; FieldType: TSQLFieldType;
   FieldTypeInfo: pointer=nil; FieldSize: integer=-1);
begin
  SetFieldType(FieldIndex(FieldName),FieldType,FieldTypeInfo,FieldSize);
end;














function TSQLTable.GetRowCount: integer;
begin
  if self=nil then
    result := 0 else
    result := fRowCount;
end;
................................................................................
procedure TSQLTable.InitFieldTypes;
var f,i,len: integer;
    sft: TSQLFieldType;
    info: pointer;
    prop: TSQLPropInfo;
    size,tableindex: integer;
    U: PPUTF8Char;

    tlog: TTimeLog;
begin
  if Assigned(fQueryColumnTypes) and (FieldCount<>length(fQueryColumnTypes)) then
    raise ESQLTableException.CreateUTF8('%.CreateWithColumnTypes() called with % '+
      'column types, whereas the result has % columns',
      [self,length(fQueryColumnTypes),FieldCount]);
  SetLength(fFieldType,FieldCount);
  for f := 0 to FieldCount-1 do begin
    prop := nil;
    info := nil;
    size := -1;
    tableindex := -1;

    // init fFieldType[] from fQueryTables/fQueryColumnTypes[]
    if Assigned(fQueryColumnTypes) then
      sft := fQueryColumnTypes[f] else
    if Assigned(QueryTables) then begin // retrieve column info from field name
      sft := FieldPropFromTables(fResults[f],prop,tableindex);
      if prop<>nil then begin
        if prop.InheritsFrom(TSQLPropInfoRTTI) then
................................................................................
        size := prop.FieldWidth;
      end;
    end else
      sft := sftUnknown;
    if sft=sftUnknown then
      // not found in fQueryTables/fQueryColumnTypes[]: guess from content
      if IsRowID(fResults[f]) then
        sft := sftInteger else

      if f in fFieldParsedAsString then  begin
        // the parser identified string values -> check if was sftDateTime
        sft := sftUTF8Text;
        U := @fResults[FieldCount+f];
        for i := 1 to fRowCount do
          if U^=nil then  // search for a non void column
            inc(U,FieldCount) else begin
            len := StrLen(U^);
            tlog := Iso8601ToTimeLogPUTF8Char(U^,len);
            if tlog<>0 then
              if (len in [8,10]) and (cardinal(tlog shr 26)-1800<300) then
                sft := sftDateTime else // e.g. YYYYMMDD date (Y=1800..2100)
              if len>=15 then
                sft := sftDateTime; // e.g. YYYYMMDDThhmmss date/time value
            break;
          end;
      end else begin
        U := @fResults[FieldCount+f];
        for i := 1 to fRowCount do begin
          sft := UTF8ContentNumberType(U^);
          inc(U,FieldCount);
          if sft=sftUnknown then
            continue else // null -> search for a non void column
          if sft=sftInteger then // may be a floating point with no decimal
            if FieldTypeIntegerDetectionOnAllRows then
              continue else
              // we only checked the first field -> best guess...
              sft := sftCurrency;
          break; // found a non-integer content (e.g. sftFloat/sftUtf8Text)
        end;
      end;

    SetFieldType(f,sft,info,size,tableindex);


  end;
end;

function TSQLTable.FieldType(Field: integer): TSQLFieldType;
begin
  if (self<>nil) and (cardinal(Field)<cardinal(FieldCount)) then begin
    if not Assigned(fFieldType) then
      InitFieldTypes;
    result := fFieldType[Field].ContentType;
  end else
    result := sftUnknown;
end;

function TSQLTable.FieldType(Field: integer; out FieldTypeInfo: TSQLTableFieldType): TSQLFieldType;
begin
  if (self<>nil) and (cardinal(Field)<cardinal(FieldCount)) then begin
    if not Assigned(fFieldType) then
      InitFieldTypes;
    FieldTypeInfo := fFieldType[Field];
    result := FieldTypeInfo.ContentType;
  end else

    result := sftUnknown;

end;

function TSQLTable.Get(Row, Field: integer): PUTF8Char;
begin
  if (self=nil) or (fResults=nil) or (cardinal(Row)>cardinal(fRowCount)) or
     (cardinal(Field)>=cardinal(FieldCount)) then // cardinal() -> test <0
    result := nil else
................................................................................
    MoveFast(pointer(Trail)^,P[len^],length(Trail));
  tmp.Done;
end;

procedure TSQLTable.GetJSONValues(JSON: TStream; Expand: boolean;
  RowFirst, RowLast, IDBinarySize: integer);
var W: TJSONWriter;
    F,R: integer;
    U: PPUTF8Char;

    i64: Int64;
    directWrites: set of 0..255;
begin
  W := TJSONWriter.Create(JSON,Expand,false);
  try
    if (self=nil) or (FieldCount<=0) or (fRowCount<=0) then begin
      W.CancelAllVoid;
      exit;
    end;
................................................................................
      RowLast := fRowCount;
    if RowFirst<=0 then
      RowFirst := 1; // start reading after first Row (Row 0 = Field Names)
    // get col names and types
    if QueryTables<>nil then
      InitFieldTypes;
    SetLength(W.ColNames,FieldCount);
    FillCharFast(directWrites,(FieldCount shr 3)+1,0);
    for F := 0 to FieldCount-1 do begin
      W.ColNames[F] := fResults[F]; // first Row is field Names
      if not Assigned(OnExportValue) then
        if F=fFieldIndexID then begin
          include(directWrites,F); // RowID is a ftInt64
          if IDBinarySize>0 then
            W.ColNames[F] := 'id'; // ajax-friendly
        end else
        if QueryTables<>nil then
          with fFieldType[F] do
          if SQLFieldTypeToDBField(ContentType,ContentTypeInfo) in
             [ftInt64,ftDouble,ftCurrency] then
            include(directWrites,F);
    end;
    W.AddColumns(RowLast-RowFirst+1); // write or init field names (see JSON Expand)
    if Expand then
      W.Add('[');
    // write rows data
    U := @fResults[FieldCount*RowFirst];
    for R := RowFirst to RowLast do begin
      if Expand then
        W.Add('{');
      for F := 0 to FieldCount-1 do begin
        if Expand then
          W.AddString(W.ColNames[F]); // '"'+ColNames[]+'":'
        if Assigned(OnExportValue) then
          W.AddString(OnExportValue(self,R,F,false)) else
        if (IDBinarySize>0) and (F=fFieldIndexID) then begin
          SetInt64(U^,i64);
          W.AddBinToHexDisplayQuoted(@i64,IDBinarySize);
        end else
        if U^=nil then
          W.AddShort('null') else
        // IsStringJSON() is fast and safe: no need to guess exact value type
        if (F in directWrites) or ((QueryTables=nil) and not IsStringJSON(U^)) then
          W.AddNoJSONEscape(U^,StrLen(U^)) else begin

          W.Add('"');
          W.AddJSONEscape(U^,StrLen(U^));
          W.Add('"');




        end;
        W.Add(',');
        inc(U); // points to next value
      end;
      W.CancelLastComma;
      if Expand then begin
        W.Add('}',',');
        if R<>RowLast then
          W.AddCR; // make expanded json more human readable
      end else
        W.Add(',');
    end;
    W.EndJSONObject(1,0); // "RowCount": set by W.AddColumns(RowLast-RowFirst+1)
  finally
    W.Free;
................................................................................
  // ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency,
     '','',' dt:type="i8"',' dt:type="float"',' dt:type="number" rs:dbtype="currency"',
  // ftDate, ftUTF8, ftBlob
     ' dt:type="dateTime"',' dt:type="string"',' dt:type="bin.hex"');
var W: TJSONWriter;
    f,r: integer;
    U: PPUTF8Char;
    fieldType: TSQLDBFieldTypeDynArray;
begin
  W := TJSONWriter.Create(Dest,16384);
  try
    W.AddShort('<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" '+
      'xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" '+
      'xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">');
    if (self<>nil) and ((FieldCount>0) or (fRowCount>0)) then begin
      // retrieve normalized field names and types
      if length(fFieldNames)<>fFieldCount then
        InitFieldNames;
      if not Assigned(fFieldType) then
        InitFieldTypes;
      SetLength(fieldType,FieldCount);
      for f := 0 to FieldCount-1 do
        with fFieldType[F] do
          fieldType[f] := SQLFieldTypeToDBField(ContentType,ContentTypeInfo);
      // check range
      if RowLast=0 then
        RowLast := fRowCount else
      if RowLast>fRowCount then
        RowLast := fRowCount;
      if RowFirst<=0 then
        RowFirst := 1; // start reading after first Row (Row 0 = Field Names)
................................................................................
      W.AddShort('<s:Schema id="RowsetSchema"><s:ElementType name="row" content="eltOnly">');
      for f := 0 to FieldCount-1 do begin
        W.AddShort('<s:AttributeType name="f');
        W.Add(f);
        W.AddShort('" rs:name="');
        W.AddString(fFieldNames[f]);
        W.Add('"');
        W.AddString(FIELDTYPE_TOXML[fieldType[f]]);
        W.Add('/','>');
      end;
      W.AddShort('</s:ElementType></s:Schema>');
      // write rows data
      U := @fResults[FieldCount*RowFirst];
      W.AddShort('<rs:data>');
      for r := RowFirst to RowLast do begin
        W.AddShort('<z:row ');
        for f := 0 to FieldCount-1 do begin
          if U^<>nil then begin
            W.Add('f');
            W.Add(f);
            W.Add('=','"');
            case fieldType[f] of
            ftUnknown:
              if IsStringJSON(U^) then // no need to guess exact value type here
                W.AddXmlEscape(U^) else
                W.AddNoJSONEscape(U^,StrLen(U^));
            ftInt64, ftDouble, ftCurrency:
              W.AddNoJSONEscape(U^,StrLen(U^));
            ftDate, ftUTF8, ftBlob:
              W.AddXmlEscape(U^);
            end;
            W.Add('"',' ');
          end;
          inc(U); // points to next value
        end;
        W.Add('/','>');
      end;
      W.AddShort('</rs:data>');
................................................................................
  '<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>'+
  '<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/></manifest:manifest>';
var Zip: TZipWriteToStream;
    Dest: TRawByteStringStream;
    content: RawUTF8;
    W: TTextWriter;
    U: PPUTF8Char;
    R,F: integer;
    fieldType: TSQLDBFieldTypeDynArray;
begin
  Dest := TRawByteStringStream.Create;
  try
    Zip := TZipWriteToStream.Create(Dest);
    try
      Zip.AddStored('mimetype',pointer(ODSmimetype),length(ODSmimetype));
      Zip.AddDeflated('styles.xml',pointer(ODSstyles),length(ODSstyles));
................................................................................
        if (self<>nil) and ((FieldCount>0) or (fRowCount>0)) then begin
          if withColumnTypes then begin
            // retrieve normalized field names and types
            if length(fFieldNames)<>fFieldCount then
              InitFieldNames;
            if not Assigned(fFieldType) then
              InitFieldTypes;
            SetLength(fieldType,FieldCount);
            for f := 0 to FieldCount-1 do
              with fFieldType[F] do
                fieldType[f] := SQLFieldTypeToDBField(ContentType,ContentTypeInfo);
          end;
          // write column names
          W.AddShort('<table:table-row>');
          U := pointer(fResults);
          for F := 1 to FieldCount do begin
            W.AddShort('<table:table-cell office:value-type="string"><text:p>');
            W.AddXmlEscape(U^);
            W.AddShort('</text:p></table:table-cell>');
            inc(U); // points to next value
          end;
          W.AddShort('</table:table-row>');
          // and values
          for R := 1 to fRowCount do begin
            W.AddShort('<table:table-row>');
            if withColumnTypes then begin
              for F := 0 to FieldCount-1 do begin
                W.AddShort('<table:table-cell office:value-type="');
                case fieldType[F] of
                  ftInt64, ftDouble, ftCurrency: begin
                    W.AddShort('float" office:value="');
                    W.AddXmlEscape(U^);
                  end;
                  ftDate: begin
                   W.AddShort('date" office:date-value="');
                   W.AddXmlEscape(U^);
                  end;
                else //ftUnknown, ftNull, ftUTF8, ftBlob:
                  W.AddShort('string');
                end;
                W.AddShort('"><text:p>');
                if fieldType[F] in [ftUnknown, ftUTF8, ftBlob] then
                  W.AddXmlEscape(U^);
                W.AddShort('</text:p></table:table-cell>');
                inc(U); // points to next value
              end;
            end else
              for F := 0 to FieldCount-1 do begin
                W.AddShort('<table:table-cell office:value-type="string"><text:p>');
                W.AddXmlEscape(U^);
                W.AddShort('</text:p></table:table-cell>');
                inc(U);
              end;
            W.AddShort('</table:table-row>');
          end;
................................................................................
  StartRow, FieldIndex: integer; Client: TObject; Lang: TSynSoundExPronunciation;
  UnicodeComparison: boolean): integer;
var U: PPUTF8Char;
    Kind: TSQLFieldType;
    Search: PAnsiChar;
    UpperUnicode: RawUnicode;
    UpperUnicodeLen: integer;
    info: TSQLTableFieldType;
    Val64: Int64;
    ValTimeLog: TTimelogBits absolute Val64;
    i,err: integer;
    EnumValue: RawUTF8;
    s: string;
    P: PShortString;
    EnumValues: set of 0..63;
................................................................................
function TSQLTable.GetVariant(Row, Field: integer): Variant;
begin
  GetVariant(Row,Field,result);
end;

procedure TSQLTable.GetVariant(Row,Field: integer; var result: variant);
var aType: TSQLFieldType;
    info: TSQLTableFieldType;
begin
  if Row=0 then // Field Name
    RawUTF8ToVariant(GetU(0,Field),result) else begin
    aType := FieldType(Field,info);
    ValueVarToVariant(Get(Row,Field),aType,TVarData(result),true,info.ContentTypeInfo);
  end;
end;
................................................................................
    GetVariant(r,v,Result);
end;

{$endif NOVARIANTS}

function TSQLTable.ExpandAsString(Row, Field: integer; Client: TObject;
  out Text: string; const CustomFormat: string): TSQLFieldType;
var info: TSQLTableFieldType;
    err: integer;
    Value: Int64;
    ValueTimeLog: TTimeLogBits absolute Value;
    ValueDateTime: TDateTime;
    Ref: RecordRef absolute Value;
label IsDateTime;
begin // Text was already forced to '' because was defined as "out" parameter
................................................................................
  sftEnumerate, sftSet, sftRecord, sftID, sftTID, sftRecordVersion, sftSessionUserID,
  sftTimeLog, sftModTime, sftCreateTime, sftUnixTime, sftUnixMSTime: begin
    Value := GetInt64(Get(Row,Field),err);
    if err<>0 then
      // not an integer -> to be displayed as sftUTF8Text
      result := sftUTF8Text else
    case result of
      sftEnumerate: begin

        Text := PEnumType(info.ContentTypeInfo)^.GetCaption(Value);
        exit;
      end;
      sftTimeLog, sftModTime, sftCreateTime:
        goto IsDateTime;
      sftUnixTime: begin
        ValueTimeLog.FromUnixTime(Value);
        goto IsDateTime;
      end;
      sftUnixMSTime: 
................................................................................
  Update(Row,Field,U);
end;
{$endif NOVARIANTS}

procedure TSQLTableWritable.Join(From: TSQLTable; const FromKeyField, KeyField: RawUTF8);
var fk,dk,f,i,k,ndx: integer;
    n,fn: RawUTF8;
    info: TSQLTableFieldType;
    new: TIntegerDynArray;
begin
  dk := FieldIndexExisting(KeyField);
  SetLength(new,FieldCount);
  fk := From.FieldIndexExisting(FromKeyField);
  From.SortFields(fk); // faster merge with O(log(n)) binary search
  for f := 0 to From.FieldCount-1 do // add From fields (excluding FromKeyField)
................................................................................
      if From.FieldType(f,info)=sftUnknown then  // set TSQLTableFieldType
        i := AddField(fn) else
        if info.TableIndex>=0 then
          i := AddField(fn,From.QueryTables[info.TableIndex],n) else begin
          i := AddField(fn);
          if i>=length(fFieldType) then
            SetLength(fFieldType,i+1);
          fFieldType[i] := info;
        end;
      new[f] := i;
    end;
  ndx := FieldCount;
  for i := 1 to RowCount do begin // merge data
    k := From.SearchFieldSorted(fResults[ndx+dk],fk);
    if k>0 then begin 
................................................................................
    if IdemPropName(BlobFields[i].PropInfo^.Name,PropName,PropNameLen) then begin
      result := BlobFields[i].PropInfo;
      exit;
    end;
  result := nil;
end;

const
  DBTOFIELDTYPE: array[TSQLDBFieldType] of TSQLFieldType  = (sftUnknown,
    sftUnknown,sftInteger,sftFloat,sftCurrency,sftDateTime,sftUTF8Text,sftBlob);

function TSQLRecordProperties.SQLFieldTypeToSQL(FieldIndex: integer): RawUTF8;
const
  /// simple wrapper from each SQL used type into SQLite3 field datatype
  // - set to '' for fields with no column created in the database
  DEFAULT_SQLFIELDTYPETOSQL: array[TSQLFieldType] of RawUTF8 =
   ('',                              // sftUnknown
    ' TEXT COLLATE NOCASE, ',        // sftAnsiText






>
>
>







>







 







|







 







>
>







 







>





|



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







 







>












>







 







|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>

>
>













|




|
|
|
>

>







 







<

>

|







 







<
|
|

|
<
<
|
<
<
<
<
<
<






|


|

|

|
|





|
|
|
>
|
|
|
>
>
>
>







|







 







<












<
<
<
<







 







|













<
<
<
<
<
<
<
<
|
<







 







|
<







 







<
<
<
<




|







|


|

|
|




|
|





|





|







 







|







 







|







 







|







 







|
>
|
|
|







 







|







 







|







 







<
<
<
<







8146
8147
8148
8149
8150
8151
8152
8153
8154
8155
8156
8157
8158
8159
8160
8161
8162
8163
8164
8165
8166
8167
8168
8169
8170
....
8571
8572
8573
8574
8575
8576
8577
8578
8579
8580
8581
8582
8583
8584
8585
....
8622
8623
8624
8625
8626
8627
8628
8629
8630
8631
8632
8633
8634
8635
8636
8637
.....
25292
25293
25294
25295
25296
25297
25298
25299
25300
25301
25302
25303
25304
25305
25306
25307
25308
25309
25310
25311
25312
25313
25314
25315
25316
25317
25318
25319
25320
25321
25322
25323
25324
25325
25326
25327
25328
.....
25330
25331
25332
25333
25334
25335
25336
25337
25338
25339
25340
25341
25342
25343
25344
25345
25346
25347
25348
25349
25350
25351
25352
25353
25354
25355
25356
25357
.....
25359
25360
25361
25362
25363
25364
25365
25366
25367
25368
25369
25370
25371
25372
25373
25374
25375
25376
25377
25378
25379
25380
25381
25382
25383
25384
25385
25386
25387
25388
25389
25390
25391
25392
25393
25394
25395
25396
25397
25398
25399
25400
25401
25402
25403
25404
25405
25406
25407
25408
25409
25410
25411
25412
25413
25414
25415
25416
25417
25418
25419
25420
25421
25422
25423
25424
25425
25426
25427
25428
25429
25430
25431
25432
25433
.....
25798
25799
25800
25801
25802
25803
25804

25805
25806
25807
25808
25809
25810
25811
25812
25813
25814
25815
.....
25820
25821
25822
25823
25824
25825
25826

25827
25828
25829
25830


25831






25832
25833
25834
25835
25836
25837
25838
25839
25840
25841
25842
25843
25844
25845
25846
25847
25848
25849
25850
25851
25852
25853
25854
25855
25856
25857
25858
25859
25860
25861
25862
25863
25864
25865
25866
25867
25868
25869
25870
25871
25872
25873
25874
25875
25876
25877
.....
25948
25949
25950
25951
25952
25953
25954

25955
25956
25957
25958
25959
25960
25961
25962
25963
25964
25965
25966




25967
25968
25969
25970
25971
25972
25973
.....
25975
25976
25977
25978
25979
25980
25981
25982
25983
25984
25985
25986
25987
25988
25989
25990
25991
25992
25993
25994
25995








25996

25997
25998
25999
26000
26001
26002
26003
.....
26037
26038
26039
26040
26041
26042
26043
26044

26045
26046
26047
26048
26049
26050
26051
.....
26061
26062
26063
26064
26065
26066
26067




26068
26069
26070
26071
26072
26073
26074
26075
26076
26077
26078
26079
26080
26081
26082
26083
26084
26085
26086
26087
26088
26089
26090
26091
26092
26093
26094
26095
26096
26097
26098
26099
26100
26101
26102
26103
26104
26105
26106
26107
26108
26109
26110
26111
.....
27227
27228
27229
27230
27231
27232
27233
27234
27235
27236
27237
27238
27239
27240
27241
.....
27422
27423
27424
27425
27426
27427
27428
27429
27430
27431
27432
27433
27434
27435
27436
.....
27448
27449
27450
27451
27452
27453
27454
27455
27456
27457
27458
27459
27460
27461
27462
.....
27513
27514
27515
27516
27517
27518
27519
27520
27521
27522
27523
27524
27525
27526
27527
27528
27529
27530
27531
.....
28587
28588
28589
28590
28591
28592
28593
28594
28595
28596
28597
28598
28599
28600
28601
.....
28611
28612
28613
28614
28615
28616
28617
28618
28619
28620
28621
28622
28623
28624
28625
.....
50510
50511
50512
50513
50514
50515
50516




50517
50518
50519
50520
50521
50522
50523
  TOnSQLTableGetValue = function(Sender: TSQLTable; Row, Field: integer;
    HumanFriendly: boolean): RawJSON of object;

  /// store TSQLFieldType and RTTI for a given TSQLTable field
  TSQLTableFieldType = record
    /// the field kind, as in JSON (match TSQLPropInfo.SQLFieldTypeStored)
    ContentType: TSQLFieldType;
    /// how this field could be stored in a database
    // - equals ftUnknown if InitFields guessed the field type
    ContentDB: TSQLDBFieldType;
    /// the field size in bytes; -1 means not computed yet
    ContentSize: integer;
    /// used for sftEnumerate, sftSet and sftBlobDynArray fields
    ContentTypeInfo: pointer;
    /// the corresponding index in fQueryTables[]
    TableIndex: integer;
  end;
  PSQLTableFieldType = ^TSQLTableFieldType;

  /// wrapper to an ORM result table, staticaly stored as UTF-8 text
  // - contain all result in memory, until destroyed
  // - first row contains the field names
  // - following rows contains the data itself
  // - GetString() can be used in a TDrawString
  // - will be implemented as TSQLTableJSON for remote access through optimized
................................................................................
    /// guess the field type from first non null data row
    // - if QueryTables[] are set, exact field type and (enumerate) TypeInfo() is
    // retrieved from the Delphi RTTI; otherwise, get from the cells content
    // - return sftUnknown is all data fields are null
    // - sftBlob is returned if the field is encoded as SQLite3 BLOB literals
    // (X'53514C697465' e.g.)
    // - since TSQLTable data is PUTF8Char, string type is sftUTF8Text only
    function FieldType(Field: integer; out FieldTypeInfo: PSQLTableFieldType): TSQLFieldType; overload;
    /// get the appropriate Sort comparaison function for a field,
    // nil if not available (bad field index or field is blob)
    // - field type is guessed from first data row
    function SortCompare(Field: integer): TUTF8Compare;
    /// get the mean of characters length of all fields
    // - the character length is for the first line of text only (stop counting
    // at every newline character, i.e. #10 or #13 char)
................................................................................
    // - you can define a specific type for a given column, and optionally
    // a maximum column size
    // - FieldTypeInfo can be specified for sets or enumerations, as such:
    // ! aTable.SetFieldType('Sample',sftEnumerate,TypeInfo(TEnumSample));
    // ! aTable.SetFieldType('Samples',sftSet,TypeInfo(TSetSamples));
    procedure SetFieldType(const FieldName: RawUTF8; FieldType: TSQLFieldType;
      FieldTypeInfo: pointer=nil; FieldSize: integer=-1); overload;
    /// set the exact type of all fields, from the DB-like information
    procedure SetFieldTypes(const DBTypes: TSQLDBFieldTypeDynArray);
    /// increase a particular Field Length Mean value
    // - to be used to customize the field appareance (e.g. for adding of left
    // checkbox for Marked[] fields)
    procedure FieldLengthMeanIncrease(aField, aIncrease: integer);

    /// copy the parameters of a TSQLTable into this instance
    // - the fResults remain in the source TSQLTable: source TSQLTable has not to
................................................................................
        ContentTypeInfo := FieldTypeInfo;
        ContentType := NullableTypeToSQLFieldType(FieldTypeInfo);
        if ContentType=sftUnknown then
          ContentType := sftNullable;
      end;
      {$endif}
      end;
    ContentDB := SQLFieldTypeToDBField(ContentType,ContentTypeInfo);
    TableIndex := FieldTableIndex;
  end;
end;

procedure TSQLTable.SetFieldType(const FieldName: RawUTF8; FieldType: TSQLFieldType;
   FieldTypeInfo: pointer; FieldSize: integer);
begin
  SetFieldType(FieldIndex(FieldName),FieldType,FieldTypeInfo,FieldSize);
end;

const
  DBTOFIELDTYPE: array[TSQLDBFieldType] of TSQLFieldType  = (sftUnknown,
    sftUnknown,sftInteger,sftFloat,sftCurrency,sftDateTime,sftUTF8Text,sftBlob);

procedure TSQLTable.SetFieldTypes(const DBTypes: TSQLDBFieldTypeDynArray);
var f: integer;
begin
  if length(DBTypes)<>FieldCount then
    raise ESQLTableException.CreateUTF8('%.SetFieldTypes(DBTypes?)',[self]);
  for f := 0 to FieldCount-1 do
    SetFieldType(f,DBTOFIELDTYPE[DBTypes[f]]);
end;

function TSQLTable.GetRowCount: integer;
begin
  if self=nil then
    result := 0 else
    result := fRowCount;
end;
................................................................................
procedure TSQLTable.InitFieldTypes;
var f,i,len: integer;
    sft: TSQLFieldType;
    info: pointer;
    prop: TSQLPropInfo;
    size,tableindex: integer;
    U: PPUTF8Char;
    guessed: boolean;
    tlog: TTimeLog;
begin
  if Assigned(fQueryColumnTypes) and (FieldCount<>length(fQueryColumnTypes)) then
    raise ESQLTableException.CreateUTF8('%.CreateWithColumnTypes() called with % '+
      'column types, whereas the result has % columns',
      [self,length(fQueryColumnTypes),FieldCount]);
  SetLength(fFieldType,FieldCount);
  for f := 0 to FieldCount-1 do begin
    prop := nil;
    info := nil;
    size := -1;
    tableindex := -1;
    guessed := false;
    // init fFieldType[] from fQueryTables/fQueryColumnTypes[]
    if Assigned(fQueryColumnTypes) then
      sft := fQueryColumnTypes[f] else
    if Assigned(QueryTables) then begin // retrieve column info from field name
      sft := FieldPropFromTables(fResults[f],prop,tableindex);
      if prop<>nil then begin
        if prop.InheritsFrom(TSQLPropInfoRTTI) then
................................................................................
        size := prop.FieldWidth;
      end;
    end else
      sft := sftUnknown;
    if sft=sftUnknown then
      // not found in fQueryTables/fQueryColumnTypes[]: guess from content
      if IsRowID(fResults[f]) then
        sft := sftInteger else begin
        guessed := true;
        if f in fFieldParsedAsString then  begin
          // the parser identified string values -> check if was sftDateTime
          sft := sftUTF8Text;
          U := @fResults[FieldCount+f];
          for i := 1 to fRowCount do
            if U^=nil then  // search for a non void column
              inc(U,FieldCount) else begin
              len := StrLen(U^);
              tlog := Iso8601ToTimeLogPUTF8Char(U^,len);
              if tlog<>0 then
                if (len in [8,10]) and (cardinal(tlog shr 26)-1800<300) then
                  sft := sftDateTime else // e.g. YYYYMMDD date (Y=1800..2100)
                if len>=15 then
                  sft := sftDateTime; // e.g. YYYYMMDDThhmmss date/time value
              break;
            end;
        end else begin
          U := @fResults[FieldCount+f];
          for i := 1 to fRowCount do begin
            sft := UTF8ContentNumberType(U^);
            inc(U,FieldCount);
            if sft=sftUnknown then
              continue else // null -> search for a non void column
            if sft=sftInteger then // may be a floating point with no decimal
              if FieldTypeIntegerDetectionOnAllRows then
                continue else
                // we only checked the first field -> best guess...
                sft := sftCurrency;
            break; // found a non-integer content (e.g. sftFloat/sftUtf8Text)
          end;
        end;
      end;
    SetFieldType(f,sft,info,size,tableindex);
    if guessed then
      fFieldType[f].ContentDB := ftUnknown; // may fail on some later row
  end;
end;

function TSQLTable.FieldType(Field: integer): TSQLFieldType;
begin
  if (self<>nil) and (cardinal(Field)<cardinal(FieldCount)) then begin
    if not Assigned(fFieldType) then
      InitFieldTypes;
    result := fFieldType[Field].ContentType;
  end else
    result := sftUnknown;
end;

function TSQLTable.FieldType(Field: integer; out FieldTypeInfo: PSQLTableFieldType): TSQLFieldType;
begin
  if (self<>nil) and (cardinal(Field)<cardinal(FieldCount)) then begin
    if not Assigned(fFieldType) then
      InitFieldTypes;
    FieldTypeInfo := @fFieldType[Field];
    result := FieldTypeInfo^.ContentType;
  end else begin
    FieldTypeInfo := nil;
    result := sftUnknown;
  end;
end;

function TSQLTable.Get(Row, Field: integer): PUTF8Char;
begin
  if (self=nil) or (fResults=nil) or (cardinal(Row)>cardinal(fRowCount)) or
     (cardinal(Field)>=cardinal(FieldCount)) then // cardinal() -> test <0
    result := nil else
................................................................................
    MoveFast(pointer(Trail)^,P[len^],length(Trail));
  tmp.Done;
end;

procedure TSQLTable.GetJSONValues(JSON: TStream; Expand: boolean;
  RowFirst, RowLast, IDBinarySize: integer);
var W: TJSONWriter;

    U: PPUTF8Char;
    f,r: integer;
    i64: Int64;
label nostr,str;
begin
  W := TJSONWriter.Create(JSON,Expand,false);
  try
    if (self=nil) or (FieldCount<=0) or (fRowCount<=0) then begin
      W.CancelAllVoid;
      exit;
    end;
................................................................................
      RowLast := fRowCount;
    if RowFirst<=0 then
      RowFirst := 1; // start reading after first Row (Row 0 = Field Names)
    // get col names and types
    if QueryTables<>nil then
      InitFieldTypes;
    SetLength(W.ColNames,FieldCount);

    for f := 0 to FieldCount-1 do begin
      W.ColNames[f] := fResults[f]; // first Row is field Names
      if not Assigned(OnExportValue) then
        if (f=fFieldIndexID) and (IDBinarySize>0) then


          W.ColNames[f] := 'id'; // ajax-friendly






    end;
    W.AddColumns(RowLast-RowFirst+1); // write or init field names (see JSON Expand)
    if Expand then
      W.Add('[');
    // write rows data
    U := @fResults[FieldCount*RowFirst];
    for r := RowFirst to RowLast do begin
      if Expand then
        W.Add('{');
      for f := 0 to FieldCount-1 do begin
        if Expand then
          W.AddString(W.ColNames[f]); // '"'+ColNames[]+'":'
        if Assigned(OnExportValue) then
          W.AddString(OnExportValue(self,r,f,false)) else
        if (IDBinarySize>0) and (f=fFieldIndexID) then begin
          SetInt64(U^,i64);
          W.AddBinToHexDisplayQuoted(@i64,IDBinarySize);
        end else
        if U^=nil then
          W.AddShort('null') else
        case fFieldType[f].ContentDB of
          ftInt64,ftDouble,ftCurrency:
nostr:      W.AddNoJSONEscape(U^,StrLen(U^));
          ftDate,ftUTF8,ftBlob: begin
str:        W.Add('"');
            W.AddJSONEscape(U^,StrLen(U^));
            W.Add('"');
          end;
          else if IsStringJSON(U^) then // fast and safe enough
            goto str else
            goto nostr;
        end;
        W.Add(',');
        inc(U); // points to next value
      end;
      W.CancelLastComma;
      if Expand then begin
        W.Add('}',',');
        if r<>RowLast then
          W.AddCR; // make expanded json more human readable
      end else
        W.Add(',');
    end;
    W.EndJSONObject(1,0); // "RowCount": set by W.AddColumns(RowLast-RowFirst+1)
  finally
    W.Free;
................................................................................
  // ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency,
     '','',' dt:type="i8"',' dt:type="float"',' dt:type="number" rs:dbtype="currency"',
  // ftDate, ftUTF8, ftBlob
     ' dt:type="dateTime"',' dt:type="string"',' dt:type="bin.hex"');
var W: TJSONWriter;
    f,r: integer;
    U: PPUTF8Char;

begin
  W := TJSONWriter.Create(Dest,16384);
  try
    W.AddShort('<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" '+
      'xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" '+
      'xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">');
    if (self<>nil) and ((FieldCount>0) or (fRowCount>0)) then begin
      // retrieve normalized field names and types
      if length(fFieldNames)<>fFieldCount then
        InitFieldNames;
      if not Assigned(fFieldType) then
        InitFieldTypes;




      // check range
      if RowLast=0 then
        RowLast := fRowCount else
      if RowLast>fRowCount then
        RowLast := fRowCount;
      if RowFirst<=0 then
        RowFirst := 1; // start reading after first Row (Row 0 = Field Names)
................................................................................
      W.AddShort('<s:Schema id="RowsetSchema"><s:ElementType name="row" content="eltOnly">');
      for f := 0 to FieldCount-1 do begin
        W.AddShort('<s:AttributeType name="f');
        W.Add(f);
        W.AddShort('" rs:name="');
        W.AddString(fFieldNames[f]);
        W.Add('"');
        W.AddString(FIELDTYPE_TOXML[fFieldType[f].ContentDB]);
        W.Add('/','>');
      end;
      W.AddShort('</s:ElementType></s:Schema>');
      // write rows data
      U := @fResults[FieldCount*RowFirst];
      W.AddShort('<rs:data>');
      for r := RowFirst to RowLast do begin
        W.AddShort('<z:row ');
        for f := 0 to FieldCount-1 do begin
          if U^<>nil then begin
            W.Add('f');
            W.Add(f);
            W.Add('=','"');








            W.AddXmlEscape(U^);

            W.Add('"',' ');
          end;
          inc(U); // points to next value
        end;
        W.Add('/','>');
      end;
      W.AddShort('</rs:data>');
................................................................................
  '<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>'+
  '<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/></manifest:manifest>';
var Zip: TZipWriteToStream;
    Dest: TRawByteStringStream;
    content: RawUTF8;
    W: TTextWriter;
    U: PPUTF8Char;
    r,f: integer;

begin
  Dest := TRawByteStringStream.Create;
  try
    Zip := TZipWriteToStream.Create(Dest);
    try
      Zip.AddStored('mimetype',pointer(ODSmimetype),length(ODSmimetype));
      Zip.AddDeflated('styles.xml',pointer(ODSstyles),length(ODSstyles));
................................................................................
        if (self<>nil) and ((FieldCount>0) or (fRowCount>0)) then begin
          if withColumnTypes then begin
            // retrieve normalized field names and types
            if length(fFieldNames)<>fFieldCount then
              InitFieldNames;
            if not Assigned(fFieldType) then
              InitFieldTypes;




          end;
          // write column names
          W.AddShort('<table:table-row>');
          U := pointer(fResults);
          for f := 1 to FieldCount do begin
            W.AddShort('<table:table-cell office:value-type="string"><text:p>');
            W.AddXmlEscape(U^);
            W.AddShort('</text:p></table:table-cell>');
            inc(U); // points to next value
          end;
          W.AddShort('</table:table-row>');
          // and values
          for r := 1 to fRowCount do begin
            W.AddShort('<table:table-row>');
            if withColumnTypes then begin
              for f := 0 to FieldCount-1 do begin
                W.AddShort('<table:table-cell office:value-type="');
                case fFieldType[f].ContentDB of
                  ftInt64,ftDouble,ftCurrency: begin
                    W.AddShort('float" office:value="');
                    W.AddXmlEscape(U^);
                  end;
                  ftDate: begin
                    W.AddShort('date" office:date-value="');
                    W.AddXmlEscape(U^);
                  end;
                else //ftUnknown, ftNull, ftUTF8, ftBlob:
                  W.AddShort('string');
                end;
                W.AddShort('"><text:p>');
                if fFieldType[f].ContentDB in [ftUnknown, ftUTF8, ftBlob] then
                  W.AddXmlEscape(U^);
                W.AddShort('</text:p></table:table-cell>');
                inc(U); // points to next value
              end;
            end else
              for f := 0 to FieldCount-1 do begin
                W.AddShort('<table:table-cell office:value-type="string"><text:p>');
                W.AddXmlEscape(U^);
                W.AddShort('</text:p></table:table-cell>');
                inc(U);
              end;
            W.AddShort('</table:table-row>');
          end;
................................................................................
  StartRow, FieldIndex: integer; Client: TObject; Lang: TSynSoundExPronunciation;
  UnicodeComparison: boolean): integer;
var U: PPUTF8Char;
    Kind: TSQLFieldType;
    Search: PAnsiChar;
    UpperUnicode: RawUnicode;
    UpperUnicodeLen: integer;
    info: PSQLTableFieldType;
    Val64: Int64;
    ValTimeLog: TTimelogBits absolute Val64;
    i,err: integer;
    EnumValue: RawUTF8;
    s: string;
    P: PShortString;
    EnumValues: set of 0..63;
................................................................................
function TSQLTable.GetVariant(Row, Field: integer): Variant;
begin
  GetVariant(Row,Field,result);
end;

procedure TSQLTable.GetVariant(Row,Field: integer; var result: variant);
var aType: TSQLFieldType;
    info: PSQLTableFieldType;
begin
  if Row=0 then // Field Name
    RawUTF8ToVariant(GetU(0,Field),result) else begin
    aType := FieldType(Field,info);
    ValueVarToVariant(Get(Row,Field),aType,TVarData(result),true,info.ContentTypeInfo);
  end;
end;
................................................................................
    GetVariant(r,v,Result);
end;

{$endif NOVARIANTS}

function TSQLTable.ExpandAsString(Row, Field: integer; Client: TObject;
  out Text: string; const CustomFormat: string): TSQLFieldType;
var info: PSQLTableFieldType;
    err: integer;
    Value: Int64;
    ValueTimeLog: TTimeLogBits absolute Value;
    ValueDateTime: TDateTime;
    Ref: RecordRef absolute Value;
label IsDateTime;
begin // Text was already forced to '' because was defined as "out" parameter
................................................................................
  sftEnumerate, sftSet, sftRecord, sftID, sftTID, sftRecordVersion, sftSessionUserID,
  sftTimeLog, sftModTime, sftCreateTime, sftUnixTime, sftUnixMSTime: begin
    Value := GetInt64(Get(Row,Field),err);
    if err<>0 then
      // not an integer -> to be displayed as sftUTF8Text
      result := sftUTF8Text else
    case result of
      sftEnumerate:
        if info.ContentTypeInfo<>nil then begin
          Text := PEnumType(info.ContentTypeInfo)^.GetCaption(Value);
          exit;
        end;
      sftTimeLog, sftModTime, sftCreateTime:
        goto IsDateTime;
      sftUnixTime: begin
        ValueTimeLog.FromUnixTime(Value);
        goto IsDateTime;
      end;
      sftUnixMSTime: 
................................................................................
  Update(Row,Field,U);
end;
{$endif NOVARIANTS}

procedure TSQLTableWritable.Join(From: TSQLTable; const FromKeyField, KeyField: RawUTF8);
var fk,dk,f,i,k,ndx: integer;
    n,fn: RawUTF8;
    info: PSQLTableFieldType;
    new: TIntegerDynArray;
begin
  dk := FieldIndexExisting(KeyField);
  SetLength(new,FieldCount);
  fk := From.FieldIndexExisting(FromKeyField);
  From.SortFields(fk); // faster merge with O(log(n)) binary search
  for f := 0 to From.FieldCount-1 do // add From fields (excluding FromKeyField)
................................................................................
      if From.FieldType(f,info)=sftUnknown then  // set TSQLTableFieldType
        i := AddField(fn) else
        if info.TableIndex>=0 then
          i := AddField(fn,From.QueryTables[info.TableIndex],n) else begin
          i := AddField(fn);
          if i>=length(fFieldType) then
            SetLength(fFieldType,i+1);
          fFieldType[i] := info^;
        end;
      new[f] := i;
    end;
  ndx := FieldCount;
  for i := 1 to RowCount do begin // merge data
    k := From.SearchFieldSorted(fResults[ndx+dk],fk);
    if k>0 then begin 
................................................................................
    if IdemPropName(BlobFields[i].PropInfo^.Name,PropName,PropNameLen) then begin
      result := BlobFields[i].PropInfo;
      exit;
    end;
  result := nil;
end;





function TSQLRecordProperties.SQLFieldTypeToSQL(FieldIndex: integer): RawUTF8;
const
  /// simple wrapper from each SQL used type into SQLite3 field datatype
  // - set to '' for fields with no column created in the database
  DEFAULT_SQLFIELDTYPETOSQL: array[TSQLFieldType] of RawUTF8 =
   ('',                              // sftUnknown
    ' TEXT COLLATE NOCASE, ',        // sftAnsiText

Changes to SynopseCommit.inc.

1
'1.18.4163'
|
1
'1.18.4164'