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

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

Overview
Comment:
  • changed the non expanded JSON format to use lowercase first column names: {"fieldCount":1,"values":["col1"... instead of {"FieldCount":1,"Values":[..
  • fixed regression issue about FTS virtual tables not working any more
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: f641049835f641bb3c24132c552346b799ecb2cd
User & Date: ab 2012-07-26 09:32:19
Context
2012-07-26
09:33
performance benchmark now includes either OFF, either FULL synchronous mode for SQlite3 engine on file check-in: 3ccbcc1e29 user: ab tags: trunk
09:32
  • changed the non expanded JSON format to use lowercase first column names: {"fieldCount":1,"values":["col1"... instead of {"FieldCount":1,"Values":[..
  • fixed regression issue about FTS virtual tables not working any more
check-in: f641049835 user: ab tags: trunk
2012-07-25
15:51
  • added TSQLDataBase.Synchronous and TSQLDataBase.WALMode properties
  • added TSQLDataBase.ExecuteNoException() overloaded methods
check-in: 18fc67d3db user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/SQLite3Commons.pas.

605
606
607
608
609
610
611


612
613
614
615
616
617
618
....
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
....
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
....
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
.....
11145
11146
11147
11148
11149
11150
11151
11152
11153
11154
11155
11156
11157
11158
11159
11160
11161
11162
11163
.....
11166
11167
11168
11169
11170
11171
11172
11173
11174
11175
11176
11177
11178
11179
11180
11181
11182
11183
11184
11185
11186
11187
11188
11189
11190
11191
.....
11356
11357
11358
11359
11360
11361
11362
11363
11364
11365
11366
11367
11368
11369
11370
.....
13262
13263
13264
13265
13266
13267
13268
13269
13270
13271
13272
13273
13274
13275
13276
.....
13278
13279
13280
13281
13282
13283
13284
13285
13286
13287
13288
13289
13290
13291
13292
13293
13294
13295
.....
14500
14501
14502
14503
14504
14505
14506
14507
14508
14509
14510
14511
14512
14513
14514
.....
19183
19184
19185
19186
19187
19188
19189
19190
19191
19192
19193
19194
19195
19196
19197
      Weak interface assignment (with small performance penalty and memory use),
      corresponding to the ARC's Zeroing Weak pointers model
    - CopyObject() procedure now handle TCollection kind of object not only
      as sub properties
    - introducing TInterfacedCollection dedicated class, properly handling
      collection item creation on the Server side, with interface-based services:
      all contract operations shall use it instead of TCollection


    - added TSQLTable.FieldLengthMax() and ExpandAsSynUnicode() methods
    - added BlobToBytes() function and TSQLTable.GetBytes/GetStream methods
    - added virtual TSQLRestServer.FlushInternalDBCache method and dedicated
      TSQLRestServerStaticInMemoryExternal class, to properly handle external
      DB modification for virtual tables (i.e. flush SQL/JSON cache as expected)
    - added TSQLRestServerStatic.InternalBatchStart / InternalBatchStop methods
      to handle fast grouped sending to remote database engine (e.g. Oracle
................................................................................

    /// save the table values in JSON format
    // - JSON data is added to TStream, with UTF-8 encoding
    // - if Expand is true, JSON data is an array of objects, for direct use
    // with any Ajax or .NET client:
    // & [ {"col1":val11,"col2":"val12"},{"col1":val21,... ]
    // - if Expand is false, JSON data is serialized (used in TSQLTableJSON)
    // & { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
    // - RowFirst and RowLast can be used to ask for a specified row extent
    // of the returned data (by default, all rows are retrieved)
    procedure GetJSONValues(JSON: TStream; Expand: boolean;
      RowFirst: integer=0; RowLast: integer=0); overload;
    /// same as above, but returning result into a RawUTF8
    function GetJSONValues(Expand: boolean): RawUTF8; overload;
    /// save the table in CSV format
................................................................................

/// get the FIRST field value of the FIRST row, from a JSON content
// - e.g. usefull to get an ID without converting a JSON content into a TSQLTableJSON
function UnJSONFirstField(var P: PUTF8Char): RawUTF8;

/// returns TRUE if the JSON content is in expanded format
// - i.e. as plain [{"ID":10,"FirstName":"John","LastName":"Smith"}...]
// - i.e. not as '{"FieldCount":3,"Values":["ID","FirstName","LastName",...']}
function IsNotAjaxJSON(P: PUTF8Char): Boolean;

/// get the number of rows stored in the not-expanded JSON content
function GetRowCountNotExpanded(P: PUTF8Char; FieldCount: integer; var RowCount: integer): PUTF8Char;

/// go to the end of a field name in a JSON '"FieldName":Value' pair
// - returns nil if P was not formatted as expected
................................................................................
    (** return the UTF-8 encoded JSON objects for the values contained
      in the current published fields of a TSQLRecord child
      - only simple fields (i.e. not TSQLRawBlob/TSQLRecordMany) are retrieved:
        BLOB fields are ignored (use direct access via dedicated methods instead)
      - if Expand is true, JSON data is an object, for direct use with any Ajax or .NET client:
      ! {"col1":val11,"col2":"val12"}
      - if Expand is false, JSON data is serialized (as used in TSQLTableJSON)
      ! { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
      - if withID is true, then the first ID field value is included *)
    procedure GetJSONValues(JSON: TStream; Expand: boolean; withID: boolean;
      Occasion: TSQLOccasion); overload;
    /// same as above, but returning result into a RawUTF8
    // - if UsingStream is not set, it will use a temporary THeapMemoryStream instance
    function GetJSONValues(Expand: boolean; withID: boolean; Occasion: TSQLOccasion;
      UsingStream: TCustomMemoryStream=nil): RawUTF8; overload;
................................................................................
      inc(P);
    until false;
  end;
end;

function UnJSONFirstField(var P: PUTF8Char): RawUTF8;
// expand=true: [ {"col1":val11} ] -> val11
// expand=false: { "FieldCount":1,"Values":["col1",val11] } -> vall11
begin
  result := '';
  if P=nil then exit;
  if Expect(P,'{"FieldCount":') then begin
    // not expanded format
    if GetJSONIntegerVar(P)<>1 then
      exit; // wrong field count
    while P^<>'[' do if P^=#0 then exit else inc(P); // go to ["col1"
    inc(P); // go to "col1"
  end else begin
    // expanded format
................................................................................
  end;
  GetJSONField(P,P); // ignore field name
  result := GetJSONField(P,P); // get field value
end;

function IsNotAjaxJSON(P: PUTF8Char): Boolean;
begin
  result := Expect(P,'{"FieldCount":');
end;

function IsNotExpanded(var P: PUTF8Char; var FieldCount: integer): boolean;
begin
  if not IsNotAjaxJSON(P) then begin
    result := false;
    exit;
  end;
  inc(P,14);
  FieldCount := GetJSONIntegerVar(P);
  result := (FieldCount<>0) and Expect(P,',"Values":[');
end;

function GetRowCountNotExpanded(P: PUTF8Char; FieldCount: integer; var RowCount: integer): PUTF8Char;
begin
  RowCount := 0;
  result := P; // no data at all of unexpected end
  repeat
................................................................................
  result := false; // error on parsing
  if (self=nil) or (Buffer=nil) then
    exit;
  // go to start of object
  P := GotoNextNotSpace(Buffer);
  if IsNotExpanded(P,fFieldCount) then begin
    // A. Not Expanded format
(* {"FieldCount":9,"Values":["ID","Int","Test","Unicode","Ansi","ValFloat","ValWord",
    "ValDate","Next",0,0,"abcde+�ef+�+�","abcde+�ef+�+�","abcde+�ef+�+�",
    3.14159265300000E+0000,1203,"2009-03-10T21:19:36",0,..]} *)
    // 1. get RowCount and DataLen
    DataLen := GetRowCountNotExpanded(P,FieldCount,fRowCount)-P; // also check P^ format
    if DataLen=0 then begin
      fRowCount := 0;
      exit;
................................................................................
      Freemem(tmp);
    end;
  end;
end;

procedure TSQLRecord.FillFrom(P: PUTF8Char);
(* two possible formats = first not expanded, 2nd is expanded (most usefull)
 {"FieldCount":9,"Values":["ID","Int","Test","Unicode","Ansi","ValFloat","ValWord",
  "ValDate","Next",0,0,"abcde+�ef+�+�","abcde+�ef+�+�","abcde+�ef+�+�",
  3.14159265300000E+0000,1203,"2009-03-10T21:19:36",0]}
 {"ID":0,"Int":0,"Test":"abcde+�ef+�+�","Unicode":"abcde+�ef+�+�","Ansi":
 "abcde+�ef+�+�","ValFloat": 3.14159265300000E+0000,"ValWord":1203,
 "ValDate":"2009-03-10T21:19:36","Next":0} *)
var F: array[0..MAX_SQLFIELDS-1] of PUTF8Char; // store field/property names
    i, n: integer;
................................................................................
    Value: PUTF8Char;
begin
  // go to start of object
  if P=nil then
    exit;
  while P^<>'{' do
    if P^=#0 then exit else inc(P);
  if Expect(P,'{"FieldCount":') then begin
    // not expanded format
    n := GetJSONIntegerVar(P)-1;
    if (cardinal(n)>high(F)) or not Expect(P,',"Values":[') then
      exit;
    for i := 0 to n do
      F[i] := GetJSONField(P,P); // get field names
    for i := 0 to n do begin
      Prop := F[i]; // share shortstring on stack with code below
      FillValue(Prop,GetJSONField(P,P)); // set properties from values
    end;
................................................................................
procedure TSQLModel.SetTableProps(aTable: TSQLRecordClass; aIndex: integer;
  Last, VirtualsRemain: boolean);
var f, R: integer;
    Props: TSQLRecordProperties;
    PT: PTypeInfo;
begin
  Props := aTable.RecordProps;
  if not VirtualsRemain then
    Props.Kind := rSQLite3; // reset to internal table at model creation
  if Props.Model=nil then begin
    Props.Model := self;
    Props.ModelTableIndex := aIndex;
  end;
  TableProps[aIndex] := Props;
  fTablesName[aIndex] := Props.SQLTableName;
................................................................................
        W.Add(',');
      end;
      result := fValue.Count;
    end else
      result := FindWhereEqual(WhereField,WhereValue,GetJSONValuesEvent,W);
    if (result=0) and W.Expand then begin
      // we want the field names at least, even with no data
      Expand := false; //  {"FieldCount":2,"Values":["col1","col2"]}
      W.Expand := false;
      W.CancelAll;
      fStoredClassProps.SetJSONWriterColumnNames(W);
    end;
    W.CancelLastComma; // cancel last ','
    // end the JSON object
    W.Add(']');






>
>







 







|







 







|







 







|







 







|



|







 







|










|







 







|







 







|







 







|


|







 







|







 







|







605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
....
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
....
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
....
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
.....
11147
11148
11149
11150
11151
11152
11153
11154
11155
11156
11157
11158
11159
11160
11161
11162
11163
11164
11165
.....
11168
11169
11170
11171
11172
11173
11174
11175
11176
11177
11178
11179
11180
11181
11182
11183
11184
11185
11186
11187
11188
11189
11190
11191
11192
11193
.....
11358
11359
11360
11361
11362
11363
11364
11365
11366
11367
11368
11369
11370
11371
11372
.....
13264
13265
13266
13267
13268
13269
13270
13271
13272
13273
13274
13275
13276
13277
13278
.....
13280
13281
13282
13283
13284
13285
13286
13287
13288
13289
13290
13291
13292
13293
13294
13295
13296
13297
.....
14502
14503
14504
14505
14506
14507
14508
14509
14510
14511
14512
14513
14514
14515
14516
.....
19185
19186
19187
19188
19189
19190
19191
19192
19193
19194
19195
19196
19197
19198
19199
      Weak interface assignment (with small performance penalty and memory use),
      corresponding to the ARC's Zeroing Weak pointers model
    - CopyObject() procedure now handle TCollection kind of object not only
      as sub properties
    - introducing TInterfacedCollection dedicated class, properly handling
      collection item creation on the Server side, with interface-based services:
      all contract operations shall use it instead of TCollection
    - changed the non expanded JSON format to use lowercase first column names:
      {"fieldCount":1,"values":["col1"... instead of {"FieldCount":1,"Values":[..
    - added TSQLTable.FieldLengthMax() and ExpandAsSynUnicode() methods
    - added BlobToBytes() function and TSQLTable.GetBytes/GetStream methods
    - added virtual TSQLRestServer.FlushInternalDBCache method and dedicated
      TSQLRestServerStaticInMemoryExternal class, to properly handle external
      DB modification for virtual tables (i.e. flush SQL/JSON cache as expected)
    - added TSQLRestServerStatic.InternalBatchStart / InternalBatchStop methods
      to handle fast grouped sending to remote database engine (e.g. Oracle
................................................................................

    /// save the table values in JSON format
    // - JSON data is added to TStream, with UTF-8 encoding
    // - if Expand is true, JSON data is an array of objects, for direct use
    // with any Ajax or .NET client:
    // & [ {"col1":val11,"col2":"val12"},{"col1":val21,... ]
    // - if Expand is false, JSON data is serialized (used in TSQLTableJSON)
    // & { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
    // - RowFirst and RowLast can be used to ask for a specified row extent
    // of the returned data (by default, all rows are retrieved)
    procedure GetJSONValues(JSON: TStream; Expand: boolean;
      RowFirst: integer=0; RowLast: integer=0); overload;
    /// same as above, but returning result into a RawUTF8
    function GetJSONValues(Expand: boolean): RawUTF8; overload;
    /// save the table in CSV format
................................................................................

/// get the FIRST field value of the FIRST row, from a JSON content
// - e.g. usefull to get an ID without converting a JSON content into a TSQLTableJSON
function UnJSONFirstField(var P: PUTF8Char): RawUTF8;

/// returns TRUE if the JSON content is in expanded format
// - i.e. as plain [{"ID":10,"FirstName":"John","LastName":"Smith"}...]
// - i.e. not as '{"fieldCount":3,"values":["ID","FirstName","LastName",...']}
function IsNotAjaxJSON(P: PUTF8Char): Boolean;

/// get the number of rows stored in the not-expanded JSON content
function GetRowCountNotExpanded(P: PUTF8Char; FieldCount: integer; var RowCount: integer): PUTF8Char;

/// go to the end of a field name in a JSON '"FieldName":Value' pair
// - returns nil if P was not formatted as expected
................................................................................
    (** return the UTF-8 encoded JSON objects for the values contained
      in the current published fields of a TSQLRecord child
      - only simple fields (i.e. not TSQLRawBlob/TSQLRecordMany) are retrieved:
        BLOB fields are ignored (use direct access via dedicated methods instead)
      - if Expand is true, JSON data is an object, for direct use with any Ajax or .NET client:
      ! {"col1":val11,"col2":"val12"}
      - if Expand is false, JSON data is serialized (as used in TSQLTableJSON)
      ! { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
      - if withID is true, then the first ID field value is included *)
    procedure GetJSONValues(JSON: TStream; Expand: boolean; withID: boolean;
      Occasion: TSQLOccasion); overload;
    /// same as above, but returning result into a RawUTF8
    // - if UsingStream is not set, it will use a temporary THeapMemoryStream instance
    function GetJSONValues(Expand: boolean; withID: boolean; Occasion: TSQLOccasion;
      UsingStream: TCustomMemoryStream=nil): RawUTF8; overload;
................................................................................
      inc(P);
    until false;
  end;
end;

function UnJSONFirstField(var P: PUTF8Char): RawUTF8;
// expand=true: [ {"col1":val11} ] -> val11
// expand=false: { "fieldCount":1,"values":["col1",val11] } -> vall11
begin
  result := '';
  if P=nil then exit;
  if Expect(P,'{"fieldCount":') then begin
    // not expanded format
    if GetJSONIntegerVar(P)<>1 then
      exit; // wrong field count
    while P^<>'[' do if P^=#0 then exit else inc(P); // go to ["col1"
    inc(P); // go to "col1"
  end else begin
    // expanded format
................................................................................
  end;
  GetJSONField(P,P); // ignore field name
  result := GetJSONField(P,P); // get field value
end;

function IsNotAjaxJSON(P: PUTF8Char): Boolean;
begin
  result := Expect(P,'{"fieldCount":');
end;

function IsNotExpanded(var P: PUTF8Char; var FieldCount: integer): boolean;
begin
  if not IsNotAjaxJSON(P) then begin
    result := false;
    exit;
  end;
  inc(P,14);
  FieldCount := GetJSONIntegerVar(P);
  result := (FieldCount<>0) and Expect(P,',"values":[');
end;

function GetRowCountNotExpanded(P: PUTF8Char; FieldCount: integer; var RowCount: integer): PUTF8Char;
begin
  RowCount := 0;
  result := P; // no data at all of unexpected end
  repeat
................................................................................
  result := false; // error on parsing
  if (self=nil) or (Buffer=nil) then
    exit;
  // go to start of object
  P := GotoNextNotSpace(Buffer);
  if IsNotExpanded(P,fFieldCount) then begin
    // A. Not Expanded format
(* {"fieldCount":9,"values":["ID","Int","Test","Unicode","Ansi","ValFloat","ValWord",
    "ValDate","Next",0,0,"abcde+�ef+�+�","abcde+�ef+�+�","abcde+�ef+�+�",
    3.14159265300000E+0000,1203,"2009-03-10T21:19:36",0,..]} *)
    // 1. get RowCount and DataLen
    DataLen := GetRowCountNotExpanded(P,FieldCount,fRowCount)-P; // also check P^ format
    if DataLen=0 then begin
      fRowCount := 0;
      exit;
................................................................................
      Freemem(tmp);
    end;
  end;
end;

procedure TSQLRecord.FillFrom(P: PUTF8Char);
(* two possible formats = first not expanded, 2nd is expanded (most usefull)
 {"fieldCount":9,"values":["ID","Int","Test","Unicode","Ansi","ValFloat","ValWord",
  "ValDate","Next",0,0,"abcde+�ef+�+�","abcde+�ef+�+�","abcde+�ef+�+�",
  3.14159265300000E+0000,1203,"2009-03-10T21:19:36",0]}
 {"ID":0,"Int":0,"Test":"abcde+�ef+�+�","Unicode":"abcde+�ef+�+�","Ansi":
 "abcde+�ef+�+�","ValFloat": 3.14159265300000E+0000,"ValWord":1203,
 "ValDate":"2009-03-10T21:19:36","Next":0} *)
var F: array[0..MAX_SQLFIELDS-1] of PUTF8Char; // store field/property names
    i, n: integer;
................................................................................
    Value: PUTF8Char;
begin
  // go to start of object
  if P=nil then
    exit;
  while P^<>'{' do
    if P^=#0 then exit else inc(P);
  if Expect(P,'{"fieldCount":') then begin
    // not expanded format
    n := GetJSONIntegerVar(P)-1;
    if (cardinal(n)>high(F)) or not Expect(P,',"values":[') then
      exit;
    for i := 0 to n do
      F[i] := GetJSONField(P,P); // get field names
    for i := 0 to n do begin
      Prop := F[i]; // share shortstring on stack with code below
      FillValue(Prop,GetJSONField(P,P)); // set properties from values
    end;
................................................................................
procedure TSQLModel.SetTableProps(aTable: TSQLRecordClass; aIndex: integer;
  Last, VirtualsRemain: boolean);
var f, R: integer;
    Props: TSQLRecordProperties;
    PT: PTypeInfo;
begin
  Props := aTable.RecordProps;
  if (not VirtualsRemain) and (Props.Kind in IS_CUSTOM_VIRTUAL) then
    Props.Kind := rSQLite3; // reset to internal table at model creation
  if Props.Model=nil then begin
    Props.Model := self;
    Props.ModelTableIndex := aIndex;
  end;
  TableProps[aIndex] := Props;
  fTablesName[aIndex] := Props.SQLTableName;
................................................................................
        W.Add(',');
      end;
      result := fValue.Count;
    end else
      result := FindWhereEqual(WhereField,WhereValue,GetJSONValuesEvent,W);
    if (result=0) and W.Expand then begin
      // we want the field names at least, even with no data
      Expand := false; //  {"fieldCount":2,"values":["col1","col2"]}
      W.Expand := false;
      W.CancelAll;
      fStoredClassProps.SetJSONWriterColumnNames(W);
    end;
    W.CancelLastComma; // cancel last ','
    // end the JSON object
    W.Add(']');

Changes to SynCommons.pas.

308
309
310
311
312
313
314


315
316
317
318
319
320
321
....
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
....
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
....
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
.....
20194
20195
20196
20197
20198
20199
20200
20201
20202
20203
20204
20205
20206
20207
20208
20209
20210
20211
20212
20213
20214
20215
20216
20217
20218
20219
20220
20221
20222
20223
20224
20225
20226
20227
20228
20229
20230
20231
    TObjectListPropertyHashed class, which allows hashing of a sub-property
    of an object (including some changes made to TDynArray/TDynArrayHshed)
  - new TTextWriter.AddDateTime() overloaded method able to quote the output
  - both TTextWriter.AddDateTime() overloaded methods will store '' when value
    is 0, or a pure ISO-8601 date or time if the value is defined as such,
    just as expected by http://www.sqlite.org/lang_datefunc.html - it will also
    reduce average generated JSON/text content size


  - new SetInt64() procedure for direct assignment of the result
  - TSynTableStatement class now accepts '_' in table and column identifiers
  - fixed implementation issue in function FindNextUTF8WordBegin()
  - fixed false negative issue in TSynSoundEx.UTF8 and TSynSoundEx.Ansi
  - added an optional parameter to StrToCurr64() function, able to return
    a true Int64 value if no decimal is supplied within the input text buffer
  - enhanced TSynAnsiFixedWidth.UnicodeBufferToAnsi average process speed
................................................................................
    /// used to store output format for TSQLRecord.GetJSONValues()
    fWithID: boolean;
    /// used to store field for TSQLRecord.GetJSONValues()
    fFields: TSQLFieldBits;
    fFieldMax: integer;
    /// if not Expanded format, contains the Stream position of the first
    // usefull Row of data; i.e. ',val11' position in:
    // & { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
    fStartDataPosition: integer;
  public
    /// used internally to store column names and count for AddColumns
    ColNames: TRawUTF8DynArray;
    /// the data will be written to the specified Stream
    // - if no Stream is supplied, a temporary memory stream will be created
    // (it's faster to supply one, e.g. any TSQLRest.TempMemoryStream)
................................................................................
    /// Read-Only access to the field bits set for each column to be stored
    property Fields: TSQLFieldBits read fFields write fFields;
    /// Read-Only access to the higher field index to be stored
    // - i.e. the highest bit set in Fields
    property FieldMax: integer read fFieldMax write fFieldMax;
    /// if not Expanded format, contains the Stream position of the first
    // usefull Row of data; i.e. ',val11' position in:
    // & { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
    property StartDataPosition: integer read fStartDataPosition;
  end;

  /// implement a cache of some key/value pairs, e.g. to improve reading speed
  // - used e.g. by TSQLDataBase for caching the SELECT statements results in an
  // internal JSON format (which is faster than a query to the SQLite3 engine)
  // - internally make use of an efficient hashing algorithm for fast response
................................................................................
      const Fields: TSQLFieldBits): TJSONWriter;
    (** return the UTF-8 encoded JSON objects for the values contained
      in the specified RecordBuffer encoded in our SBF compact binary format,
      according to the Expand/WithID/Fields parameters of W
      - if W.Expand is true, JSON data is an object, for direct use with any Ajax or .NET client:
      ! {"col1":val11,"col2":"val12"}
      - if W.Expand is false, JSON data is serialized (as used in TSQLTableJSON)
      ! { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
      - only fields with a bit set in W.Fields will be appended
      - if W.WithID is true, then the first ID field value is included *)
    procedure GetJSONValues(aID: integer; RecordBuffer: PUTF8Char; W: TJSONWriter);
    /// can be used to retrieve all values matching a preparated TSynTableStatement
    // - this method matchs the TSynBigTableIterateEvent callback definition
    // - Sender will be the TSynBigTable instance, and Opaque will point to a
    // TSynTableStatement instance (with all fields initialized, including Writer)
................................................................................
procedure TJSONWriter.AddColumns;
var i: integer;
begin
  if fExpand then begin
    for i := 0 to High(ColNames) do
      ColNames[i] := '"'+ColNames[i]+'":';
  end else begin
    AddShort('{"FieldCount":');
    Add(length(ColNames));
    AddShort(',"Values":["');
    // first row is FieldNames
    for i := 0 to High(ColNames) do begin
      AddString(ColNames[i]);
      AddNoJSONEscape(PAnsiChar('","'),3);
    end;
    CancelLastChar; // cancel last '"'
    fStartDataPosition := fStream.Position+(B-fTempBuf);
     // B := buf-1 at startup -> need ',val11' position in
     // 'Values":["col1","col2",val11,' i.e. current pos without the ','
  end;
end;

procedure TJSONWriter.TrimFirstRow;
var P, PBegin, PEnd: PUTF8Char;
begin
  if (self=nil) or not fStream.InheritsFrom(TMemoryStream) or
     fExpand or (fStartDataPosition=0) then
    exit;
  // go to begin of first row
  Flush; // we need the data to be in fStream memory
  // PBegin^=val11 in { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
  PBegin := TMemoryStream(fStream).Memory;
  PEnd := PBegin+fStream.Position;
  PEnd^ := #0; // mark end of current values
  inc(PBegin,fStartDataPosition+1); // +1 to include ',' of ',val11'
  // jump to end of first row
  P := GotoNextJSONField(PBegin,length(ColNames));
  if P=nil then exit; // unexpected end






>
>







 







|







 







|







 







|







 







|

|








|











|







308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
....
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
....
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
....
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
5682
5683
.....
20196
20197
20198
20199
20200
20201
20202
20203
20204
20205
20206
20207
20208
20209
20210
20211
20212
20213
20214
20215
20216
20217
20218
20219
20220
20221
20222
20223
20224
20225
20226
20227
20228
20229
20230
20231
20232
20233
    TObjectListPropertyHashed class, which allows hashing of a sub-property
    of an object (including some changes made to TDynArray/TDynArrayHshed)
  - new TTextWriter.AddDateTime() overloaded method able to quote the output
  - both TTextWriter.AddDateTime() overloaded methods will store '' when value
    is 0, or a pure ISO-8601 date or time if the value is defined as such,
    just as expected by http://www.sqlite.org/lang_datefunc.html - it will also
    reduce average generated JSON/text content size
  - changed the non expanded JSON format to use lowercase first column names:
    {"fieldCount":1,"values":["col1"... instead of {"FieldCount":1,"Values":[..
  - new SetInt64() procedure for direct assignment of the result
  - TSynTableStatement class now accepts '_' in table and column identifiers
  - fixed implementation issue in function FindNextUTF8WordBegin()
  - fixed false negative issue in TSynSoundEx.UTF8 and TSynSoundEx.Ansi
  - added an optional parameter to StrToCurr64() function, able to return
    a true Int64 value if no decimal is supplied within the input text buffer
  - enhanced TSynAnsiFixedWidth.UnicodeBufferToAnsi average process speed
................................................................................
    /// used to store output format for TSQLRecord.GetJSONValues()
    fWithID: boolean;
    /// used to store field for TSQLRecord.GetJSONValues()
    fFields: TSQLFieldBits;
    fFieldMax: integer;
    /// if not Expanded format, contains the Stream position of the first
    // usefull Row of data; i.e. ',val11' position in:
    // & { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
    fStartDataPosition: integer;
  public
    /// used internally to store column names and count for AddColumns
    ColNames: TRawUTF8DynArray;
    /// the data will be written to the specified Stream
    // - if no Stream is supplied, a temporary memory stream will be created
    // (it's faster to supply one, e.g. any TSQLRest.TempMemoryStream)
................................................................................
    /// Read-Only access to the field bits set for each column to be stored
    property Fields: TSQLFieldBits read fFields write fFields;
    /// Read-Only access to the higher field index to be stored
    // - i.e. the highest bit set in Fields
    property FieldMax: integer read fFieldMax write fFieldMax;
    /// if not Expanded format, contains the Stream position of the first
    // usefull Row of data; i.e. ',val11' position in:
    // & { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
    property StartDataPosition: integer read fStartDataPosition;
  end;

  /// implement a cache of some key/value pairs, e.g. to improve reading speed
  // - used e.g. by TSQLDataBase for caching the SELECT statements results in an
  // internal JSON format (which is faster than a query to the SQLite3 engine)
  // - internally make use of an efficient hashing algorithm for fast response
................................................................................
      const Fields: TSQLFieldBits): TJSONWriter;
    (** return the UTF-8 encoded JSON objects for the values contained
      in the specified RecordBuffer encoded in our SBF compact binary format,
      according to the Expand/WithID/Fields parameters of W
      - if W.Expand is true, JSON data is an object, for direct use with any Ajax or .NET client:
      ! {"col1":val11,"col2":"val12"}
      - if W.Expand is false, JSON data is serialized (as used in TSQLTableJSON)
      ! { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
      - only fields with a bit set in W.Fields will be appended
      - if W.WithID is true, then the first ID field value is included *)
    procedure GetJSONValues(aID: integer; RecordBuffer: PUTF8Char; W: TJSONWriter);
    /// can be used to retrieve all values matching a preparated TSynTableStatement
    // - this method matchs the TSynBigTableIterateEvent callback definition
    // - Sender will be the TSynBigTable instance, and Opaque will point to a
    // TSynTableStatement instance (with all fields initialized, including Writer)
................................................................................
procedure TJSONWriter.AddColumns;
var i: integer;
begin
  if fExpand then begin
    for i := 0 to High(ColNames) do
      ColNames[i] := '"'+ColNames[i]+'":';
  end else begin
    AddShort('{"fieldCount":');
    Add(length(ColNames));
    AddShort(',"values":["');
    // first row is FieldNames
    for i := 0 to High(ColNames) do begin
      AddString(ColNames[i]);
      AddNoJSONEscape(PAnsiChar('","'),3);
    end;
    CancelLastChar; // cancel last '"'
    fStartDataPosition := fStream.Position+(B-fTempBuf);
     // B := buf-1 at startup -> need ',val11' position in
     // "values":["col1","col2",val11,' i.e. current pos without the ','
  end;
end;

procedure TJSONWriter.TrimFirstRow;
var P, PBegin, PEnd: PUTF8Char;
begin
  if (self=nil) or not fStream.InheritsFrom(TMemoryStream) or
     fExpand or (fStartDataPosition=0) then
    exit;
  // go to begin of first row
  Flush; // we need the data to be in fStream memory
  // PBegin^=val11 in { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
  PBegin := TMemoryStream(fStream).Memory;
  PEnd := PBegin+fStream.Position;
  PEnd^ := #0; // mark end of current values
  inc(PBegin,fStartDataPosition+1); // +1 to include ',' of ',val11'
  // jump to end of first row
  P := GotoNextJSONField(PBegin,length(ColNames));
  if P=nil then exit; // unexpected end