Firebird 3 UDR in Pascal/Delphi

Themen rund um den praktischen Einsatz von Firebird. Fragen zu SQL, Performance, Datenbankstrukturen, etc.

Moderator: thorben.braun

jhoehne
Beiträge: 44
Registriert: Di 11. Dez 2018, 09:19

MWA Software hat in der aktuellen Version des Firebird Interface für Pascal eine Demo für das Schreiben von UDR nebst einer Dokumentation dazu: https://www.mwasoftware.co.uk/downloads ... bintf1-4-0
--
Joachim
vr2
Beiträge: 246
Registriert: Fr 13. Apr 2018, 00:13

Danke für den Hinweis. Jetzt hat Tony die fbintf-Version 1.4.0 also veröffentlicht.

Ich habe zwischen 12/21 und 04/22 mit Tony Whyman (dem Entwickler hinter MWA) an dieser Erweiterung seiner Firebird-Schnittstelle gearbeitet. Mein Part war dabei nur das Schreiben und Testen von UDRs und das Testen des batch-APIs. Ich kann Delphi/Freepascal-Entwicklern die fbintf nur empfehlen, es ist zur Zeit m.W. die einzige Delphi/FreePascal-lib, die die Features von Firebird 4 voll unterstützt, und insbesondere das Schreiben von UDRs, was nochmal ein eigener großer Bereich ist. Tony dokumentiert sehr gut, dh, die API-Doku lässt kaum Fragen offen und es gibt reichlich Anwendungsbeispiele, was für Delphi/FreePascal lange fehlte. Selbst für C++ gibt es wenig.

Falls hier im Forum Interesse besteht, kann ich den Code einiger UDRs mal posten. UDRs sind ein sehr mächtiges Feature.

Grüße, Volker
bfuerchau
Beiträge: 546
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Ich arbeite nun fast seit 16 Jahren mit der FB (seit 1.5) und frage mich allen Ernstes wofür man UDR's wirklich benötigt.
Ich habe bisher alles mit reinen SQL's sowie "execute block" durchgeführt. Insbesonders unter der Berücksichtigung von Transaktionen.
In meinem Aufgabenbereich (BI und Statistik) ist mir der Bedarf von UDR's noch nicht untergekommen.
Zumal man in C# mit LINQ und DynamicLINQ ein mächtigeres Werkzeug hat, als UDR's mir bieten können.
Mit entsprechender Indizierung können 10.000de Sätze je Sekunde eingelesen werden (ODBC oder C# FB-Library), der Rest passiert im Client und entlastet die DB da auch z.B. Parallel-LINQ gleichzeitige und effektivere Wege bietet als ein nicht parallelisierbare UDR.
Und während der Client in Sekunden oder Bruchteilen davon die Rechenarbeit erledigt, kann ein DB-Server viele weitere parallele Abfragen durchführen.

Fazit:
Verteilte Rechenleistung ist oft sinnvoller als einem DB-Server Aufgaben zu geben, die den Gesamtbetrieb dann eher stören.

Aber:
Das sind meine ganz persönlichen Erkenntnisse mit der FB aber auch mit anderen DBM's.
vr2
Beiträge: 246
Registriert: Fr 13. Apr 2018, 00:13

UDRs sind für die Leute interessant, die Logik wo möglich in der Datenbank ansiedeln statt im Client. Damit ist die Logik vom Client unabhängig - was nicht heißt, dass es im Client keine Logik mehr geben soll. Einfach eine andere Schwerpunktsetzung.

UDRs können immer im Transaktionskontext laufen, meistens will/braucht man genau das, man hat den vollen DB-Kontext, und präzise TX-Steuerung ist möglich.
UDRs sind parallel nutzbar, da sie ihren eigenen Kontext haben, wie interne Stored Procedures oder Stored Functions auch, über Connections.

Einfache Anwendungsfälle sind

- Einlesen einer Datei und zeilenweise Bereitstellung in der DB
- Export eines Abfrageergebnisses in Datei
- Ping eines Servers
- Prepare - tabellarische Bereitstellung der Metadaten eines Statements

weiter

- Erzeugung einer neuen Tabelle aus einer Abfrage (select_into)
- das gleiche mit ODBC als Datenquelle (select_odbc_into)
- Absetzen eines web requests und Bereitstellung des Ergebnisses in der DB

da gibt es viele spannende Aufgaben. Man kann das alles auch außerhalb, in einem Client umsetzen, klar. Im Grunde sind UDRs auch Client Code, aber in die DB integriert und von dort aus nutzbar. Man kann den Datenbankserver regulär um DB-Funktionen erweitern. Das ist der zentrale Unterschied.

Grüße, Volker
Zuletzt geändert von vr2 am Sa 3. Sep 2022, 23:56, insgesamt 1-mal geändert.
jhoehne
Beiträge: 44
Registriert: Di 11. Dez 2018, 09:19

vr2 hat geschrieben: Sa 27. Aug 2022, 04:13 Falls hier im Forum Interesse besteht, kann ich den Code einiger UDRs mal posten. UDRs sind ein sehr mächtiges Feature.
Das fände ich phantastisch.
--
Joachim
Gerd
Beiträge: 243
Registriert: Di 1. Okt 2019, 17:13

Hallo.

Ich bin der Meinung, dass so etwas hier sehr gut platziert wäre.

Danke für die Bemühungen. :)


Viele Grüße
Gerd
ISQL Version: LI-V5.0.1.1469
Linux Mint 22 Cinnamon 6.2.9
bfuerchau
Beiträge: 546
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

"UDRs sind ein sehr mächtiges Feature."
Dem stimme ich ja uneingeschränkt zu, aber bei lange dauernden Funktionen/Prozeduren geht dies z.T. extrem zu Lasten der DB.
Also immer schön den Performancegedanken nie aus den Augen verlieren :idea:.
vr2
Beiträge: 246
Registriert: Fr 13. Apr 2018, 00:13

Dann los: UDRs sind verpackt in eine oder mehrere libs (.dll oder .so etc). Die Beispiel-lib hab ich PascalUDR genannt. Deren PascalUDR.dpr sieht so aus:

Code: Alles auswählen

library PascalUDR;

uses
  System.SysUtils,
  System.Classes,
  FBUDRController,
  UdrReadText in 'UdrReadText.pas';

{$R *.res}

exports firebird_udr_plugin;

begin
  with FBUDRControllerOptions do
  begin
    ModuleName := 'PascalUDR';
    AllowConfigFileOverrides := true;
    LogFileNameTemplate := '$UDRDIR$MODULE.log';
    LogOptions := [loLogFunctions, loLogProcedures, loLogTriggers, loDetails];
    ThreadSafeLogging := True;
  end;
end.
Oben mit UdrReadText die unit der ersten Beispiel-UDR.

Die ControllerOptions steuern hauptsächlich das Logging, kann man auch ausmachen, wenn man es nicht braucht, am Anfang ist es ganz interessant.

Die unit UDRReadText.pas beherbergt die UDR read_text (die unit könnte natürlich auch mehrere UDRs definieren), die zeilenweise aus einer Textdatei liest, die mit path angegeben wird. Die Zeilen werden satzweise zurückgegeben. read_text ist vom Typ TFBUDRSelectProcedure, da sie mehr als einen Satz zurückgeben soll. Es gibt noch die Typen TFBUDRExecuteProcedure und TFBUDRFunction und TFBUDRTrigger.

Als TFBUDRSelectProcedure muss read_txt die drei Methoden open, fetch und close mit Inhalt füllen.

open - Initialisierung
fetch - Daten eines Satzes bereitstellen
close - aufräumen - im Beispiel leer

Oben im Kopf der unit steht auskommentiert die Deklaration, mit der diese UDR der Datenbank bekannt gemacht werden muss.

Code: Alles auswählen

unit UdrReadText;

{
create or alter procedure read_text (
  path varchar(300) not null
) returns (
  text varchar(3000)
)
external name 'pascaludr!read_text'
engine udr;
}

interface

uses Classes, SysUtils, IB, FBUDRController, FBUDRIntf;

type
  TReadTextFile  = class(TFBUDRSelectProcedure)
    private
      FTextFile: TStreamReader;
    public
      procedure open(context: IFBUDRExternalContext; ProcMetadata: IFBUDRProcMetadata; InputParams: IFBUDRInputParams);  override;
      function fetch(OutputData: IFBUDROutputData): boolean;  override;
      procedure close; override;
    end;


implementation

procedure TReadTextFile.open(context: IFBUDRExternalContext; ProcMetadata: IFBUDRProcMetadata; InputParams: IFBUDRInputParams);
begin
  FTextFile := TStreamReader.Create(InputParams.ByName('path').AsString, TEncoding.ANSI);
end;

function TReadTextFile.fetch(OutputData: IFBUDROutputData): boolean;
begin
  Result := not FTextFile.EndOfStream;
  if Result then
    OutputData.ByName('text').AsString := FTextFile.ReadLine;
end;

procedure TReadTextFile.close;
begin
  if FTextFile <> nil then
    FTextFile.Free;
  FTextFile := nil;
end;

initialization

FBRegisterUDRProcedure('read_text', TReadTextFile);

end.
Ich benutze zur Zeit Delphi 10.4 zur Übersetzung, aber fbintf ist eigentlich sogar eher für Freepascal geschrieben, damit sollte es auch gehen.
Die übersetzte lib PascalUDR.dll kopiert man am einfachsten ins Verzeichnis ...\Firebird_4\plugins\udr, dann muss man dort nichts weiter konfigurieren. Anschließend in der Zieldatenbank die UDR registrieren mittels der auskommentierten Deklaration oben im unit-Kopf:

Code: Alles auswählen

create or alter procedure read_text (
  path varchar(300) not null
) returns (
  text varchar(3000)
)
external name 'pascaludr!read_text'
engine udr;
Aufruf dann zb

Code: Alles auswählen

select * from read_text('C:\Programme\Firebird\Firebird_4\firebird.log')
und wenn alles geklappt hat, sieht man das firebird.log tabellarisch im Abfrageergebnis.

und mit

Code: Alles auswählen

select count(*) abschuss
from read_text('C:\Programme\Firebird\Firebird_4\firebird.log')
where text containing 'abnormally'
sieht man die Anzahl der Serverabschüsse gemäß log ;-)

Das Beispiel ist bewusst einfach gehalten, um erstmal einen Überblick zu bekommen, wie das prinzipiell läuft bei UDRs. Hier ist noch nichts mit Attachments, Transaktionen, Blobs usw gemacht, das kommt in späteren Beispielen.

Grüße, Volker
Zuletzt geändert von vr2 am So 4. Sep 2022, 22:02, insgesamt 1-mal geändert.
bfuerchau
Beiträge: 546
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Und das geht halt ohne Umwege mit einem Direktzugriff erheblich einfacher;-).
In C#/VB.Net gibt's einen CSV-Reader, der mir alles in Felder zerlegt.
Es gibt XML-Reader, JSON-Reader, die ich erst alle mühsam in relationale Zugriffe umbiegen muss um sie sequentiell verarbeiten zu können.

- UDR's für komplexe, multithreaded, Rechenvorgänge würde ich da eher erwarten.
- UDR's, die mir mathematische Formelsammlungen für SQL zur Verfügung stellen.
- u.U. CustomAggregates, also Aggregaterweiterungen aus der Statistik.

Wie schon gesagt, bisher lösen wir alles DB-Clientseitig, aber durchaus serverseitig (z.B. Web-Server). Als Sofwareanbieter darf man sich auch nicht unbedingt auf eine DB fixieren und UDR's macht eben jede DB anders.
jhoehne
Beiträge: 44
Registriert: Di 11. Dez 2018, 09:19

bfuerchau hat geschrieben: So 4. Sep 2022, 12:42 Und das geht halt ohne Umwege mit einem Direktzugriff erheblich einfacher;-).
In C#/VB.Net gibt's einen CSV-Reader, der mir alles in Felder zerlegt.
Aber wie stehts mit der Performance, selbst einlesen vs UDR? Das ist doch der interessante Part.
--
Joachim
Antworten