ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/branches/udr/udr/source/FBUDRController.pas
Revision: 394
Committed: Sat Feb 12 23:26:48 2022 UTC (2 years, 2 months ago) by tony
Content type: text/x-pascal
File size: 78506 byte(s)
Log Message:
Use FieldNames for param names

File Contents

# Content
1 (*
2 * Firebird UDR Support (fbudr). The fbudr components provide a set of
3 * Pascal language bindings for the Firebird API in support of server
4 * side User Defined Routines (UDRs). The fbudr package is an extension
5 * to the Firebird Pascal API.
6 *
7 * The contents of this file are subject to the Initial Developer's
8 * Public License Version 1.0 (the "License"); you may not use this
9 * file except in compliance with the License. You may obtain a copy
10 * of the License here:
11 *
12 * http://www.firebirdsql.org/index.php?op=doc&id=idpl
13 *
14 * Software distributed under the License is distributed on an "AS
15 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
16 * implied. See the License for the specific language governing rights
17 * and limitations under the License.
18 *
19 * The Initial Developer of the Original Code is Tony Whyman.
20 *
21 * The Original Code is (C) 2021 Tony Whyman, MWA Software
22 * (http://www.mwasoftware.co.uk).
23 *
24 * All Rights Reserved.
25 *
26 * Contributor(s): ______________________________________.
27 *
28 *)
29 unit FBUDRController;
30
31 {$IFDEF MSWINDOWS}
32 {$DEFINE WINDOWS}
33 {$WRITEABLECONST ON}
34 {$ENDIF}
35
36 {$IFDEF FPC}
37 {$mode delphi}
38 {$codepage UTF8}
39 {$interfaces COM}
40 {$ENDIF}
41
42 interface
43
44 uses
45 Classes, SysUtils, SyncObjs,Firebird, IB, FBUDRIntf, FBSQLData,
46 FB30Statement, IniFiles;
47
48 type
49 {The Controller is based on the UnloadDetector class in <firebird>/src/include/firebird/UdrCppEngine.h
50 It provides the entry point for the library and is also responsible for cleaning
51 up on library unload.
52
53 It order to operate similar to a C++ static class such as UnloadDetector, when
54 instantiated, it saves a reference to itself as a COM interface in a private
55 class var. This is a managed variable and hence automatically initialised to
56 nil, and automatically freed on exit.
57
58 The controller also handles the registration of UDRs, access to a configuration
59 file and logging.
60 }
61
62 TFBUDRControllerLogOption = (loLogFunctions, loLogProcedures, loLogTriggers, loLogFetches,
63 loModifyQueries, loReadOnlyQueries, loDetails);
64 TFBUDRControllerLogOptions = set of TFBUDRControllerLogOption;
65
66 { In order to customise log handling, the logging options may be
67 statically set by overridding the values of the writable constant
68 FBControllerLogOptions, or through configuration file overrides.
69 }
70
71 TFBUDRControllerOptions = record
72 ModuleName: AnsiString;
73 AllowConfigFileOverrides: boolean;
74 LogFileNameTemplate: AnsiString;
75 ConfigFileNameTemplate: AnsiString;
76 ForceWriteLogEntries: boolean;
77 LogOptions: TFBUDRControllerLogOptions;
78 ThreadSafeLogging: boolean;
79 end;
80
81 {LogFileNameTemplate and ConfigFileName macros:
82 $LOGDIR = Firebird log directory
83 $UDRDIR = Firebird UDR directory
84 $TEMP = System temp directory
85 $MODULE = Module Name
86 $TIMESTAMP = date/time in "yyyymmddhhnnss format
87 }
88
89 const FBUDRControllerOptions: TFBUDRControllerOptions = (
90 ModuleName: 'untitled';
91 AllowConfigFileOverrides: false;
92 LogFileNameTemplate:'$LOGDIR$TIMESTAMP$MODULE.log';
93 ConfigFileNameTemplate: '$UDRDIR$MODULE.conf';
94 ForceWriteLogEntries: false;
95 LogOptions: [];
96 ThreadSafeLogging: false);
97
98 {$if declared(TStringArray)}
99 FalseStrings: TStringArray = ['false','no'];
100 TrueStrings: TStringArray = ['true','yes'];
101 {$ifend}
102 type
103
104 { TFBUDRController }
105
106 TFBUDRController = class(TInterfacedObject)
107 private
108 class var FFBController: IUnknown; {A Managed object and hence should be initialised
109 by the compiler to nil. Also, guaranteed to be
110 destoyed on exit. No need for finalization clause.}
111 class var FUDRFactoryList: TStringList;
112 class var FMyUnloadFlag: boolean;
113 function CharSetIDToText(att: IAttachment; id: integer): AnsiString;
114 function GetStrValue(item: TColumnMetaData): Ansistring;
115 function LogOptionsToStr(aLogOptions: TFBUDRControllerLogOptions
116 ): AnsiString;
117 private
118 const sLogFormat = '@%s:%s';
119 private
120 FTheirUnloadFlag: Firebird.BooleanPtr;
121 FLogStream: TStream;
122 FCriticalSection: TCriticalSection;
123 FMaster: IMaster;
124 FMessageBuffer: AnsiString;
125 FConfigFile: TIniFile;
126 FJnlOpenAppend: boolean;
127 function GetDateTimeFmt: AnsiString;
128 procedure RegisterUDRFactories(status: Firebird.IStatus; udrPlugin: Firebird.IUdrPlugin);
129 procedure RegisterUDRFactory(status: Firebird.IStatus; udrPlugin: Firebird.IUdrPlugin;
130 aName: AnsiString; factory: TObject);
131 procedure FreeFactoryList;
132 procedure LoadConfig;
133 function NeedLogStream: boolean;
134 public
135 constructor Create(status: Firebird.IStatus; udrPlugin: Firebird.IUdrPlugin;
136 aTheirUnloadFlag: booleanPtr; var aMyUnloadFlag: booleanPtr);
137 destructor Destroy; override;
138 procedure FBSetStatusFromException(E: Exception; aStatus: Firebird.IStatus);
139 function ProcessTemplateMacros(aTemplate: AnsiString): AnsiString;
140 procedure WriteToLog(Msg: AnsiString); overload;
141 procedure WriteToLog(aTitle: AnsiString; Params: IFBUDRInputParams); overload;
142 procedure WriteToLog(aTitle: AnsiString; OutputData: IFBUDROutputData); overload;
143 procedure StartJournaling(context: IFBUDRExternalContext);
144 function HasConfigFile: boolean;
145 function ReadConfigString(Section, Ident, DefaultValue: AnsiString): AnsiString;
146 function ReadConfigInteger(Section, Ident: AnsiString; DefaultValue: integer): integer;
147 function ReadConfigBool(Section, Ident: AnsiString; DefaultValue: boolean): boolean;
148 end;
149
150 { TFBUDRInputParams }
151
152 TFBUDRInputParams = class(TResults,IFBUDRInputParams)
153 public
154 function ParamExists(Idx: AnsiString): boolean;
155 function ByName(Idx: AnsiString): ISQLData ; override;
156 end;
157
158 { TFBUDROutputParams }
159
160 TFBUDROutputParams = class(TSQLParams,IFBUDROutputData)
161 public
162 function FieldExists(Idx: AnsiString): boolean;
163 function ByName(Idx: AnsiString): ISQLParam ; override;
164 end;
165
166 {TFBUDROutParamsSQLDA subclasses a TIBXINPUTSQLDA. TIBXINPUTSQLDA is defined
167 in support of executable statements and is usually used to prepare the
168 input parameters to a query. Here, a TFBUDROutParamsSQLDA is used to record the
169 values returned by a function or procedure in the "outMsg" buffer provided
170 when the UDR is invoked.
171 }
172
173 { TFBUDROutParamsSQLDA }
174
175 TFBUDROutParamsSQLDA = class(TIBXINPUTSQLDA) {note semantics reversed}
176 private
177 FBuffer: PByte;
178 FAttachment: IAttachment;
179 FTransaction: ITransaction;
180 protected
181 procedure AllocMessageBuffer(len: integer); override;
182 procedure FreeMessageBuffer; override;
183 function GetAttachment: IAttachment; override;
184 function GetTransaction: ITransaction; override;
185 function IsInputDataArea: boolean; override;
186 public
187 {created with the UDR output metadata and a pointer to the outMsg buffer.}
188 constructor Create(context: IFBUDRExternalContext; aMetadata: Firebird.IMessageMetaData; aBuffer: PByte);
189
190 {We override CanChangeMetaData to stop a UDR writer trying to change the output
191 metadata and hence invalidate the outMsg buffer.}
192 function CanChangeMetaData: boolean; override;
193
194 {Finalise is called after the UDR completes and copies the output variable values
195 into the outMsg buffer}
196 procedure Finalise;
197 end;
198
199 {TFBUDRTriggerNewValuesSQLDA additionally initialises the field values to
200 those found in the messagebuffer. This is appropriate for "before" triggers
201 only}
202
203 TFBUDRTriggerNewValuesSQLDA = class(TFBUDROutParamsSQLDA)
204 public
205 {created with the UDR output metadata and a pointer to the outMsg buffer.}
206 constructor Create(context: IFBUDRExternalContext; aMetadata: Firebird.IMessageMetaData; aBuffer: PByte);
207 end;
208
209 {TFBUDRInParamsSQLDA subclasses TIBXOUTPUTSQLDA. TIBXOUTPUTSQLDA is defined
210 in support of executable statements and is usually used to return either
211 a singleton or multiple rows from a query. Here, a TFBUDRInParamsSQLDA is used to
212 hold the input parameter values to a function, procedure or trigger, as provided
213 in the "inMsg" buffer.
214 }
215
216 { TFBUDRInParamsSQLDA }
217
218 TFBUDRInParamsSQLDA = class(TIBXOUTPUTSQLDA) {note semantics reversed}
219 private
220 FBuffer: PByte;
221 FAttachment: IAttachment;
222 FTransaction: ITransaction;
223 protected
224 procedure AllocMessageBuffer(len: integer); override;
225 procedure FreeMessageBuffer; override;
226 function GetAttachment: IAttachment; override;
227 function GetTransaction: ITransaction; override;
228 function IsInputDataArea: boolean; override;
229 public
230 {created with the input messge metadata and a pointer to the inMsg buffer}
231 constructor Create(context: IFBUDRExternalContext;
232 aMetadata: Firebird.IMessageMetaData; aBuffer: PByte);
233 end;
234
235 {A TFBUDRFunction object is instantiated by a TUDRFunctionFactory when a
236 "newItem" is requested. This is an abstract class and is subclassed by a
237 UDR writer for each UDR function required. TFBUDRFunction subclasses and their
238 "Routine Name" are registered at initialisation time by the RegisterUDRFunction
239 procedure.
240
241 { TFBUDRFunction }
242
243 { TFBUDRFunction }
244
245 TFBUDRFunction = class(Firebird.IExternalFunctionImpl)
246 private
247 FController: TFBUDRController;
248 FName: AnsiString;
249 FRoutineMetadata: IFBUDRRoutineMetadata;
250 FFirebirdAPI: IFirebirdAPI;
251 public
252 constructor Create(aController: TFBUDRController;
253 aName: AnsiString;
254 routineMetadata: IFBUDRRoutineMetadata);
255 public
256 {External Function Implementation}
257
258 {Override getCharSet when the function returns strings in a different charset
259 to that used by the database connection.}
260 function getCharSet(context: IFBUDRExternalContext): AnsiString; overload; virtual;
261
262 {Execute must be overridden by each subclass in order to define a new UDR
263 function. The function returns its output value in a variant. The value
264 contained in the variant must be type compatible with the result type in
265 the SQL function declaration.}
266 function Execute(context: IFBUDRExternalContext;
267 ProcMetadata: IFBUDRProcMetadata;
268 InputParams: IFBUDRInputParams;
269 ResultSQLType: cardinal): variant; overload; virtual;
270
271 {alternatively, you can override this version to return any value that ISQLParam
272 can take. The Result is returned as a procedure parameter rather than
273 as a function result}
274 procedure Execute(context: IFBUDRExternalContext;
275 ProcMetadata: IFBUDRProcMetadata;
276 InputParams: IFBUDRInputParams;
277 ReturnValue: ISQLParam); overload; virtual;
278
279 {setup is a class procedure and called by the UDR's Function factory when its
280 setup procedure is called. This is typically called the first time a
281 function factory is first used and may be used to initialise any class vars
282 used by the Execute function. This is normally only required with stateful
283 functions.}
284 class procedure setup(context: IFBUDRExternalContext;
285 metadata: IFBUDRRoutineMetadata;
286 inBuilder: IFBUDRMetadataBuilder;
287 outBuilder: IFBUDRMetadataBuilder); virtual;
288 property Name: AnsiString read FName;
289 property Controller: TFBUDRController read FController;
290 property FirebirdAPI: IFirebirdAPI read FFirebirdAPI;
291 public
292 {IExternalFunction}
293 procedure dispose(); override;
294 procedure getCharSet(status: Firebird.IStatus; context: Firebird.IExternalContext;
295 name: PAnsiChar; nameSize: Cardinal); overload; override;
296 procedure execute(status: Firebird.IStatus; context: Firebird.IExternalContext;
297 inMsg: Pointer; outMsg: Pointer); overload; override;
298 end;
299
300 TFBUDRFunctionClass = class of TFBUDRFunction;
301
302 {A new instance of TFBUDRFunctionFactory is instantiated for each UDR function
303 registered with the controller. It is called by the Firebird engine to "setup"
304 each UDR function and to create a new instance of each UDR function.
305 TFBUDRFunctionFactory is used internally by the UDR Controller and may be ignored
306 by a UDR writer.}
307
308 {see <firebird>/src/plugins/udr_engine/UdrEngine.cpp for how Firebird UDR Engine
309 uses the factory classes}
310
311 { TFBUDRFunctionFactory }
312
313 TFBUDRFunctionFactory = class(Firebird.IUdrFunctionFactoryImpl)
314 private
315 FController: TFBUDRController;
316 FName: AnsiString;
317 FFunction: TFBUDRFunctionClass;
318 procedure SetController(AValue: TFBUDRController);
319 public
320 constructor Create(aName: AnsiString; aFunction: TFBUDRFunctionClass);
321 property Controller: TFBUDRController read FController write SetController;
322 public
323 {IUdrFunctionFactory}
324 procedure dispose(); override;
325 procedure setup(status: Firebird.IStatus; context: Firebird.IExternalContext;
326 metadata: Firebird.IRoutineMetadata;
327 inBuilder: Firebird.IMetadataBuilder;
328 outBuilder: Firebird.IMetadataBuilder); override;
329 function newItem(status: Firebird.IStatus; context: Firebird.IExternalContext; metadata:
330 Firebird.IRoutineMetadata): Firebird.IExternalFunction; override;
331 end;
332
333 {A TFBUDRProcedure object is instantiated by a TUDRPRocedureFactory when a
334 "newItem" is requested. This is an abstract class and is the base class for
335 UDR Execute Procedures (TUDRExecuteProcedure) and UDR Select Procedures (TUDRSelectProcedure).
336 }
337
338 { TFBUDRProcedure }
339
340 TFBUDRProcedure = class(Firebird.IExternalProcedureImpl)
341 private
342 FController: TFBUDRController;
343 FName: AnsiString;
344 FRoutineMetadata: IFBUDRRoutineMetadata;
345 FRefCount: integer;
346 FFirebirdAPI: IFirebirdAPI;
347 public
348 constructor Create(aController: TFBUDRController;
349 aName: AnsiString;
350 routineMetadata: IFBUDRRoutineMetadata);
351 property FirebirdAPI: IFirebirdAPI read FFirebirdAPI;
352 public
353 {External Procedure Implementation}
354
355 {Override getCharSet when the procedure returns strings in a different charset
356 to that used by the database connection.}
357 function getCharSet(context: IFBUDRExternalContext): AnsiString; overload; virtual;
358
359 {setup is a class procedure and called by the UDR's Procedure factory when its
360 setup procedure is called. This is typically called the first time a
361 procedure factory is first used and may be used to initialise any class vars
362 used by the procedure. This is normally only required with stateful
363 procedures.}
364 class procedure setup(context: IFBUDRExternalContext;
365 metadata: IFBUDRRoutineMetadata;
366 inBuilder: IFBUDRMetadataBuilder;
367 outBuilder: IFBUDRMetadataBuilder); virtual;
368 property Name: AnsiString read FName;
369 property Controller: TFBUDRController read FController;
370 public
371 {IExternalProcedure}
372 procedure dispose(); override;
373 procedure getCharSet(status: Firebird.IStatus; context: Firebird.IExternalContext;
374 name: PAnsiChar; nameSize: Cardinal); overload; override;
375 end;
376
377 TFBUDRProcedureClass = class of TFBUDRProcedure;
378
379 {The Firebird UDR model requires that a procedure's "open" function returns a
380 Firebird.IExternalResultSet object. The TFBUDRExternalResultsSet is used to implement
381 this object and is subclassed separately to provide the results set for
382 Execute and Select Procedures.
383 }
384
385 { TFBUDRExternalResultsSet }
386
387 TFBUDRExternalResultsSet = class(Firebird.IExternalResultSetImpl)
388 private
389 FUDRProcedure: TFBUDRProcedure;
390 FOutputDataSQLDA: TFBUDROutParamsSQLDA;
391 FOutputData: IFBUDROutputData;
392 protected
393 procedure Close; virtual;
394 public
395 constructor Create(UDRProcedure: TFBUDRProcedure; context: IFBUDRExternalContext;
396 metadata: Firebird.IMessageMetadata;
397 outMsg: pointer);
398 destructor Destroy; override;
399 property OutputData: IFBUDROutputData read FOutputData;
400 public
401 {IExternalResultSetImpl}
402 procedure dispose(); override;
403 end;
404
405 {TFBUDRSingletonRow subclasses TFBUDRExternalResultsSet in order to provide the
406 Firebird.IExternalResultSet object returned by an Execute procedure. In a
407 TFBUDRExecuteProcedure, the output parameters are set in the main body of
408 the UDR writer's Execute procedure and are returned to the Firebird engine
409 via the "fetch" function implemented by this object.
410 }
411
412 { TFBUDRSingletonRow }
413
414 TFBUDRSingletonRow = class(TFBUDRExternalResultsSet)
415 private
416 FFetchCalled: boolean;
417 public
418 {IExternalResultSetImpl}
419 function fetch(status: Firebird.IStatus): Boolean; override;
420 end;
421
422 {TFBUDRResultsCursor subclasses TFBUDRExternalResultsSet in order to provide the
423 Firebird.IExternalResultSet object returned by s Select procedure. In a
424 TUDRSelectProcedure, the work is divided into two methods "open" and "fetch".
425 The former initialises procedure, while "fetch" is called to return each row
426 in the output dataset. A "close" nethod may also be provided to perform any
427 tidy up following the last call to "fetch".
428
429 }
430 { TFBUDRResultsCursor }
431
432 TFBUDRResultsCursor = class(TFBUDRExternalResultsSet)
433 private
434 FDone: boolean;
435 protected
436 procedure Close; override;
437 public
438 {IExternalResultSetImpl}
439 function fetch(status: Firebird.IStatus): Boolean; override;
440 end;
441
442 {TFBUDRExecuteProcedure subclasses a TFBUDRProcedure for a UDR Execute Procedure.
443 A TFBUDRExecuteProcedure object is instantiated by a TUDRProcedureFactory when a
444 "newItem" is requested. This is an abstract class and is subclassed by a
445 UDR writer for each UDR Execute procedure required. TFBUDRExecuteProcedure
446 subclasses and their "Routine Name" are registered at initialisation time by
447 the RegisterUDRProcedure procedure.
448 }
449
450 { TFBUDRExecuteProcedure }
451
452 TFBUDRExecuteProcedure = class(TFBUDRProcedure)
453 public
454 {Execute must be overridden by each subclass in order to define a new UDR
455 Execute Procedure. The procedure may read its input parameter values from the
456 InputParams interface, and return the output parameter values using the
457 outputData interface.}
458 procedure Execute(context: IFBUDRExternalContext;
459 ProcMetadata: IFBUDRProcMetadata;
460 InputParams: IFBUDRInputParams;
461 OutputData: IFBUDROutputData); virtual; abstract;
462 public
463 {IExternalProcedure}
464 function open(status: Firebird.IStatus; context: Firebird.IExternalContext;
465 inMsg: Pointer; outMsg: Pointer): Firebird.IExternalResultSet; override;
466 end;
467
468 {TFBUDRSelectProcedure subclasses a TFBUDRProcedure for a UDR Select Procedure.
469 A TFBUDRSelectProcedure object is instantiated by a TUDRProcedureFactory when a
470 "newItem" is requested. This is an abstract class and is subclassed by a
471 UDR writer for each UDR Select procedure required. TFBUDRSelectProcedure
472 subclasses and their "Routine Name" are registered at initialisation time by
473 the RegisterUDRProcedure procedure.
474 }
475
476 { TFBUDRSelectProcedure }
477
478 TFBUDRSelectProcedure = class(TFBUDRProcedure)
479 public
480 {open must be overridden by a subclass in order to process the input parameter
481 values and to set up the select procedure. }
482 procedure open(context: IFBUDRExternalContext;
483 ProcMetadata: IFBUDRProcMetadata;
484 InputParams: IFBUDRInputParams); overload; virtual; abstract;
485
486 {After "open" returns the "fetch" method is called by the Firebird engine
487 to return each row in the output dataset. The values of the row's columns
488 are retuned in the Outputdata. Fetch returns false to indicate End of data.}
489 function fetch(OutputData: IFBUDROutputData): boolean; virtual;
490
491 {The "close" method is called after "fetch" returns false and may be overridden
492 in order to tidy up if necessary.}
493 procedure close; virtual;
494 public
495 {IExternalProcedure}
496 function open(status: Firebird.IStatus; context: Firebird.IExternalContext;
497 inMsg: Pointer; outMsg: Pointer): Firebird.IExternalResultSet; overload; override;
498 end;
499
500 {A new instance of TFBUDRProcedureFactory is instantiated for each UDR procedure
501 registered with the controller. It is called by the Firebird engine to "setup"
502 each UDR procedure and to create a new instance of each UDR procedure.
503 TFBUDRProcedureFactory is used internally by the UDR Controller and may be ignored
504 by a UDR writer.}
505
506 { TFBUDRProcedureFactory }
507
508 TFBUDRProcedureFactory = class(Firebird.IUdrProcedureFactoryImpl)
509 private
510 FController: TFBUDRController;
511 FName: AnsiString;
512 FProcedure: TFBUDRProcedureClass;
513 procedure SetController(AValue: TFBUDRController);
514 public
515 constructor Create(aName: AnsiString; aProcedure: TFBUDRProcedureClass);
516 property Controller: TFBUDRController read FController write SetController;
517 public
518 {IUdrProcedureFactory}
519 procedure dispose(); override;
520 procedure setup(status: Firebird.IStatus; context: Firebird.IExternalContext;
521 metadata:Firebird.IRoutineMetadata; inBuilder: Firebird.IMetadataBuilder;
522 outBuilder: Firebird.IMetadataBuilder); override;
523 function newItem(status: Firebird.IStatus; context: Firebird.IExternalContext;
524 metadata: Firebird.IRoutineMetadata): IExternalProcedure; override;
525 end;
526
527 TFBUDRTriggerAction = (taInsert, taUpdate, taDelete, taConnect, taDisconnect,
528 taTransactionStart, taTransactionCommit, taTransactionRollback,
529 taDDL);
530 {A TFBUDRTrigger object is instantiated by a TUDRTriggerFactory when a
531 "newItem" is requested. This is an abstract class and is subclassed by a
532 UDR writer for each UDR Trigger required. TFBUDRTrigger subclasses and their
533 "Routine Name" are registered at initialisation time by the RegisterUDRTrigger
534 procedure.}
535
536 { TFBUDRTrigger }
537
538 TFBUDRTrigger = class(Firebird.IExternalTriggerImpl)
539 private
540 FController: TFBUDRController;
541 FName: AnsiString;
542 FRoutineMetadata: IFBUDRRoutineMetadata;
543 FFirebirdAPI: IFirebirdAPI;
544 public
545 constructor Create(aController: TFBUDRController;
546 aName: AnsiString;
547 routineMetadata: IFBUDRRoutineMetadata);
548 public
549 {External Trigger Implementation}
550
551 {Override getCharSet when the Trigger returns (?) strings in a different charset
552 to that used by the database connection.}
553 function getCharSet(context: IFBUDRExternalContext): AnsiString; overload; virtual;
554
555 {Override AfterTrigger in order to carry out an after trigger's function.
556 Separate interfaces are used to provide the "old" and "new" values of each
557 of the parent dataset's columns. Note that these are both read only for an
558 after trigger}
559
560 procedure AfterTrigger(context: IFBUDRExternalContext;
561 TriggerMetaData: IFBUDRTriggerMetaData;
562 action: TFBUDRTriggerAction;
563 OldParams: IFBUDRInputParams;
564 NewParams: IFBUDRInputParams); virtual;
565
566
567 {Override BeforeTrigger in order to carry out a before trigger's function.
568 Separate interfaces are used to provide the "old" and "new" values of each
569 of the parent dataset's columns. Note that the new values are writeable for an
570 after trigger}
571
572 procedure BeforeTrigger(context: IFBUDRExternalContext;
573 TriggerMetaData: IFBUDRTriggerMetaData;
574 action: TFBUDRTriggerAction;
575 OldParams: IFBUDRInputParams;
576 NewParams: IFBUDROutputData); virtual;
577
578
579 {Override DatabaseTrigger in order to carry out a before trigger's function.
580 Note that a database trigger does not have any input values}
581
582 procedure DatabaseTrigger(context: IFBUDRExternalContext;
583 TriggerMetaData: IFBUDRTriggerMetaData); virtual;
584
585 {setup is a class procedure and called by the UDR's Trigger factory when its
586 setup procedure is called. This is typically called the first time a
587 trigger factory is first used and may be used to initialise any class vars
588 used by the Execute function. This is normally only required with stateful
589 triggers.}
590
591 class procedure setup(context: IFBUDRExternalContext;
592 metadata: IFBUDRRoutineMetadata;
593 fieldsBuilder: IFBUDRMetadataBuilder); virtual;
594
595 property Name: AnsiString read FName;
596 property Controller: TFBUDRController read FController;
597 property FirebirdAPI: IFirebirdAPI read FFirebirdAPI;
598 public
599 {IExternalTrigger}
600 procedure dispose(); override;
601 procedure getCharSet(status: Firebird.IStatus; context: Firebird.IExternalContext;
602 name: PAnsiChar; nameSize: Cardinal); overload; override;
603 procedure execute(status: Firebird.IStatus; context: Firebird.IExternalContext;
604 action: Cardinal; oldMsg: Pointer; newMsg: Pointer); overload; override;
605 end;
606
607 TFBUDRTriggerClass = class of TFBUDRTrigger;
608
609 {A new instance of TFBUDRTriggerFactory is instantiated for each UDR trigger
610 registered with the controller. It is called by the Firebird engine to "setup"
611 each UDR trigger and to create a new instance of each UDR trigger.
612 TFBUDRTriggerFactory is used internally by the UDR Controller and may be ignored
613 by a UDR writer.}
614
615 { TFBUDRTriggerFactory }
616
617 TFBUDRTriggerFactory = class(Firebird.IUdrTriggerFactoryImpl)
618 private
619 FController: TFBUDRController;
620 FName: AnsiString;
621 FTrigger: TFBUDRTriggerClass;
622 procedure SetController(AValue: TFBUDRController);
623 public
624 constructor Create(aName: AnsiString; aTrigger: TFBUDRTriggerClass);
625 property Controller: TFBUDRController read FController write SetController;
626 public
627 procedure dispose(); override;
628 procedure setup(status: Firebird.IStatus; context: Firebird.IExternalContext;
629 metadata: Firebird.IRoutineMetadata;
630 fieldsBuilder: Firebird.IMetadataBuilder); override;
631 function newItem(status: Firebird.IStatus; context: Firebird.IExternalContext;
632 metadata: Firebird.IRoutineMetadata): Firebird.IExternalTrigger; override;
633 end;
634
635 {firebird_udr_plugin is the UDR library entry point and must be exported by the
636 library.}
637
638 function firebird_udr_plugin(status: Firebird.IStatus; aTheirUnloadFlag: Firebird.BooleanPtr;
639 udrPlugin: Firebird.IUdrPlugin): Firebird.BooleanPtr; cdecl;
640
641 {The register functions are called at initialisation time to register each function,
642 procedure and trigger defined by the library. Note: "aName" is the routine name
643 in the corresponding SQL declaration for the function, procedure or trigger.}
644
645 procedure FBRegisterUDRFunction(aName: AnsiString; aFunction: TFBUDRFunctionClass);
646 procedure FBRegisterUDRProcedure (aName: AnsiString; aProcedure: TFBUDRProcedureClass);
647 procedure FBRegisterUDRTrigger(aName: AnsiString; aTrigger: TFBUDRTriggerClass);
648
649 implementation
650
651 uses FBUDRUtils, FBUDRMessage, IBUtils, FBClientAPI, FB30ClientAPI, IBErrorCodes,
652 IBExternals;
653
654 resourcestring
655 SFetchCalled = 'Fetch called for ';
656 SOpenExecuteProc = 'Open Execute Procedure ';
657 SOpenSelectProc = 'Open Select Procedure ';
658 SSetupTrigger = 'Setup Trigger ';
659 STriggerDisposed = 'Trigger %s: dispose called';
660 STriggerCharset = 'GetCharSet for Trigger %s charset name = "%s"';
661 STriggerExecute = 'Execute Trigger ';
662 STriggerNew = 'New Field Values:';
663 STriggerOld = 'Old Field Values:';
664 SProcSetup = 'Setup Procedure ';
665 SProcDispose = 'Procedure %s: dispose called with refcount = %d';
666 SProcCharset = 'GetCharSet for Procedure %s charset name = "%s"';
667 sFuncCreated = 'Function %s created';
668 SInputParams = 'Input Parameters';
669 SOutputParams = 'Output Parameters';
670 SOutputData = 'Output Parameters with data';
671 SFuncDispose = 'Function %s: dispose called';
672 SFuncCharset = 'GetCharSet for Function %s charset name = "%s"';
673 SFuncExecute = 'Execute Function ';
674 SFuncSetup = 'Setup Function ';
675 SRoutineMetadata = 'Routine Metadata';
676 SExceptionRaised = 'Exception raised';
677 SFuncRegister = 'Registering Function ';
678 SProcRegister = 'Registering Procedure ';
679 STriggerRegister = 'Registering Trigger ';
680 SBadLogOptionsStr = 'Malformed LogOptions Config string "%s" at position %d';
681 SNoConfigFile = 'Unable to find/load configuration file';
682 SReadingConfigFile = 'Reading Configuration File: %s';
683 SFuncNotOverridden = 'UDR Function %s is undefined';
684 SNoReturnValue = 'Function %s does not have a return value!';
685 STriggerIsNotImplemented = 'Trigger %s is not implemented';
686 STriggerNewAfter = 'New Field Values after trigger execution';
687 SUnknownFieldName = 'Unknown Field Name - %s';
688 SEof = 'No More Rows';
689
690 function firebird_udr_plugin(status: Firebird.IStatus;
691 aTheirUnloadFlag: Firebird.BooleanPtr; udrPlugin: Firebird.IUdrPlugin
692 ): Firebird.BooleanPtr; cdecl;
693 begin
694 if TFBUDRController.FFBController = nil then
695 TFBUDRController.Create(status,udrPlugin,aTheirUnloadFlag,Result); {create a default instance}
696 end;
697
698 procedure RegisterUDRFactory(aName: AnsiString; aFactory: TObject);
699 begin
700 if TFBUDRController.FUDRFactoryList = nil then
701 TFBUDRController.FUDRFactoryList := TStringList.Create;
702
703 TFBUDRController.FUDRFactoryList.AddObject(aName,aFactory);
704 end;
705
706 procedure FBRegisterUDRFunction(aName: AnsiString; aFunction: TFBUDRFunctionClass);
707 begin
708 RegisterUDRFactory(aName,TFBUDRFunctionFactory.Create(aName,aFunction));
709 end;
710
711 procedure FBRegisterUDRProcedure(aName: AnsiString;
712 aProcedure: TFBUDRProcedureClass);
713 begin
714 RegisterUDRFactory(aName,TFBUDRProcedureFactory.Create(aName,aProcedure));
715 end;
716
717 procedure FBRegisterUDRTrigger(aName: AnsiString; aTrigger: TFBUDRTriggerClass);
718 begin
719 RegisterUDRFactory(aName,TFBUDRTriggerFactory.Create(aName,aTrigger));
720 end;
721
722 { TFBUDRInputParams }
723
724 function TFBUDRInputParams.ParamExists(Idx: AnsiString): boolean;
725 begin
726 Result := FieldExists(Idx);
727 end;
728
729 function TFBUDRInputParams.ByName(Idx: AnsiString): ISQLData;
730 begin
731 Result := inherited ByName(Idx);
732 if Result = nil then
733 raise Exception.CreateFmt(SUnknownFieldName,[idx]);
734 end;
735
736 { TFBUDROutputParams }
737
738 function TFBUDROutputParams.FieldExists(Idx: AnsiString): boolean;
739 begin
740 Result := ParamExists(Idx);
741 end;
742
743 function TFBUDROutputParams.ByName(Idx: AnsiString): ISQLParam;
744 begin
745 Result := inherited ByName(Idx);
746 if Result = nil then
747 raise Exception.CreateFmt(SUnknownFieldName,[idx]);
748 end;
749
750 { TFBUDRTriggerNewValuesSQLDA }
751
752 constructor TFBUDRTriggerNewValuesSQLDA.Create(context: IFBUDRExternalContext;
753 aMetadata: Firebird.IMessageMetaData; aBuffer: PByte);
754 var i: integer;
755 SQLNullIndicator: PShort;
756 data: PByte;
757 strlen: integer;
758 rs: rawbytestring;
759 begin
760 inherited Create(context,aMetadata,aBuffer);
761 for i := 0 to Count - 1 do
762 with TIBXSQLVar(Column[i]) do
763 begin
764 SQLNullIndicator := PShort(aBuffer + aMetaData.getNullOffset(context.getStatus,i));
765 context.CheckStatus;
766 if SQLNullIndicator^ = -1 then
767 IsNull := true
768 else
769 begin
770 IsNull := false;
771 data := aBuffer + aMetaData.getOffset(context.getStatus,i);
772 context.CheckStatus;
773 if SQLType = SQL_VARYING then
774 begin
775 strlen := (context.GetFirebirdAPI as TFBClientAPI).DecodeInteger(data,2);
776 DataLength := strlen;
777 setLength(rs,strlen);
778 Move((data+2)^,rs[1],strlen);
779 SetCodePage(rs,CodePage,false);
780 SetString(rs);
781 end
782 else
783 Move(data^,SQLData^,DataLength);
784 end;
785 end;
786 end;
787
788 { TFBUDRResultsCursor }
789
790 procedure TFBUDRResultsCursor.Close;
791 begin
792 inherited Close;
793 if not FDone then
794 (FUDRProcedure as TFBUDRSelectProcedure).Close;
795 FDone := true;
796 end;
797
798 function TFBUDRResultsCursor.fetch(status: Firebird.IStatus): Boolean;
799 begin
800 Result := false;
801 try
802 if loLogFetches in FBUDRControllerOptions.LogOptions then
803 FUDRProcedure.FController.WriteToLog(SFetchCalled + FUDRProcedure.FName);
804
805 if FOutputDataSQLDA <> nil then
806 begin
807 Result := (FUDRProcedure as TFBUDRSelectProcedure).fetch(FOutputData);
808 if [loLogFetches,loDetails] <= FBUDRControllerOptions.LogOptions then
809 if Result then
810 FUDRProcedure.FController.WriteToLog(SOutputData,FOutputData)
811 else
812 FUDRProcedure.FController.WriteToLog(SEof);
813 if Result then
814 FOutputDataSQLDA.Finalise;
815 end
816 else
817 Result := false;
818
819 if not Result then
820 begin
821 if not FDone then
822 (FUDRProcedure as TFBUDRSelectProcedure).Close;
823 FDone := true;
824 end;
825 except on E: Exception do
826 FUDRProcedure.FController.FBSetStatusFromException(E,status);
827 end;
828 end;
829
830 { TFBUDRSingletonRow }
831
832 function TFBUDRSingletonRow.fetch(status: Firebird.IStatus): Boolean;
833 begin
834 try
835 Result := (FOutputDataSQLDA <> nil) and not FFetchCalled;
836 if Result then
837 begin
838 if [loLogProcedures,loDetails] <= FBUDRControllerOptions.LogOptions then
839 FUDRProcedure.FController.WriteToLog(SOutputData,FOutputData);
840 FOutputDataSQLDA.Finalise; {copy output row to outMsg}
841 FFetchCalled := true;
842 end;
843 except on E: Exception do
844 FUDRProcedure.FController.FBSetStatusFromException(E,status);
845 end;
846 end;
847
848 { TFBUDRExecuteProcedure }
849
850 function TFBUDRExecuteProcedure.open(status: Firebird.IStatus;
851 context: Firebird.IExternalContext; inMsg: Pointer; outMsg: Pointer
852 ): Firebird.IExternalResultSet;
853 var aProcMetadata: IFBUDRProcMetadata;
854 InputParamsSQLDA: TFBUDRInParamsSQLDA;
855 InputParams: IFBUDRInputParams;
856 metadata: Firebird.IMessageMetadata;
857 FBContext: IFBUDRExternalContext;
858 begin
859 InputParams := nil;
860 InputParamsSQLDA := nil;
861 Result := nil;
862 try
863 if loLogProcedures in FBUDRControllerOptions.LogOptions then
864 FController.WriteToLog(SOpenExecuteProc + FName);
865
866 if FRoutineMetadata.QueryInterface(IFBUDRProcMetadata,aProcMetadata) <> S_OK then
867 FBUDRError(ibxeNoProcMetadata,[nil])
868 else
869 begin
870 FBContext := TFBUDRExternalContext.Create(Controller,context);
871 FFirebirdAPI := FBContext.GetFirebirdAPI;
872 try
873 if [loLogProcedures,loDetails] <= FBUDRControllerOptions.LogOptions then
874 FController.WriteToLog((FBContext as TFBUDRExternalContext).AsText);
875
876 if FRoutineMetadata.HasInputMetadata then
877 begin
878 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getInputMetadata;
879 try
880 InputParamsSQLDA := TFBUDRInParamsSQLDA.Create(FBContext,
881 metadata,
882 inMsg);
883 finally
884 metadata.release;
885 end;
886 end;
887
888 try
889 if InputParamsSQLDA <> nil then
890 begin
891 InputParams := TFBUDRInputParams.Create(InputParamsSQLDA);
892 if [loLogProcedures,loDetails] <= FBUDRControllerOptions.LogOptions then
893 FController.WriteToLog(SInputParams,InputParams);
894 end;
895
896 metadata := nil;
897 if FRoutineMetadata.HasOutputMetadata then
898 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getOutputMetadata;
899
900 try
901 Result := TFBUDRSingletonRow.Create(self, FBContext,
902 metadata,
903 outMsg);
904
905 finally
906 if metadata <> nil then
907 metadata.release;
908 end;
909
910 Execute(FBContext,aProcMetadata,InputParams,
911 (Result as TFBUDRSingletonRow).OutputData);
912 finally
913 InputParams := nil;
914 if InputParamsSQLDA <> nil then
915 InputParamsSQLDA.Free;
916 end;
917
918 finally
919 FFirebirdAPI := nil;
920 end;
921
922 end;
923 except on E: Exception do
924 begin
925 if Result <> nil then
926 Result.dispose;
927 Result := nil;
928 FController.FBSetStatusFromException(E,status);
929 end;
930 end;
931 end;
932
933 { TFBUDRSelectProcedure }
934
935 function TFBUDRSelectProcedure.fetch(OutputData: IFBUDROutputData): boolean;
936 begin
937 Result := false;
938 end;
939
940 procedure TFBUDRSelectProcedure.close;
941 begin
942 //override this method to tidy up once all rows have been returned
943 end;
944
945 function TFBUDRSelectProcedure.open(status: Firebird.IStatus;
946 context: Firebird.IExternalContext; inMsg: Pointer; outMsg: Pointer
947 ): Firebird.IExternalResultSet;
948 var aProcMetadata: IFBUDRProcMetadata;
949 InputParamsSQLDA: TFBUDRInParamsSQLDA;
950 InputParams: IFBUDRInputParams;
951 metadata: Firebird.IMessageMetadata;
952 FBContext: IFBUDRExternalContext;
953 begin
954 Result := nil;
955 try
956 if loLogProcedures in FBUDRControllerOptions.LogOptions then
957 FController.WriteToLog(SOpenSelectProc + FName);
958
959 InputParamsSQLDA := nil;
960 InputParams := nil;
961 if FRoutineMetadata.QueryInterface(IFBUDRProcMetadata,aProcMetadata) <> S_OK then
962 FBUDRError(ibxeNoProcMetadata,[nil])
963 else
964 begin
965 FBContext := TFBUDRExternalContext.Create(Controller,context);
966 FFirebirdAPI := FBContext.GetFirebirdAPI;
967 try
968 if [loLogProcedures,loDetails] <= FBUDRControllerOptions.LogOptions then
969 FController.WriteToLog((FBContext as TFBUDRExternalContext).AsText);
970
971 if FRoutineMetadata.HasInputMetadata then
972 begin
973 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getInputMetadata;
974 try
975 InputParamsSQLDA := TFBUDRInParamsSQLDA.Create(FBContext,
976 metadata,
977 inMsg);
978 finally
979 metadata.release;
980 end;
981 end;
982
983 try
984 if InputParamsSQLDA <> nil then
985 begin
986 InputParams := TFBUDRInputParams.Create(InputParamsSQLDA);
987 if [loLogProcedures,loDetails] <= FBUDRControllerOptions.LogOptions then
988 FController.WriteToLog(SInputParams,InputParams);
989 end;
990
991 metadata := nil;
992 if FRoutineMetadata.HasOutputMetadata then
993 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getOutputMetadata;
994
995 try
996 open(FBContext,aProcMetadata,InputParams);
997 Result := TFBUDRResultsCursor.Create(self, FBContext,
998 metadata,
999 outMsg);
1000 finally
1001 if metadata <> nil then
1002 metadata.release;
1003 end;
1004 finally
1005 InputParams := nil;
1006 if InputParamsSQLDA <> nil then
1007 InputParamsSQLDA.Free;
1008 end;
1009 finally
1010 FFirebirdAPI := nil;
1011 end;
1012 end;
1013
1014 except on E: Exception do
1015 begin
1016 if Result <> nil then
1017 Result.dispose;
1018 Result := nil;
1019 FController.FBSetStatusFromException(E,status);
1020 end;
1021 end;
1022 end;
1023
1024 { TFBUDRTriggerFactory }
1025
1026 procedure TFBUDRTriggerFactory.SetController(AValue: TFBUDRController);
1027 begin
1028 if FController = AValue then Exit;
1029 FController := AValue;
1030 end;
1031
1032 constructor TFBUDRTriggerFactory.Create(aName: AnsiString;
1033 aTrigger: TFBUDRTriggerClass);
1034 begin
1035 inherited Create;
1036 FName := aName;
1037 FTrigger := aTrigger;
1038 end;
1039
1040 procedure TFBUDRTriggerFactory.dispose();
1041 begin
1042 Free;
1043 end;
1044
1045 procedure TFBUDRTriggerFactory.setup(status: Firebird.IStatus;
1046 context: Firebird.IExternalContext; metadata: Firebird.IRoutineMetadata;
1047 fieldsBuilder: Firebird.IMetadataBuilder);
1048 var FBRoutineMetadata: IFBUDRRoutineMetadata;
1049 FBFieldsBuilder: IFBUDRMetadataBuilder;
1050 FBContext: IFBUDRExternalContext;
1051 begin
1052 FBFieldsBuilder := nil;
1053 try
1054 if loLogTriggers in FBUDRControllerOptions.LogOptions then
1055 FController.WriteToLog(SSetupTrigger + FName);
1056
1057 FBContext := TFBUDRExternalContext.Create(Controller,context);
1058
1059 FBRoutineMetadata := TFBUDRRoutineMetadata.Create(FBContext,metadata);
1060 if fieldsBuilder <> nil then
1061 FBFieldsBuilder := TFBUDRMetadataBuilder.Create(FBContext,fieldsBuilder);
1062 if [loLogTriggers, loDetails] <= FBUDRControllerOptions.LogOptions then
1063 FController.WriteToLog(SRoutineMetadata + LineEnding + (FBRoutineMetadata as TFBUDRRoutineMetadata).AsText);
1064 TFBUDRTrigger.setup(FBContext,FBRoutineMetadata,FBFieldsBuilder);
1065 except on E: Exception do
1066 FController.FBSetStatusFromException(E,status);
1067 end;
1068 end;
1069
1070 function TFBUDRTriggerFactory.newItem(status: Firebird.IStatus;
1071 context: Firebird.IExternalContext; metadata: Firebird.IRoutineMetadata
1072 ): Firebird.IExternalTrigger;
1073 var FBRoutineMetadata: IFBUDRRoutineMetadata;
1074 FBContext: IFBUDRExternalContext;
1075 begin
1076 try
1077 FBContext := TFBUDRExternalContext.Create(Controller,context);
1078 FBRoutineMetadata := TFBUDRRoutineMetadata.Create(FBContext,metadata);
1079 Result := FTrigger.Create(FController,FName,FBRoutineMetadata);
1080 except on E: Exception do
1081 FController.FBSetStatusFromException(E,status);
1082 end;
1083 end;
1084
1085 { TFBUDRTrigger }
1086
1087 constructor TFBUDRTrigger.Create(aController: TFBUDRController;
1088 aName: AnsiString; routineMetadata: IFBUDRRoutineMetadata);
1089 begin
1090 inherited Create;
1091 FName := aName;
1092 FController := aController;
1093 FRoutineMetaData := routineMetadata;
1094 end;
1095
1096 function TFBUDRTrigger.getCharSet(context: IFBUDRExternalContext): AnsiString;
1097 begin
1098 Result := '';
1099 end;
1100
1101 procedure TFBUDRTrigger.AfterTrigger(context: IFBUDRExternalContext;
1102 TriggerMetaData: IFBUDRTriggerMetaData; action: TFBUDRTriggerAction;
1103 OldParams: IFBUDRInputParams; NewParams: IFBUDRInputParams);
1104 begin
1105 raise Exception.CreateFmt(STriggerIsNotImplemented,[FName]);
1106 end;
1107
1108 procedure TFBUDRTrigger.BeforeTrigger(context: IFBUDRExternalContext;
1109 TriggerMetaData: IFBUDRTriggerMetaData; action: TFBUDRTriggerAction;
1110 OldParams: IFBUDRInputParams; NewParams: IFBUDROutputData);
1111 begin
1112 raise Exception.CreateFmt(STriggerIsNotImplemented,[FName]);
1113 end;
1114
1115 procedure TFBUDRTrigger.DatabaseTrigger(context: IFBUDRExternalContext;
1116 TriggerMetaData: IFBUDRTriggerMetaData);
1117 begin
1118 raise Exception.CreateFmt(STriggerIsNotImplemented,[FName]);
1119 end;
1120
1121 class procedure TFBUDRTrigger.setup(context: IFBUDRExternalContext;
1122 metadata: IFBUDRRoutineMetadata; fieldsBuilder: IFBUDRMetadataBuilder);
1123 begin
1124 //Override in subclass
1125 end;
1126
1127 procedure TFBUDRTrigger.dispose();
1128 begin
1129 if loLogTriggers in FBUDRControllerOptions.LogOptions then
1130 FController.WriteToLog(Format(STriggerDisposed,[FName]));
1131
1132 Free;
1133 end;
1134
1135 procedure TFBUDRTrigger.getCharSet(status: Firebird.IStatus;
1136 context: Firebird.IExternalContext; name: PAnsiChar; nameSize: Cardinal);
1137 var charset: AnsiString;
1138 FBContext: IFBUDRExternalContext;
1139 begin
1140 try
1141 FBContext := TFBUDRExternalContext.Create(Controller,context);
1142 FFirebirdAPI := FBContext.GetFirebirdAPI;
1143 try
1144 charset := getCharSet(FBContext);
1145 if charset <> '' then
1146 begin
1147 StrPLCopy(name,charset,nameSize);
1148 if loLogTriggers in FBUDRControllerOptions.LogOptions then
1149 FController.WriteToLog(Format(STriggerCharset,[FName,charset]));
1150 end;
1151 finally
1152 FFirebirdAPI := nil;
1153 end;
1154 except on E: Exception do
1155 FController.FBSetStatusFromException(E,status);
1156 end;
1157 end;
1158
1159 procedure TFBUDRTrigger.execute(status: Firebird.IStatus;
1160 context: Firebird.IExternalContext; action: Cardinal; oldMsg: Pointer;
1161 newMsg: Pointer);
1162 var aTriggerMetadata: IFBUDRTriggerMetaData;
1163 OldParamsSQLDA: TFBUDRInParamsSQLDA;
1164 NewParamsSQLDA: TFBUDRInParamsSQLDA;
1165 WritableParamsSQLDA: TFBUDRTriggerNewValuesSQLDA;
1166 OldParams: IFBUDRInputParams;
1167 NewParams: IFBUDRInputParams;
1168 NewWritableParams: IFBUDROutputData;
1169 TriggerAction: TFBUDRTriggerAction;
1170 FBContext: IFBUDRExternalContext;
1171
1172 procedure SetUpOldParams;
1173 var metadata: Firebird.IMessageMetadata;
1174 begin
1175 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getTriggerMetadata;
1176 try
1177 OldParamsSQLDA := TFBUDRInParamsSQLDA.Create(FBContext,
1178 metadata,
1179 oldMsg);
1180 OldParams := TFBUDRInputParams.Create(OldParamsSQLDA);
1181 if [loLogTriggers,loDetails] <= FBUDRControllerOptions.LogOptions then
1182 begin
1183 FController.WriteToLog(STriggerOld,OldParams);
1184 end;
1185 finally
1186 metadata.release;
1187 end;
1188 end;
1189
1190 procedure SetupNewParams;
1191 var metadata: Firebird.IMessageMetadata;
1192 begin
1193 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getTriggerMetadata;
1194 try
1195 NewParamsSQLDA := TFBUDRInParamsSQLDA.Create(FBContext,
1196 metadata,
1197 newMsg);
1198 NewParams := TFBUDRInputParams.Create(NewParamsSQLDA);
1199 if [loLogTriggers,loDetails] <= FBUDRControllerOptions.LogOptions then
1200 begin
1201 FController.WriteToLog(STriggerNew,NewParams);
1202 end;
1203 finally
1204 metadata.release;
1205 end;
1206 end;
1207
1208 procedure SetupWritableNewParams;
1209 var metadata: Firebird.IMessageMetadata;
1210 begin
1211 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getTriggerMetadata;
1212 try
1213 WritableParamsSQLDA := TFBUDRTriggerNewValuesSQLDA.Create(FBContext,
1214 metadata,
1215 newMsg);
1216 NewWritableParams := TFBUDROutputParams.Create(WritableParamsSQLDA);
1217 if [loLogTriggers,loDetails] <= FBUDRControllerOptions.LogOptions then
1218 begin
1219 FController.WriteToLog(STriggerNew,NewWritableParams);
1220 end;
1221 finally
1222 metadata.release;
1223 end;
1224 end;
1225
1226 begin
1227 try
1228 if loLogTriggers in FBUDRControllerOptions.LogOptions then
1229 FController.WriteToLog(STriggerExecute + FName);
1230 OldParamsSQLDA := nil;
1231 NewParamsSQLDA := nil;
1232 WritableParamsSQLDA := nil;
1233 OldParams := nil;
1234 NewParams := nil;
1235 NewWritableParams := nil;
1236
1237 if FRoutineMetadata.QueryInterface(IFBUDRTriggerMetaData,aTriggerMetadata) <> S_OK then
1238 FBUDRError(ibxeNoTriggerMetadata,[nil])
1239 else
1240 begin
1241 FBContext := TFBUDRExternalContext.Create(Controller,context);
1242 FFirebirdAPI := FBContext.GetFirebirdAPI;
1243 try
1244 with Firebird.IExternalTrigger do
1245 case action of
1246 ACTION_INSERT:
1247 TriggerAction := taInsert;
1248 ACTION_UPDATE:
1249 TriggerAction := taUpdate;
1250 ACTION_DELETE:
1251 TriggerAction := taDelete;
1252 ACTION_CONNECT:
1253 TriggerAction := taConnect;
1254 ACTION_DISCONNECT:
1255 TriggerAction := taDisconnect;
1256 ACTION_TRANS_START:
1257 TriggerAction := taTransactionStart;
1258 ACTION_TRANS_COMMIT:
1259 TriggerAction := taTransactionCommit;
1260 ACTION_TRANS_ROLLBACK:
1261 TriggerAction := taTransactionRollback;
1262 ACTION_DDL:
1263 TriggerAction := taDDL;
1264 else
1265 FBUDRError(ibxeUnknownTransactionAction,[action]);
1266 end;
1267
1268 case FRoutineMetadata.getTriggerType of
1269 ttBefore:
1270 begin
1271 if FRoutineMetadata.HasTriggerMetadata then
1272 begin
1273 if TriggerAction in [taUpdate, taDelete] then
1274 SetUpOldParams;
1275 if TriggerAction in [taInsert,taUpdate] then
1276 SetupWritableNewParams;
1277 end;
1278 BeforeTrigger(FBContext,aTriggerMetadata,TriggerAction,OldParams,NewWritableParams);
1279 WritableParamsSQLDA.Finalise;
1280 if [loLogTriggers,loDetails] <= FBUDRControllerOptions.LogOptions then
1281 FController.WriteToLog(STriggerNewAfter,NewWritableParams);
1282 end;
1283 ttAfter:
1284 begin
1285 if FRoutineMetadata.HasTriggerMetadata then
1286 begin
1287 if TriggerAction in [taUpdate, taDelete] then
1288 SetUpOldParams;
1289 if TriggerAction in [taInsert,taUpdate] then
1290 SetupNewParams;
1291 AfterTrigger(FBContext,aTriggerMetadata,TriggerAction,OldParams,NewParams);
1292 end;
1293 end;
1294 ttDatabase:
1295 begin
1296 DatabaseTrigger(FBContext,aTriggerMetadata);
1297 end;
1298 end;
1299 finally
1300 NewParams := nil;
1301 OldParams := nil;
1302 NewWritableParams := nil;
1303 if OldParamsSQLDA <> nil then
1304 OldParamsSQLDA.Free;
1305 if NewParamsSQLDA <> nil then
1306 NewParamsSQLDA.Free;
1307 if WritableParamsSQLDA <> nil then
1308 WritableParamsSQLDA.Free;
1309 FFirebirdAPI := nil;
1310 end;
1311 end;
1312 except on E: Exception do
1313 FController.FBSetStatusFromException(E,status);
1314 end;
1315 end;
1316
1317 { TFBUDRProcedureFactory }
1318
1319 procedure TFBUDRProcedureFactory.SetController(AValue: TFBUDRController);
1320 begin
1321 if FController = AValue then Exit;
1322 FController := AValue;
1323 end;
1324
1325 constructor TFBUDRProcedureFactory.Create(aName: AnsiString;
1326 aProcedure: TFBUDRProcedureClass);
1327 begin
1328 inherited Create;
1329 FName := aName;
1330 FProcedure := aProcedure;
1331 end;
1332
1333 procedure TFBUDRProcedureFactory.dispose();
1334 begin
1335 Free;
1336 end;
1337
1338 procedure TFBUDRProcedureFactory.setup(status: Firebird.IStatus;
1339 context: Firebird.IExternalContext; metadata: Firebird.IRoutineMetadata;
1340 inBuilder: Firebird.IMetadataBuilder; outBuilder: Firebird.IMetadataBuilder);
1341 var FBRoutineMetadata: IFBUDRRoutineMetadata;
1342 FBInBuilder: IFBUDRMetadataBuilder;
1343 FBOutBuilder: IFBUDRMetadataBuilder;
1344 FBContext: IFBUDRExternalContext;
1345 begin
1346 try
1347 if loLogProcedures in FBUDRControllerOptions.LogOptions then
1348 FController.WriteToLog(SProcSetup + FName);
1349
1350 FBInBuilder := nil;
1351 FBOutBuilder := nil;
1352 FBContext := TFBUDRExternalContext.Create(Controller,context);
1353
1354 FBRoutineMetadata := TFBUDRRoutineMetadata.Create(FBContext,metadata);
1355 if inBuilder <> nil then
1356 FBInBuilder := TFBUDRMetadataBuilder.Create(FBContext,inBuilder);
1357 if outBuilder <> nil then
1358 FBOutBuilder := TFBUDRMetadataBuilder.Create(FBContext,outBuilder);
1359 if [loLogProcedures, loDetails] <= FBUDRControllerOptions.LogOptions then
1360 FController.WriteToLog(SRoutineMetadata + LineEnding + (FBRoutineMetadata as TFBUDRRoutineMetadata).AsText);
1361 TFBUDRProcedure.setup(FBContext,FBRoutineMetadata,FBInBuilder,FBOutBuilder);
1362 except on E: Exception do
1363 FController.FBSetStatusFromException(E,status);
1364 end;
1365 end;
1366
1367 function TFBUDRProcedureFactory.newItem(status: Firebird.IStatus;
1368 context: Firebird.IExternalContext; metadata: Firebird.IRoutineMetadata
1369 ): IExternalProcedure;
1370 var FBRoutineMetadata: IFBUDRRoutineMetadata;
1371 FBContext: IFBUDRExternalContext;
1372 begin
1373 try
1374 FBContext := TFBUDRExternalContext.Create(Controller,context);
1375 FBRoutineMetadata := TFBUDRRoutineMetadata.Create(FBContext,metadata);
1376 Result := FProcedure.Create(FController,FName,FBRoutineMetadata);
1377 except on E: Exception do
1378 FController.FBSetStatusFromException(E,status);
1379 end;
1380 end;
1381
1382 { TFBUDRExternalResultsSet }
1383
1384 procedure TFBUDRExternalResultsSet.Close;
1385 begin
1386 //do nothing by default
1387 end;
1388
1389 constructor TFBUDRExternalResultsSet.Create(UDRProcedure: TFBUDRProcedure;
1390 context: IFBUDRExternalContext; metadata: Firebird.IMessageMetadata;
1391 outMsg: pointer);
1392 begin
1393 inherited Create;
1394 FUDRProcedure := UDRProcedure;
1395 Inc(FUDRProcedure.FRefCount);
1396 if metadata <> nil then
1397 begin
1398 FOutputDataSQLDA := TFBUDROutParamsSQLDA.Create(context,metadata,outMsg);
1399 FOutputData := TFBUDROutputParams.Create(FOutputDataSQLDA);
1400 if [loLogProcedures,loDetails] <= FBUDRControllerOptions.LogOptions then
1401 FUDRProcedure.FController.WriteToLog(SOutputParams,FOutputData);
1402 end;
1403 end;
1404
1405 destructor TFBUDRExternalResultsSet.Destroy;
1406 begin
1407 FOutputData := nil;
1408 if FOutputDataSQLDA <> nil then
1409 FOutputDataSQLDA.Free;
1410 inherited Destroy;
1411 end;
1412
1413 procedure TFBUDRExternalResultsSet.dispose();
1414 begin
1415 Close;
1416 FUDRProcedure.dispose;
1417 Free;
1418 end;
1419
1420 { TFBUDRProcedure }
1421
1422 constructor TFBUDRProcedure.Create(aController: TFBUDRController;
1423 aName: AnsiString; routineMetadata: IFBUDRRoutineMetadata);
1424 begin
1425 inherited Create;
1426 FController := aController;
1427 FName := aName;
1428 FRefCount := 1;
1429 FRoutineMetaData := routineMetadata;
1430 end;
1431
1432 function TFBUDRProcedure.getCharSet(context: IFBUDRExternalContext): AnsiString;
1433 begin
1434 Result := '';
1435 end;
1436
1437 class procedure TFBUDRProcedure.setup(context: IFBUDRExternalContext;
1438 metadata: IFBUDRRoutineMetadata; inBuilder: IFBUDRMetadataBuilder;
1439 outBuilder: IFBUDRMetadataBuilder);
1440 begin
1441 //Override in subclass
1442 end;
1443
1444 procedure TFBUDRProcedure.dispose();
1445 begin
1446 if loLogProcedures in FBUDRControllerOptions.LogOptions then
1447 FController.WriteToLog(Format(SProcDispose,[FName,FRefCount]));
1448
1449 Dec(FRefCount);
1450 if FRefCount = 0 then Free;
1451 end;
1452
1453 procedure TFBUDRProcedure.getCharSet(status: Firebird.IStatus;
1454 context: Firebird.IExternalContext; name: PAnsiChar; nameSize: Cardinal);
1455 var charset: AnsiString;
1456 FBContext: IFBUDRExternalContext;
1457 begin
1458 try
1459 FBContext := TFBUDRExternalContext.Create(Controller,context);
1460 FFirebirdAPI := FBContext.GetFirebirdAPI;
1461 try
1462 charset := getCharSet(FBContext);
1463 if charset <> '' then
1464 begin
1465 StrPLCopy(name,charset,nameSize);
1466 if loLogProcedures in FBUDRControllerOptions.LogOptions then
1467 FController.WriteToLog(Format(SProcCharset,[FName,charset]));
1468 end;
1469 finally
1470 FFirebirdAPI := nil;
1471 end;
1472 except on E: Exception do
1473 FController.FBSetStatusFromException(E,status);
1474 end;
1475 end;
1476
1477 { TFBUDRInParamsSQLDA }
1478
1479 procedure TFBUDRInParamsSQLDA.AllocMessageBuffer(len: integer);
1480 begin
1481 FMessageBuffer := FBuffer;
1482 FMsgLength := len;
1483 end;
1484
1485 procedure TFBUDRInParamsSQLDA.FreeMessageBuffer;
1486 begin
1487 FBuffer := nil;
1488 FMsgLength := 0;
1489 end;
1490
1491 function TFBUDRInParamsSQLDA.GetAttachment: IAttachment;
1492 begin
1493 Result := FAttachment;
1494 end;
1495
1496 function TFBUDRInParamsSQLDA.GetTransaction: ITransaction;
1497 begin
1498 Result := FTransaction;
1499 end;
1500
1501 function TFBUDRInParamsSQLDA.IsInputDataArea: boolean;
1502 begin
1503 Result := true;
1504 end;
1505
1506 constructor TFBUDRInParamsSQLDA.Create(context: IFBUDRExternalContext;
1507 aMetadata: Firebird.IMessageMetaData; aBuffer: PByte);
1508 begin
1509 inherited Create(context.GetFirebirdAPI);
1510 FAttachment := context.GetAttachment;
1511 FTransaction := context.GetTransaction;
1512 FBuffer := aBuffer;
1513 Bind(aMetaData);
1514 end;
1515
1516 { TFBUDROutParamsSQLDA }
1517
1518 procedure TFBUDROutParamsSQLDA.AllocMessageBuffer(len: integer);
1519 begin
1520 FillChar(FBuffer^,len,0);
1521 FMessageBuffer := FBuffer;
1522 FMsgLength := len;
1523 end;
1524
1525 procedure TFBUDROutParamsSQLDA.FreeMessageBuffer;
1526 begin
1527 FMessageBuffer := nil;
1528 FMsgLength := 0;
1529 end;
1530
1531 function TFBUDROutParamsSQLDA.GetAttachment: IAttachment;
1532 begin
1533 Result := FAttachment;
1534 end;
1535
1536 function TFBUDROutParamsSQLDA.GetTransaction: ITransaction;
1537 begin
1538 Result := FTransaction;
1539 end;
1540
1541 function TFBUDROutParamsSQLDA.IsInputDataArea: boolean;
1542 begin
1543 Result := true;
1544 end;
1545
1546 constructor TFBUDROutParamsSQLDA.Create(context: IFBUDRExternalContext;
1547 aMetadata: Firebird.IMessageMetaData; aBuffer: PByte);
1548 begin
1549 inherited Create(context.GetFirebirdAPI);
1550 FAttachment := context.GetAttachment;
1551 FTransaction := context.GetTransaction;
1552 FBuffer := aBuffer;
1553 Bind(aMetadata);
1554 end;
1555
1556 function TFBUDROutParamsSQLDA.CanChangeMetaData: boolean;
1557 begin
1558 Result := false;
1559 end;
1560
1561 procedure TFBUDROutParamsSQLDA.Finalise;
1562 begin
1563 PackBuffer;
1564 end;
1565
1566 { TFBUDRFunction }
1567
1568 constructor TFBUDRFunction.Create(aController: TFBUDRController;
1569 aName: AnsiString; routineMetadata: IFBUDRRoutineMetadata);
1570 begin
1571 inherited Create;
1572 FController := aController;
1573 FName := aName;
1574 FRoutineMetaData := routineMetadata;
1575 if loLogFunctions in FBUDRControllerOptions.LogOptions then
1576 begin
1577 FController.WriteToLog(Format(sFuncCreated,[aName]));
1578 if loDetails in FBUDRControllerOptions.LogOptions then
1579 FController.WriteToLog((FRoutineMetaData as TFBUDRRoutineMetadata).AsText);
1580 end;
1581 end;
1582
1583 function TFBUDRFunction.getCharSet(context: IFBUDRExternalContext): AnsiString;
1584 begin
1585 Result := '';
1586 end;
1587
1588 function TFBUDRFunction.Execute(context: IFBUDRExternalContext;
1589 ProcMetadata: IFBUDRProcMetadata; InputParams: IFBUDRInputParams;
1590 ResultSQLType: cardinal): variant;
1591 begin
1592 raise Exception.CreateFmt(SFuncNotOverridden,[FName]);
1593 end;
1594
1595 procedure TFBUDRFunction.Execute(context: IFBUDRExternalContext;
1596 ProcMetadata: IFBUDRProcMetadata; InputParams: IFBUDRInputParams;
1597 ReturnValue: ISQLParam);
1598 begin
1599 ReturnValue.AsVariant := Execute(context,ProcMetadata,InputParams,ReturnValue.GetSQLType);
1600 end;
1601
1602 class procedure TFBUDRFunction.setup(context: IFBUDRExternalContext;
1603 metadata: IFBUDRRoutineMetadata; inBuilder: IFBUDRMetadataBuilder;
1604 outBuilder: IFBUDRMetadataBuilder);
1605 begin
1606 //Do nothing be default
1607 end;
1608
1609 procedure TFBUDRFunction.dispose();
1610 begin
1611 if loLogFunctions in FBUDRControllerOptions.LogOptions then
1612 FController.WriteToLog(Format(SFuncDispose,[FName]));
1613
1614 Free;
1615 end;
1616
1617 procedure TFBUDRFunction.getCharSet(status: Firebird.IStatus;
1618 context: Firebird.IExternalContext; name: PAnsiChar; nameSize: Cardinal);
1619 var charset: AnsiString;
1620 FBContext: IFBUDRExternalContext;
1621 begin
1622 try
1623 FBContext := TFBUDRExternalContext.Create(Controller,context);
1624 FFirebirdAPI := FBContext.GetFirebirdAPI;
1625 try
1626 charset := getCharSet(FBContext);
1627 if charset <> '' then
1628 begin
1629 StrPLCopy(name,charset,nameSize);
1630 if loLogFunctions in FBUDRControllerOptions.LogOptions then
1631 FController.WriteToLog(Format(SFuncCharset,[FName,charset]));
1632 end;
1633 finally
1634 FFirebirdAPI := nil;
1635 end;
1636 except on E: Exception do
1637 FController.FBSetStatusFromException(E,status);
1638 end;
1639 end;
1640
1641 procedure TFBUDRFunction.execute(status: Firebird.IStatus;
1642 context: Firebird.IExternalContext; inMsg: Pointer; outMsg: Pointer);
1643 var aProcMetadata: IFBUDRProcMetadata;
1644 OutParamsSQLDA: TFBUDROutParamsSQLDA;
1645 InParamsSQLDA: TFBUDRInParamsSQLDA;
1646 InputParams: IFBUDRInputParams;
1647 OutputData: IFBUDROutputData;
1648 metadata: Firebird.IMessageMetadata;
1649 FBContext: IFBUDRExternalContext;
1650 begin
1651 try
1652 if loLogFunctions in FBUDRControllerOptions.LogOptions then
1653 FController.WriteToLog(SFuncExecute + FName);
1654
1655 OutParamsSQLDA := nil;
1656 InParamsSQLDA := nil;
1657 InputParams := nil;
1658 OutputData := nil;
1659 if FRoutineMetadata.QueryInterface(IFBUDRProcMetadata,aProcMetadata) <> S_OK then
1660 FBUDRError(ibxeNoProcMetadata,[nil])
1661 else
1662 begin
1663 FBContext := TFBUDRExternalContext.Create(Controller,context);
1664 FFirebirdAPI := FBContext.GetFirebirdAPI;
1665 if [loLogFunctions,loDetails] <= FBUDRControllerOptions.LogOptions then
1666 FController.WriteToLog((FBContext as TFBUDRExternalContext).AsText);
1667
1668 try
1669 if FRoutineMetadata.HasInputMetadata then
1670 begin
1671 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getInputMetadata;
1672 try
1673 InParamsSQLDA := TFBUDRInParamsSQLDA.Create(FBContext,
1674 metadata,
1675 inMsg);
1676 finally
1677 metadata.release;
1678 end;
1679
1680 InputParams := TFBUDRInputParams.Create(InParamsSQLDA);
1681 if [loLogFunctions,loDetails] <= FBUDRControllerOptions.LogOptions then
1682 FController.WriteToLog(SInputParams,InputParams);
1683 end;
1684
1685 if FRoutineMetadata.HasOutputMetadata then
1686 begin
1687 metadata := (FRoutineMetadata as TFBUDRRoutineMetadata).getOutputMetadata;
1688 try
1689 OutParamsSQLDA := TFBUDROutParamsSQLDA.Create(FBContext,
1690 metadata,
1691 outMsg);
1692 finally
1693 metadata.release;
1694 end;
1695 OutputData := TFBUDROutputParams.Create(OutParamsSQLDA);
1696 end
1697 else
1698 raise Exception.CreateFmt(SNoReturnValue,[FName]);
1699
1700 Execute(FBContext,aProcMetadata,InputParams,OutputData[0]);
1701
1702 if [loLogFunctions,loDetails] <= FBUDRControllerOptions.LogOptions then
1703 FController.WriteToLog(SOutputData,OutputData);
1704
1705 OutParamsSQLDA.Finalise; {copy result to OutMsg buffer}
1706 finally
1707 OutputData := nil;
1708 InputParams := nil;
1709 if OutParamsSQLDA <> nil then
1710 OutParamsSQLDA.Free;
1711 if InParamsSQLDA <> nil then
1712 InParamsSQLDA.Free;
1713 FFirebirdAPI := nil;
1714 end;
1715 end;
1716 except on E: Exception do
1717 FController.FBSetStatusFromException(E,status);
1718 end;
1719 end;
1720
1721 { TFBUDRFunctionFactory }
1722
1723 procedure TFBUDRFunctionFactory.SetController(AValue: TFBUDRController);
1724 begin
1725 if FController = AValue then Exit;
1726 FController := AValue;
1727 end;
1728
1729 constructor TFBUDRFunctionFactory.Create(aName: AnsiString;
1730 aFunction: TFBUDRFunctionClass);
1731 begin
1732 inherited Create;
1733 FName := aName;
1734 FFunction := aFunction;
1735 end;
1736
1737 procedure TFBUDRFunctionFactory.dispose();
1738 begin
1739 Free;
1740 end;
1741
1742 procedure TFBUDRFunctionFactory.setup(status: Firebird.IStatus;
1743 context: Firebird.IExternalContext; metadata: Firebird.IRoutineMetadata;
1744 inBuilder: Firebird.IMetadataBuilder; outBuilder: Firebird.IMetadataBuilder);
1745 var FBRoutineMetadata: IFBUDRRoutineMetadata;
1746 FBInBuilder: IFBUDRMetadataBuilder;
1747 FBOutBuilder: IFBUDRMetadataBuilder;
1748 FBContext: IFBUDRExternalContext;
1749 begin
1750 FBInBuilder := nil;
1751 FBOutBuilder := nil;
1752 try
1753 if loLogFunctions in FBUDRControllerOptions.LogOptions then
1754 FController.WriteToLog(SFuncSetup + FName);
1755
1756 FBContext := TFBUDRExternalContext.Create(Controller,context);
1757
1758 FBRoutineMetadata := TFBUDRRoutineMetadata.Create(FBContext,metadata);
1759
1760 if inBuilder <> nil then
1761 FBInBuilder := TFBUDRMetadataBuilder.Create(FBContext,inBuilder);
1762 if outBuilder <> nil then
1763 FBOutBuilder := TFBUDRMetadataBuilder.Create(FBContext,outBuilder);
1764 if [loLogFunctions, loDetails] <= FBUDRControllerOptions.LogOptions then
1765 FController.WriteToLog(SRoutineMetadata + LineEnding + (FBRoutineMetadata as TFBUDRRoutineMetadata).AsText);
1766
1767 TFBUDRFunction.setup(FBContext,FBRoutineMetadata,FBInBuilder,FBOutBuilder);
1768 except on E: Exception do
1769 FController.FBSetStatusFromException(E,status);
1770 end;
1771 end;
1772
1773 function TFBUDRFunctionFactory.newItem(status: Firebird.IStatus;
1774 context: Firebird.IExternalContext; metadata: Firebird.IRoutineMetadata
1775 ): Firebird.IExternalFunction;
1776 var FBRoutineMetadata: IFBUDRRoutineMetadata;
1777 FBContext: IFBUDRExternalContext;
1778 begin
1779 try
1780 FBContext := TFBUDRExternalContext.Create(Controller,context);
1781 FBRoutineMetadata := TFBUDRRoutineMetadata.Create(FBContext,metadata);
1782 Result := FFunction.Create(FController,FName,FBRoutineMetadata);
1783 except on E: Exception do
1784 FController.FBSetStatusFromException(E,status);
1785 end;
1786 end;
1787
1788 { TFBUDRController }
1789
1790 function TFBUDRController.GetDateTimeFmt: AnsiString;
1791 begin
1792 {$IF declared(DefaultFormatSettings)}
1793 with DefaultFormatSettings do
1794 {$ELSE}
1795 {$IF declared(FormatSettings)}
1796 with FormatSettings do
1797 {$IFEND}
1798 {$IFEND}
1799 Result := ShortDateFormat + ' ' + LongTimeFormat + '.zzzz'
1800 end;
1801
1802 function TFBUDRController.ProcessTemplateMacros(aTemplate: AnsiString
1803 ): AnsiString;
1804
1805 function CleanDirName(aDirName: PAnsiChar): AnsiString;
1806 begin
1807 Result := Trim(strpas(aDirName));
1808 {$IFDEF WINDOWS}
1809 Result := StringReplace(Result,'/',DirectorySeparator,[rfReplaceAll]);
1810 {$ELSE}
1811 Result := StringReplace(Result,'\',DirectorySeparator,[rfReplaceAll]);
1812 {$ENDIF}
1813 if (Length(Result) > 0) and (Result[Length(aDirName)] <> DirectorySeparator) then
1814 Result := Result + DirectorySeparator;
1815 end;
1816
1817 var udr_config: Firebird.IConfig;
1818 config_entry: Firebird.IConfigEntry;
1819 aStatus: Firebird.IStatus;
1820 begin
1821 if assigned(FMaster) then
1822 with FMaster.getConfigManager do
1823 begin
1824 Result := StringReplace(aTemplate,'$LOGDIR',CleanDirName(getDirectory(DIR_LOG)),[rfReplaceAll, rfIgnoreCase]);
1825 udr_config := getPluginConfig('UDR');
1826 if udr_config <> nil then
1827 try
1828 aStatus := FMaster.getStatus;
1829 try
1830 config_entry := udr_config.find(aStatus,'path');
1831 with aStatus do
1832 if (getState and STATE_ERRORS) <> 0 then
1833 raise EFBUDRException.Create(aStatus);
1834 finally
1835 aStatus.dispose;
1836 end;
1837
1838 if config_entry <> nil then
1839 try
1840 with config_entry do
1841 Result := StringReplace(Result,'$UDRDIR',CleanDirName(config_entry.getValue),[rfReplaceAll, rfIgnoreCase]);
1842 finally
1843 config_entry.release;
1844 end;
1845 finally
1846 udr_config.release();
1847 end;
1848 end;
1849 Result := StringReplace(Result,'$TEMP',GetTempDir,[rfReplaceAll, rfIgnoreCase]);
1850 Result := StringReplace(Result,'$MODULE',FBUDRControllerOptions.ModuleName,[rfReplaceAll, rfIgnoreCase]);
1851 Result := StringReplace(Result,'$TIMESTAMP',FormatDateTime('yyyymmddhhnnss',Now),[rfReplaceAll, rfIgnoreCase]);
1852 end;
1853
1854 procedure TFBUDRController.RegisterUDRFactories(status: Firebird.IStatus;
1855 udrPlugin: Firebird.IUdrPlugin);
1856 var i: integer;
1857 begin
1858 if FUDRFactoryList <> nil then
1859 for i := 0 to FUDRFactoryList.Count - 1 do
1860 try
1861 RegisterUDRFactory(status,udrPlugin,FUDRFactoryList[i], FUDRFactoryList.Objects[i]);
1862 with status do
1863 if (getState and STATE_ERRORS) <> 0 then break;
1864 except on E: Exception do
1865 FBSetStatusFromException(E,status);
1866 end;
1867 end;
1868
1869 procedure TFBUDRController.RegisterUDRFactory(status: Firebird.IStatus;
1870 udrPlugin: Firebird.IUdrPlugin; aName: AnsiString; factory: TObject);
1871 begin
1872 if factory is TFBUDRFunctionFactory then
1873 begin
1874 if loLogFunctions in FBUDRControllerOptions.LogOptions then
1875 WriteToLog(SFuncRegister + aName);
1876 udrPlugin.registerFunction(status,PAnsiChar(aName),Firebird.IUdrFunctionFactory(factory));
1877 TFBUDRFunctionFactory(factory).Controller := self;
1878 end
1879 else
1880 if factory is TFBUDRProcedureFactory then
1881 begin
1882 if loLogProcedures in FBUDRControllerOptions.LogOptions then
1883 WriteToLog(SProcRegister + aName);
1884 udrPlugin.registerProcedure(status,PAnsiChar(aName),Firebird.IUdrProcedureFactory(factory));
1885 TFBUDRProcedureFactory(factory).Controller := self;
1886 end
1887 else
1888 if factory is TFBUDRTriggerFactory then
1889 begin
1890 if loLogTriggers in FBUDRControllerOptions.LogOptions then
1891 WriteToLog(STriggerRegister + aName);
1892 udrPlugin.registerTrigger(status,PAnsiChar(aName),Firebird.IUdrTriggerFactory(factory));
1893 TFBUDRTriggerFactory(factory).Controller := self;
1894 end
1895 else
1896 FBUDRError(ibxeInvalidFactoryObject,[factory.ClassName,aName]);
1897 end;
1898
1899 procedure TFBUDRController.FreeFactoryList;
1900 //var i: integer;
1901 begin
1902 {if FUDRFactoryList <> nil then
1903 for i := 0 to FUDRFactoryList.Count - 1 do
1904 if FUDRFactoryList.Objects[i] <> nil then
1905 FUDRFactoryList.Objects[i].Free;} {disposed of by Firebird Engine}
1906 FreeAndNil(FUDRFactoryList);
1907 end;
1908
1909 const
1910 LogOptionsTable: array [TFBUDRControllerLogOption] of AnsiString = (
1911 'loLogFunctions',
1912 'loLogProcedures',
1913 'loLogTriggers',
1914 'loLogFetches',
1915 'loModifyQueries',
1916 'loReadOnlyQueries',
1917 'loDetails'
1918 );
1919
1920 procedure TFBUDRController.LoadConfig;
1921
1922 function GetLogOptions(LogOptionsStr: AnsiString; var aLogOptions: TFBUDRControllerLogOptions): boolean;
1923 var s: AnsiString;
1924 p1, p2, len: integer;
1925 i: TFBUDRControllerLogOption;
1926 found: boolean;
1927 begin
1928 Result := LogOptionsStr <> '';
1929 if Result then
1930 begin
1931 aLogOptions := [];
1932 p2 := 1;
1933 {skip past opening square bracket}
1934 while (p2 <= length(LogOptionsStr)) and (LogOptionsStr[p2] <> '[') do
1935 Inc(p2);
1936
1937 {parse into words separated by commas}
1938 Inc(p2);
1939 p1 := p2;
1940 while p2 <= length(LogOptionsStr) do
1941 begin
1942 if LogOptionsStr[p2] in [',',']'] then
1943 begin
1944 s := Trim(system.copy(LogOptionsStr,p1,p2-p1));
1945
1946 {Now convert string to LogOption}
1947 found := false;
1948 for i := low(TFBUDRControllerLogOption) to high(TFBUDRControllerLogOption) do
1949 if CompareText(s,LogOptionsTable[i]) = 0 then
1950 begin
1951 aLogOptions := aLogOptions + [i];
1952 found := true;
1953 break;
1954 end;
1955 if not found then
1956 WriteToLog(Format(SBadLogOptionsStr,[LogOptionsStr,p2]));
1957 if LogOptionsStr[p2] = ']' then
1958 break;
1959 p1 := p2 + 1;
1960 end;
1961 Inc(p2);
1962 end;
1963 if p2 > length(LogOptionsStr) then
1964 WriteToLog(Format(SBadLogOptionsStr,[LogOptionsStr,p2]));
1965 Result := true;
1966 end;
1967 end;
1968
1969 var aLogOptions: TFBUDRControllerLogOptions;
1970 aConfigFileName: Ansistring;
1971 begin
1972 aConfigFileName := ProcessTemplateMacros(FBUDRControllerOptions.ConfigFileNameTemplate);
1973 if (FConfigFile = nil) and (aConfigFileName <> '') and FileExists(aConfigFileName) then
1974 begin
1975 FConfigFile := TIniFile.Create(aConfigFileName);
1976 {$if declared(TStringArray)}
1977 FConfigFile.BoolFalseStrings := FalseStrings;
1978 FConfigFile.BoolTrueStrings := TrueStrings;
1979 {$ifend}
1980 WriteToLog(Format(SReadingConfigFile,[aConfigFileName]));
1981 with FBUDRControllerOptions do
1982 if AllowConfigFileOverrides then
1983 begin
1984 LogFileNameTemplate := FConfigFile.ReadString('Controller','LogFileNameTemplate',LogFileNameTemplate);
1985 WriteToLog('LogFileNameTemplate = ' + LogFileNameTemplate);
1986 ForceWriteLogEntries := FConfigFile.ReadBool('Controller','ForceWriteLogEntries',ForceWriteLogEntries);
1987 WriteToLog('ForceWriteLogEntries = ' + BooleanToStr(ForceWriteLogEntries ,'true','false'));
1988 ThreadSafeLogging := FConfigFile.ReadBool('Controller','ThreadSafeLogging',ThreadSafeLogging);
1989 WriteToLog('ThreadSafeLogging = ' + BooleanToStr(ThreadSafeLogging,'true','false'));
1990 if GetLogOptions( FConfigFile.ReadString('Controller','LogOptions',''),aLogOptions) then
1991 LogOptions := aLogOptions;
1992 WriteToLog('LogOptions = ' + LogOptionsToStr(LogOptions));
1993 end;
1994 end;
1995 end;
1996
1997 function TFBUDRController.NeedLogStream: boolean;
1998 var FilePathName: AnsiString;
1999 begin
2000 Result := false;
2001 if FLogStream = nil then
2002 begin
2003 FilePathName := ProcessTemplateMacros(FBUDRControllerOptions.LogFileNameTemplate);
2004 if FilePathName = '' then
2005 Exit;
2006 if FJnlOpenAppend then
2007 begin
2008 FLogStream := TFileStream.Create(FilePathName,fmOpenWrite or fmShareDenyNone);
2009 FLogStream.Seek(0, soFromEnd);
2010 end
2011 else
2012 begin
2013 FLogStream := TFileStream.Create(FilePathName,fmCreate or fmShareDenyNone);
2014 FJnlOpenAppend := true;
2015 end;
2016 end;
2017 Result := true;
2018 end;
2019
2020 function TFBUDRController.LogOptionsToStr(aLogOptions: TFBUDRControllerLogOptions): AnsiString;
2021 var i: TFBUDRControllerLogOption;
2022 separator: AnsiString;
2023 begin
2024 Result := '[';
2025 separator := '';
2026 for i := low(TFBUDRControllerLogOption) to high(TFBUDRControllerLogOption) do
2027 if i in aLogOptions then
2028 begin
2029 Result := Result + separator + LogOptionsTable[i];
2030 separator := ',';
2031 end;
2032 Result := Result + ']';
2033 end;
2034
2035 constructor TFBUDRController.Create(status: Firebird.IStatus;
2036 udrPlugin: Firebird.IUdrPlugin; aTheirUnloadFlag: booleanPtr;
2037 var aMyUnloadFlag: booleanPtr);
2038 begin
2039 try
2040 inherited Create;
2041 FFBController := self;
2042 FTheirUnloadFlag := aTheirUnloadFlag;
2043 FMyUnloadFlag := false;
2044 aMyUnloadFlag := @FMyUnloadFlag;
2045 FMaster := udrPlugin.getMaster;
2046 FCriticalSection := TCriticalSection.Create;
2047 RegisterUDRFactories(status,udrPlugin);
2048 LoadConfig;
2049 except on E: Exception do
2050 FBSetStatusFromException(E,status);
2051 end;
2052 end;
2053
2054 destructor TFBUDRController.Destroy;
2055 begin
2056 if FConfigFile <> nil then
2057 FConfigFile.Free;
2058 if FLogStream <> nil then
2059 FLogStream.Free;
2060 FreeFactoryList;
2061 if FCriticalSection <> nil then
2062 FCriticalSection.Free;
2063 if (FTheirUnloadFlag <> nil) and not FMyUnloadFlag then
2064 FTheirUnloadFlag^ := true; {notify unload of module}
2065 inherited Destroy;
2066 end;
2067
2068 procedure TFBUDRController.FBSetStatusFromException(E: Exception; aStatus: Firebird.IStatus);
2069 var StatusVector: TStatusVector;
2070 ErrorVector: NativeIntPtr;
2071 begin
2072 if E is EFBUDRException then
2073 aStatus.setErrors((E as EFBUDRException).Status.getErrors())
2074 else
2075 if E is EIBInterBaseError then
2076 begin
2077 ErrorVector := ((E as EIBInterBaseError).Status as TFB30Status).GetStatus.getErrors();
2078 astatus.setErrors(ErrorVector);
2079 end
2080 else
2081 begin
2082 FMessageBuffer := E.Message;
2083 StatusVector[0] := isc_arg_gds;
2084 StatusVector[1] := NativeInt(isc_random);
2085 StatusVector[2] := isc_arg_string;
2086 StatusVector[3] := NativeInt(PAnsiChar(FMessageBuffer));
2087 StatusVector[4] := isc_arg_end;
2088 astatus.setErrors(@StatusVector);
2089 end;
2090 try
2091 WriteToLog(SExceptionRaised + LineEnding + E.Message);
2092 except
2093 //ignore
2094 end;
2095 end;
2096
2097 procedure TFBUDRController.WriteToLog(Msg: AnsiString);
2098 var LogEntry: AnsiString;
2099 begin
2100 if not NeedLogStream then
2101 Exit; {no log file available}
2102 LogEntry := Format(sLogFormat,[FBFormatDateTime(GetDateTimeFmt,Now),Msg]) + LineEnding;
2103 if FBUDRControllerOptions.ThreadSafeLogging then
2104 begin
2105 FCriticalSection.Enter;
2106 try
2107 FLogStream.Write(LogEntry[1],Length(LogEntry));
2108 if FBUDRControllerOptions.ForceWriteLogEntries then
2109 FreeAndNil(FLogStream);
2110 finally
2111 FCriticalSection.Leave;
2112 end;
2113 end
2114 else
2115 FLogStream.Write(LogEntry[1],Length(LogEntry));
2116 end;
2117
2118 function TFBUDRController.CharSetIDToText(att: IAttachment; id: integer): AnsiString;
2119 begin
2120 if att = nil then
2121 Result := IntToStr(id)
2122 else
2123 Result := att.GetCharsetName(id);
2124 end;
2125
2126 procedure TFBUDRController.WriteToLog(aTitle: AnsiString; Params: IFBUDRInputParams
2127 );
2128 var i: integer;
2129 Msg: AnsiString;
2130 begin
2131 Msg := aTitle + LineEnding;
2132 for i := 0 to Params.getCount - 1 do
2133 with Params[i] do
2134 begin
2135 Msg := Msg +
2136 'Parameter ' + IntToStr(i) + ':' + NewLineTAB +
2137 'Field Name = ' + getName + NewLineTab +
2138 'Alias Name = ' + getAliasName + NewLineTab +
2139 'SQLType = ' + GetSQLTypeName + NewLineTAB +
2140 'sub type = ' + IntToStr(getSubType) + NewLineTAB +
2141 'Scale = ' + IntToStr(getScale) + NewLineTAB +
2142 'Charset = ' + CharSetIDToText((Params as TFBUDRInputParams).GetAttachment,getCharSetID) + NewLineTAB +
2143 BooleanToStr(getIsNullable,'Nullable','Not Nullable') + NewLineTAB +
2144 'Size = ' + IntToStr(GetSize) + NewLineTAB +
2145 'Value = ' + BooleanToStr(IsNull,'NULL',GetStrValue(Params[i] as TColumnMetaData)) + LineEnding;
2146 end;
2147 WriteToLog(Msg);
2148 end;
2149
2150 function TFBUDRController.GetStrValue(item: TColumnMetaData): Ansistring;
2151
2152 function HexString(s: AnsiString): AnsiString;
2153 var i: integer;
2154 begin
2155 Result := '';
2156 for i := 1 to length(s) do
2157 Result := Result + Format('%x ',[byte(s[i])]);
2158 end;
2159
2160 begin
2161 with Item do
2162 case SQLType of
2163 SQL_ARRAY:
2164 Result := '(array)';
2165 SQL_BLOB:
2166 if getSubtype = 1 then
2167 begin
2168 if GetCharSetID = 1 then
2169 Result := HexString(AsString)
2170 else
2171 Result := AsString;
2172 end
2173 else
2174 Result := '(Blob)';
2175 SQL_TEXT,SQL_VARYING:
2176 if GetCharSetID = 1 then
2177 Result := HexString(AsString)
2178 else
2179 Result := TrimRight(AsString);
2180 else
2181 Result := AsString;
2182 end;
2183 end;
2184
2185 procedure TFBUDRController.WriteToLog(aTitle: AnsiString; OutputData: IFBUDROutputData);
2186 var i: integer;
2187 Msg: AnsiString;
2188 begin
2189 Msg := aTitle + LineEnding;
2190 for i := 0 to OutputData.getCount - 1 do
2191 with OutputData[i] do
2192 begin
2193 Msg := Msg + 'Column ' + IntToStr(i) + NewLineTAB +
2194 'Field Name = ' + getName + NewLineTab +
2195 'SQLType = ' + GetSQLTypeName + NewLineTAB +
2196 'sub type = ' + IntToStr(getSubType) + NewLineTAB +
2197 'Scale = ' + IntToStr(getScale) + NewLineTAB +
2198 'Charset = ' + CharSetIDToText((OutputData as TFBUDROutputParams).GetAttachment,getCharSetID) + NewLineTAB +
2199 BooleanToStr(getIsNullable,'Nullable','Not Nullable') + NewLineTAB +
2200 'Size = ' + IntToStr(GetSize) + NewLineTAB +
2201 'Value = ' + BooleanToStr(IsNull,'NULL', GetStrValue(OutputData[i] as TColumnMetaData)) + LineEnding;
2202 end;
2203 WriteToLog(Msg);
2204 end;
2205
2206 procedure TFBUDRController.StartJournaling(context: IFBUDRExternalContext);
2207 var JnlOptions: TJournalOptions;
2208 begin
2209 JnlOptions := [joNoServerTable,joReadWriteTransactions];
2210 if loModifyQueries in FBUDRControllerOptions.LogOptions then
2211 JnlOptions := JnlOptions + [joModifyQueries];
2212 if loReadOnlyQueries in FBUDRControllerOptions.LogOptions then
2213 JnlOptions := JnlOptions + [joReadOnlyQueries];
2214 if JnlOptions <> [] then
2215 begin
2216 if NeedLogStream then
2217 context.GetAttachment.StartJournaling(FLogStream,JnlOptions);
2218 end;
2219 end;
2220
2221 function TFBUDRController.HasConfigFile: boolean;
2222 begin
2223 Result := FConfigFile <> nil;
2224 end;
2225
2226 function TFBUDRController.ReadConfigString(Section, Ident,
2227 DefaultValue: AnsiString): AnsiString;
2228 begin
2229 if HasConfigFile then
2230 Result := FConfigFile.ReadString(Section, Ident, DefaultValue)
2231 else
2232 raise Exception.Create(SNoConfigFile);
2233 end;
2234
2235 function TFBUDRController.ReadConfigInteger(Section, Ident: AnsiString;
2236 DefaultValue: integer): integer;
2237 begin
2238 if HasConfigFile then
2239 Result := FConfigFile.ReadInteger(Section, Ident, DefaultValue)
2240 else
2241 raise Exception.Create(SNoConfigFile);
2242 end;
2243
2244 function TFBUDRController.ReadConfigBool(Section, Ident: AnsiString;
2245 DefaultValue: boolean): boolean;
2246 begin
2247 if HasConfigFile then
2248 Result := FConfigFile.ReadBool(Section, Ident, DefaultValue)
2249 else
2250 raise Exception.Create(SNoConfigFile);
2251 end;
2252
2253 end.
2254

Properties

Name Value
svn:eol-style native