forked from synopse/mORMot2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmormot.db.sql.pas
8476 lines (8063 loc) · 331 KB
/
mormot.db.sql.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// Database Framework Shared Abstract SQL Types and Classes
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.db.sql;
{
*****************************************************************************
Shared Types and Definitions for SQL Database Access
- SQL Fields and Columns Definitions
- Define Database Engine Specific Behavior
- General SQL Processing Functions
- Abstract SQL DB Classes and Interfaces
- Parent Classes for Thread-Safe and Parametrized Connections
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
variants,
mormot.core.base,
mormot.core.os,
mormot.core.buffers,
mormot.core.unicode,
mormot.core.text,
mormot.core.perf,
mormot.core.datetime,
mormot.core.data,
mormot.core.variants,
mormot.core.json,
mormot.crypt.secure,
mormot.core.rtti,
mormot.core.log,
mormot.db.core;
{.$define SYNDB_SILENCE}
// if defined, this unit won't log the statement execution
{ ************ SQL Fields and Columns Definitions }
type
/// an array of RawUtf8, for each existing column type
// - used e.g. by SqlCreate method
// - ftUnknown maps int32 field (e.g. boolean), ftNull maps RawUtf8 index # field,
// ftUtf8 maps RawUtf8 blob field, other types map their default kind
// - for UTF-8 text, ftUtf8 will define the BLOB field, whereas ftNull will
// expect to be formated with an expected field length in ColumnAttr
// - the RowID definition will expect the ORM to create an unique identifier,
// and will use the ftInt64 type definition for this
// and send it with the INSERT statement (some databases, like Oracle, do not
// support standard's IDENTITY attribute) - see http://troels.arvin.dk/db/rdbms
TSqlDBFieldTypeDefinition = array[TSqlDBFieldType] of RawUtf8;
/// the diverse type of bound parameters during a statement execution
// - will be paramIn by default, which is the case 90% of time
// - could be set to paramOut or paramInOut if must be refereshed after
// execution (for calling a stored procedure expecting such parameters)
TSqlDBParamInOutType = (
paramIn,
paramOut,
paramInOut);
/// used to define a field/column layout in a table schema
// - for TSqlDBConnectionProperties.SqlCreate to describe the new table
// - for TSqlDBConnectionProperties.GetFields to retrieve the table layout
TSqlDBColumnDefine = packed record
/// the Column name
ColumnName: RawUtf8;
/// the Column type, as retrieved from the database provider
// - returned as plain text by GetFields method, to be used e.g. by
// TSqlDBConnectionProperties.GetFieldDefinitions method
// - SqlCreate will check for this value to override the default type
ColumnTypeNative: RawUtf8;
/// the Column default width (in chars or bytes) of ftUtf8 or ftBlob
// - can be set to value <0 for CLOB or BLOB column type, i.e. for
// a value without any maximal length
ColumnLength: PtrInt;
/// the Column data precision
// - used e.g. for numerical values
ColumnPrecision: PtrInt;
/// the Column data scale
// - used e.g. for numerical values
// - may be -1 if the metadata SQL statement returned NULL
ColumnScale: PtrInt;
/// the Column type, as recognized by our mormot.db.sql classes
// - should not be ftUnknown nor ftNull
ColumnType: TSqlDBFieldType;
/// specify if column is indexed
ColumnIndexed: boolean;
end;
/// used to define the column layout of a table schema
// - e.g. for TSqlDBConnectionProperties.GetFields
TSqlDBColumnDefineDynArray = array of TSqlDBColumnDefine;
/// used to describe extended Index definition of a table schema
TSqlDBIndexDefine = packed record
/// name of the index
IndexName: RawUtf8;
/// description of the index type
// - for MS SQL possible values are:
// $ HEAP | CLUSTERED | NONCLUSTERED | XML |SPATIAL
// - for Oracle:
// $ NORMAL | BITMAP | FUNCTION-BASED NORMAL | FUNCTION-BASED BITMAP | DOMAIN
// see @http://docs.oracle.com/cd/B19306_01/server.102/b14237/statviews_1069.htm
TypeDesc: RawUtf8;
/// Expression for the subset of rows included in the filtered index
// - only set for MS SQL - not retrieved for other DB types yet
Filter: RawUtf8;
/// comma separated list of indexed column names, in order of their definition
KeyColumns: RawUtf8;
/// comma separaded list of a nonkey column added to the index by using the CREATE INDEX INCLUDE clause
// - only set for MS SQL - not retrieved for other DB types yet
IncludedColumns: RawUtf8;
/// if Index is unique
IsUnique: boolean;
/// if Index is part of a PRIMARY KEY constraint
// - only set for MS SQL - not retrieved for other DB types yet
IsPrimaryKey: boolean;
/// if Index is part of a UNIQUE constraint
// - only set for MS SQL - not retrieved for other DB types yet
IsUniqueConstraint: boolean;
end;
/// used to describe extended Index definition of a table schema
// - e.g. for TSqlDBConnectionProperties.GetIndexes
TSqlDBIndexDefineDynArray = array of TSqlDBIndexDefine;
/// used to define a parameter/column layout in a stored procedure schema
// - for TSqlDBConnectionProperties.GetProcedureParameters to retrieve the stored procedure parameters
// - can be extended according to https://msdn.microsoft.com/en-us/library/ms711701(v=vs.85).aspx
TSqlDBProcColumnDefine = packed record
/// the Column name
ColumnName: RawUtf8;
/// the Column type, as retrieved from the database provider
// - used e.g. by TSqlDBConnectionProperties.GetProcedureParameters method
ColumnTypeNative: RawUtf8;
/// the Column default width (in chars or bytes) of ftUtf8 or ftBlob
// - can be set to value <0 for CLOB or BLOB column type, i.e. for
// a value without any maximal length
ColumnLength: PtrInt;
/// the Column data precision
// - used e.g. for numerical values
ColumnPrecision: PtrInt;
/// the Column data scale
// - used e.g. for numerical values
// - may be -1 if the metadata SQL statement returned NULL
ColumnScale: PtrInt;
/// the Column type, as recognized by our mormot.db.sql classes
// - should not be ftUnknown nor ftNull
ColumnType: TSqlDBFieldType;
/// defines the procedure column as a parameter or a result set column
ColumnParamType: TSqlDBParamInOutType;
end;
/// used to define the parameter/column layout of a stored procedure schema
// - e.g. for TSqlDBConnectionProperties.GetProcedureParameters
TSqlDBProcColumnDefineDynArray = array of TSqlDBProcColumnDefine;
/// possible column retrieval patterns
// - used by TSqlDBColumnProperty.ColumnValueState
TSqlDBStatementGetCol = (
colNone,
colNull,
colWrongType,
colDataFilled,
colDataTruncated);
/// used to define a field/column layout
// - for TSqlDBConnectionProperties.SqlCreate to describe the table
// - for T*Statement.Execute/Column*() methods to map the IRowSet content
TSqlDBColumnProperty = packed record
/// the Column name
ColumnName: RawUtf8;
/// a general purpose integer value
// - for SqlCreate: default width (in WideChars or Bytes) of ftUtf8 or ftBlob;
// if set to 0, a CLOB or BLOB column type will be created - note that
// UTF-8 encoding is expected when calculating the maximum column byte size
// for the CREATE TABLE statement (e.g. for Oracle 1333=4000/3 is used)
// - for TOleDBStatement: the offset of this column in the IRowSet data,
// starting with a DBSTATUSENUM, the data, then its length (for inlined
// oftUtf8 and oftBlob only)
// - for TSqlDBOracleStatement: contains an offset to this column values
// inside fRowBuffer[] internal buffer
// - for TSqlDBDatasetStatement: maps TField pointer value
// - for TSqlDBPostgresStatement: contains the column type OID
ColumnAttr: PtrUInt;
/// the Column type, used for storage
// - for SqlCreate: should not be ftUnknown nor ftNull
// - for TOleDBStatement: should not be ftUnknown
// - for mormot.db.sql.oracle: never ftUnknown, may be ftNull (for SQLT_RSET)
ColumnType: TSqlDBFieldType;
/// set if the Column must exists (i.e. should not be null)
ColumnNonNullable: boolean;
/// set if the Column shall have unique value (add the corresponding constraint)
ColumnUnique: boolean;
/// set if the Column data is inlined within the main rows buffer
// - for TOleDBStatement: set if column was NOT defined as DBTYPE_BYREF
// which is the most common case, when column data < 4 KB
// - for TSqlDBOracleStatement: FALSE if column is an array of
// POCILobLocator (SQLT_CLOB/SQLT_BLOB) or POCIStmt (SQLT_RSET)
// - for TSqlDBOdbcStatement: FALSE if bigger than 255 WideChar (ftUtf8) or
// 255 bytes (ftBlob)
ColumnValueInlined: boolean;
/// expected column data size
// - for TSqlDBOracleStatement/TOleDBStatement/TODBCStatement: used to store
// one column size (in bytes)
ColumnValueDBSize: cardinal;
/// optional character set encoding for ftUtf8 columns
// - for SQLT_STR/SQLT_CLOB (mormot.db.sql.oracle): equals to the OCI char set
ColumnValueDBCharSet: integer;
/// internal DB column data type
// - for TSqlDBOracleStatement: used to store the DefineByPos() TypeCode,
// can be SQLT_STR/SQLT_CLOB, SQLT_FLT, SQLT_INT, SQLT_DAT, SQLT_BLOB,
// SQLT_BIN and SQLT_RSET
// - for TSqlDBOdbcStatement: used to store the DataType as returned
// by ODBC.DescribeColW() - use private ODBC_TYPE_TO[ColumnType] to
// retrieve the marshalled type used during column retrieval
// - for TSqlDBOleDBStatement: used to store the DBTYPE value
// - for TSqlDBFirebirdStatement: used to store XSQLVAR.sqltype
// - for TSqlDBDatasetStatement: indicates the TField class type, i.e.
// 0=TField, 1=TLargeIntField, 2=TWideStringField
ColumnValueDBType: smallint;
/// driver-specific encoding information
// - for mormot.db.sql.oracle: used to store the ftUtf8 column encoding, i.e.
// for SQLT_CLOB, equals either to SQLCS_NCHAR or SQLCS_IMPLICIT
ColumnValueDBForm: byte;
/// may contain the current status of the column value
// - for mormot.db.sql.odbc: state of the latest SqlGetData() call
ColumnDataState: TSqlDBStatementGetCol;
/// may contain the current column size for not FIXEDLENGTH_SQLDBFIELDTYPE
// - for mormot.db.sql.odbc: size (in bytes) in corresponding fColData[]
// - TSqlDBProxyStatement: the actual maximum column size
ColumnDataSize: integer;
end;
PSqlDBColumnProperty = ^TSqlDBColumnProperty;
/// used to define a table/field column layout
TSqlDBColumnPropertyDynArray = array of TSqlDBColumnProperty;
/// used to define how a column to be created
TSqlDBColumnCreate = record
/// the data type
// - here, ftUnknown is used for Int32 values, ftInt64 for Int64 values,
// as expected by TSqlDBFieldTypeDefinition
DBType: TSqlDBFieldType;
/// the column name
Name: RawUtf8;
/// the width, e.g. for VARCHAR() types
Width: cardinal;
/// if the column should be unique
Unique: boolean;
/// if the column should be non null
NonNullable: boolean;
/// if the column is the ID primary key
PrimaryKey: boolean;
end;
/// used to define how a table is to be created
TSqlDBColumnCreateDynArray = array of TSqlDBColumnCreate;
/// identify a CRUD mode of a statement
// - in addition to CRUD states, cPostgreBulkArray would identify if the ORM
// should generate unnested/any bound array statements - currently only
// supported by mormot.db.sql.postgres for bulk insert/update/delete
TSqlDBStatementCRUD = (
cCreate,
cRead,
cUpdate,
cDelete,
cPostgreBulkArray);
/// identify the CRUD modes of a statement
// - used e.g. for batch send abilities of a DB engine
TSqlDBStatementCRUDs = set of TSqlDBStatementCRUD;
const
/// a magic constant used e.g. by TSqlDBStatement.FetchAllToBinary
FETCHALLTOBINARY_MAGIC = 1;
{ ************ Define Database Engine Specific Behavior }
type
/// where the LIMIT clause should be inserted for a given SQL syntax
// - used by TSqlDBDefinitionLimitClause and SqlLimitClause() method
TSqlDBDefinitionLimitPosition = (
posNone,
posWhere,
posSelect,
posAfter,
posOuter);
/// defines the LIMIT clause to be inserted for a given SQL syntax
// - used by TSqlDBDefinitionLimitClause and SqlLimitClause() method
TSqlDBDefinitionLimitClause = record
Position: TSqlDBDefinitionLimitPosition;
InsertFmt: PUtf8Char;
end;
const
/// the known column data types corresponding to our TSqlDBFieldType types
// - will be used e.g. for TSqlDBConnectionProperties.SqlFieldCreate()
// - see TSqlDBFieldTypeDefinition documentation to find out the mapping:
// ftUnknown will be used for 32-bit integers, and ftNull for UTF-8 text
DB_FIELDS: array[TSqlDBDefinition] of TSqlDBFieldTypeDefinition = (
// dUnknown
(' INT', // ftUnknown = int32
' NVARCHAR(%)', // ftNull = UTF-8
' BIGINT', // ftInt64
' DOUBLE', // ftDouble
' NUMERIC(19,4)', // ftCurrency
' TIMESTAMP', // ftDate
' CLOB', // ftUtf8
' BLOB'), // ftBlob
// dDefault
(' INT', // ftUnknown = int32
' NVARCHAR(%)', // ftNull = UTF-8
' BIGINT', // ftInt64
' DOUBLE', // ftDouble
' NUMERIC(19,4)', // ftCurrency
' TIMESTAMP', // ftDate
' CLOB', // ftUtf8
' BLOB'), // ftBlob
// dOracle
(' NUMBER(22,0)', // ftUnknown = int32
' NVARCHAR2(%)', // ftNull = UTF-8
' NUMBER(22,0)', // ftInt64
' BINARY_DOUBLE', // ftDouble
' NUMBER(19,4)', // ftCurrency
' DATE', // ftDate
' NCLOB', // ftUtf8
' BLOB'), // ftBlob
// NCLOB (National Character Large Object) is an Oracle data type that can hold
// up to 4 GB of character data. It's similar to a CLOB, but characters are
// stored in a NLS or multibyte national character set (like NVARCHAR2)
// dMSSQL
(' int', // ftUnknown = int32
' nvarchar(%)', // ftNull = UTF-8
' bigint', // ftInt64
' float', // ftDouble
' money', // ftCurrency
' datetime', // ftDate
' nvarchar(max)', // ftUtf8
' varbinary(max)'), // ftBlob
// dJet
(' Long', // ftUnknown = int32
' VarChar(%)', // ftNull = UTF-8
' Decimal(19,0)', // ftInt64
' Double', // ftDouble
' Currency', // ftCurrency
' DateTime', // ftDate
' LongText', // ftUtf8
' LongBinary'), // ftBlob
// dMySQL
(' int', // ftUnknown = int32
' varchar(%) character set utf8', // ftNull = UTF-8
' bigint', // ftInt64
' double', // ftDouble
' decimal(19,4)', // ftCurrency
' datetime', // ftDate
' mediumtext character set utf8', // ftUtf8
' mediumblob'), // ftBlob
// dSQLite
(' INTEGER', // ftUnknown = int32
' TEXT', // ftNull = UTF-8
' INTEGER', // ftInt64
' FLOAT', // ftDouble
' FLOAT', // ftCurrency
' TEXT', // ftDate
' TEXT', // ftUtf8
' BLOB'), // ftBlob
// dFirebird
(' INTEGER', // ftUnknown = int32
' VARCHAR(%) CHARACTER SET UTF8', // ftNull = UTF-8
' BIGINT', // ftInt64
' FLOAT', // ftDouble
' DECIMAL(18,4)', // ftCurrency
' TIMESTAMP', // ftDate
' BLOB SUB_TYPE 1 SEGMENT SIZE 2000 ' + // ftUtf8
'CHARACTER SET UTF8',
' BLOB SUB_TYPE 0 SEGMENT SIZE 2000'), // ftBlob
// about BLOB: http://www.ibphoenix.com/resources/documents/general/doc_54
// dNexusDB
(' INTEGER', // ftUnknown = int32
' NVARCHAR(%)', // ftNull = UTF-8
' LARGEINT', // ftInt64
' REAL', // ftDouble
' MONEY', // ftCurrency
' DATETIME', // ftDate
' NCLOB', // ftUtf8
' BLOB'), // ftBlob
// VARCHAR(%) CODEPAGE 65001 just did not work well with Delphi<2009
// dPostgreSQL
(' INTEGER', // ftUnknown = int32
' TEXT', // ftNull = UTF-8
' BIGINT', // ftInt64
' DOUBLE PRECISION', // ftDouble
' NUMERIC(19,4)', // ftCurrency
' TIMESTAMP', // ftDate
' TEXT', // ftUtf8
' BYTEA'), // ftBlob
// like SQLite3, we will create TEXT column instead of VARCHAR(%), as stated
// by http://www.postgresql.org/docs/current/static/datatype-character.html
// dDB2 (for CCSID Unicode tables)
(' int', // ftUnknown = int32
' varchar(%)', // ftNull = UTF-8
' bigint', // ftInt64
' real', // ftDouble
' decimal(19,4)', // ftCurrency
' timestamp', // ftDate
' clob', // ftUtf8
' blob'), // ftBlob
// note: bigint needs 9.1 and up
// dInformix
(' int', // ftUnknown = int32
' lvarchar(%)', // ftNull = UTF-8
' bigint', // ftInt64
' smallfloat', // ftDouble
' decimal(19,4)', // ftCurrency
' datetime year to fraction(3)', // ftDate
' clob', // ftUtf8
' blob'), // ftBlob
// dMariaDB
(' int', // ftUnknown = int32
' varchar(%) character set utf8', // ftNull = UTF-8
' bigint', // ftInt64
' double', // ftDouble
' decimal(19,4)', // ftCurrency
' datetime', // ftDate
' mediumtext character set utf8', // ftUtf8
' mediumblob')); // ftBlob
/// the known column data types corresponding to our TSqlDBFieldType types
// - will be used e.g. for TSqlDBConnectionProperties.SqlFieldCreate()
// - SQLite3 doesn't expect any field length, neither PostgreSQL, so set to 0
DB_FIELDSMAX: array[TSqlDBDefinition] of cardinal = (
1000, // dUnknown
1000, // dDefault
1333, // dOracle =4000/3 since WideChar is up to 3 bytes in UTF-8
4000, // dMSSQL
255, // dJet
4000, // dMySQL
0, // dSQLite
32760, // dFirebird
32767, // dNexusDB
0, // dPostgreSQL
32700, // dDB2
32700, // dInformix
4000); // dMariaDB
/// the maximum number of bound parameters to a SQL statement
// - will be used e.g. for Batch process multi-insert
// - those values were done empirically, assuring total count is < 656,
// which is the maximum within :AA..:ZZ range, excuding 20 reserved keywords
// - see http://stackoverflow.com/a/6582902 for theoritical high limits
DB_PARAMSMAX: array[TSqlDBDefinition] of cardinal = (
0, // dUnknown
0, // dDefault
500, // dOracle empirical value (from ODBC)
500, // dMSSQL theoritical=2100
0, // dJet
500, // dMySQL theoritical=60000
MAX_SQLPARAMS, // dSQLite theoritical=999 - see mormot.orm.sqlite3
0, // dFirebird
100, // dNexusDB empirical limit (above is slower)
500, // dPostgreSQL theoritical=34000
500, // dDB2 empirical value (from ODBC)
0, // dInformix
500); // dMariaDB
/// the known SQL statement to retrieve the server date and time
// - contains '' for the engines with local time
DB_SERVERTIME: array[TSqlDBDefinition] of RawUtf8 = (
'', // dUnknown
'', // dDefault
'select sysdate from dual', // dOracle
'select GETDATE()', // dMSSQL
'', // dJet
'SELECT NOW()', // dMySQL
'', // dSQLite
'select current_timestamp from rdb$database', // dFirebird
'SELECT CURRENT_TIMESTAMP', // dNexusDB
'SELECT LOCALTIMESTAMP', // dPostgreSQL
'select current timestamp from sysibm.sysdummy1', // dDB2
'select CURRENT YEAR TO FRACTION(3) ' + // dInformix
'from SYSTABLES where tabid = 1',
'select NOW()'); // dMariaDB
const
/// the known SQL syntax to limit the number of returned rows in a SELECT
// - Position indicates if should be included within the WHERE clause,
// at the beginning of the SQL statement, or at the end of the SQL statement
// - InsertFmt will replace '%' with the maximum number of lines to be retrieved
// - used by TSqlDBConnectionProperties.AdaptSqlLimitForEngineList()
DB_SQLLIMITCLAUSE: array[TSqlDBDefinition] of TSqlDBDefinitionLimitClause = (
( // dUnknown
Position: posNone;
InsertFmt: nil
),
( // dDefault
Position: posNone;
InsertFmt: nil
),
( // dOracle
Position: posWhere;
InsertFmt: 'rownum<=%'
),
( // dMSSQL
Position: posSelect;
InsertFmt: 'top(%) '
),
( // dJet
Position: posSelect;
InsertFmt: 'top % '
),
( // dMySQL
Position: posAfter;
InsertFmt: ' limit %'
),
( // dSQLite
Position: posAfter;
InsertFmt: ' limit %'
),
( // dFireBird
Position: posSelect;
InsertFmt: 'first % '
),
( // dNexusDB
Position: posSelect;
InsertFmt: 'top % '
),
( // dPostgreSQL
Position: posAfter;
InsertFmt: ' limit %'
),
( // dDB2
Position: posAfter;
InsertFmt: ' fetch first % rows only'
),
( // dInformix
Position: posAfter;
InsertFmt: ' first % '
),
( // dMariaDB
Position: posAfter;
InsertFmt: ' limit %'
));
/// the known database engines handling CREATE INDEX IF NOT EXISTS statement
DB_HANDLECREATEINDEXIFNOTEXISTS = [dSQLite, dPostgreSQL, dMariaDB];
/// the known database engines handling CREATE INDEX on BLOB columns
// - SQLite3 does not have any issue about indexing any column
// - PostgreSQL is able to index TEXT columns, which are some kind of CLOB
DB_HANDLEINDEXONBLOBS = [dSQLite, dPostgreSQL];
/// where the DESC clause shall be used for a CREATE INDEX statement
// - only identified syntax exception is for FireBird
DB_SQLDESENDINGINDEXPOS: array[TSqlDBDefinition] of
(posWithColumn, posGlobalBefore) =
(posWithColumn, // dUnknown
posWithColumn, // dDefault
posWithColumn, // dOracle
posWithColumn, // dMSSQL
posWithColumn, // dJet
posWithColumn, // dMySQL
posWithColumn, // dSQLite
posGlobalBefore, // dFirebird
posWithColumn, // dNexusDB
posWithColumn, // dPostgreSQL
posWithColumn, // dDB2
posWithColumn, // dInformix
posWithColumn); // dMariaDB
/// the SQL text corresponding to the identified WHERE operators for a SELECT
DB_SQLOPERATOR: array[opEqualTo..opLike] of RawUtf8 = (
'=', // opEqualTo
'<>', // opNotEqualTo
'<', // opLessThan
'<=', // opLessThanOrEqualTo
'>', // opGreaterThan
'>=', // opGreaterThanOrEqualTo
' in ', // opIn
' is null', // opIsNull
' is not null', // opIsNotNull
' like '); // opLike
/// retrieve the text of a given Database SQL dialect enumeration
// - see also TSqlDBConnectionProperties.GetDbmsName() method
function ToText(Dbms: TSqlDBDefinition): PShortString; overload;
{ ************ General SQL Processing Functions }
/// function helper logging some column truncation information text
// - is called by low-level mormot.db.sql.* providers when the driver notifies
// that a column content has been truncated when retrieved
procedure LogTruncatedColumn(Sender: TObject; const Col: TSqlDBColumnProperty);
/// retrieve a table name without any left schema
// - e.g. TrimLeftSchema('SCHEMA.TABLENAME')='TABLENAME'
function TrimLeftSchema(const TableName: RawUtf8): RawUtf8;
/// replace all '?' in the SQL statement with named parameters like :AA :AB..
// - returns the number of ? parameters found within aSql
// - won't generate any SQL keyword parameters (e.g. :AS :OF :BY), to be
// compliant with Oracle OCI expectations - allow up to 656 parameters
// - any ending ';' character is deleted, unless aStripSemicolon is unset
function ReplaceParamsByNames(const aSql: RawUtf8; var aNewSql: RawUtf8;
aStripSemicolon: boolean = true): integer;
/// replace all '?' in the SQL statement with indexed parameters like $1 $2 ...
// - returns the number of ? parameters found within aSql
// - as used e.g. by PostgreSQL & Oracle (:1 :2) library
// - if AllowSemicolon is false (by default), reject any statement with ;
// (Postgres do not allow ; inside prepared statement); it should be
// true for Oracle
function ReplaceParamsByNumbers(const aSql: RawUtf8; var aNewSql: RawUtf8;
IndexChar: AnsiChar = '$'; AllowSemicolon: boolean = false): integer;
/// create a JSON array from an array of UTF-8 SQL bound values
// - as generated during array binding, i.e. with quoted strings
// 'one','t"wo' -> '{"one","t\"wo"}' and 1,2,3 -> '{1,2,3}'
// - as used e.g. by PostgreSQL library (note that its syntax as {} not []
// unless you change the Open/Close optional parameters)
function BoundArrayToJsonArray(const Values: TRawUtf8DynArray;
Open: AnsiChar = '{'; Close: AnsiChar = '}'): RawUtf8; overload;
/// create a JSON array from an array of UTF-8 SQL bound values
procedure BoundArrayToJsonArray(const Values: TRawUtf8DynArray;
out Result: RawUtf8; Open: AnsiChar = '{'; Close: AnsiChar = '}'); overload;
/// create an array of UTF-8 SQL bound values from a JSON array
// - as generated during array binding, i.e. with quoted strings
// '["one","t\"wo"]' -> 'one','t"wo' and '[1,2,3]' -> 1,2,3
// - as used e.g. by BindArrayJson outside of PostgreSQL library
// - warning: input JSON buffer will be parsed in-place, so will be modified
// - here syntax is regular [] so not an exact reverse to BoundArrayToJsonArray
function JsonArrayToBoundArray(Json: PUtf8Char; ParamType: TSqlDBFieldType;
TimeSeparator: AnsiChar; DateMS: boolean; out Values: TRawUtf8DynArray): boolean;
{ ************ Abstract SQL DB Classes and Interfaces }
var
/// the TSynLog class used for logging for all our mormot.db.sql related units
// - since not all exceptions are handled specificaly by this unit, you
// may better use a common TSynLog class for the whole application or module
SynDBLog: TSynLogClass = TSynLog;
type
/// a custom variant type used to have direct access to a result row content
// - use ISqlDBRows.RowData method to retrieve such a Variant
TSqlDBRowVariantType = class(TSynInvokeableVariantType)
public
/// overriden method for actual getter by name implementation
function IntGet(var Dest: TVarData; const Instance: TVarData;
Name: PAnsiChar; NameLen: PtrInt; NoException: boolean): boolean; override;
end;
{$M+}
TSqlDBStatement = class;
TSqlDBConnection = class;
TSqlDBConnectionProperties = class;
{$M-}
/// generic Exception type, as used by mormot.db.sql and mormot.db.sql.* units
ESqlDBException = class(ECoreDBException)
protected
fStatement: TSqlDBStatement;
public
/// constructor which will use FormatUtf8() instead of Format()
// - if the first Args[0] is a TSqlDBStatement class instance, the current
// SQL statement will be part of the exception message
// - will also call SetDbError() with the resulting message text
constructor CreateUtf8(const Format: RawUtf8; const Args: array of const); override;
published
/// associated TSqlDBStatement instance, if supplied as first parameter
property Statement: TSqlDBStatement
read fStatement;
end;
/// generic interface to access a SQL query result rows
// - not all TSqlDBStatement methods are available, but only those to retrieve
// data from a statement result: the purpose of this interface is to make
// easy access to result rows, not provide all available features - therefore
// you only have access to the Step() and Column*() methods
ISqlDBRows = interface
['{11291095-9C15-4984-9118-974F1926DB9F}']
/// after a prepared statement has been prepared returning a ISqlDBRows
// interface, this method must be called one or more times to evaluate it
// - you shall call this method before calling any Column*() methods
// - return TRUE on success, with data ready to be retrieved by Column*()
// - return FALSE if no more row is available (e.g. if the SQL statement
// is not a SELECT but an UPDATE or INSERT command)
// - access the first or next row of data from the SQL Statement result:
// 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
// - should raise an Exception on any error
// - typical use may be:
// ! var Customer: Variant;
// ! begin
// ! with Props.Execute(
// ! 'select * from Sales.Customer where AccountNumber like ?',
// ! ['AW000001%'], @Customer) do
// ! begin
// ! while Step do // loop through all matching data rows
// ! assert(Copy(Customer.AccountNumber, 1, 8)='AW000001');
// ! ReleaseRows;
// ! end;
// ! end;
function Step(SeekFirst: boolean = false): boolean;
/// retrieve one row of the result set as a JSON object
// - see also FetchAllAsJson to retrieve all the raws as a JSON result
// - returns '' if the step was not possible (i.e. reached end of results)
function StepAsJson(SeekFirst: boolean = false): RawUtf8;
/// retrieve one row of the resultset as a JSON object into a TResultsWriter
// - see also FetchAllToJson to retrieve all the raws into a JSON result
// - returns false if the step was not possible (i.e. reached end of results)
function StepToJson(W: TJsonWriter; SeekFirst: boolean = false): boolean;
/// release cursor memory and resources once Step loop is finished
// - this method call is optional, but is better be used if the ISqlDBRows
// statement from taken from cache, and returned a lot of content which
// may still be in client (and server) memory
// - will also free all temporary memory used for optional logging
procedure ReleaseRows;
/// the column/field count of the current Row
function ColumnCount: integer;
/// the Column name of the current Row
// - Columns numeration (i.e. Col value) starts with 0
// - it's up to the implementation to ensure than all column names are unique
function ColumnName(Col: integer): RawUtf8;
/// returns the Column index of a given Column name
// - Columns numeration (i.e. Col value) starts with 0
// - returns -1 if the Column name is not found (via case insensitive search)
function ColumnIndex(const aColumnName: RawUtf8): integer;
/// the Column type of the current Row
// - FieldSize can be set to store the size in chars of a ftUtf8 column
// (0 means BLOB kind of TEXT column)
function ColumnType(Col: integer; FieldSize: PInteger = nil): TSqlDBFieldType;
/// returns TRUE if the column contains NULL, first Col is 0
function ColumnNull(Col: integer): boolean;
/// return a Column integer value of the current Row, first Col is 0
function ColumnInt(Col: integer): Int64; overload;
/// return a Column floating point value of the current Row, first Col is 0
function ColumnDouble(Col: integer): double; overload;
/// return a Column floating point value of the current Row, first Col is 0
function ColumnDateTime(Col: integer): TDateTime; overload;
/// return a column date and time value of the current Row, first Col is 0
function ColumnTimestamp(Col: integer): TTimeLog; overload;
/// return a Column currency value of the current Row, first Col is 0
function ColumnCurrency(Col: integer): currency; overload;
/// return a Column UTF-8 encoded text value of the current Row, first Col is 0
function ColumnUtf8(Col: integer): RawUtf8; overload;
/// return a Column UTF-8 text buffer of the current Row, first Col is 0
// - low-level function: may return nil if not supported by the provider
// - returned pointer is likely to last only until next Step or Reset call
function ColumnPUtf8(Col: integer): PUtf8Char;
/// return a Column text value as RTL string of the current Row, first Col is 0
function ColumnString(Col: integer): string; overload;
/// return a Column as a blob value of the current Row, first Col is 0
function ColumnBlob(Col: integer): RawByteString; overload;
/// return a Column as a blob value of the current Row, first Col is 0
function ColumnBlobBytes(Col: integer): TBytes; overload;
/// read a blob Column into the Stream parameter
procedure ColumnBlobToStream(Col: integer; Stream: TStream); overload;
/// write a blob Column into the Stream parameter
// - expected to be used with 'SELECT .. FOR UPDATE' locking statements
procedure ColumnBlobFromStream(Col: integer; Stream: TStream); overload;
/// return a Column as a TSqlVar value, first Col is 0
// - the specified Temp variable will be used for temporary storage of
// ftUtf8/ftBlob values
procedure ColumnToSqlVar(Col: integer; var Value: TSqlVar; var Temp: RawByteString);
/// append a Column as a JSON value, first Col is 0
procedure ColumnToJson(Col: integer; W: TJsonWriter);
/// return a Column as a variant
// - a ftUtf8 TEXT content will be mapped into a generic WideString variant
// for pre-Unicode version of Delphi, and a generic UnicodeString (=string)
// since Delphi 2009: you may not loose any data during charset conversion
// - a ftBlob BLOB content will be mapped into a TBlobData AnsiString variant
function ColumnVariant(Col: integer): Variant; overload;
/// return a Column as a variant, first Col is 0
// - this default implementation will call Column*() method above
// - a ftUtf8 TEXT content will be mapped into a generic WideString variant
// for pre-Unicode version of Delphi, and a generic UnicodeString (=string)
// since Delphi 2009: you may not loose any data during charset conversion
// - a ftBlob BLOB content will be mapped into a TBlobData AnsiString variant
function ColumnToVariant(Col: integer; var Value: Variant): TSqlDBFieldType; overload;
/// return a special CURSOR Column content as a mormot.db.sql result set
// - Cursors are not handled internally by mORMot, but some databases (e.g.
// Oracle) usually use such structures to get data from stored procedures
// - such columns are mapped as ftNull internally - so this method is the only
// one giving access to the data rows
// - see also BoundCursor() if you want to access a CURSOR out parameter
function ColumnCursor(Col: integer): ISqlDBRows; overload;
/// return a Column integer value of the current Row, from a supplied column name
function ColumnInt(const ColName: RawUtf8): Int64; overload;
/// return a Column floating point value of the current Row, from a supplied column name
function ColumnDouble(const ColName: RawUtf8): double; overload;
/// return a Column floating point value of the current Row, from a supplied column name
function ColumnDateTime(const ColName: RawUtf8): TDateTime; overload;
/// return a column date and time value of the current Row, from a supplied column name
function ColumnTimestamp(const ColName: RawUtf8): TTimeLog; overload;
/// return a Column currency value of the current Row, from a supplied column name
function ColumnCurrency(const ColName: RawUtf8): currency; overload;
/// return a Column UTF-8 encoded text value of the current Row, from a supplied column name
function ColumnUtf8(const ColName: RawUtf8): RawUtf8; overload;
/// return a Column text value as RTL string of the current Row, from a supplied column name
function ColumnString(const ColName: RawUtf8): string; overload;
/// return a Column as a blob value of the current Row, from a supplied column name
function ColumnBlob(const ColName: RawUtf8): RawByteString; overload;
/// return a Column as a blob value of the current Row, from a supplied column name
function ColumnBlobBytes(const ColName: RawUtf8): TBytes; overload;
/// read a blob Column into the Stream parameter
procedure ColumnBlobToStream(const ColName: RawUtf8; Stream: TStream); overload;
/// write a blob Column into the Stream parameter
procedure ColumnBlobFromStream(const ColName: RawUtf8; Stream: TStream); overload;
/// return a Column as a variant, from a supplied column name
function ColumnVariant(const ColName: RawUtf8): Variant; overload;
/// return a Column as a variant, from a supplied column name
// - since a property getter can't be an overloaded method, we define one
// for the Column[] property
function GetColumnVariant(const ColName: RawUtf8): Variant;
/// return a special CURSOR Column content as a mormot.db.sql result set
// - Cursors are not handled internally by mORMot, but some databases (e.g.
// Oracle) usually use such structures to get data from strored procedures
// - such columns are mapped as ftNull internally - so this method is the only
// one giving access to the data rows
function ColumnCursor(const ColName: RawUtf8): ISqlDBRows; overload;
/// return a Column as a variant
// - this default property can be used to write simple code like this:
// ! procedure WriteFamily(const aName: RawUtf8);
// ! var I: ISqlDBRows;
// ! begin
// ! I := MyConnProps.Execute('select * from table where name=?',[aName]);
// ! while I.Step do
// ! writeln(I['FirstName'],' ',DateToStr(I['BirthDate']));
// ! I.ReleaseRows;
// ! end;
// - of course, using a variant and a column name will be a bit slower than
// direct access via the Column*() dedicated methods, but resulting code
// is fast in practice
property Column[const ColName: RawUtf8]: Variant
read GetColumnVariant; default;
/// create a TSqlDBRowVariantType able to access any field content via late binding
// - i.e. you can use Data.Name to access the 'Name' column of the current row
// - this Variant will point to the corresponding TSqlDBStatement instance,
// so it's not necessary to retrieve its value for each row; but once the
// associated ISqlDBRows instance is released, you won't be able to access
// its data - use RowDocVariant instead
// - typical use is:
// ! var Row: Variant;
// ! (...)
// ! with MyConnProps.Execute('select * from table where name=?',[aName]) do
// ! begin
// ! Row := RowData;
// ! while Step do
// ! writeln(Row.FirstName,Row.BirthDate);
// ! ReleaseRows;
// ! end;
function RowData: Variant;
/// create a TDocVariant custom variant containing all columns values
// - will create a "fast" TDocVariant object instance with all fields
procedure RowDocVariant(out aDocument: variant;
aOptions: TDocVariantOptions = JSON_FAST);
/// return the associated statement instance
function Instance: TSqlDBStatement;
// return all rows content as a JSON string
// - JSON data is retrieved with UTF-8 encoding
// - if Expanded is true, JSON output is a standard array of objects, for
// direct use with any Ajax or .NET client:
// & [{"f1":"1v1","f2":1v2},{"f2":"2v1","f2":2v2}...]
// - if Expanded is false, JSON data is serialized in non-expanded format:
// & {"fieldCount":2,"values":["f1","f2","1v1",1v2,"2v1",2v2...],"rowCount":20}
// resulting in lower space use and faster process - it could be parsed by
// TOrmTableJson or TDocVariantData.InitArrayFromResults
// - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"'
// format and contains true BLOB data
// - if ReturnedRowCount points to an integer variable, it will be filled with
// the number of row data returned (excluding field names)
// - similar to corresponding TSqlRequest.Execute method in the
// mormot.db.raw.sqlite3 unit
function FetchAllAsJson(Expanded: boolean; ReturnedRowCount: PPtrInt = nil): RawUtf8;
// append all rows content as a JSON stream
// - JSON data is added to the supplied TStream, with UTF-8 encoding
// - if Expanded is true, JSON output is a standard array of objects, for
// direct use with any Ajax or .NET client:
// & [{"f1":"1v1","f2":1v2},{"f2":"2v1","f2":2v2}...]
// - if Expanded is false, JSON data is serialized in non-expanded format:
// & {"fieldCount":2,"values":["f1","f2","1v1",1v2,"2v1",2v2...],"rowCount":20}
// resulting in lower space use and faster process - it could be parsed by
// TOrmTableJson or TDocVariantData.InitArrayFromResults
// - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"'
// format and contains true BLOB data
// - similar to corresponding TSqlRequest.Execute method in the
// mormot.db.raw.sqlite3 unit
// - returns the number of row data returned (excluding field names)
function FetchAllToJson(Json: TStream; Expanded: boolean): PtrInt;
/// append all rows content as binary stream
// - will save the column types and name, then every data row in optimized
// binary format (faster and smaller than JSON)
// - you can specify a LIMIT for the data extent (default 0 meaning all data)
// - generates the format expected by TSqlDBProxyStatement
function FetchAllToBinary(Dest: TStream; MaxRowCount: cardinal = 0;
DataRowPosition: PCardinalDynArray = nil): cardinal;
end;
/// generic interface to bind to prepared SQL query
// - inherits from ISqlDBRows, so gives access to the result columns data
// - not all TSqlDBStatement methods are available, but only those to bind
// parameters and retrieve data after execution
// - reference counting mechanism of this interface will feature statement
// cache (if available) for NewThreadSafeStatementPrepared() or PrepareInlined()
ISqlDBStatement = interface(ISqlDBRows)
['{EC27B81C-BD57-47D4-9711-ACFA27B583D7}']
// some raw properties getter/setter
function GetForceBlobAsNull: boolean;
procedure SetForceBlobAsNull(value: boolean);
function GetForceDateWithMS: boolean;
procedure SetForceDateWithMS(value: boolean);
/// bind a NULL value to a parameter
// - the leftmost SQL parameter has an index of 1
// - some providers (e.g. OleDB during MULTI INSERT statements) expect the
// proper column type to be set in BoundType, even for NULL values
procedure BindNull(Param: integer; IO: TSqlDBParamInOutType = paramIn;
BoundType: TSqlDBFieldType = ftNull);
/// bind an integer value to a parameter
// - the leftmost SQL parameter has an index of 1
procedure Bind(Param: integer; Value: Int64;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a double value to a parameter
// - the leftmost SQL parameter has an index of 1
procedure Bind(Param: integer; Value: double;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a TDateTime value to a parameter
// - the leftmost SQL parameter has an index of 1
procedure BindDateTime(Param: integer; Value: TDateTime;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a currency value to a parameter
// - the leftmost SQL parameter has an index of 1
procedure BindCurrency(Param: integer; Value: currency;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a UTF-8 encoded string to a parameter
// - the leftmost SQL parameter has an index of 1
procedure BindTextU(Param: integer; const Value: RawUtf8;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a UTF-8 encoded buffer text (#0 ended) to a parameter
// - the leftmost SQL parameter has an index of 1
procedure BindTextP(Param: integer; Value: PUtf8Char;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a UTF-8 encoded string to a parameter
// - the leftmost SQL parameter has an index of 1
procedure BindTextS(Param: integer; const Value: string;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a UTF-8 encoded string to a parameter
// - the leftmost SQL parameter has an index of 1
procedure BindTextW(Param: integer; const Value: WideString;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a Blob buffer to a parameter
// - the leftmost SQL parameter has an index of 1
procedure BindBlob(Param: integer; Data: pointer; Size: integer;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a Blob buffer to a parameter
// - the leftmost SQL parameter has an index of 1
procedure BindBlob(Param: integer; const Data: RawByteString;
IO: TSqlDBParamInOutType = paramIn); overload;
/// bind a Variant value to a parameter
// - the leftmost SQL parameter has an index of 1
// - will call all virtual Bind*() methods from the Data type
// - if DataIsBlob is TRUE, will call BindBlob(RawByteString(Data)) instead
// of BindTextW(WideString(Variant)) - used e.g. by TQuery.AsBlob/AsBytes
procedure BindVariant(Param: integer; const Data: Variant;
DataIsBlob: boolean; IO: TSqlDBParamInOutType = paramIn);