* Add ADO packet data streamer

git-svn-id: trunk@41618 -
This commit is contained in:
michael 2019-03-06 08:22:37 +00:00
parent 0b6b748b15
commit 0fb747857e
2 changed files with 378 additions and 0 deletions

1
.gitattributes vendored
View File

@ -3460,6 +3460,7 @@ packages/fcl-web/src/jsonrpc/fpextdirect.pp svneol=native#text/plain
packages/fcl-web/src/jsonrpc/fpjsonrpc.pp svneol=native#text/plain
packages/fcl-web/src/jsonrpc/readme.txt svneol=native#text/plain
packages/fcl-web/src/jsonrpc/webjsonrpc.pp svneol=native#text/plain
packages/fcl-web/src/restbridge/sqldbrestado.pp svneol=native#text/plain
packages/fcl-web/src/restbridge/sqldbrestauth.pp svneol=native#text/plain
packages/fcl-web/src/restbridge/sqldbrestauthini.pp svneol=native#text/plain
packages/fcl-web/src/restbridge/sqldbrestbridge.pp svneol=native#text/plain

View File

@ -0,0 +1,377 @@
{
This file is part of the Free Pascal run time library.
Copyright (c) 2019 by the Free Pascal development team
SQLDB REST bridge : ADO-styled XML input/output
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
unit sqldbrestado;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DateUtils, db,fpjson, dom, XMLRead, XMLWrite,sqldbrestschema,sqldbrestio, sqldbrestbridge;
Type
{ TADOInputStreamer }
TADOInputStreamer = Class(TRestInputStreamer)
private
FDataName: UTF8String;
FRowName: UTF8String;
FXML: TXMLDocument;
FPacket : TDOMElement;
FData : TDOMElement; // Equals FPacket
FRow : TDOMElement;
Protected
function GetNodeText(N: TDOmNode): UnicodeString;
Public
Destructor Destroy; override;
Class Function GetContentType: String; override;
Function SelectObject(aIndex : Integer) : Boolean; override;
function GetContentField(aName: UTF8string): TJSONData; override;
procedure InitStreaming; override;
Property XML : TXMLDocument Read FXML;
Property Packet : TDOMElement Read FPacket;
Property Data : TDOMElement Read FData;
Property Row : TDOMElement Read FRow;
Property DataName : UTF8String Read FDataName Write FDataName;
Property RowName : UTF8String Read FRowName Write FRowName;
end;
{ TADOOutputStreamer }
TADOOutputStreamer = Class(TRestOutputStreamer)
Private
FDataName: UTF8String;
FRowName: UTF8String;
FXML: TXMLDocument;
FData : TDOMElement; // Equals FRoot
FRow: TDOMElement;
FRoot: TDomElement;
function CreateXSD: TDomElement;
Public
procedure EndData; override;
procedure EndRow; override;
procedure FinalizeOutput; override;
procedure StartData; override;
procedure StartRow; override;
// Return Nil for null field.
function FieldToXML(aPair: TRestFieldPair): TDOMElement; virtual;
procedure WriteField(aPair: TRestFieldPair); override;
procedure WriteMetadata(aFieldList: TRestFieldPairArray); override;
Procedure CreateErrorContent(aCode : Integer; Const aMessage: String); override;
Property XML : TXMLDocument Read FXML;
Property Data : TDOMelement Read FData;
Property Row : TDOMelement Read FRow;
Public
Destructor Destroy; override;
Class Function GetContentType: String; override;
function RequireMetadata : Boolean; override;
procedure InitStreaming; override;
Property DataName : UTF8String Read FDataName Write FDataName;
Property RowName : UTF8String Read FRowName Write FRowName;
end;
implementation
uses sqldbrestconst;
{ TADOInputStreamer }
destructor TADOInputStreamer.Destroy;
begin
FreeAndNil(FXML);
inherited Destroy;
end;
class function TADOInputStreamer.GetContentType: String;
begin
Result:='text/xml';
end;
function TADOInputStreamer.SelectObject(aIndex: Integer): Boolean;
Var
N : TDomNode;
NN : UnicodeString;
begin
Result:=False;
NN:=UTF8Decode(RowName);
N:=FData.FindNode(NN);
While (aIndex>0) and (N<>Nil) and (N.NodeName<>NN) and (N.NodeType<>ELEMENT_NODE) do
begin
N:=N.NextSibling;
Dec(aIndex);
end;
Result:=(aIndex=0) and (N<>Nil);
If Result then
FRow:=N as TDomElement
else
FRow:=Nil;
end;
function TADOInputStreamer.GetNodeText(N: TDOmNode): UnicodeString;
Var
V : TDomNode;
begin
Result:='';
V:=N.FirstChild;
While (V<>Nil) and (V.NodeType<>TEXT_NODE) do
V:=V.NextSibling;
If Assigned(V) then
Result:=V.NodeValue;
end;
function TADOInputStreamer.GetContentField(aName: UTF8string): TJSONData;
Var
NN : UnicodeString;
N : TDomNode;
begin
NN:=UTF8Decode(aName);
N:=FRow.FindNode(NN);
if Assigned(N) and (N.NodeType=ELEMENT_NODE) then
Result:=TJSONString.Create(UTF8Encode(GetNodeText(N)));
end;
procedure TADOInputStreamer.InitStreaming;
Var
Msg : String;
N : TDomNode;
NN : UnicodeString;
begin
if DataName='' then
DataName:='Data';
if RowName='' then
RowName:='Row';
FreeAndNil(FXML);
if Stream.Size<=0 then
exit;
try
ReadXMLFile(FXML,Stream);
except
On E : Exception do
begin
Msg:=E.Message;
FXML:=Nil;
end;
end;
if (FXML=Nil) then
Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsInvalidContent),SErrInvalidXMLInput,[Msg]);
FPacket:=FXML.DocumentElement;
NN:=UTF8Decode(DataName);
if FPacket.NodeName<>NN then
Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsInvalidContent),SErrInvalidXMLInput,[SErrMissingDocumentRoot]);
FData:=FPacket;
end;
{ TADOOutputStreamer }
procedure TADOOutputStreamer.EndData;
begin
FData:=Nil;
end;
procedure TADOOutputStreamer.EndRow;
begin
FRow:=Nil;
end;
procedure TADOOutputStreamer.FinalizeOutput;
begin
xmlwrite.WriteXML(FXML,Stream);
FreeAndNil(FXML);
end;
procedure TADOOutputStreamer.StartData;
begin
// Rows are straight under the Data packet
FData:=FRoot;
end;
procedure TADOOutputStreamer.StartRow;
begin
if (FRow<>Nil) then
Raise ESQLDBRest.Create(Statuses.GetStatusCode(rsError),SErrDoubleRowStart);
FRow:=FXML.CreateElement(UTF8Decode(RowName));
FData.AppendChild(FRow);
end;
function TADOOutputStreamer.FieldToXML(aPair: TRestFieldPair): TDOMElement;
Var
F : TField;
S : UTF8String;
begin
Result:=Nil;
F:=aPair.DBField;;
If (aPair.RestField.FieldType=rftUnknown) then
raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsError),SErrUnsupportedRestFieldType, [aPair.RestField.PublicName]);
If (F.IsNull) then
Exit;
S:=FieldToString(aPair.RestField.FieldType,F);
Result:=FXML.CreateElement(UTF8Decode(aPair.RestField.PublicName));
Result.AppendChild(FXML.CreateTextNode(UTF8Decode(S)));
end;
procedure TADOOutputStreamer.WriteField(aPair: TRestFieldPair);
Var
D : TDOMElement;
N : UTF8String;
begin
N:=aPair.RestField.PublicName;
if FRow=Nil then
Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsError),SErrFieldWithoutRow,[N]);
D:=FieldToXML(aPair);
if (D=Nil) and (not HasOption(ooSparse)) then
D:=FXML.CreateElement(UTF8Decode(aPair.RestField.PublicName));
if D<>Nil then
FRow.AppendChild(D);
end;
function TADOOutputStreamer.CreateXSD: TDomElement;
// Create XSD and append to root. Return element to which field list must be appended.
Var
SN,N,E,TLN : TDomElement;
begin
SN:=FXML.CreateElement('xs:schema');
SN['id']:=Utf8Decode(DataName);
SN['xmlns']:='';
SN['xmlns:xs']:='http://www.w3.org/2001/XMLSchema';
SN['xmlns:msdata']:= 'urn:schemas-microsoft-com:xml-msdata';
FRoot.AppendChild(SN);
// Add table list with 1 table.
// Element
N:=FXML.CreateElement('xs:element');
SN.AppendChild(N);
N['name']:=UTF8Decode(DataName);
N['msdata:IsDataSet']:='true';
N['msdata:UseCurrentLocale']:='true';
// element is a complex type
TLN:=FXML.CreateElement('xs:complexType');
N.AppendChild(TLN);
// Complex type is a choice (0..Unbounded] of records
N:=FXML.CreateElement('xs:choice');
TLN.AppendChild(N);
N['minOccurs']:='0';
N['maxOccurs']:='unbounded';
// Each record is an element
E:=FXML.CreateElement('xs:element');
N.AppendChild(E);
E['name']:=Utf8Decode(RowName);
// Record is a complex type of fields
N:=FXML.CreateElement('xs:complexType');
E.AppendChild(N);
// Fields are a sequence. To this sequence, the fields may be appended.
Result:=FXML.CreateElement('xs:sequence');
N.AppendChild(Result);
end;
Const
XMLPropTypeNames : Array [TRestFieldType] of string = (
'unknown', { rtfUnknown }
'xs:int', { rftInteger }
'xs:int', { rftLargeInt}
'xs:double', { rftFloat }
'xs:dateTime', { rftDate }
'xs:dateTime', { rftTime }
'xs:dateTime', { rftDateTime }
'xs:string', { rftString }
'xs:boolean', { rftBoolean }
'xs:base64Binary' { rftBlob }
);
procedure TADOOutputStreamer.WriteMetadata(aFieldList: TRestFieldPairArray);
Var
FMetadata : TDOMElement;
F : TDomElement;
P : TREstFieldPair;
I : integer;
S : Utf8String;
K : TRestFieldType;
begin
FMetadata:=CreateXSD;
For I:=0 to Length(aFieldList)-1 do
begin
P:=aFieldList[i];
K:=P.RestField.FieldType;
S:=XMLPropTypeNames[K];
F:=FXML.CreateElement('xs:element');
F['name']:=Utf8Decode(P.Restfield.PublicName);
F['type']:=Utf8decode(S);
F['minOccurs']:='0';
FMetaData.AppendChild(F);
end;
end;
class function TADOOutputStreamer.GetContentType: String;
begin
Result:='text/xml';
end;
function TADOOutputStreamer.RequireMetadata: Boolean;
begin
Result:=True;
end;
procedure TADOOutputStreamer.CreateErrorContent(aCode: Integer; const aMessage: String);
Var
ErrorObj : TDomElement;
begin
ErrorObj:=FXML.CreateElement(UTF8Decode(GetString(rpErrorRoot)));
ErrorObj['code']:=UTF8Decode(IntToStr(aCode));
ErrorObj['message']:=UTF8Decode(aMessage);
FRoot.AppendChild(ErrorObj);
end;
destructor TADOOutputStreamer.Destroy;
begin
FreeAndNil(FXML);
inherited Destroy;
end;
procedure TADOOutputStreamer.InitStreaming;
begin
FXML:=TXMLDocument.Create;
FXML.XMLStandalone:=True;
if DataName='' then
DataName:='Data';
FRoot:=FXML.CreateElement('Data');
FXML.AppendChild(FRoot);
if RowName='' then
RowName:='Row';
end;
Initialization
TADOInputStreamer.RegisterStreamer('ado');
TADOOutputStreamer.RegisterStreamer('ado');
end.