Freitag, 29. August 2008

entwickler.com Magazine Konferenzen Akademie Entwickler-Forum Jobbörse Bücher
Software & Support Verlag




April 2006
aus Der Entwickler Ausgabe: 01.2004
Erweiterte Vorgänge
Serverseitige Programmierung unter Advantage Database Server 7
von Joachim Dürr

Datenintensive Berechnungen gehören rein aus Performancegründen auf den Server ausgelagert. Aber ebenso kann dieser Server aus Gründen der Datenstabilität und -Integrität Aufgaben der Geschäftslogik in Form von Stored Procedures und Triggern übernehmen.


Im Entwickler 6/03 habe ich bereits die neuen Features des Advantage Database Server (ADS) 7 vorgestellt. Dieses Mal sollen wie angekündigt die Themen Advantage Extended Procedures eingehend betrachtet werden. Advantage Extended Procedures (AEPs) wurden auch schon in Entwickler 4/03 behandelt und deshalb soll an dieser Stelle nur das Arbeiten mit dem neuen Interface vorgestellt werden. Eine Besonderheit, die das neue Interface leider nicht mehr hat, ist das Bearbeiten der Ausgabetabelle, doch das alte Interface kann weiter benutzt werden.

Was bedeutet das Bearbeiten der Ausgabetabelle eigentlich genau? Nun, das Einfügen von Datensätzen wohl nicht, da ohne diese Aufgabe eine Tabelle eigentlich sinnlos ist. Ich spreche hier vom wirklichen Bearbeiten der Tabelle, also dem Löschen, Neuanlegen und Ändern der Felddefinitionen. Dies war eigentlich ungeplant und selbst die Entwicklungsmannschaft des ADS war beim Bekannt werden dieser Option überrascht. Starten wir also gleich mit einem Beispiel. Die erste AEP nach altem Interface soll je einen Eingabe- und Ausgabeparameter bekommen. Der Eingabeparameter ist vom Tap Integer, der Typ des Ausgabeparameters ist irrelevant. Wir benötigen nur einen Ausgabeparameter, dass die Ausgabetabelle überhaupt erst angelegt wird. Interface 1 sieht wie folgt aus:

function MyProcedure( ulConnectionID: UNSIGNED32;
pusUserName: PChar;
pucPassword: PChar;
pucProcName: PChar;
ulRecNum: UNSIGNED32;
pucTable1: PChar;
pucTable2: PChar ): UNSIGNED32;
{$IFDEF WIN32}stdcall;{$ENDIF}{$IFDEF LINUX}cdecl;{$ENDIF}

Was die einzelnen Parameter bedeuten, möchte ich an dieser Stelle nicht wiederholen. Nur auf einen möchte ich hinweisen: Die Ausgabetabelle wird hier per Namen in pucTable2 übergeben. Dieser Tabellenname beinhaltet den kompletten Pfad zu dieser Tabelle (Listing 1)

Listing 1

strTable2 := StrPas( pucTable2 );
ParamsConn.ConnectPath := ExtractFileDir( strTable2 );
tblOutput.tablename := ExtractFileName( strTable2 );
[]
//Tabelle löschen
tblOutput.DeleteTable;
//neue Tabellenstruktur definieren
tblOutput.FieldDefs.Clear;
tblOutput.FieldDefs.Add('Id', ftAutoInc);
tblOutput.FieldDefs.Add('text', ftString, 40);
tblOutput.FieldDefs.Add('text2', ftString, 40);
//Tabelle anlegen
tblOutput.CreateTable;
//Tabelle öffnen und mit Inhalt füllen
tblOutput.open;
for i:=1 to iCount do
begin
tblOutput.append;
tblOutput.FieldByName('text').AsString :=
'Hello World';
tblOutput.FieldByName('text2').AsString :=
'This is Record No '+ IntToStr(i);
tblOutput.post;
end;
tblOutput.close;

Wird nun diese AEP in der Datenbank mit SQL Skript angelegt und ausgeführt, so wird nicht wie erwartet eine Ausgabe mit nur einem Feld zurückgeliefert, sondern eine mit drei (Abb. 1).


Abb. 1: AEP1 nach der Ausführung im Data Architect

Sie werden sich nun sicherlich fragen, warum dieses undokumentierte (und ungeplante) Feature mir so wichtig erscheint. Bei uns taucht immer wieder die Frage auf, wie denn nun Kreuztabellenabfragen (Pivot-Tabellen) mit dem ADS realisiert werden können. Wenn die Struktur bekannt ist, ist dies mit einem mehr oder weniger aufwändigen SQL-Statement zu erlangen. Gibt es aber nun eine undefinierte Anzahl von Spalten, so kann eben solch eine Stored Procedure genutzt werden, die Abfrage selbst zu berechnen und die Struktur der Ausgabetabelle entsprechend auszugeben. Auch kann diese flexible Gestaltung der Ausgabetabellen dazu genutzt werden, eine Fremddatenbank in die eigene einzubinden. Erstellen Sie hierzu eine AEP, welche als Input-Parameter zwei Strings bekommt. Der erste könnte ein ADO Connection String sein, der zweite ein SQL-Statement. Die AEP verbindet nun über den Connection String zu einer beliebigen Datenbank und führt das übergebene SQL-Statement aus. Wird ein Cursor (also eine Datenmenge) zurückgeliefert, so wird einfach die Ausgabetabelle entsprechend erstellt und gefüllt.

AEP Interface 2
Wenn diese Interface so mächtig ist, warum benötigen wir dann überhaupt eine Modellpflege? Schauen Sie sich hierzu den Prototypen des Interface 2 einmal an und vergleichen es mit dem des Interface 1:

function MyProcedure( ulConnectionID: UNSIGNED32;
hConnection: ADSHANDLE;
pulNumRowsAffected: PUNSIGNED32 ): UNSIGNED32;
{$IFDEF WIN32}stdcall;{$ENDIF}{$IFDEF LINUX}cdecl;{$ENDIF}

Dieser Prototyp benötigt nur noch drei Parameter (anstatt der sieben des Interface 1). In diesen Parametern kommt keinerlei Information über die Temporärtabellen oder den angemeldeten Benutzer vor. Übergeben wird hier in ulConnectionID ein eindeutiger Identifier für die Instanz der AEP und ein AdsHandle, genauer das Connection-Handle, welches von der Workstation zum ADS verwendet wird, um diese AEP auszuführen. Über dieses Handle stehen alle Benutzer- und Verbindungsinformationen bereit, um im Kontext der Workstation zur Datenbank zu verbinden. Dieser Kontext der Workstation kann ungemein wichtig werden, wenn Sie mit Transaktionen arbeiten (Zur Erinnerung: Der Advantage Local Server hat keinerlei Transaktionsunterstützung). Die kompletten Daten, welche innerhalb der AEP verändert werden, werden so im Transaktionskontext der Workstation verarbeitet. Wird am Client eine Transaktion zurückgenommen (Rollback), so werden auch alle Datenänderungen, welche innerhalb der AEP stattgefunden haben, mit zurückgenommen. Interface 1 bietet diese Möglichkeit nur über mehrere Umwege. Zusammenfassend kann man sagen, dass dieses neue Interface nur eingesetzt wurde, um dem Programmierer das Schreiben von AEP zu vereinfachen. Der dritte Parameter (pulNumRowsAffected) kann dazu verwendet werden, dem aufrufenden Programm einen Status zurückzuliefern.

Kommen wir also zu einem einfachen Beispiel. Was bietet sich bei Transaktionen mehr an, als das Beispiel auf einer kleinen Kontenverwaltung aufzubauen. Erstellen Sie sich mit dem Architect ein Data Dictinonary (z B über SQL: CREATE DATABASE entwickler), verbinden Sie im SQL Utility auf dieses Dictionary und erstellen die benötigten Tabellen mit folgendem Skript:

//Tabelle BankAccnt erzeugen
create table bankaccnt(
accnt char(8),
name char (40),
balance money
);

//Tabelle TransAct erzeugen
create table transact(
accntFrom char(8),
accntTo char(8),
balance money,
username char(50),
transdate timestamp
);

// 2 Kunden (Konten) angeben
insert into bankaccnt values ('11111111', 'kunde 1',0);
insert into bankaccnt values ('22222222', 'kunde 2',0);

Nun erstellen Sie in Delphi über Datei|Neu|Andere eine Advantage Extended Procedure Interface 2. Sollte diese in der Objektablage nicht sichtbar sein, so könne Sie diese über die Eigenschaften dieser Objektablage einer sichtbaren Seite zuordnen. Dem Template fügen wir im Datenmodul zwei TAdsTable-Komponenten hinzu, welche in der Methode MyProcedure verwendet werden (Listing 2).

Listing 2

function MyProcedure( ulConnectionID: UNSIGNED32;
hConnection: ADSHANDLE;
pulNumRowsAffected: PUNSIGNED32 ): UNSIGNED32;
{$IFDEF WIN32}stdcall;{$ENDIF}{$IFDEF LINUX}cdecl;{$ENDIF} // Do not change prototype
var
DM1 : TDM1;
sAccntFrom: string;
sAccntTo: string;
dBalance: Double;
begin

Result := AE_SUCCESS;

{* Get this connection's data module from the session manager. *}
DM1 := TDM1( AEPSessionMgr.GetDM( ulConnectionID ) );
try
with DM1 do
begin

//Inputparameter lesen und temporär speichern
tblInput.open;
sAccntFrom := tblInput.FieldByName('AccntFrom').AsString;
sAccntTo := tblInput.FieldByName('AccntTo').AsString;
dBalance := tblInput.FieldByName('Balance').AsFloat;
tblInput.close;
tbAccnt.DatabaseName := 'DataConn';
tbAccnt.TableName := 'BankAccnt';
tbAccnt.Open;
//Betrag abbuchen
if tbAccnt.Locate('accnt', VarArrayOf([sAccntFrom]), [])
then begin
tbAccnt.Edit;
tbAccnt.FieldByName('balance').AsFloat :=
tbAccnt.FieldByName('balance').AsFloat - dBalance;
tbAccnt.Post;
end
else begin
DataConn.Execute( 'INSERT INTO __error VALUES ( 1, ''Quellkonto nicht vorhanden'')');
end;
//Betrag zubuchen
if tbAccnt.Locate('accnt', VarArrayOf([sAccntTo]), [])
then begin
tbAccnt.Edit;
tbAccnt.FieldByName('balance').AsFloat :=
tbAccnt.FieldByName('balance').AsFloat + dBalance;
tbAccnt.Post;
end
else begin
DataConn.Execute( 'INSERT INTO __error VALUES ( 1, ''Zielkonto nicht vorhanden'')');
end;
tbAccnt.Close;

//und nun noch die ganze Aktion mitloggen
tbTransAct.DatabaseName := 'DataConn';
tbTransAct.TableName := 'TransAct';
tbTransAct.Open;
tbTransAct.Append;
tbTransAct.FieldByName('AccntFrom').AsString := sAccntFrom;
tbTransAct.FieldByName('AccntTo').AsString := sAccntTo;
tbTransAct.FieldByName('balance').AsFloat := dBalance;
tbTransAct.Post;
tbTransAct.Close;
end; {* with DM1 *}

except
on E : EADSDatabaseError do
{* ADS-specific error, use ACE error code *}
DM1.DataConn.Execute( 'INSERT INTO __error VALUES ( ' + IntToStr( E.ACEErrorCode ) + ', ' + QuotedStr( E.Message ) + ' )' );
on E : Exception do
{* other error *}
DM1.DataConn.Execute( 'INSERT INTO __error VALUES ( 1, ' + QuotedStr( E.Message ) + ' )' );
end;

end;

Diese AEP kann nun in das Dictionary eingebunden werden und schon können Sie mit wilden Buchungen vom einen zum anderen Konto das Transaktionsverhalten testen.

Wie geht's weiter?
Dieses Mal habe ich mich nur mit einer Form von serverseitiger Programmierung beschäftigt. Der zweite Teil, welcher nicht weniger interessant ist, soll in der nächsten Ausgabe Beachtung finden. Dann werde ich das hier begonnene Beispiel fortsetzen und mich intensiv mit der Advantage Implementierung der Trigger auseinandersetzen. Bis dahin wünsche ich Ihnen viel Spaß beim Experimentieren.

Links und Literatur
  • J. Dürr, Nachgelegt; Der Entwickler 6/2003
  • J. Dürr, Erweiterte Vorgänge; Der Entwickler 4/2003


    Hat Ihnen dieser Artikel gefallen? Dann abonnieren Sie das Entwickler Magazin direkt über unser Online-Formular.



zur vorherigen Seite
zurück
an den Anfang der Seite
nach oben
Diesen Artikel drucken
drucken
Diesen Artikel weiterempfehlen
empfehlen
Software & Support Verlag GmbH