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

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

Overview
Comment:mORMot.pas unit interface deep refactoring: e.g. now TSQLTable will refers explicitly to TSQLRecord classes and not to plain TClass (e.g. for QueryTables[])
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: eb9bebe006434fc900a81d2a2435f998a7368d72
User & Date: abouchez 2013-03-29 07:37:16
Context
2013-03-29
11:27
fixed ticket [72b3d8e616] in TSQLRestServerDB.Restore() - and included associated regression tests check-in: 3e7da2b5e7 user: abouchez tags: trunk
07:37
mORMot.pas unit interface deep refactoring: e.g. now TSQLTable will refers explicitly to TSQLRecord classes and not to plain TClass (e.g. for QueryTables[]) check-in: eb9bebe006 user: abouchez tags: trunk
2013-03-28
18:49
fix compilation issue with Delphi 2009/2010 check-in: 7b2aaeb529 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/mORMot.pas.

767
768
769
770
771
772
773


774
775
776
777
778
779
780
781
782
...
890
891
892
893
894
895
896
897




















898
899
900
901
902
903
904
...
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
....
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
....
1927
1928
1929
1930
1931
1932
1933

1934
1935
1936
1937
1938
1939
1940
....
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
....
3273
3274
3275
3276
3277
3278
3279

























3280
3281
3282
3283
3284
3285
3286
....
3305
3306
3307
3308
3309
3310
3311

3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324







3325
3326
3327
3328
3329
3330
3331
....
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
....
4618
4619
4620
4621
4622
4623
4624
































































































































































































































































































































































































































































































4625
4626
4627
4628
4629
4630
4631
....
7195
7196
7197
7198
7199
7200
7201
7202
7203
7204
7205
7206
7207
7208
7209
.....
10486
10487
10488
10489
10490
10491
10492
10493
10494
10495
10496
10497
10498
10499
10500
10501
10502
10503
.....
13675
13676
13677
13678
13679
13680
13681
13682
13683
13684
13685
13686
13687
13688
13689
13690
13691
13692
13693
13694
13695
13696
13697
13698
13699
13700
13701
13702
13703
13704
.....
13709
13710
13711
13712
13713
13714
13715
13716
13717
13718
13719
13720
13721
13722
13723
.....
14544
14545
14546
14547
14548
14549
14550
14551
14552
14553
14554
14555
14556
14557
14558
.....
14563
14564
14565
14566
14567
14568
14569
14570
14571
14572
14573
14574
14575
14576
14577
14578
14579
14580
14581
14582
14583
14584
14585
14586
14587
14588
14589
14590
14591
14592
14593
14594
.....
14595
14596
14597
14598
14599
14600
14601
14602
14603
14604
14605
14606
14607
14608
14609
14610
14611
14612
14613
14614
14615
14616
14617
14618
14619
14620
14621
14622
14623
14624
14625
14626
14627
14628
14629
14630
14631
14632
14633
14634
.....
14635
14636
14637
14638
14639
14640
14641
14642
14643
14644
14645
14646
14647
14648
14649
14650
14651
14652
14653
14654
14655
14656
14657






14658
14659
14660
14661
14662
14663
14664
.....
15934
15935
15936
15937
15938
15939
15940
15941
15942
15943
15944
15945
15946
15947
15948
15949
15950
15951
15952
15953
15954
15955
.....
20803
20804
20805
20806
20807
20808
20809
20810
20811
20812
20813
20814
20815
20816
20817
.....
20887
20888
20889
20890
20891
20892
20893
20894
20895
20896
20897
20898
20899
20900
20901
20902
20903
20904
20905
20906
20907
20908
20909
.....
20929
20930
20931
20932
20933
20934
20935
20936
20937
20938
20939
20940
20941
20942
20943
.....
22040
22041
22042
22043
22044
22045
22046
22047
22048
22049
22050
22051
22052
22053
22054
    - introducing TSQLRestClientURI.InternalCheckOpen/InternalClose methods to
      properly handle remote connection and re-connection
    - added TSQLRestClientURI.LastErrorCode/LastErrorMessage/LastErrorException
      properties, to retrieve additional information about remote URL() execution
    - added TSQLRestClientURI.ServiceRegister() and ServiceRegisterClientDriven()
      methods for easier Client-side interface-based services initialization
    - added JSONFileToObject() and ObjectToJSONFile() functions


    - speed up of TSQLTable.FieldIndex() method (using binary search)
    - added TSQLTable.ToObjectList() and ToObjectList<T: class>() methods
    - added TSQLTable.Step(), FieldBuffer() and Field() methods, handling a
      cursor at TSQLTable level, with optional late-binding column access
    - added TSQLRestClientURI.ServerTimeStampSynchronize method to force time
      synchronization with the server - can be handy to test the connection 
    - added TSQLRest.TableHasRows/TableRowCount methods, and overriden direct
      implementation for TSQLRestServer/TSQLRestServerStaticInMemory (including
      SQL pattern recognition for TSQLRestServerStaticInMemory)
................................................................................
{$ifdef SSPIAUTH}
  SynSSPIAuth,
{$endif}
  SynCommons;



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





















{.$define PUBLISHRECORD}
// - if defined, a sftBlobRecord will be defined in order to serialize published
// properties with some record type: but even if such properties can be defined,
// there is no RTTI generated for them: so it won't work :( see
// http://docwiki.embarcadero.com/RADStudio/en/Classes_and_Objects#Published_Members
// - should be defined globaly, e.g. in Synopse.inc
................................................................................
  // - this constant is used internaly to optimize memory usage in the
  // generated asm code
  // - you should not change it to a value lower than expected in an existing
  // database (e.g. as expected by TSQLAccessRights or such)
  MAX_SQLTABLES = 256;


  /// the used TAuthSession.IDCardinal value if the session not started yet
  // - i.e. if the session handling is still in its handshaking phase
  CONST_AUTHENTICATION_SESSION_NOT_STARTED = 0;

  /// the used TAuthSession.IDCardinal value if authentication mode is not set
  // - i.e. if TSQLRest.HandleAuthentication equals FALSE
  CONST_AUTHENTICATION_NOT_USED = 1;

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
................................................................................
    soInsert,
    soUpdate,
    soDelete);

  /// used to defined a set of CRUD associated SQL statement of a command
  TSQLOccasions = set of TSQLOccasion;

  /// abstract array of classes, will be filled e.g. with TSQLRecord descendants
  TClasses = array of TClass;

  /// 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
  // - GetA() or GetW() can be used in a TDrawString
  // - will be implemented as TSQLTableDB for direct SQLite3 database engine call,
  //  or as TSQLTableJSON for remote access through optimized JSON messages
  TSQLTable = class
  private
    fQueryTables: TClasses;
    fQuerySQL: RawUTF8;
    fFieldLengthMean: TIntegerDynArray;
    fFieldLengthMeanSum: integer;
  protected
    fRowCount,
    fFieldCount: integer;
    /// contains the data, as returned by sqlite3_get_table()
    fResults: PPUTF8CharArray;
    /// contains the TSQLFieldType and TypeInfo(enumerate), after calculation
    // from the fQueryTables values
    fFieldType: array of record
      // the field kind
      ContentType: TSQLFieldType;
      // initialized for both sftEnumerate and sftSet field kinds
      EnumTypeInfo: pointer;
      // the corresponding index in fQueryTables[]
      TableIndex: integer;
    end;
    /// used by FieldIndex() for fast binary searcg
    fFieldNameOrder: TCardinalDynArray;
    /// contain the fResults[] pointers, after a IDColumnHide() call
    fIDColumn, fNotIDColumn: array of PUTF8Char;
    /// bit set at parsing to mark was a string value (e.g. "..." in JSON)
    fFieldParsedAsString: set of 0..255;
    /// index of a 'ID' field, -1 if none (e.g. after IDColumnHide method call)
    fFieldIndexID: integer;
    /// the internal state counter of the database when the data was retrieved
    fInternalState: cardinal;
    /// contains the parameters used for sorting
    fSortParams: TSQLTableSortParams;
    /// contains the TSQLRecord instances created by NewRecord method
    fOwnedRecords: TObjectList;
    /// if the TSQLRecord is the owner of this table, i.e. if it must free it
    fOwnerMustFree: Boolean;
    /// current cursor row (1..RowCount), as set by the Step() method
    fStepRow: integer;
    /// fill the fFieldType[] array (from fQueryTables[] or fResults[] content)
    procedure InitFieldTypes;
  public
    /// initialize the result table
    // - you can use RecordClassesToClasses() wrapper function to convert an
    // array of TSQLRecordClass into the expected array of TClass
    constructor Create(const Tables: array of TClass; const aSQL: RawUTF8);
    /// free associated memory and owned records
    destructor Destroy; override;
    {/ read-only access to a particular field value, as UTF-8 encoded buffer
     - points to memory buffer allocated by Init() }
    function Get(Row,Field: integer): PUTF8Char;
    {/ read-only access to a particular field value, as RawUTF8 text }
    function GetU(Row,Field: integer): RawUTF8;
    {/ read-only access to a particular field value, as Win Ansi text }
    function GetA(Row,Field: integer): WinAnsiString;
    {/ read-only access to a particular field value, as Win Ansi text shortstring}
    function GetS(Row,Field: integer): shortstring;
    {$ifdef USEVARIANTS}
    {/ read-only access to a particular field value, as a Variant
     - will try to use the most approriate Variant type for conversion (will
       use e.g. TDateTime for sftDateTime or sftTimeLog, Extended for sftFloat...)
     - will handle any necessary conversion to VCL string text, ready to be displayed
     - the global UTF8ToString() function will be used for the conversion:
       for proper i18n handling before Delphi 2009, you should use the
       overloaded method with aUTF8ToString=Language.UTF8ToString }
    function GetVariant(Row,Field: integer; Client: TObject): variant; overload;
    {/ read-only access to a particular field value, as a Variant
     - will try to use the most approriate Variant type for conversion (will
       use e.g. TDateTime for sftDateTime or sftTimeLog, Extended for sftFloat...)
     - will handle any necessary conversion to VCL string text, ready to be displayed
     - the global UTF8ToString() function will be used for the conversion:
       for proper i18n handling before Delphi 2009, you should use the
       overloaded method with aUTF8ToString=Language.UTF8ToString }
    procedure GetVariant(Row,Field: integer; Client: TObject; var result: variant); overload;
    {$endif}
    {/ read-only access to a particular field value, as VCL string text
     - the global UTF8ToString() function will be used for the conversion:
     for proper i18n handling before Delphi 2009, you should use the
     overloaded method with aUTF8ToString=Language.UTF8ToString }
    function GetString(Row,Field: integer): string;
    {/ fill a unicode buffer with a particular field value
      - return number of wide characters written in Dest^ }
    function GetWP(Row,Field: integer; Dest: PWideChar; MaxDestChars: cardinal): integer;
    {/ read-only access to a particular field value, as UTF-16 Unicode text
      - Raw Unicode is WideChar(zero) terminated
      - its content is allocated to contain all WideChars (not trimed to 255,
        like GetWP() above }
    function GetW(Row,Field: integer): RawUnicode;
    {/ read-only access to a particular field value, as integer value }
    function GetAsInteger(Row,Field: integer): integer;
      {$ifdef HASINLINE}inline;{$endif}
    {/ read-only access to a particular field value, as Int64 value }
    function GetAsInt64(Row,Field: integer): Int64;
      {$ifdef HASINLINE}inline;{$endif}
    {/ read-only access to a particular field value, ready to be displayed
      - mostly used with Row=0, i.e. to get a display value from a field name
      - use "string" type, i.e. UnicodeString for Delphi 2009+
      - value is first un-camel-cased: 'OnLine' value will return 'On line' e.g.
      - then System.LoadResStringTranslate() is called if available }
    function GetCaption(Row,Field: integer): string;
    {/ read-only access to a particular Blob value
     - a new TSQLRawBlob is created
     - Blob data is converted from SQLite3 BLOB literals (X'53514C697465' e.g.)
       or Base-64 encoded content ('\uFFF0base64encodedbinary')
     - prefered manner is to directly use REST protocol to retrieve a blob field }
    function GetBlob(Row,Field: integer): TSQLRawBlob;
    {/ read-only access to a particular Blob value
     - a new TBytes is created
     - Blob data is converted from SQLite3 BLOB literals (X'53514C697465' e.g.)
       or Base-64 encoded content ('\uFFF0base64encodedbinary')
     - prefered manner is to directly use REST protocol to retrieve a blob field }
    function GetBytes(Row,Field: integer): TBytes;
    {/ read-only access to a particular Blob value
     - a new TCustomMemoryStream is created - caller shall free its instance
     - Blob data is converted from SQLite3 BLOB literals (X'53514C697465' e.g.)
       or Base-64 encoded content ('\uFFF0base64encodedbinary')
     - prefered manner is to directly use REST protocol to retrieve a blob field }
    function GetStream(Row,Field: integer): TStream;
    {/ read-only access to a particular field value, as VCL text
     - Client is one TSQLClient instance (used to display TRecordReference via
       the associated TSQLModel)
     - returns the Field Type
     - return generic string Text, i.e. UnicodeString for Delphi 2009+,
       ready to be displayed to the VCL, for sftEnumerate, sftTimeLog
       and sftRecord/sftID
     - returns '' as string Text, if text can by displayed directly
       with Get*() methods above
     - returns '' for other properties kind, if UTF8ToString is nil,
       or the ready to be displayed value if UTF8ToString event is set
       (to be used mostly with Language.UTF8ToString) }
    function ExpandAsString(Row,Field: integer; Client: TObject; out Text: string): TSQLFieldType;
    {/ read-only access to a particular field value, as VCL text
     - this method is just a wrapper around ExpandAsString method, returning
       the content as a SynUnicode string type (i.e. UnicodeString since Delphi
       2009, and WideString for non Unicode versions of Delphi)
     - it is used by the reporting layers of the framework (e.g. TSQLRibbon.AddToReport) }
    function ExpandAsSynUnicode(Row,Field: integer; Client: TObject; out Text: SynUnicode): TSQLFieldType;
    {/ read-only access to a particular DateTime field value
     - expect SQLite3 TEXT field in ISO 8601 'YYYYMMDD hhmmss' or
      'YYYY-MM-DD hh:mm:ss' format }
    function GetDateTime(Row,Field: integer): TDateTime;
      {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
    {/ read-only access to a particular TTimeLog field value
      - return the result as Iso8601.Text() Iso-8601 encoded text }
    function GetTimeLog(Row,Field: integer; Expanded: boolean; FirstTimeChar: AnsiChar = 'T'): RawUTF8;
    {/ widechar length (UTF-8 decoded) of a particular field value }
    function LengthW(Row,Field: integer): integer;
    {/ get all values for a specified field into a dynamic RawUTF8 array
     - don't perform any conversion, but just create an array of raw PUTF8Char data }
    procedure GetRowValues(Field: integer; out Values: TRawUTF8DynArray); overload;
    {/ get all values for a specified field into a dynamic Integer array }
    procedure GetRowValues(Field: integer; out Values: TIntegerDynArray); overload;
    {/ get all values for a specified field as CSV
     - don't perform any conversion, but create a CSV from raw PUTF8Char data }
    function GetRowValues(Field: integer; Sep: AnsiChar=','): RawUTF8; overload;

    /// 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
    // - if Tab=TRUE, will use TAB instead of ',' between columns
    // - you can customize the ',' separator - use e.g. the global ListSeparator
    // variable (from SysUtils) to reflect the current system definition (some
    // country use ',' as decimal separator, for instance our "douce France")
    // - AddBOM will add a UTF-8 Byte Order Mark at the beginning of the content
    procedure GetCSVValues(Dest: TStream; Tab: boolean; CommaSep: AnsiChar=',';
      AddBOM: boolean=false);

    {/ get the Field index of a FieldName
     - return -1 if not found, index (0..FieldCount-1) if found }
    function FieldIndex(FieldName: PUTF8Char): integer; overload;
    {/ get the Field index of a FieldName
     - return -1 if not found, index (0..FieldCount-1) if found }
    function FieldIndex(const FieldName: RawUTF8): integer; overload;
      {$ifdef HASINLINE}inline;{$endif}
    {/ get the Field content (encoded as UTF-8 text) from a property name
     - return nil if not found }
    function FieldValue(const FieldName: RawUTF8; Row: integer): PUTF8Char;
    {/ sort result Rows, according to a specific field
     - default is sorting by ascending order (Asc=true)
     - you can specify a Row index to be updated during the sort in PCurrentRow
     - sort is very fast, even for huge tables (more faster than any indexed
       SQL query): 500,000 rows are sorted instantly
     - this optimized sort implementation does the comparaison first by the
     designed field, and, if the field value is identical, the ID value is
     used (it will therefore sort by time all identical values) }
    procedure SortFields(Field: integer; Asc: boolean=true;
      PCurrentRow: PInteger=nil; FieldType: TSQLFieldType=sftUnknown);
    {/ sort result Rows, according to the Bits set to 1 first }
    procedure SortBitsFirst(var Bits);
    {/ 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; otherwize, 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; EnumTypeInfo: PPointer): TSQLFieldType;
    {/ 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)
    // - return the sum of all mean of character lengths
    function CalculateFieldLengthMean(var aResult: TIntegerDynArray;
      FromDisplay: boolean=false): integer;
    /// get the mean of characters length of this field
    // - the character length is for the first line of text only (stop counting
    // at every newline character, i.e. #10 or #13 char)
    // - very fast: calculated only once for all fields
    function FieldLengthMean(Field: integer): cardinal;
    /// get the sum of all mean of characters length of all fields
    // - very fast: calculated only once for all fields
    function FieldLengthMeanSum: cardinal;
    /// get the maximum number of characters of this field
    function FieldLengthMax(Field: integer; NeverReturnsZero: boolean=false): cardinal;
    /// get the record class (i.e. the table) associated to a field
    // - is nil if this table has no QueryTables property
    // - very fast: calculated only once for all fields
    function FieldTable(Field: integer): TClass;
    /// force the mean of characters length for every field
    // - expect as many parameters as fields in this table
    // - override internal fFieldLengthMean[] and fFieldLengthMeanSum values
    procedure SetFieldLengthMean(const Lengths: array of cardinal);
    /// 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
      be destroyed before this TSQLTable }
    procedure Assign(source: TSQLTable);

    /// search a text value inside the table data in a specified field
    // - the text value must already be uppercased 7-bits ANSI encoded
    // - return the Row on success, 0 on error
    // - search only in the content of FieldIndex data
    // - you can specify a Soundex pronunciation to use, or leave as sndxNone for
    // standard case insensitive character match; aUpperValue can optional
    // indicate a Soundex search, by predeceding the searched text with % for
    // English, %% for French or %%% for Spanish (only works with WinAnsi
    // char set - i.e. code page 1252)
    // - if UnicodeComparison is set to TRUE, search will use low-level Windows
    // API for Unicode-level conversion - it will be much slower, but accurate
    // for the whole range of UTF-8 encoding
    // - if UnicodeComparison is left to FALSE, UTF-8 decoding will be done only
    // if necessary: it will work only with standard western-occidental alphabet
    // (i.e. WinAnsi - code page 1252), but it will be very fast
    function SearchValue(const aUpperValue: RawUTF8; StartRow, FieldIndex: integer;
      Client: TObject; Lang: TSynSoundExPronunciation=sndxNone;
      UnicodeComparison: boolean=false): integer; overload;
    /// search a text value inside the table data in all fields
    // - the text value must already be uppercased 7-bits ANSI encoded
    // - return the Row on success, 0 on error
    // - search on all fields, returning field found in FieldIndex (if not nil)
    // - you can specify a Soundex pronunciation to use, or leave as sndxNone for
    // standard case insensitive character match; aUpperValue can optional
    // indicate a Soundex search, by predeceding the searched text with % for
    // English, %% for French or %%% for Spanish (only works with WinAnsi
    // char set - i.e. code page 1252)
    // - if UnicodeComparison is set to TRUE, search will use low-level Windows
    // API for Unicode-level conversion - it will be much slower, but accurate
    // for the whole range of UTF-8 encoding
    // - if UnicodeComparison is left to FALSE, UTF-8 decoding will be done only
    // if necessary: it will work only with standard western-occidental alphabet
    // (i.e. WinAnsi - code page 1252), but it will be very fast
    function SearchValue(const aUpperValue: RawUTF8; StartRow: integer;
      FieldIndex: PInteger; Client: TObject; Lang: TSynSoundExPronunciation=sndxNone;
      UnicodeComparison: boolean=false): integer; overload;
    /// search for a value inside the raw table data
    // - returns 0 if not found, or the matching Row number otherwise
    function SearchFieldEquals(const aValue: RawUTF8; FieldIndex: integer): integer;

    /// if the ID column is available, hides it from fResults[]
    // - usefull for simplier UI, with a hidden ID field
    // - use IDColumnHiddenValue() to get the ID of a specific row
    // - return true is ID was succesfully hidden, false if not possible
    function IDColumnHide: boolean;
    /// return the (previously hidden) ID value, 0 on error
    function IDColumnHiddenValue(Row: integer): integer;
    /// return all (previously hidden) ID values
    procedure IDColumnHiddenValues(var IDs: TIntegerDynArray);
    /// get all IDs where individual bit in Bits are set
    procedure IDArrayFromBits(const Bits; var IDs: TIntegerDynArray);
    /// get all individual bit in Bits corresponding to the supplied IDs
    // - warning: IDs integer array will be sorted within this method call
    procedure IDArrayToBits(var Bits; var IDs: TIntegerDynArray);
    /// get the Row index corresponding to a specified ID
    // - return the Row number, from 1 to RowCount
    // - return RowCount (last row index) if this ID was not found or no
    // ID field is available
    function RowFromID(aID: integer): integer;

    /// delete the specified data Row from the Table
    // - only overwrite the internal fResults[] pointers, don't free any memory,
    // nor modify the internal DataSet
    procedure DeleteRow(Row: integer);
    /// delete the specified Column text from the Table
    // - don't delete the Column: only delete UTF-8 text in all rows for this
    // field
    procedure DeleteColumnValues(Field: integer);

    /// retrieve QueryTables[0], if existing
    function QueryRecordType: TClass;

    /// create a new TSQLRecord instance for a specific Table
    // - use the specified TSQLRecord class or create one instance
    // of the first associated record class (from internal QueryTables[])
    // - use this method to create a working copy of a table's record, e.g.
    // - the record will be freed when the TSQLTable will be destroyed:
    // you don't need to make a Try..Finally..Free..end block with it
    function NewRecord(RecordType: TClass=nil): TObject;
    /// fill a TObjectList with TSQLRecord instances corresponding to this Table
    // - use the specified TSQLRecord class or create instances
    // of the first associated record class (from internal QueryTables[])
    // - returns nil if T does not inherit from TSQLRecord
    procedure ToObjectList(DestList: TObjectList; RecordType: TClass=nil); overload;
    {$ifdef UNICODE}
    /// fill a TObjectList<TSQLRecord> with TSQLRecord instances corresponding
    // - use the specified TSQLRecord class or create instances
    // of the first associated record class (from internal QueryTables[])
    // - returns a void list if T does not inherit from TSQLRecord
    function ToObjectList<T: class>: TObjectList<T>; overload;
    {$endif}

    {/ After a TSQLTable has been initialized, this method can be called
      one or more times to iterate through all data rows
     - you shall call this method before calling FieldBuffer()/Field() methods
     - return TRUE on success, with data ready to be retrieved by Field*()
     - return FALSE if no more row is available (i.e. exceeded RowCount)
     - if SeekFirst is TRUE, will put the cursor on the first row of results,
       otherwise, it will fetch one row of data, to be called within a loop
     - you can specify a variant instance (e.g. allocated on the stack) in
       optional RowVariant parameter, to access field values using late binding
     - typical use may be:
       ! while TableCustomers.Step do
       !   writeln(Field('name'));
     - or, when using a variant and late-binding:
       ! var customer: variant;
       ! ...
       !   while TableCustomers.Step(false,@customer) do
       !     writeln(customer.Name);
     }
    function Step(SeekFirst: boolean=false; RowVariant: PVariant=nil): boolean;
    /// read-only access to a particular field value, as UTF-8 encoded buffer
    // - raise an ESQLTableException if called outside valid Step() sequence
    // - similar to Get() method, but for the current Step
    function FieldBuffer(FieldIndex: Integer): PUTF8Char; overload;
    /// read-only access to a particular field value, as UTF-8 encoded buffer
    // - raise an ESQLTableException if called outside valid Step() sequence
    // - similar to Get() method, but for the current Step
    function FieldBuffer(const FieldName: RawUTF8): PUTF8Char; overload;
    {$ifdef USEVARIANTS}
    /// read-only access to a particular field value, as a variant
    // - raise an ESQLTableException if called outside valid Step() sequence
    // - will call GetVariant() method for appropriate data conversion
    function Field(FieldIndex: integer): variant; overload;
    /// read-only access to a particular field value, as a variant
    // - raise an ESQLTableException if called outside valid Step() sequence
    // - will call GetVariant() method for appropriate data conversion
    function Field(const FieldName: RawUTF8): variant; overload;
    {$endif}

    /// contains the associated record class on Query
    property QueryTables: TClasses read fQueryTables;
    /// contains the associated SQL statement on Query
    property QuerySQL: RawUTF8 read fQuerySQL;
    {/ read-only access to the number of data Row in this table
     - first row contains field name
     - then 1..RowCount rows contain the data itself }
    property RowCount: integer read fRowCount;
    {/ read-only access to the number of fields for each Row in this table }
    property FieldCount: integer read fFieldCount;
    /// read-only acccess to the current Row number, after a Step() call
    // - contains 0 if accessed outside valid Step() sequence call
    // - contains 1..RowCount after a valid Step() iteration
    property StepRow: integer read fStepRow;
    {/ this property contains the internal state counter of the server database
      when the data was retrieved from it
      - can be used to check if retrieved data may be out of date }
    property InternalState: cardinal read fInternalState write fInternalState;
    /// if the TSQLRecord is the owner of this table, i.e. if it must free it
    property OwnerMustFree: Boolean read fOwnerMustFree write fOwnerMustFree;
  end;

{$ifdef USEVARIANTS}
  /// a custom variant type used to have direct access to TSQLTable content
  // - use TSQLTable.Step(..,@Data) method to retrieve such a Variant
  TSQLTableRowVariantType = class(TSynInvokeableVariantType)
  protected
    procedure IntGet(var Dest: TVarData; const V: TVarData; Name: PAnsiChar); override;
    procedure IntSet(const V, Value: TVarData; Name: PAnsiChar); override;
  end;
{$endif USEVARIANTS}


  { Why use JSON? (extracted from the main framework documentation)
    - The JavaScript Object Notation (JSON) is a lightweight computer data
     interchange format
    - Like XML, it's a text-based, human-readable format for representing
     simple data structures and associative arrays (called objects)
    - It's easier to read, quicker to implement and smaller in size than XML
    - It's a very efficient format for cache
    - It's natively supported by the JavaScript language, making it a perfect
     serialization format for any Ajax application
    - The JSON format is specified in http://tools.ietf.org/html/rfc4627
    - The default text encoding for both JSON and SQLite3 is UTF-8, which
     allows the full Unicode charset to be stored and communicated
    - It is the default data format used by ASP.NET AJAX services created in
     Windows Communication Foundation (WCF) since .NET framework 3.5
    - For binary blob transmission, we simply encode the binary data as hexa
     using the SQLite3 BLOB literals format : hexadecimal data preceded by
     a single "x" or "X" character (for example: X'53514C697465'), or Base64
     encoding - see BlobToTSQLRawBlob() function }

  /// get a SQL result from a JSON message, and store it into its own memory
  TSQLTableJSON = class(TSQLTable)
  protected
    /// used if a private copy of the JSON buffer is needed
    fPrivateCopy: RawUTF8;
    /// contains the pointers of start of every field value in JSONData
    fJSONResults: array of PUTF8Char;
    /// contain the hash value of the last JSON data sent to ContentChanged()
    // - used to don't repeat parsing if data has not been changed
    fPrivateCopyHash: cardinal;
    /// fill the result table content from a JSON-formated Data message
    // - returns TRUE on parsing success
    // - returns FALSE if no valid JSON data was found
    // - update all content fields (fResults[], fRowCount, fFieldCount, etc...)
    // - expect the UTF-8 Buffer in either TSQLRequest.EngineExecute(DB,SQL,JSON)
    // format (i.e. expanded) or either in a not expanded format (as an
    // AJAX-ready array of objects)
    // - the conversion into PPUTF8CharArray is made inplace and is very fast
    // (no additional memory buffer is allocated)
    function ParseAndConvert(Buffer: PUTF8Char; BufferLen: integer): boolean;
    /// will check then set (if needed) internal fPrivateCopy[Hash] values
    // - returns TRUE if content changed (then fPrivateCopy+fPrivateCopyHash
    // will be updated)
    function PrivateCopyChanged(aJSON: PUTF8Char; aLen: integer): boolean;
  public
    /// create the result table from a JSON-formated Data message
    // - the JSON data is parsed and formatted in-place
    // - please note that the supplied JSON buffer content will be changed:
    // if you want to reuse this JSON content, you shall make a private copy
    // before calling this constructor and you shall NOT release the corresponding
    // variable (fResults/JSONResults[] will point inside this memory buffer):
    // use instead the overloaded Create constructor expecting aJSON parameter
    // making a private copy of the data
    // - you can use RecordClassesToClasses() wrapper function to convert an
    // array of TSQLRecordClass into the expected array of TClass
    constructor Create(const Tables: array of TClass; const aSQL: RawUTF8;
      JSONBuffer: PUTF8Char; JSONBufferLen: integer); overload;
    /// create the result table from a JSON-formated Data message
    // - the JSON data is parsed and formatted in-place, after having been
    // copied in the protected fPrivateCopy variable
    // - you can use RecordClassesToClasses() wrapper function to convert an
    // array of TSQLRecordClass into the expected array of TClass
    constructor Create(const Tables: array of TClass; const aSQL, aJSON: RawUTF8); overload;
    /// update the result table content from a JSON-formated Data message
    // - return true on parsing success, false if no valid JSON data was found
    // - set Refreshed to true if the content changed
    // - update all content fields (fResults[], fRowCount, fFieldCount, etc...)
    // - call SortFields() or IDColumnHide if was already done for this TSQLTable
    // - the conversion into PPUTF8CharArray is made inplace and is very fast
    // (only one memory buffer is allocated for the whole data)
    function UpdateFrom(const aJSON: RawUTF8; var Refreshed: boolean;
      PCurrentRow: PInteger): boolean;
    /// the private copy of the processed data buffer
    // - available e.g. for Create constructor using aJSON parameter,
    // or after the UpdateFrom() process
    // - this buffer is not to be access directly: this won't be a valid JSON
    // content, but a processed buffer, on which fResults[] elements point to
    property PrivateInternalCopy: RawUTF8 read fPrivateCopy;
  end;

const
  /// kind of fields not retrieved during normal query, update or adding
  NOT_SIMPLE_FIELDS: TSQLFieldTypes =
    [sftUnknown, sftBlob, sftMany];

  /// kind of fields which can be copied from one TSQLRecord instance to another
  COPIABLE_FIELDS: TSQLFieldTypes =
................................................................................
// - this function will call RemoveCommentsFromJSON() before process
function JSONFileToObject(const JSONFile: TFileName; var ObjectInstance;
  TObjectListItemClass: TClass=nil): boolean;

/// persist a class instance into a JSON file
procedure ObjectToJSONFile(Value: TObject; const JSONFile: TFileName;
  Options: TTextWriterWriteObjectOptions=[woHumanReadable]);



{ ************ some RTTI and SQL mapping routines }

{ type definitions below were adapted from TypInfo.pas
 - this implementation doesn't require to include Variant.pas any more (which
  allow easy server-side compile with LVCL, e.g.)
................................................................................
  SECURITY_DESCRIPTOR_REVISION = 1;
  SECURITY_DESCRIPTOR_MIN_LENGTH = 20;
{$ifend}
{$endif}
{$endif}

const
  /// maximum number of the locked record in a Table (used in TSQLLocks)
  // - code is somewhat faster and easier with a fixed cache size
  // - 512 seems big enough on practice
  MAX_SQLLOCKS = 512;

  /// maximum handled dimension for TSQLRecordRTree
  // - this value is the one used by SQLite3 R-Tree virtual table
  RTREE_MAX_DIMENSION = 5;

  /// used as "stored AS_UNIQUE" published property definition in TSQLRecord 
  AS_UNIQUE = false;

  /// HTML Status Code for "Success"
  HTML_SUCCESS = 200;
  /// HTML Status Code for "Created"
  HTML_CREATED = 201;
  /// HTML Status Code for "Not Modified"
  HTML_NOTMODIFIED = 304;
  /// HTML Status Code for "Bad Request"
................................................................................
  HTML_NOTIMPLEMENTED = 501;
  /// HTML Status Code for "Service Unavailable"
  HTML_UNAVAILABLE = 503;

/// convert any HTML_* constant to a short English text
procedure StatusCodeToErrorMsg(Code: integer; var result: RawUTF8);


























type
  /// exception raised in case of incorrect TSQLTable.Step / Field*() use
  ESQLTableException = class(ESynException);

  /// generic parent class of all custom Exception types of this unit
  EORMException = class(ESynException);

................................................................................
  /// exception dedicated to interface based service implementation
  EServiceException = class(EORMException);

  TSQLModel = class;
  TSQLRest = class;
  TSQLRestClient = class;
  TSQLModelRecordProperties = class;


{$M+} { we need the RTTI information to be compiled for the published
        properties of these classes and their children (like TPersistent),
        to enable ORM - must be defined at the forward definition level }
  TSQLRecord = class;      // published properties = ORM fields/columns
  TSQLRecordMany = class;
  TSQLAuthUser = class;
  TSQLRestServer = class;  // published methods = RESTful callbacks handlers
  TSQLRestClientURI = class;
{$M-}

  /// class-reference type (metaclass) of TSQLRecord
  TSQLRecordClass = class of TSQLRecord;








  /// information about a TSQLRecord class property
  // - sftID for TSQLRecord properties, which are pointer(RecordID), not
  // any true class instance
  // - sftMany for TSQLRecordMany properties, for which no data is
  // stored in the table itself, but in a pivot table
  // - sftObject for e.g. TStrings TRawUTF8List TCollection instances
................................................................................
    procedure SetValue(Instance: TObject; Value: PUTF8Char; wasString: boolean); override;
    procedure GetValueVar(Instance: TObject; ToSQL: boolean;
      var result: RawUTF8; wasSQLString: PBoolean); override;
    procedure GetBinary(Instance: TObject; W: TFileBufferWriter); override;
    function SetBinary(Instance: TObject; P: PAnsiChar): PAnsiChar; override;
  end;

  PClass = ^TClass;
  PSQLRecordClass = ^TSQLRecordClass;

  /// class-refrence type (metaclass) for a TSynFilter or a TSynValidate
  TSynFilterOrValidateClass = class of TSynFilterOrValidate;

  /// a dynamic array used to store the TSQLRecord classes in a Database Model
  TSQLRecordClassDynArray = array of TSQLRecordClass;

  /// the kind of SQlite3 (virtual) table
  // - TSQLRecordFTS3 will be associated with vFTS3, TSQLRecordFTS4 with vFTS4,
  // TSQLRecordRTree with vRTree, any native SQlite3 table as vSQLite3, and
  // a TSQLRecordVirtualTable*ID with rCustomForcedID/rCustomAutoID
  // - a plain TSQLRecord class can be defined as rCustomForcedID (e.g. for
  // TSQLRecordMany) after registration for an external DB via a call to
  // VirtualTableExternalRegister() from SQLite3DB unit
................................................................................
    {/ this property contains the internal state counter of the server database
      when the data was retrieved from it
      - may be used to check if retrieved data may be out of date }
    property InternalState: cardinal read fInternalState;
  published
    { published properties in inherited classes will be interpreted as SQL fields }
  end;

































































































































































































































































































































































































































































































  PSQLLocks = ^TSQLLocks;
  /// used to store the locked record list, in a specified table
  // - the maximum count of the locked list if fixed to 512 by default,
  // which seems correct for common usage
  TSQLLocks = {$ifndef ISDELPHI2010}object{$else}record{$endif}
    /// the number of locked records stored in this object
................................................................................
    // - TSQLRecordClass to be specified as its index in Rest.Model.Tables[]
    procedure NotifyDeletion(aTableIndex, aID: integer); overload;
  end;

  TSQLRestClass = class of TSQLRest;

  /// a generic REpresentational State Transfer (REST) client/server class
  TSQLRest = class(TObject)
  protected
    fModel: TSQLModel;
    fCache: TSQLRestCache;
    fTransactionActive: cardinal;
    fTransactionTable: TSQLRecordClass;
    fTransactionCriticalSession: TRTLCriticalSection;
    fAcquireWriteTimeOut: cardinal;
................................................................................

/// create a TRecordReference with the corresponding parameters
function RecordReference(Model: TSQLModel; aTable: TSQLRecordClass; aID: integer): TRecordReference;

/// convert a dynamic array of TRecordReference into its corresponding IDs
procedure RecordRefToID(var aArray: TPtrUIntDynArray);

/// wrapper to convert an array of TSQLRecordClass to a generic array of TClass
// - used e.g. by TSQLRestClientURI.List before calling TSQLTableJSON.Create
function RecordClassesToClasses(const Tables: array of TSQLRecordClass): TClasses;

/// get the order table name from a SQL statement
// - return the word following any 'ORDER BY' statement
// - return 'RowID' if none found
function SQLGetOrder(const SQL: RawUTF8): RawUTF8;

{$ifdef PUREPASCAL}{$ifdef HASINLINE}
/// this function is published only for class function TSQLRecord.RecordProps()
................................................................................
              EnumTypeInfo^ := SetEnumType;
          end;
        end;
      end;
    end;
end;

function FieldPropFromTables(const Tables: TClasses; const PropName: RawUTF8;
  EnumTypeInfo: PPointer; out TableIndex: integer): TSQLFieldType;
var SubProp: RawUTF8;
    i,t: integer;
begin
  TableIndex := -1;
  if length(Tables)=1 then begin
    result := FieldPropFromTable(TSQLRecordClass(Tables[0]),PropName,EnumTypeInfo);
    if result<>sftUnknown then
      TableIndex := 0;
  end else begin
    i := PosEx('.',PropName); 
    if i=0 then begin
      // no 'ClassName.PropertyName' format: find first exact property name
      for t := 0 to high(Tables) do begin
        result := FieldPropFromTable(TSQLRecordClass(Tables[t]),PropName,EnumTypeInfo);
        if result<>sftUnknown then begin
          TableIndex := t;
          exit;
        end;
      end;
    end else begin
      // we expect property names as 'ClassName.PropertyName'
................................................................................
        if IdemPropName( // found class?
          // new TObject.ClassName is UnicodeString (Delphi 20009) -> inline code with
          // vmtClassName = UTF-8 encoded text stored in a shortstring = -44
          PShortString(PPointer(PtrInt(Tables[t])+vmtClassName)^)^,
          pointer(PropName),i) then begin
        //if Tables[t].ClassNameIs(SubClass) then begin // not Delphi 2009 OK
          TableIndex := t;
          result := FieldPropFromTable(TSQLRecordClass(Tables[t]),SubProp,EnumTypeInfo); // get property type
          exit;
        end;
    end;
    result := sftUnknown;
  end;
end;

................................................................................
procedure TSQLTable.Assign(source: TSQLTable);
begin
  fResults := source.fResults;
  fRowCount := source.fRowCount;
  fFieldCount := source.fFieldCount;
end;

constructor TSQLTable.Create(const Tables: array of TClass; const aSQL: RawUTF8);
var n: integer;
begin
  inherited Create;
  n := length(Tables);
  if n>0 then begin
    SetLength(fQueryTables,n);
    move(Tables[0],fQueryTables[0],n*sizeof(TClass));
................................................................................

destructor TSQLTable.Destroy;
begin
  fOwnedRecords.Free;
  inherited;
end;

function TSQLTable.QueryRecordType: TClass;
begin
  if (self<>nil) and (pointer(fQueryTables)<>nil) then
      result := fQueryTables[0] else
      result := nil;
end;

function TSQLTable.NewRecord(RecordType: TClass=nil): TObject;
begin
  result := nil;
  if self=nil then
    exit;
  if RecordType=nil then begin
    RecordType := QueryRecordType;
    if RecordType=nil then
      exit;
  end;
  result := ClassInstanceCreate(RecordType);
  if fOwnedRecords=nil then
    fOwnedRecords := TObjectList.Create;
  fOwnedRecords.Add(result);
end;

{$ifdef UNICODE}
function TSQLTable.ToObjectList<T>: TObjectList<T>;
................................................................................
var R,Item: TSQLRecord;
    Row: PPUtf8Char;
    i: integer;
begin
  result := TObjectList<T>.Create;
  if (self=nil) or (RowCount=0) then
    exit;
  if not T.InheritsFrom(TSQLRecord) then
    exit;
  R := TSQLRecordClass(T).Create;
  try
    R.FillPrepare(self);
    Row := @fResults[FieldCount];     // Row^ points to first row of data
    {$ifdef ISDELPHIXE}
    result.Count := RowCount;         // faster than manual Add()
    for i := 0 to RowCount-1 do begin // TObjectList will free each instance
      Item := TSQLRecordClass(T).Create;
      PPointerArray(result.List)[i] := Item;
    {$else}
    for i := 0 to RowCount-1 do begin // TObjectList will free each instance
      Item := TSQLRecordClass(T).Create;
      Result.Add(Item);
    {$endif}
      R.fFill.Fill(pointer(Row),Item);
      Inc(Row,FieldCount); // next data row
    end;
  finally
    R.Free;
  end;
end;
{$endif}

procedure TSQLTable.ToObjectList(DestList: TObjectList; RecordType: TClass=nil);
var R: TSQLRecord;
    Row: PPUtf8Char;
    i: integer;
begin
  if DestList=nil then
    exit;
  DestList.Clear;
................................................................................
  if (self=nil) or (RowCount=0) then
    exit;
  if RecordType=nil then begin
    RecordType := QueryRecordType;
    if RecordType=nil then
      exit;
  end;
  if not RecordType.InheritsFrom(TSQLRecord) then
    exit;
  R := TSQLRecordClass(RecordType).Create;
  try
    R.FillPrepare(self);
    DestList.Count := RowCount;       // faster than manual Add()
    Row := @fResults[FieldCount];     // Row^ points to first row of data
    for i := 0 to RowCount-1 do begin // TObjectList will free each instance
      DestList.List[i] := TSQLRecordClass(RecordType).Create;
      R.fFill.Fill(pointer(Row),TSQLRecord(DestList.List[i]));
      Inc(Row,FieldCount); // next data row
    end;
  finally
    R.Free;
  end;
end;







{$ifdef USEVARIANTS}
var
  SQLTableRowVariantType: TCustomVariantType = nil;
{$endif}

function TSQLTable.Step(SeekFirst: boolean=false; RowVariant: PVariant=nil): boolean;
................................................................................
    end else
      // parse error
      result := false else
    // data didn't change (fPrivateCopyHash checked)
    result := true;
end;

constructor TSQLTableJSON.Create(const Tables: array of TClass;
  const aSQL: RawUTF8; JSONBuffer: PUTF8Char; JSONBufferLen: integer);
begin // don't raise exception on error parsing
  inherited Create(Tables,aSQL);
  ParseAndConvert(JSONBuffer,JSONBufferLen);
end;

constructor TSQLTableJSON.Create(const Tables: array of TClass; const aSQL,
  aJSON: RawUTF8);
var len: integer;
begin
  len := length(aJSON);
  PrivateCopyChanged(pointer(aJSON),len);
  Create(Tables,aSQL,pointer(fPrivateCopy),len);
end;
................................................................................
begin
  if self=nil then
    result := nil else
  with URI(Model.Root,'GET',@Resp,nil,@SQL) do
    if Lo=HTML_SUCCESS then begin // GET with SQL sent
      if high(Tables)=0 then
        result := TSQLTableJSON.Create([Tables[0]],SQL,Resp) else
        result := TSQLTableJSON.Create(RecordClassesToClasses(Tables),SQL,Resp);
      result.fInternalState := Hi;
    end else // get data
    result := nil;
end;

function TSQLRestClientURI.ServerInternalState: cardinal;
begin
................................................................................
    with TSQLRecord(Data[i]) do
      if (fID<>0) and (InternalState<>State) then begin // refresh needed?
        if not Refresh(fID,TSQLRecord(Data[i]),Refreshed) then
          result := false; // mark error retrieving new content
      end;
end;

function RecordClassesToClasses(const Tables: array of TSQLRecordClass): TClasses;
var n, i: integer;
begin
  n := length(Tables);
  SetLength(result,n);
  for i := 0 to n-1 do
    result[i] := Tables[i];
end;

function TSQLRestClientURI.List(const Tables: array of TSQLRecordClass;
  const SQLSelect, SQLWhere: RawUTF8): TSQLTableJSON;
var Resp, SQL: RawUTF8;
    U: RawUTF8;
    InternalState: cardinal;
begin
  result := nil;
................................................................................
    result := TSQLTableJSON.Create([Tables[0]],SQL,Resp); // get data
  end else begin
    // multiple tables -> send SQL statement as HTTP body
    with URI(Model.Root,'GET',@Resp,nil,@SQL) do
      if Lo<>HTML_SUCCESS then
        exit else
        InternalState := Hi;
    result := TSQLTableJSON.Create(RecordClassesToClasses(Tables),SQL,Resp); // get data
  end;
  result.fInternalState := InternalState;
end;

function TSQLRestClientURI.InternalListJSON(Table: TSQLRecordClass; const SQL: RawUTF8): TSQLTableJSON;
begin
  result := ExecuteList([Table],SQL);
................................................................................
end;

function TSQLRestServer.ExecuteList(const Tables: array of TSQLRecordClass; const SQL: RawUTF8): TSQLTableJSON;
var JSON: RawUTF8;
begin
  JSON := EngineList(SQL,false);
  if JSON<>'' then
    result := TSQLTableJSON.Create(RecordClassesToClasses(Tables),SQL,JSON) else
    result := nil;
end;

function TSQLRestServer.UnLock(Table: TSQLRecordClass; aID: integer): boolean;
begin
  result := Model.UnLock(Table,aID);
end;






>
>

|







 







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







 







<
<
<
<
<
<
<
<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







>







 







<
<
<
<
<
<
<
<
<
<
<
<







 







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







 







>













>
>
>
>
>
>
>







 







<
<
<



<
<
<







 







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







 







|







 







<
<
<
<







 







|
|





|







|







 







|







 







|







 







|






|









|







 







<
<











|











|







 







|
<
<





|







>
>
>
>
>
>







 







|






|







 







|







 







<
<
<
<
<
<
<
<
<







 







|







 







|







767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
...
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
...
930
931
932
933
934
935
936








937
938
939
940
941
942
943
....
1126
1127
1128
1129
1130
1131
1132


















































































































































































































































































































































































































































































































1133
1134
1135
1136
1137
1138
1139
....
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
....
2754
2755
2756
2757
2758
2759
2760












2761
2762
2763
2764
2765
2766
2767
....
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
....
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
....
2915
2916
2917
2918
2919
2920
2921



2922
2923
2924



2925
2926
2927
2928
2929
2930
2931
....
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
....
7207
7208
7209
7210
7211
7212
7213
7214
7215
7216
7217
7218
7219
7220
7221
.....
10498
10499
10500
10501
10502
10503
10504




10505
10506
10507
10508
10509
10510
10511
.....
13683
13684
13685
13686
13687
13688
13689
13690
13691
13692
13693
13694
13695
13696
13697
13698
13699
13700
13701
13702
13703
13704
13705
13706
13707
13708
13709
13710
13711
13712
.....
13717
13718
13719
13720
13721
13722
13723
13724
13725
13726
13727
13728
13729
13730
13731
.....
14552
14553
14554
14555
14556
14557
14558
14559
14560
14561
14562
14563
14564
14565
14566
.....
14571
14572
14573
14574
14575
14576
14577
14578
14579
14580
14581
14582
14583
14584
14585
14586
14587
14588
14589
14590
14591
14592
14593
14594
14595
14596
14597
14598
14599
14600
14601
14602
.....
14603
14604
14605
14606
14607
14608
14609


14610
14611
14612
14613
14614
14615
14616
14617
14618
14619
14620
14621
14622
14623
14624
14625
14626
14627
14628
14629
14630
14631
14632
14633
14634
14635
14636
14637
14638
14639
14640
.....
14641
14642
14643
14644
14645
14646
14647
14648


14649
14650
14651
14652
14653
14654
14655
14656
14657
14658
14659
14660
14661
14662
14663
14664
14665
14666
14667
14668
14669
14670
14671
14672
14673
14674
.....
15944
15945
15946
15947
15948
15949
15950
15951
15952
15953
15954
15955
15956
15957
15958
15959
15960
15961
15962
15963
15964
15965
.....
20813
20814
20815
20816
20817
20818
20819
20820
20821
20822
20823
20824
20825
20826
20827
.....
20897
20898
20899
20900
20901
20902
20903









20904
20905
20906
20907
20908
20909
20910
.....
20930
20931
20932
20933
20934
20935
20936
20937
20938
20939
20940
20941
20942
20943
20944
.....
22041
22042
22043
22044
22045
22046
22047
22048
22049
22050
22051
22052
22053
22054
22055
    - introducing TSQLRestClientURI.InternalCheckOpen/InternalClose methods to
      properly handle remote connection and re-connection
    - added TSQLRestClientURI.LastErrorCode/LastErrorMessage/LastErrorException
      properties, to retrieve additional information about remote URL() execution
    - added TSQLRestClientURI.ServiceRegister() and ServiceRegisterClientDriven()
      methods for easier Client-side interface-based services initialization
    - added JSONFileToObject() and ObjectToJSONFile() functions
    - unit interface deep refactoring: e.g. now TSQLTable will refers explicitly
      to TSQLRecord classes and not to plain TClass (e.g. for QueryTables[])
    - speed up of TSQLTable.FieldIndex() method (using binary search)
    - added TSQLTable.ToObjectList() and ToObjectList<T: TSQLRecord>() methods
    - added TSQLTable.Step(), FieldBuffer() and Field() methods, handling a
      cursor at TSQLTable level, with optional late-binding column access
    - added TSQLRestClientURI.ServerTimeStampSynchronize method to force time
      synchronization with the server - can be handy to test the connection 
    - added TSQLRest.TableHasRows/TableRowCount methods, and overriden direct
      implementation for TSQLRestServer/TSQLRestServerStaticInMemory (including
      SQL pattern recognition for TSQLRestServerStaticInMemory)
................................................................................
{$ifdef SSPIAUTH}
  SynSSPIAuth,
{$endif}
  SynCommons;



{ ************ low level types and constants for handling JSON and fields }

  { Why use JSON? (extracted from the main framework documentation)
    - The JavaScript Object Notation (JSON) is a lightweight computer data
     interchange format
    - Like XML, it's a text-based, human-readable format for representing
     simple data structures and associative arrays (called objects)
    - It's easier to read, quicker to implement and smaller in size than XML
    - It's a very efficient format for cache
    - It's natively supported by the JavaScript language, making it a perfect
     serialization format for any Ajax application
    - The JSON format is specified in http://tools.ietf.org/html/rfc4627
    - The default text encoding for both JSON and SQLite3 is UTF-8, which
     allows the full Unicode charset to be stored and communicated
    - It is the default data format used by ASP.NET AJAX services created in
     Windows Communication Foundation (WCF) since .NET framework 3.5
    - For binary blob transmission, we simply encode the binary data as hexa
     using the SQLite3 BLOB literals format : hexadecimal data preceded by
     a single "x" or "X" character (for example: X'53514C697465'), or Base64
     encoding - see BlobToTSQLRawBlob() function }


{.$define PUBLISHRECORD}
// - if defined, a sftBlobRecord will be defined in order to serialize published
// properties with some record type: but even if such properties can be defined,
// there is no RTTI generated for them: so it won't work :( see
// http://docwiki.embarcadero.com/RADStudio/en/Classes_and_Objects#Published_Members
// - should be defined globaly, e.g. in Synopse.inc
................................................................................
  // - this constant is used internaly to optimize memory usage in the
  // generated asm code
  // - you should not change it to a value lower than expected in an existing
  // database (e.g. as expected by TSQLAccessRights or such)
  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
................................................................................
    soInsert,
    soUpdate,
    soDelete);

  /// used to defined a set of CRUD associated SQL statement of a command
  TSQLOccasions = set of TSQLOccasion;



















































































































































































































































































































































































































































































































const
  /// kind of fields not retrieved during normal query, update or adding
  NOT_SIMPLE_FIELDS: TSQLFieldTypes =
    [sftUnknown, sftBlob, sftMany];

  /// kind of fields which can be copied from one TSQLRecord instance to another
  COPIABLE_FIELDS: TSQLFieldTypes =
................................................................................
// - this function will call RemoveCommentsFromJSON() before process
function JSONFileToObject(const JSONFile: TFileName; var ObjectInstance;
  TObjectListItemClass: TClass=nil): boolean;

/// persist a class instance into a JSON file
procedure ObjectToJSONFile(Value: TObject; const JSONFile: TFileName;
  Options: TTextWriterWriteObjectOptions=[woHumanReadable]);



{ ************ some RTTI and SQL mapping routines }

{ type definitions below were adapted from TypInfo.pas
 - this implementation doesn't require to include Variant.pas any more (which
  allow easy server-side compile with LVCL, e.g.)
................................................................................
  SECURITY_DESCRIPTOR_REVISION = 1;
  SECURITY_DESCRIPTOR_MIN_LENGTH = 20;
{$ifend}
{$endif}
{$endif}

const












  /// HTML Status Code for "Success"
  HTML_SUCCESS = 200;
  /// HTML Status Code for "Created"
  HTML_CREATED = 201;
  /// HTML Status Code for "Not Modified"
  HTML_NOTMODIFIED = 304;
  /// HTML Status Code for "Bad Request"
................................................................................
  HTML_NOTIMPLEMENTED = 501;
  /// HTML Status Code for "Service Unavailable"
  HTML_UNAVAILABLE = 503;

/// convert any HTML_* constant to a short English text
procedure StatusCodeToErrorMsg(Code: integer; var result: RawUTF8);



{ ************ main ORM / SOA classes and types }

const
  /// the used TAuthSession.IDCardinal value if the session not started yet
  // - i.e. if the session handling is still in its handshaking phase
  CONST_AUTHENTICATION_SESSION_NOT_STARTED = 0;

  /// the used TAuthSession.IDCardinal value if authentication mode is not set
  // - i.e. if TSQLRest.HandleAuthentication equals FALSE
  CONST_AUTHENTICATION_NOT_USED = 1;

  /// maximum number of the locked record in a Table (used in TSQLLocks)
  // - code is somewhat faster and easier with a fixed cache size
  // - 512 seems big enough on practice
  MAX_SQLLOCKS = 512;

  /// maximum handled dimension for TSQLRecordRTree
  // - this value is the one used by SQLite3 R-Tree virtual table
  RTREE_MAX_DIMENSION = 5;

  /// used as "stored AS_UNIQUE" published property definition in TSQLRecord
  AS_UNIQUE = false;

type
  /// exception raised in case of incorrect TSQLTable.Step / Field*() use
  ESQLTableException = class(ESynException);

  /// generic parent class of all custom Exception types of this unit
  EORMException = class(ESynException);

................................................................................
  /// exception dedicated to interface based service implementation
  EServiceException = class(EORMException);

  TSQLModel = class;
  TSQLRest = class;
  TSQLRestClient = class;
  TSQLModelRecordProperties = class;
  TSQLTable = class;

{$M+} { we need the RTTI information to be compiled for the published
        properties of these classes and their children (like TPersistent),
        to enable ORM - must be defined at the forward definition level }
  TSQLRecord = class;      // published properties = ORM fields/columns
  TSQLRecordMany = class;
  TSQLAuthUser = class;
  TSQLRestServer = class;  // published methods = RESTful callbacks handlers
  TSQLRestClientURI = class;
{$M-}

  /// class-reference type (metaclass) of TSQLRecord
  TSQLRecordClass = class of TSQLRecord;

  PClass = ^TClass;
  PSQLRecordClass = ^TSQLRecordClass;

  /// a dynamic array used to store the TSQLRecord classes in a Database Model
  TSQLRecordClassDynArray = array of TSQLRecordClass;


  /// information about a TSQLRecord class property
  // - sftID for TSQLRecord properties, which are pointer(RecordID), not
  // any true class instance
  // - sftMany for TSQLRecordMany properties, for which no data is
  // stored in the table itself, but in a pivot table
  // - sftObject for e.g. TStrings TRawUTF8List TCollection instances
................................................................................
    procedure SetValue(Instance: TObject; Value: PUTF8Char; wasString: boolean); override;
    procedure GetValueVar(Instance: TObject; ToSQL: boolean;
      var result: RawUTF8; wasSQLString: PBoolean); override;
    procedure GetBinary(Instance: TObject; W: TFileBufferWriter); override;
    function SetBinary(Instance: TObject; P: PAnsiChar): PAnsiChar; override;
  end;




  /// class-refrence type (metaclass) for a TSynFilter or a TSynValidate
  TSynFilterOrValidateClass = class of TSynFilterOrValidate;




  /// the kind of SQlite3 (virtual) table
  // - TSQLRecordFTS3 will be associated with vFTS3, TSQLRecordFTS4 with vFTS4,
  // TSQLRecordRTree with vRTree, any native SQlite3 table as vSQLite3, and
  // a TSQLRecordVirtualTable*ID with rCustomForcedID/rCustomAutoID
  // - a plain TSQLRecord class can be defined as rCustomForcedID (e.g. for
  // TSQLRecordMany) after registration for an external DB via a call to
  // VirtualTableExternalRegister() from SQLite3DB unit
................................................................................
    {/ this property contains the internal state counter of the server database
      when the data was retrieved from it
      - may be used to check if retrieved data may be out of date }
    property InternalState: cardinal read fInternalState;
  published
    { published properties in inherited classes will be interpreted as SQL fields }
  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
  // - GetA() or GetW() can be used in a TDrawString
  // - will be implemented as TSQLTableDB for direct SQLite3 database engine call,
  //  or as TSQLTableJSON for remote access through optimized JSON messages
  TSQLTable = class
  private
    fQueryTables: TSQLRecordClassDynArray;
    fQuerySQL: RawUTF8;
    fFieldLengthMean: TIntegerDynArray;
    fFieldLengthMeanSum: integer;
  protected
    fRowCount,
    fFieldCount: integer;
    /// contains the data, as returned by sqlite3_get_table()
    fResults: PPUTF8CharArray;
    /// contains the TSQLFieldType and TypeInfo(enumerate), after calculation
    // from the fQueryTables values
    fFieldType: array of record
      // the field kind
      ContentType: TSQLFieldType;
      // initialized for both sftEnumerate and sftSet field kinds
      EnumTypeInfo: pointer;
      // the corresponding index in fQueryTables[]
      TableIndex: integer;
    end;
    /// used by FieldIndex() for fast binary searcg
    fFieldNameOrder: TCardinalDynArray;
    /// contain the fResults[] pointers, after a IDColumnHide() call
    fIDColumn, fNotIDColumn: array of PUTF8Char;
    /// bit set at parsing to mark was a string value (e.g. "..." in JSON)
    fFieldParsedAsString: set of 0..255;
    /// index of a 'ID' field, -1 if none (e.g. after IDColumnHide method call)
    fFieldIndexID: integer;
    /// the internal state counter of the database when the data was retrieved
    fInternalState: cardinal;
    /// contains the parameters used for sorting
    fSortParams: TSQLTableSortParams;
    /// contains the TSQLRecord instances created by NewRecord method
    fOwnedRecords: TObjectList;
    /// if the TSQLRecord is the owner of this table, i.e. if it must free it
    fOwnerMustFree: Boolean;
    /// current cursor row (1..RowCount), as set by the Step() method
    fStepRow: integer;
    /// fill the fFieldType[] array (from fQueryTables[] or fResults[] content)
    procedure InitFieldTypes;
  public
    /// initialize the result table
    // - you can optionaly associate the corresponding TSQLRecordClass types,
    // by which the results were computed (it will use RTTI for column typing)
    constructor Create(const Tables: array of TSQLRecordClass; const aSQL: RawUTF8);
    /// free associated memory and owned records
    destructor Destroy; override;
    {/ read-only access to a particular field value, as UTF-8 encoded buffer
     - points to memory buffer allocated by Init() }
    function Get(Row,Field: integer): PUTF8Char;
    {/ read-only access to a particular field value, as RawUTF8 text }
    function GetU(Row,Field: integer): RawUTF8;
    {/ read-only access to a particular field value, as Win Ansi text }
    function GetA(Row,Field: integer): WinAnsiString;
    {/ read-only access to a particular field value, as Win Ansi text shortstring}
    function GetS(Row,Field: integer): shortstring;
    {$ifdef USEVARIANTS}
    {/ read-only access to a particular field value, as a Variant
     - will try to use the most approriate Variant type for conversion (will
       use e.g. TDateTime for sftDateTime or sftTimeLog, Extended for sftFloat...)
     - will handle any necessary conversion to VCL string text, ready to be displayed
     - the global UTF8ToString() function will be used for the conversion:
       for proper i18n handling before Delphi 2009, you should use the
       overloaded method with aUTF8ToString=Language.UTF8ToString }
    function GetVariant(Row,Field: integer; Client: TObject): variant; overload;
    {/ read-only access to a particular field value, as a Variant
     - will try to use the most approriate Variant type for conversion (will
       use e.g. TDateTime for sftDateTime or sftTimeLog, Extended for sftFloat...)
     - will handle any necessary conversion to VCL string text, ready to be displayed
     - the global UTF8ToString() function will be used for the conversion:
       for proper i18n handling before Delphi 2009, you should use the
       overloaded method with aUTF8ToString=Language.UTF8ToString }
    procedure GetVariant(Row,Field: integer; Client: TObject; var result: variant); overload;
    {$endif}
    {/ read-only access to a particular field value, as VCL string text
     - the global UTF8ToString() function will be used for the conversion:
     for proper i18n handling before Delphi 2009, you should use the
     overloaded method with aUTF8ToString=Language.UTF8ToString }
    function GetString(Row,Field: integer): string;
    {/ fill a unicode buffer with a particular field value
      - return number of wide characters written in Dest^ }
    function GetWP(Row,Field: integer; Dest: PWideChar; MaxDestChars: cardinal): integer;
    {/ read-only access to a particular field value, as UTF-16 Unicode text
      - Raw Unicode is WideChar(zero) terminated
      - its content is allocated to contain all WideChars (not trimed to 255,
        like GetWP() above }
    function GetW(Row,Field: integer): RawUnicode;
    {/ read-only access to a particular field value, as integer value }
    function GetAsInteger(Row,Field: integer): integer;
      {$ifdef HASINLINE}inline;{$endif}
    {/ read-only access to a particular field value, as Int64 value }
    function GetAsInt64(Row,Field: integer): Int64;
      {$ifdef HASINLINE}inline;{$endif}
    {/ read-only access to a particular field value, ready to be displayed
      - mostly used with Row=0, i.e. to get a display value from a field name
      - use "string" type, i.e. UnicodeString for Delphi 2009+
      - value is first un-camel-cased: 'OnLine' value will return 'On line' e.g.
      - then System.LoadResStringTranslate() is called if available }
    function GetCaption(Row,Field: integer): string;
    {/ read-only access to a particular Blob value
     - a new TSQLRawBlob is created
     - Blob data is converted from SQLite3 BLOB literals (X'53514C697465' e.g.)
       or Base-64 encoded content ('\uFFF0base64encodedbinary')
     - prefered manner is to directly use REST protocol to retrieve a blob field }
    function GetBlob(Row,Field: integer): TSQLRawBlob;
    {/ read-only access to a particular Blob value
     - a new TBytes is created
     - Blob data is converted from SQLite3 BLOB literals (X'53514C697465' e.g.)
       or Base-64 encoded content ('\uFFF0base64encodedbinary')
     - prefered manner is to directly use REST protocol to retrieve a blob field }
    function GetBytes(Row,Field: integer): TBytes;
    {/ read-only access to a particular Blob value
     - a new TCustomMemoryStream is created - caller shall free its instance
     - Blob data is converted from SQLite3 BLOB literals (X'53514C697465' e.g.)
       or Base-64 encoded content ('\uFFF0base64encodedbinary')
     - prefered manner is to directly use REST protocol to retrieve a blob field }
    function GetStream(Row,Field: integer): TStream;
    {/ read-only access to a particular field value, as VCL text
     - Client is one TSQLClient instance (used to display TRecordReference via
       the associated TSQLModel)
     - returns the Field Type
     - return generic string Text, i.e. UnicodeString for Delphi 2009+,
       ready to be displayed to the VCL, for sftEnumerate, sftTimeLog
       and sftRecord/sftID
     - returns '' as string Text, if text can by displayed directly
       with Get*() methods above
     - returns '' for other properties kind, if UTF8ToString is nil,
       or the ready to be displayed value if UTF8ToString event is set
       (to be used mostly with Language.UTF8ToString) }
    function ExpandAsString(Row,Field: integer; Client: TObject; out Text: string): TSQLFieldType;
    {/ read-only access to a particular field value, as VCL text
     - this method is just a wrapper around ExpandAsString method, returning
       the content as a SynUnicode string type (i.e. UnicodeString since Delphi
       2009, and WideString for non Unicode versions of Delphi)
     - it is used by the reporting layers of the framework (e.g. TSQLRibbon.AddToReport) }
    function ExpandAsSynUnicode(Row,Field: integer; Client: TObject; out Text: SynUnicode): TSQLFieldType;
    {/ read-only access to a particular DateTime field value
     - expect SQLite3 TEXT field in ISO 8601 'YYYYMMDD hhmmss' or
      'YYYY-MM-DD hh:mm:ss' format }
    function GetDateTime(Row,Field: integer): TDateTime;
      {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
    {/ read-only access to a particular TTimeLog field value
      - return the result as Iso8601.Text() Iso-8601 encoded text }
    function GetTimeLog(Row,Field: integer; Expanded: boolean; FirstTimeChar: AnsiChar = 'T'): RawUTF8;
    {/ widechar length (UTF-8 decoded) of a particular field value }
    function LengthW(Row,Field: integer): integer;
    {/ get all values for a specified field into a dynamic RawUTF8 array
     - don't perform any conversion, but just create an array of raw PUTF8Char data }
    procedure GetRowValues(Field: integer; out Values: TRawUTF8DynArray); overload;
    {/ get all values for a specified field into a dynamic Integer array }
    procedure GetRowValues(Field: integer; out Values: TIntegerDynArray); overload;
    {/ get all values for a specified field as CSV
     - don't perform any conversion, but create a CSV from raw PUTF8Char data }
    function GetRowValues(Field: integer; Sep: AnsiChar=','): RawUTF8; overload;

    /// 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
    // - if Tab=TRUE, will use TAB instead of ',' between columns
    // - you can customize the ',' separator - use e.g. the global ListSeparator
    // variable (from SysUtils) to reflect the current system definition (some
    // country use ',' as decimal separator, for instance our "douce France")
    // - AddBOM will add a UTF-8 Byte Order Mark at the beginning of the content
    procedure GetCSVValues(Dest: TStream; Tab: boolean; CommaSep: AnsiChar=',';
      AddBOM: boolean=false);

    {/ get the Field index of a FieldName
     - return -1 if not found, index (0..FieldCount-1) if found }
    function FieldIndex(FieldName: PUTF8Char): integer; overload;
    {/ get the Field index of a FieldName
     - return -1 if not found, index (0..FieldCount-1) if found }
    function FieldIndex(const FieldName: RawUTF8): integer; overload;
      {$ifdef HASINLINE}inline;{$endif}
    {/ get the Field content (encoded as UTF-8 text) from a property name
     - return nil if not found }
    function FieldValue(const FieldName: RawUTF8; Row: integer): PUTF8Char;
    {/ sort result Rows, according to a specific field
     - default is sorting by ascending order (Asc=true)
     - you can specify a Row index to be updated during the sort in PCurrentRow
     - sort is very fast, even for huge tables (more faster than any indexed
       SQL query): 500,000 rows are sorted instantly
     - this optimized sort implementation does the comparaison first by the
     designed field, and, if the field value is identical, the ID value is
     used (it will therefore sort by time all identical values) }
    procedure SortFields(Field: integer; Asc: boolean=true;
      PCurrentRow: PInteger=nil; FieldType: TSQLFieldType=sftUnknown);
    {/ sort result Rows, according to the Bits set to 1 first }
    procedure SortBitsFirst(var Bits);
    {/ 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; otherwize, 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; EnumTypeInfo: PPointer): TSQLFieldType;
    {/ 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)
    // - return the sum of all mean of character lengths
    function CalculateFieldLengthMean(var aResult: TIntegerDynArray;
      FromDisplay: boolean=false): integer;
    /// get the mean of characters length of this field
    // - the character length is for the first line of text only (stop counting
    // at every newline character, i.e. #10 or #13 char)
    // - very fast: calculated only once for all fields
    function FieldLengthMean(Field: integer): cardinal;
    /// get the sum of all mean of characters length of all fields
    // - very fast: calculated only once for all fields
    function FieldLengthMeanSum: cardinal;
    /// get the maximum number of characters of this field
    function FieldLengthMax(Field: integer; NeverReturnsZero: boolean=false): cardinal;
    /// get the record class (i.e. the table) associated to a field
    // - is nil if this table has no QueryTables property
    // - very fast: calculated only once for all fields
    function FieldTable(Field: integer): TClass;
    /// force the mean of characters length for every field
    // - expect as many parameters as fields in this table
    // - override internal fFieldLengthMean[] and fFieldLengthMeanSum values
    procedure SetFieldLengthMean(const Lengths: array of cardinal);
    /// 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
      be destroyed before this TSQLTable }
    procedure Assign(source: TSQLTable);

    /// search a text value inside the table data in a specified field
    // - the text value must already be uppercased 7-bits ANSI encoded
    // - return the Row on success, 0 on error
    // - search only in the content of FieldIndex data
    // - you can specify a Soundex pronunciation to use, or leave as sndxNone for
    // standard case insensitive character match; aUpperValue can optional
    // indicate a Soundex search, by predeceding the searched text with % for
    // English, %% for French or %%% for Spanish (only works with WinAnsi
    // char set - i.e. code page 1252)
    // - if UnicodeComparison is set to TRUE, search will use low-level Windows
    // API for Unicode-level conversion - it will be much slower, but accurate
    // for the whole range of UTF-8 encoding
    // - if UnicodeComparison is left to FALSE, UTF-8 decoding will be done only
    // if necessary: it will work only with standard western-occidental alphabet
    // (i.e. WinAnsi - code page 1252), but it will be very fast
    function SearchValue(const aUpperValue: RawUTF8; StartRow, FieldIndex: integer;
      Client: TObject; Lang: TSynSoundExPronunciation=sndxNone;
      UnicodeComparison: boolean=false): integer; overload;
    /// search a text value inside the table data in all fields
    // - the text value must already be uppercased 7-bits ANSI encoded
    // - return the Row on success, 0 on error
    // - search on all fields, returning field found in FieldIndex (if not nil)
    // - you can specify a Soundex pronunciation to use, or leave as sndxNone for
    // standard case insensitive character match; aUpperValue can optional
    // indicate a Soundex search, by predeceding the searched text with % for
    // English, %% for French or %%% for Spanish (only works with WinAnsi
    // char set - i.e. code page 1252)
    // - if UnicodeComparison is set to TRUE, search will use low-level Windows
    // API for Unicode-level conversion - it will be much slower, but accurate
    // for the whole range of UTF-8 encoding
    // - if UnicodeComparison is left to FALSE, UTF-8 decoding will be done only
    // if necessary: it will work only with standard western-occidental alphabet
    // (i.e. WinAnsi - code page 1252), but it will be very fast
    function SearchValue(const aUpperValue: RawUTF8; StartRow: integer;
      FieldIndex: PInteger; Client: TObject; Lang: TSynSoundExPronunciation=sndxNone;
      UnicodeComparison: boolean=false): integer; overload;
    /// search for a value inside the raw table data
    // - returns 0 if not found, or the matching Row number otherwise
    function SearchFieldEquals(const aValue: RawUTF8; FieldIndex: integer): integer;

    /// if the ID column is available, hides it from fResults[]
    // - usefull for simplier UI, with a hidden ID field
    // - use IDColumnHiddenValue() to get the ID of a specific row
    // - return true is ID was succesfully hidden, false if not possible
    function IDColumnHide: boolean;
    /// return the (previously hidden) ID value, 0 on error
    function IDColumnHiddenValue(Row: integer): integer;
    /// return all (previously hidden) ID values
    procedure IDColumnHiddenValues(var IDs: TIntegerDynArray);
    /// get all IDs where individual bit in Bits are set
    procedure IDArrayFromBits(const Bits; var IDs: TIntegerDynArray);
    /// get all individual bit in Bits corresponding to the supplied IDs
    // - warning: IDs integer array will be sorted within this method call
    procedure IDArrayToBits(var Bits; var IDs: TIntegerDynArray);
    /// get the Row index corresponding to a specified ID
    // - return the Row number, from 1 to RowCount
    // - return RowCount (last row index) if this ID was not found or no
    // ID field is available
    function RowFromID(aID: integer): integer;

    /// delete the specified data Row from the Table
    // - only overwrite the internal fResults[] pointers, don't free any memory,
    // nor modify the internal DataSet
    procedure DeleteRow(Row: integer);
    /// delete the specified Column text from the Table
    // - don't delete the Column: only delete UTF-8 text in all rows for this
    // field
    procedure DeleteColumnValues(Field: integer);

    /// retrieve QueryTables[0], if existing
    function QueryRecordType: TSQLRecordClass;

    /// create a new TSQLRecord instance for a specific Table
    // - use the specified TSQLRecord class or create one instance
    // of the first associated record class (from internal QueryTables[])
    // - use this method to create a working copy of a table's record, e.g.
    // - the record will be freed when the TSQLTable will be destroyed:
    // you don't need to make a Try..Finally..Free..end block with it
    function NewRecord(RecordType: TSQLRecordClass=nil): TSQLRecord;
    /// create a TObjectList with TSQLRecord instances corresponding to this
    // TSQLTable result set
    // - use the specified TSQLRecord class or create instances
    // of the first associated record class (from internal QueryTables[])
    // - always returns an instance, even if the TSQLTable is nil or void
    function ToObjectList(RecordType: TSQLRecordClass=nil): TObjectList; overload;
    /// fill an existing TObjectList with TSQLRecord instances corresponding
    // to this TSQLTable result set
    // - use the specified TSQLRecord class or create instances
    // of the first associated record class (from internal QueryTables[])
    procedure ToObjectList(DestList: TObjectList; RecordType: TSQLRecordClass=nil); overload;
    {$ifdef UNICODE}
    /// create a TObjectList<TSQLRecord> with TSQLRecord instances corresponding
    // to this TSQLTable result set
    // - use the specified TSQLRecord class or create instances
    // of the first associated record class (from internal QueryTables[])
    // - always returns an instance, even if the TSQLTable is nil or void
    function ToObjectList<T: TSQLRecord>: TObjectList<T>; overload;
    {$endif}

    {/ After a TSQLTable has been initialized, this method can be called
      one or more times to iterate through all data rows
     - you shall call this method before calling FieldBuffer()/Field() methods
     - return TRUE on success, with data ready to be retrieved by Field*()
     - return FALSE if no more row is available (i.e. exceeded RowCount)
     - if SeekFirst is TRUE, will put the cursor on the first row of results,
       otherwise, it will fetch one row of data, to be called within a loop
     - you can specify a variant instance (e.g. allocated on the stack) in
       optional RowVariant parameter, to access field values using late binding
     - typical use may be:
       ! while TableCustomers.Step do
       !   writeln(Field('name'));
     - or, when using a variant and late-binding:
       ! var customer: variant;
       ! ...
       !   while TableCustomers.Step(false,@customer) do
       !     writeln(customer.Name);
     }
    function Step(SeekFirst: boolean=false; RowVariant: PVariant=nil): boolean;
    /// read-only access to a particular field value, as UTF-8 encoded buffer
    // - raise an ESQLTableException if called outside valid Step() sequence
    // - similar to Get() method, but for the current Step
    function FieldBuffer(FieldIndex: Integer): PUTF8Char; overload;
    /// read-only access to a particular field value, as UTF-8 encoded buffer
    // - raise an ESQLTableException if called outside valid Step() sequence
    // - similar to Get() method, but for the current Step
    function FieldBuffer(const FieldName: RawUTF8): PUTF8Char; overload;
    {$ifdef USEVARIANTS}
    /// read-only access to a particular field value, as a variant
    // - raise an ESQLTableException if called outside valid Step() sequence
    // - will call GetVariant() method for appropriate data conversion
    function Field(FieldIndex: integer): variant; overload;
    /// read-only access to a particular field value, as a variant
    // - raise an ESQLTableException if called outside valid Step() sequence
    // - will call GetVariant() method for appropriate data conversion
    function Field(const FieldName: RawUTF8): variant; overload;
    {$endif}

    /// contains the associated record class on Query
    property QueryTables: TSQLRecordClassDynArray read fQueryTables;
    /// contains the associated SQL statement on Query
    property QuerySQL: RawUTF8 read fQuerySQL;
    {/ read-only access to the number of data Row in this table
     - first row contains field name
     - then 1..RowCount rows contain the data itself }
    property RowCount: integer read fRowCount;
    {/ read-only access to the number of fields for each Row in this table }
    property FieldCount: integer read fFieldCount;
    /// read-only acccess to the current Row number, after a Step() call
    // - contains 0 if accessed outside valid Step() sequence call
    // - contains 1..RowCount after a valid Step() iteration
    property StepRow: integer read fStepRow;
    {/ this property contains the internal state counter of the server database
      when the data was retrieved from it
      - can be used to check if retrieved data may be out of date }
    property InternalState: cardinal read fInternalState write fInternalState;
    /// if the TSQLRecord is the owner of this table, i.e. if it must free it
    property OwnerMustFree: Boolean read fOwnerMustFree write fOwnerMustFree;
  end;

{$ifdef USEVARIANTS}
  /// a custom variant type used to have direct access to TSQLTable content
  // - use TSQLTable.Step(..,@Data) method to initialize such a Variant
  // - the variant members/fields are read-only by design 
  TSQLTableRowVariantType = class(TSynInvokeableVariantType)
  protected
    procedure IntGet(var Dest: TVarData; const V: TVarData; Name: PAnsiChar); override;
    procedure IntSet(const V, Value: TVarData; Name: PAnsiChar); override;
  end;
{$endif USEVARIANTS}


  /// get a SQL result from a JSON message, and store it into its own memory
  TSQLTableJSON = class(TSQLTable)
  protected
    /// used if a private copy of the JSON buffer is needed
    fPrivateCopy: RawUTF8;
    /// contains the pointers of start of every field value in JSONData
    fJSONResults: array of PUTF8Char;
    /// contain the hash value of the last JSON data sent to ContentChanged()
    // - used to don't repeat parsing if data has not been changed
    fPrivateCopyHash: cardinal;
    /// fill the result table content from a JSON-formated Data message
    // - returns TRUE on parsing success
    // - returns FALSE if no valid JSON data was found
    // - update all content fields (fResults[], fRowCount, fFieldCount, etc...)
    // - expect the UTF-8 Buffer in either TSQLRequest.EngineExecute(DB,SQL,JSON)
    // format (i.e. expanded) or either in a not expanded format (as an
    // AJAX-ready array of objects)
    // - the conversion into PPUTF8CharArray is made inplace and is very fast
    // (no additional memory buffer is allocated)
    function ParseAndConvert(Buffer: PUTF8Char; BufferLen: integer): boolean;
    /// will check then set (if needed) internal fPrivateCopy[Hash] values
    // - returns TRUE if content changed (then fPrivateCopy+fPrivateCopyHash
    // will be updated)
    function PrivateCopyChanged(aJSON: PUTF8Char; aLen: integer): boolean;
  public
    /// create the result table from a JSON-formated Data message
    // - the JSON data is parsed and formatted in-place
    // - please note that the supplied JSON buffer content will be changed:
    // if you want to reuse this JSON content, you shall make a private copy
    // before calling this constructor and you shall NOT release the corresponding
    // variable (fResults/JSONResults[] will point inside this memory buffer):
    // use instead the overloaded Create constructor expecting aJSON parameter
    // making a private copy of the data
    constructor Create(const Tables: array of TSQLRecordClass; const aSQL: RawUTF8;
      JSONBuffer: PUTF8Char; JSONBufferLen: integer); overload;
    /// create the result table from a JSON-formated Data message
    // - the JSON data is parsed and formatted in-place, after having been
    // copied in the protected fPrivateCopy variable
    constructor Create(const Tables: array of TSQLRecordClass; const aSQL, aJSON: RawUTF8); overload;
    /// update the result table content from a JSON-formated Data message
    // - return true on parsing success, false if no valid JSON data was found
    // - set Refreshed to true if the content changed
    // - update all content fields (fResults[], fRowCount, fFieldCount, etc...)
    // - call SortFields() or IDColumnHide if was already done for this TSQLTable
    // - the conversion into PPUTF8CharArray is made inplace and is very fast
    // (only one memory buffer is allocated for the whole data)
    function UpdateFrom(const aJSON: RawUTF8; var Refreshed: boolean;
      PCurrentRow: PInteger): boolean;
    /// the private copy of the processed data buffer
    // - available e.g. for Create constructor using aJSON parameter,
    // or after the UpdateFrom() process
    // - this buffer is not to be access directly: this won't be a valid JSON
    // content, but a processed buffer, on which fResults[] elements point to
    property PrivateInternalCopy: RawUTF8 read fPrivateCopy;
  end;

  PSQLLocks = ^TSQLLocks;
  /// used to store the locked record list, in a specified table
  // - the maximum count of the locked list if fixed to 512 by default,
  // which seems correct for common usage
  TSQLLocks = {$ifndef ISDELPHI2010}object{$else}record{$endif}
    /// the number of locked records stored in this object
................................................................................
    // - TSQLRecordClass to be specified as its index in Rest.Model.Tables[]
    procedure NotifyDeletion(aTableIndex, aID: integer); overload;
  end;

  TSQLRestClass = class of TSQLRest;

  /// a generic REpresentational State Transfer (REST) client/server class
  TSQLRest = class
  protected
    fModel: TSQLModel;
    fCache: TSQLRestCache;
    fTransactionActive: cardinal;
    fTransactionTable: TSQLRecordClass;
    fTransactionCriticalSession: TRTLCriticalSection;
    fAcquireWriteTimeOut: cardinal;
................................................................................

/// create a TRecordReference with the corresponding parameters
function RecordReference(Model: TSQLModel; aTable: TSQLRecordClass; aID: integer): TRecordReference;

/// convert a dynamic array of TRecordReference into its corresponding IDs
procedure RecordRefToID(var aArray: TPtrUIntDynArray);





/// get the order table name from a SQL statement
// - return the word following any 'ORDER BY' statement
// - return 'RowID' if none found
function SQLGetOrder(const SQL: RawUTF8): RawUTF8;

{$ifdef PUREPASCAL}{$ifdef HASINLINE}
/// this function is published only for class function TSQLRecord.RecordProps()
................................................................................
              EnumTypeInfo^ := SetEnumType;
          end;
        end;
      end;
    end;
end;

function FieldPropFromTables(const Tables: TSQLRecordClassDynArray;
  const PropName: RawUTF8; EnumTypeInfo: PPointer; out TableIndex: integer): TSQLFieldType;
var SubProp: RawUTF8;
    i,t: integer;
begin
  TableIndex := -1;
  if length(Tables)=1 then begin
    result := FieldPropFromTable(Tables[0],PropName,EnumTypeInfo);
    if result<>sftUnknown then
      TableIndex := 0;
  end else begin
    i := PosEx('.',PropName); 
    if i=0 then begin
      // no 'ClassName.PropertyName' format: find first exact property name
      for t := 0 to high(Tables) do begin
        result := FieldPropFromTable(Tables[t],PropName,EnumTypeInfo);
        if result<>sftUnknown then begin
          TableIndex := t;
          exit;
        end;
      end;
    end else begin
      // we expect property names as 'ClassName.PropertyName'
................................................................................
        if IdemPropName( // found class?
          // new TObject.ClassName is UnicodeString (Delphi 20009) -> inline code with
          // vmtClassName = UTF-8 encoded text stored in a shortstring = -44
          PShortString(PPointer(PtrInt(Tables[t])+vmtClassName)^)^,
          pointer(PropName),i) then begin
        //if Tables[t].ClassNameIs(SubClass) then begin // not Delphi 2009 OK
          TableIndex := t;
          result := FieldPropFromTable(Tables[t],SubProp,EnumTypeInfo); // get property type
          exit;
        end;
    end;
    result := sftUnknown;
  end;
end;

................................................................................
procedure TSQLTable.Assign(source: TSQLTable);
begin
  fResults := source.fResults;
  fRowCount := source.fRowCount;
  fFieldCount := source.fFieldCount;
end;

constructor TSQLTable.Create(const Tables: array of TSQLRecordClass; const aSQL: RawUTF8);
var n: integer;
begin
  inherited Create;
  n := length(Tables);
  if n>0 then begin
    SetLength(fQueryTables,n);
    move(Tables[0],fQueryTables[0],n*sizeof(TClass));
................................................................................

destructor TSQLTable.Destroy;
begin
  fOwnedRecords.Free;
  inherited;
end;

function TSQLTable.QueryRecordType: TSQLRecordClass;
begin
  if (self<>nil) and (pointer(fQueryTables)<>nil) then
      result := fQueryTables[0] else
      result := nil;
end;

function TSQLTable.NewRecord(RecordType: TSQLRecordClass=nil): TSQLRecord;
begin
  result := nil;
  if self=nil then
    exit;
  if RecordType=nil then begin
    RecordType := QueryRecordType;
    if RecordType=nil then
      exit;
  end;
  result := RecordType.Create;
  if fOwnedRecords=nil then
    fOwnedRecords := TObjectList.Create;
  fOwnedRecords.Add(result);
end;

{$ifdef UNICODE}
function TSQLTable.ToObjectList<T>: TObjectList<T>;
................................................................................
var R,Item: TSQLRecord;
    Row: PPUtf8Char;
    i: integer;
begin
  result := TObjectList<T>.Create;
  if (self=nil) or (RowCount=0) then
    exit;


  R := TSQLRecordClass(T).Create;
  try
    R.FillPrepare(self);
    Row := @fResults[FieldCount];     // Row^ points to first row of data
    {$ifdef ISDELPHIXE}
    result.Count := RowCount;         // faster than manual Add()
    for i := 0 to RowCount-1 do begin // TObjectList will free each instance
      Item := TSQLRecordClass(T).Create;
      PPointerArray(result.List)[i] := Item;
    {$else}
    for i := 0 to RowCount-1 do begin // TObjectList will free each instance
      Item := T.Create;
      Result.Add(Item);
    {$endif}
      R.fFill.Fill(pointer(Row),Item);
      Inc(Row,FieldCount); // next data row
    end;
  finally
    R.Free;
  end;
end;
{$endif}

procedure TSQLTable.ToObjectList(DestList: TObjectList; RecordType: TSQLRecordClass=nil);
var R: TSQLRecord;
    Row: PPUtf8Char;
    i: integer;
begin
  if DestList=nil then
    exit;
  DestList.Clear;
................................................................................
  if (self=nil) or (RowCount=0) then
    exit;
  if RecordType=nil then begin
    RecordType := QueryRecordType;
    if RecordType=nil then
      exit;
  end;
  R := RecordType.Create;


  try
    R.FillPrepare(self);
    DestList.Count := RowCount;       // faster than manual Add()
    Row := @fResults[FieldCount];     // Row^ points to first row of data
    for i := 0 to RowCount-1 do begin // TObjectList will free each instance
      DestList.List[i] := RecordType.Create;
      R.fFill.Fill(pointer(Row),TSQLRecord(DestList.List[i]));
      Inc(Row,FieldCount); // next data row
    end;
  finally
    R.Free;
  end;
end;

function TSQLTable.ToObjectList(RecordType: TSQLRecordClass=nil): TObjectList;
begin
  result := TObjectList.Create;
  ToObjectList(result,RecordType);
end;

{$ifdef USEVARIANTS}
var
  SQLTableRowVariantType: TCustomVariantType = nil;
{$endif}

function TSQLTable.Step(SeekFirst: boolean=false; RowVariant: PVariant=nil): boolean;
................................................................................
    end else
      // parse error
      result := false else
    // data didn't change (fPrivateCopyHash checked)
    result := true;
end;

constructor TSQLTableJSON.Create(const Tables: array of TSQLRecordClass;
  const aSQL: RawUTF8; JSONBuffer: PUTF8Char; JSONBufferLen: integer);
begin // don't raise exception on error parsing
  inherited Create(Tables,aSQL);
  ParseAndConvert(JSONBuffer,JSONBufferLen);
end;

constructor TSQLTableJSON.Create(const Tables: array of TSQLRecordClass; const aSQL,
  aJSON: RawUTF8);
var len: integer;
begin
  len := length(aJSON);
  PrivateCopyChanged(pointer(aJSON),len);
  Create(Tables,aSQL,pointer(fPrivateCopy),len);
end;
................................................................................
begin
  if self=nil then
    result := nil else
  with URI(Model.Root,'GET',@Resp,nil,@SQL) do
    if Lo=HTML_SUCCESS then begin // GET with SQL sent
      if high(Tables)=0 then
        result := TSQLTableJSON.Create([Tables[0]],SQL,Resp) else
        result := TSQLTableJSON.Create(Tables,SQL,Resp);
      result.fInternalState := Hi;
    end else // get data
    result := nil;
end;

function TSQLRestClientURI.ServerInternalState: cardinal;
begin
................................................................................
    with TSQLRecord(Data[i]) do
      if (fID<>0) and (InternalState<>State) then begin // refresh needed?
        if not Refresh(fID,TSQLRecord(Data[i]),Refreshed) then
          result := false; // mark error retrieving new content
      end;
end;










function TSQLRestClientURI.List(const Tables: array of TSQLRecordClass;
  const SQLSelect, SQLWhere: RawUTF8): TSQLTableJSON;
var Resp, SQL: RawUTF8;
    U: RawUTF8;
    InternalState: cardinal;
begin
  result := nil;
................................................................................
    result := TSQLTableJSON.Create([Tables[0]],SQL,Resp); // get data
  end else begin
    // multiple tables -> send SQL statement as HTTP body
    with URI(Model.Root,'GET',@Resp,nil,@SQL) do
      if Lo<>HTML_SUCCESS then
        exit else
        InternalState := Hi;
    result := TSQLTableJSON.Create(Tables,SQL,Resp); // get data
  end;
  result.fInternalState := InternalState;
end;

function TSQLRestClientURI.InternalListJSON(Table: TSQLRecordClass; const SQL: RawUTF8): TSQLTableJSON;
begin
  result := ExecuteList([Table],SQL);
................................................................................
end;

function TSQLRestServer.ExecuteList(const Tables: array of TSQLRecordClass; const SQL: RawUTF8): TSQLTableJSON;
var JSON: RawUTF8;
begin
  JSON := EngineList(SQL,false);
  if JSON<>'' then
    result := TSQLTableJSON.Create(Tables,SQL,JSON) else
    result := nil;
end;

function TSQLRestServer.UnLock(Table: TSQLRecordClass; aID: integer): boolean;
begin
  result := Model.UnLock(Table,aID);
end;

Changes to SQLite3/mORMotSQLite3.pas.

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
...
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
....
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
      a special request explicitely (note that JSON format returns BLOB data)
     - uses a TSQLTableJSON internaly: all currency is transformed to its floating
       point TEXT representation, and allows efficient caching
     - if the SQL statement is in the DB cache, it's retrieved from its cached
       value: our JSON parsing is a lot faster than SQLite3 engine itself,
       and uses less memory
     - will raise an ESQLException on any error }
    constructor Create(aDB: TSQLDatabase; const Tables: array of TClass;
      const aSQL: RawUTF8; Expand: boolean);
  end;

  TSQLRestServerDBClass = class of TSQLRestServerDB;

  TSQLVirtualTableModuleServerDB = class;

................................................................................


implementation


{ TSQLTableDB }

constructor TSQLTableDB.Create(aDB: TSQLDataBase; const Tables: array of TClass;
  const aSQL: RawUTF8; Expand: boolean);
var JSONCached: RawUTF8;
    R: TSQLRequest;
    n: PtrInt;
begin
  JSONCached := aDB.LockJSON(aSQL,@n);
  if JSONCached='' then // not retrieved from cache -> call SQLite3 engine
................................................................................
  if (self<>nil) and (n>0) then
  try // will use JSON cache if available:
    aSQL := Model.SQLFromSelectWhere(Tables,SQLSelect,SQLWhere);
    if n=1 then
      // InternalListJSON will handle both static and DB tables
      result := fServer.InternalListJSON(TSQLRecordClass(Tables[0]),aSQL) else
      // we access localy the DB -> TSQLTableDB handle Tables parameter
      result := TSQLTableDB.Create(fServer.DB,
        RecordClassesToClasses(Tables),aSQL,not fServer.NoAJAXJSON);
    if fServer.DB.InternalState<>nil then
      result.InternalState := fServer.DB.InternalState^;
  except
    on ESQLite3Exception do
      result := nil;
  end;
end;






|







 







|







 







|
<







284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
...
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
....
1407
1408
1409
1410
1411
1412
1413
1414

1415
1416
1417
1418
1419
1420
1421
      a special request explicitely (note that JSON format returns BLOB data)
     - uses a TSQLTableJSON internaly: all currency is transformed to its floating
       point TEXT representation, and allows efficient caching
     - if the SQL statement is in the DB cache, it's retrieved from its cached
       value: our JSON parsing is a lot faster than SQLite3 engine itself,
       and uses less memory
     - will raise an ESQLException on any error }
    constructor Create(aDB: TSQLDatabase; const Tables: array of TSQLRecordClass;
      const aSQL: RawUTF8; Expand: boolean);
  end;

  TSQLRestServerDBClass = class of TSQLRestServerDB;

  TSQLVirtualTableModuleServerDB = class;

................................................................................


implementation


{ TSQLTableDB }

constructor TSQLTableDB.Create(aDB: TSQLDataBase; const Tables: array of TSQLRecordClass;
  const aSQL: RawUTF8; Expand: boolean);
var JSONCached: RawUTF8;
    R: TSQLRequest;
    n: PtrInt;
begin
  JSONCached := aDB.LockJSON(aSQL,@n);
  if JSONCached='' then // not retrieved from cache -> call SQLite3 engine
................................................................................
  if (self<>nil) and (n>0) then
  try // will use JSON cache if available:
    aSQL := Model.SQLFromSelectWhere(Tables,SQLSelect,SQLWhere);
    if n=1 then
      // InternalListJSON will handle both static and DB tables
      result := fServer.InternalListJSON(TSQLRecordClass(Tables[0]),aSQL) else
      // we access localy the DB -> TSQLTableDB handle Tables parameter
      result := TSQLTableDB.Create(fServer.DB,Tables,aSQL,not fServer.NoAJAXJSON);

    if fServer.DB.InternalState<>nil then
      result.InternalState := fServer.DB.InternalState^;
  except
    on ESQLite3Exception do
      result := nil;
  end;
end;

Changes to SynSelfTests.pas.

6121
6122
6123
6124
6125
6126
6127
6128
6129
6130
6131
6132
6133
6134
6135
....
6178
6179
6180
6181
6182
6183
6184
6185
6186
6187
6188
6189
6190

6191
6192
6193
6194
6195
6196
6197
6198
6199
6200
6201
6202
6203
6204
6205
  {$endif}
end;

procedure TTestSQLite3Engine._TSQLTableJSON;
var J: TSQLTableJSON;
    aR, aF, n: integer;
    Comp: TUTF8Compare;
    List: TObjectList;
    {$ifdef UNICODE}
    Peoples: TObjectList<TSQLRecordPeople>;
    {$endif}
    row: variant;
begin
  J := TSQLTableJSON.Create([],'',JS);
  try
................................................................................
          Check(row.FirstName=J.GetString(StepRow,FieldIndex('FirstName')));
          Check(row.LastName=J.GetString(StepRow,FieldIndex('LastName')));
          Check(row.YearOfBirth=J.GetAsInteger(StepRow,FieldIndex('YearOfBirth')));
          Check(row.YearOfDeath=J.GetAsInteger(StepRow,FieldIndex('YearOfDeath')));
          inc(n);
       until not Step(false,@row);
      check(n=J.RowCount);
      List := TObjectList.Create;
      try
        ToObjectList(List,TSQLRecordPeople);
        check(List.Count=J.RowCount);
        for aR := 1 to List.Count do
        with TSQLRecordPeople(List.Items[aR-1]) do begin

          Check(fID=J.GetAsInteger(aR,FieldIndex('ID')));
          Check(FirstName=J.GetU(aR,FieldIndex('FirstName')));
          Check(LastName=J.GetU(aR,FieldIndex('LastName')));
          Check(YearOfBirth=J.GetAsInteger(aR,FieldIndex('YearOfBirth')));
          Check(YearOfDeath=J.GetAsInteger(aR,FieldIndex('YearOfDeath')));
        end;
      finally
        List.Free;
      end;
      {$ifdef UNICODE}
      Peoples := ToObjectList<TSQLRecordPeople>;
      try
        Check(Peoples.Count=J.RowCount);
        for aR := 1 to Peoples.Count do
        with Peoples[aR-1] do begin






<







 







|

<
|
|
<
>







|







6121
6122
6123
6124
6125
6126
6127

6128
6129
6130
6131
6132
6133
6134
....
6177
6178
6179
6180
6181
6182
6183
6184
6185

6186
6187

6188
6189
6190
6191
6192
6193
6194
6195
6196
6197
6198
6199
6200
6201
6202
6203
  {$endif}
end;

procedure TTestSQLite3Engine._TSQLTableJSON;
var J: TSQLTableJSON;
    aR, aF, n: integer;
    Comp: TUTF8Compare;

    {$ifdef UNICODE}
    Peoples: TObjectList<TSQLRecordPeople>;
    {$endif}
    row: variant;
begin
  J := TSQLTableJSON.Create([],'',JS);
  try
................................................................................
          Check(row.FirstName=J.GetString(StepRow,FieldIndex('FirstName')));
          Check(row.LastName=J.GetString(StepRow,FieldIndex('LastName')));
          Check(row.YearOfBirth=J.GetAsInteger(StepRow,FieldIndex('YearOfBirth')));
          Check(row.YearOfDeath=J.GetAsInteger(StepRow,FieldIndex('YearOfDeath')));
          inc(n);
       until not Step(false,@row);
      check(n=J.RowCount);
      with ToObjectList(TSQLRecordPeople) do
      try

        check(Count=J.RowCount);
        for aR := 1 to Count do

        with Items[aR-1] as TSQLRecordPeople do begin
          Check(fID=J.GetAsInteger(aR,FieldIndex('ID')));
          Check(FirstName=J.GetU(aR,FieldIndex('FirstName')));
          Check(LastName=J.GetU(aR,FieldIndex('LastName')));
          Check(YearOfBirth=J.GetAsInteger(aR,FieldIndex('YearOfBirth')));
          Check(YearOfDeath=J.GetAsInteger(aR,FieldIndex('YearOfDeath')));
        end;
      finally
        Free;
      end;
      {$ifdef UNICODE}
      Peoples := ToObjectList<TSQLRecordPeople>;
      try
        Check(Peoples.Count=J.RowCount);
        for aR := 1 to Peoples.Count do
        with Peoples[aR-1] do begin