-
-
Notifications
You must be signed in to change notification settings - Fork 563
Expand file tree
/
Copy pathdbstructures.pas
More file actions
322 lines (278 loc) · 10.6 KB
/
dbstructures.pas
File metadata and controls
322 lines (278 loc) · 10.6 KB
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
unit dbstructures;
// Column structures, dll loading
// For server constants, variables and data types see dbstructures.XXX.pas
interface
uses
gnugettext, Vcl.Graphics, Winapi.Windows, System.SysUtils, System.Classes, System.IOUtils,
System.Generics.Collections, StrUtils;
type
TNetType = (
ntMySQL_TCPIP,
ntMySQL_NamedPipe,
ntMySQL_SSHtunnel,
ntMSSQL_NamedPipe,
ntMSSQL_TCPIP,
ntMSSQL_SPX,
ntMSSQL_VINES,
ntMSSQL_RPC,
ntPgSQL_TCPIP,
ntPgSQL_SSHtunnel,
ntSQLite,
ntMySQL_ProxySQLAdmin,
ntInterbase_TCPIP,
ntInterbase_Local,
ntFirebird_TCPIP,
ntFirebird_Local,
ntMySQL_RDS,
ntSQLiteEncrypted
);
TNetTypeGroup = (ngMySQL, ngMSSQL, ngPgSQL, ngSQLite, ngInterbase);
TNetTypeLibs = TDictionary<TNetType, TStringList>;
// SQL query ids and provider
TStringMap = TDictionary<string,string>;
TQueryId = (qDatabaseTable, qDatabaseTableId, qDatabaseDrop,
qDbObjectsTable, qDbObjectsCreateCol, qDbObjectsUpdateCol, qDbObjectsTypeCol,
qEmptyTable, qRenameTable, qRenameView, qCurrentUserHost, qLikeCompare,
qAddColumn, qChangeColumn, qRenameColumn, qForeignKeyEventAction,
qGlobalStatus, qCommandsCounters, qSessionVariables, qGlobalVariables,
qISSchemaCol,
qUSEQuery, qKillQuery, qKillProcess,
qFuncLength, qFuncCeil, qFuncLeft, qFuncNow, qFuncLastAutoIncNumber,
qLockedTables, qDisableForeignKeyChecks, qEnableForeignKeyChecks,
qOrderAsc, qOrderDesc, qGetRowCountExact, qGetRowCountApprox,
qForeignKeyDrop, qGetTableColumns, qGetCollations, qGetCollationsExtended, qGetCharsets,
qGetReverseForeignKeys, qExplain, qSetTimezone,
qShowFunctionStatus, qShowProcedureStatus, qShowTriggers, qShowEvents, qShowCreateTrigger,
qHelpKeyword, qShowWarnings, qGetEnumTypes,
qDropUser, qCreateRole, qDropRole, qReloadPrivileges, qGrantRole, qRevokeRole, qSetDefaultRole,
qAutoInc, qIndexVisible, qIndexInvisible);
TSqlProvider = class
strict protected
FNetType: TNetType;
FServerVersion: Integer;
public
constructor Create(ANetType: TNetType);
function Has(AId: TQueryId): Boolean;
// Base version, just returns the original SQL string
function GetSql(AId: TQueryId): string; overload; virtual;
// Version for simple strings passed to Format()
function GetSql(AId: TQueryId; const Args: array of const): string; overload;
// Version for named parameters
function GetSql(AId: TQueryId; NamedParameters: TStringMap): string; overload;
property ServerVersion: Integer read FServerVersion write FServerVersion;
end;
// Column types
TDBDatatypeIndex = (dbdtTinyint, dbdtSmallint, dbdtMediumint, dbdtInt, dbdtUint, dbdtBigint, dbdtSerial, dbdtBigSerial,
dbdtFloat, dbdtDouble, dbdtDecimal, dbdtNumeric, dbdtReal, dbdtDoublePrecision, dbdtMoney, dbdtSmallmoney,
dbdtDate, dbdtTime, dbdtYear, dbdtDatetime, dbdtDatetime2, dbdtDatetimeOffset, dbdtSmalldatetime, dbdtTimestamp, dbdtInterval,
dbdtChar, dbdtNchar, dbdtVarchar, dbdtNvarchar, dbdtTinytext, dbdtText, dbdtCiText, dbdtNtext, dbdtMediumtext, dbdtLongtext,
dbdtJson, dbdtJsonB, dbdtCidr, dbdtInet, dbdtMacaddr,
dbdtBinary, dbdtVarbinary, dbdtTinyblob, dbdtBlob, dbdtMediumblob, dbdtLongblob, dbdtVector, dbdtImage,
dbdtEnum, dbdtSet, dbdtBit, dbdtVarBit, dbdtBool, dbdtRegClass, dbdtRegProc, dbdtUnknown,
dbdtCursor, dbdtSqlvariant, dbdtTable, dbdtUniqueidentifier, dbdtInet4, dbdtInet6, dbdtHierarchyid, dbdtXML,
dbdtPoint, dbdtLinestring, dbdtLineSegment, dbdtPolygon, dbdtGeometry, dbdtBox, dbdtPath, dbdtCircle, dbdtMultipoint, dbdtMultilinestring, dbdtMultipolygon, dbdtGeometrycollection
);
// Column type categorization
TDBDatatypeCategoryIndex = (dtcInteger, dtcReal, dtcText, dtcBinary, dtcTemporal, dtcSpatial, dtcOther);
// Column type structure
TDBDatatype = record
Index: TDBDatatypeIndex;
NativeType: Integer; // MySQL column type constant (e.g. 1 = TINYINT). See include/mysql.h.pp.
NativeTypes: String; // Same as above, but for multiple ids (e.g. PostgreSQL oids). Prefer over NativeType. See GetDatatypeByNativeType.
Name: String;
Names: String;
Description: String;
HasLength: Boolean; // Can have Length- or Set-attribute?
RequiresLength: Boolean; // Must have a Length- or Set-attribute?
MaxSize: Int64;
DefaultSize: Int64; // TEXT and BLOB allow custom length, but we want to leave the default max length away from ALTER TABLE's
HasBinary: Boolean; // Can be binary?
HasDefault: Boolean; // Can have a default value?
LoadPart: Boolean; // Select per SUBSTR() or LEFT()
DefLengthSet: String; // Should be set for types which require a length/set
Format: String; // Used for date/time values when displaying and generating queries
ValueMustMatch: String;
Category: TDBDatatypeCategoryIndex;
MinVersion: Integer;
end;
// Column type category structure
TDBDatatypeCategory = record
Index: TDBDatatypeCategoryIndex;
Name: String;
Color: TColor;
NullColor: TColor;
end;
// Server variables
TVarScope = (vsGlobal, vsSession, vsBoth);
TServerVariable = record
Name: String;
IsDynamic: Boolean;
VarScope: TVarScope;
EnumValues: String;
end;
// Custom exception class for any connection or database related error
EDbError = class(Exception)
private
FErrorCode: Cardinal;
FHint: String;
public
property ErrorCode: Cardinal read FErrorCode;
property Hint: String read FHint;
constructor Create(const Msg: string; const ErrorCode_: Cardinal=0; const Hint_: String='');
end;
// DLL loading
TDbLib = class(TObject)
const
LIB_PROC_ERROR: Cardinal = 1000;
private
FHandle: HMODULE;
protected
FDllFile: String;
procedure AssignProc(var Proc: FARPROC; Name: PAnsiChar; Mandantory: Boolean=True);
procedure AssignProcedures; virtual; abstract;
public
property Handle: HMODULE read FHandle;
property DllFile: String read FDllFile;
constructor Create(UsedDllFile, HintDefaultDll: String); virtual;
destructor Destroy; override;
end;
var
// Column type categories
DatatypeCategories: array[TDBDatatypeCategoryIndex] of TDBDatatypeCategory = (
(
Index: dtcInteger;
Name: 'Integer'
),
(
Index: dtcReal;
Name: 'Real'
),
(
Index: dtcText;
Name: 'Text'
),
(
Index: dtcBinary;
Name: 'Binary'
),
(
Index: dtcTemporal;
Name: 'Temporal (time)'
),
(
Index: dtcSpatial;
Name: 'Spatial (geometry)'
),
(
Index: dtcOther;
Name: 'Other'
)
);
implementation
uses apphelpers;
{ TSqlProvider }
constructor TSqlProvider.Create(ANetType: TNetType);
begin
FNetType := ANetType;
FServerVersion := 0;
end;
function TSqlProvider.Has(AId: TQueryId): Boolean;
begin
Result := not GetSql(AId).IsEmpty;
end;
function TSqlProvider.GetSql(AId: TQueryId): string;
begin
// Basic default SQL snippets compatible to all or most servers
case AId of
qEmptyTable: Result := 'DELETE FROM %s';
qForeignKeyEventAction: Result := 'RESTRICT,CASCADE,SET NULL,NO ACTION';
qOrderAsc: Result := 'ASC';
qOrderDesc: Result := 'DESC';
qGetRowCountExact: Result := 'SELECT COUNT(*) FROM :QuotedDbAndTableName';
qAutoInc: Result := 'AUTO_INCREMENT';
else Result := '';
end;
end;
function TSqlProvider.GetSql(AId: TQueryId; const Args: array of const): string;
begin
Result := GetSql(AId);
if Result.IsEmpty then
Exit;
Result := Format(Result, Args);
end;
function TSqlProvider.GetSql(AId: TQueryId; NamedParameters: TStringMap): string;
var
Key: String;
begin
Result := GetSql(AId);
if Result.IsEmpty then
Exit;
for Key in NamedParameters.Keys do begin
Result := StringReplace(Result, ':'+Key, NamedParameters[Key], [rfReplaceAll]);
end;
end;
{ EDbError }
constructor EDbError.Create(const Msg: string; const ErrorCode_: Cardinal=0; const Hint_: String='');
begin
FErrorCode := ErrorCode_;
FHint := Hint_;
inherited Create(Msg);
end;
{ TDbLib }
constructor TDbLib.Create(UsedDllFile, HintDefaultDll: String);
var
msg, ErrorHint: String;
begin
// Load DLL as is (with or without path)
inherited Create;
FDllFile := UsedDllFile;
// On Windows, we have the full path to the dll file here, so even if the file portion is empty, FDllFile contains a path / non-empty string
if not FileExists(FDllFile) then begin
Raise EdbError.Create(_('No library selected. Please select one of the provided libraries in the drop-down.'));
end;
FHandle := LoadLibrary(PWideChar(FDllFile));
if FHandle = 0 then begin
msg := f_('Library %s could not be loaded. Please select a different one.',
[ExtractFileName(FDllFile)]
);
if GetLastError <> 0 then begin
msg := msg + sLineBreak + sLineBreak + f_('Internal error %d: %s', [GetLastError, SysErrorMessage(GetLastError)]);
end;
if (HintDefaultDll <> '') and (ExtractFileName(FDllFile) <> HintDefaultDll) then begin
ErrorHint := f_('You could try the default library %s in your session settings. (Current: %s)',
[HintDefaultDll, ExtractFileName(FDllFile)]
);
end else begin
ErrorHint := '';
end;
Raise EDbError.Create(msg, GetLastError, ErrorHint);
end;
// Dll was loaded, now initialize required procedures
AssignProcedures;
end;
destructor TDbLib.Destroy;
begin
if FHandle <> 0 then begin
FreeLibrary(FHandle);
FHandle := 0;
end;
inherited;
end;
procedure TDbLib.AssignProc(var Proc: FARPROC; Name: PAnsiChar; Mandantory: Boolean=True);
var
msg: String;
begin
// Map library procedure to internal procedure
Proc := GetProcAddress(FHandle, Name);
if Proc = nil then begin
if Mandantory then begin
msg := f_('Library error in %s: Could not find procedure address for "%s"',
[ExtractFileName(FDllFile), Name]
);
if GetLastError <> 0 then
msg := msg + sLineBreak + sLineBreak + f_('Internal error %d: %s', [GetLastError, SysErrorMessage(GetLastError)]);
Raise EDbError.Create(msg, LIB_PROC_ERROR);
end;
end;
end;
end.