sqldbrestado.pp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by the Free Pascal development team
  4. SQLDB REST bridge : ADO-styled XML input/output
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit sqldbrestado;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, DateUtils, db,fpjson, dom, XMLRead, XMLWrite,sqldbrestschema,sqldbrestio, sqldbrestbridge;
  16. Type
  17. { TADOInputStreamer }
  18. TADOInputStreamer = Class(TRestInputStreamer)
  19. private
  20. FDataName: UTF8String;
  21. FRowName: UTF8String;
  22. FXML: TXMLDocument;
  23. FPacket : TDOMElement;
  24. FData : TDOMElement; // Equals FPacket
  25. FRow : TDOMElement;
  26. Protected
  27. function GetNodeText(N: TDOmNode): UnicodeString;
  28. Public
  29. Destructor Destroy; override;
  30. Class Function GetContentType: String; override;
  31. Function SelectObject(aIndex : Integer) : Boolean; override;
  32. function GetContentField(aName: UTF8string): TJSONData; override;
  33. procedure InitStreaming; override;
  34. Property XML : TXMLDocument Read FXML;
  35. Property Packet : TDOMElement Read FPacket;
  36. Property Data : TDOMElement Read FData;
  37. Property Row : TDOMElement Read FRow;
  38. Property DataName : UTF8String Read FDataName Write FDataName;
  39. Property RowName : UTF8String Read FRowName Write FRowName;
  40. end;
  41. { TADOOutputStreamer }
  42. TADOOutputStreamer = Class(TRestOutputStreamer)
  43. Private
  44. FDataName: UTF8String;
  45. FRowName: UTF8String;
  46. FXML: TXMLDocument;
  47. FData : TDOMElement; // Equals FRoot
  48. FRow: TDOMElement;
  49. FRoot: TDomElement;
  50. function CreateXSD: TDomElement;
  51. Public
  52. procedure EndData; override;
  53. procedure EndRow; override;
  54. procedure FinalizeOutput; override;
  55. procedure StartData; override;
  56. procedure StartRow; override;
  57. // Return Nil for null field.
  58. function FieldToXML(aPair: TRestFieldPair): TDOMElement; virtual;
  59. procedure WriteField(aPair: TRestFieldPair); override;
  60. procedure WriteMetadata(aFieldList: TRestFieldPairArray); override;
  61. Procedure CreateErrorContent(aCode : Integer; Const aMessage: String); override;
  62. Property XML : TXMLDocument Read FXML;
  63. Property Data : TDOMelement Read FData;
  64. Property Row : TDOMelement Read FRow;
  65. Public
  66. Destructor Destroy; override;
  67. Class Function GetContentType: String; override;
  68. function RequireMetadata : Boolean; override;
  69. procedure InitStreaming; override;
  70. Property DataName : UTF8String Read FDataName Write FDataName;
  71. Property RowName : UTF8String Read FRowName Write FRowName;
  72. end;
  73. implementation
  74. uses sqldbrestconst;
  75. { TADOInputStreamer }
  76. destructor TADOInputStreamer.Destroy;
  77. begin
  78. FreeAndNil(FXML);
  79. inherited Destroy;
  80. end;
  81. class function TADOInputStreamer.GetContentType: String;
  82. begin
  83. Result:='text/xml';
  84. end;
  85. function TADOInputStreamer.SelectObject(aIndex: Integer): Boolean;
  86. Var
  87. N : TDomNode;
  88. NN : UnicodeString;
  89. begin
  90. Result:=False;
  91. NN:=UTF8Decode(RowName);
  92. N:=FData.FindNode(NN);
  93. While (aIndex>0) and (N<>Nil) and (N.NodeName<>NN) and (N.NodeType<>ELEMENT_NODE) do
  94. begin
  95. N:=N.NextSibling;
  96. Dec(aIndex);
  97. end;
  98. Result:=(aIndex=0) and (N<>Nil);
  99. If Result then
  100. FRow:=N as TDomElement
  101. else
  102. FRow:=Nil;
  103. end;
  104. function TADOInputStreamer.GetNodeText(N: TDOmNode): UnicodeString;
  105. Var
  106. V : TDomNode;
  107. begin
  108. Result:='';
  109. V:=N.FirstChild;
  110. While (V<>Nil) and (V.NodeType<>TEXT_NODE) do
  111. V:=V.NextSibling;
  112. If Assigned(V) then
  113. Result:=V.NodeValue;
  114. end;
  115. function TADOInputStreamer.GetContentField(aName: UTF8string): TJSONData;
  116. Var
  117. NN : UnicodeString;
  118. N : TDomNode;
  119. begin
  120. NN:=UTF8Decode(aName);
  121. N:=FRow.FindNode(NN);
  122. if Assigned(N) and (N.NodeType=ELEMENT_NODE) then
  123. Result:=TJSONString.Create(UTF8Encode(GetNodeText(N)));
  124. end;
  125. procedure TADOInputStreamer.InitStreaming;
  126. Var
  127. Msg : String;
  128. NN : UnicodeString;
  129. begin
  130. if DataName='' then
  131. DataName:='Data';
  132. if RowName='' then
  133. RowName:='Row';
  134. FreeAndNil(FXML);
  135. if Stream.Size<=0 then
  136. exit;
  137. try
  138. ReadXMLFile(FXML,Stream);
  139. except
  140. On E : Exception do
  141. begin
  142. Msg:=E.Message;
  143. FXML:=Nil;
  144. end;
  145. end;
  146. if (FXML=Nil) then
  147. Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsInvalidContent),SErrInvalidXMLInput,[Msg]);
  148. FPacket:=FXML.DocumentElement;
  149. NN:=UTF8Decode(DataName);
  150. if FPacket.NodeName<>NN then
  151. Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsInvalidContent),SErrInvalidXMLInput,[SErrMissingDocumentRoot]);
  152. FData:=FPacket;
  153. end;
  154. { TADOOutputStreamer }
  155. procedure TADOOutputStreamer.EndData;
  156. begin
  157. FData:=Nil;
  158. end;
  159. procedure TADOOutputStreamer.EndRow;
  160. begin
  161. FRow:=Nil;
  162. end;
  163. procedure TADOOutputStreamer.FinalizeOutput;
  164. begin
  165. {$IFNDEF VER3_0}
  166. if Not (ooHumanReadable in OutputOptions) then
  167. begin
  168. With TDOMWriter.Create(Stream,FXML) do
  169. try
  170. LineBreak:='';
  171. IndentSize:=0;
  172. WriteNode(FXML);
  173. finally
  174. Free;
  175. end;
  176. end
  177. else
  178. {$ENDIF}
  179. xmlwrite.WriteXML(FXML,Stream);
  180. FreeAndNil(FXML);
  181. end;
  182. procedure TADOOutputStreamer.StartData;
  183. begin
  184. // Rows are straight under the Data packet
  185. FData:=FRoot;
  186. end;
  187. procedure TADOOutputStreamer.StartRow;
  188. begin
  189. if (FRow<>Nil) then
  190. Raise ESQLDBRest.Create(Statuses.GetStatusCode(rsError),SErrDoubleRowStart);
  191. FRow:=FXML.CreateElement(UTF8Decode(RowName));
  192. FData.AppendChild(FRow);
  193. end;
  194. function TADOOutputStreamer.FieldToXML(aPair: TRestFieldPair): TDOMElement;
  195. Var
  196. F : TField;
  197. S : UTF8String;
  198. begin
  199. Result:=Nil;
  200. F:=aPair.DBField;;
  201. If (aPair.RestField.FieldType=rftUnknown) then
  202. raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsError),SErrUnsupportedRestFieldType, [aPair.RestField.PublicName]);
  203. If (F.IsNull) then
  204. Exit;
  205. S:=FieldToString(aPair.RestField.FieldType,F);
  206. Result:=FXML.CreateElement(UTF8Decode(aPair.RestField.PublicName));
  207. Result.AppendChild(FXML.CreateTextNode(UTF8Decode(S)));
  208. end;
  209. procedure TADOOutputStreamer.WriteField(aPair: TRestFieldPair);
  210. Var
  211. D : TDOMElement;
  212. N : UTF8String;
  213. begin
  214. N:=aPair.RestField.PublicName;
  215. if FRow=Nil then
  216. Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsError),SErrFieldWithoutRow,[N]);
  217. D:=FieldToXML(aPair);
  218. if (D=Nil) and (not HasOption(ooSparse)) then
  219. D:=FXML.CreateElement(UTF8Decode(aPair.RestField.PublicName));
  220. if D<>Nil then
  221. FRow.AppendChild(D);
  222. end;
  223. function TADOOutputStreamer.CreateXSD: TDomElement;
  224. // Create XSD and append to root. Return element to which field list must be appended.
  225. Var
  226. SN,N,E,TLN : TDomElement;
  227. begin
  228. SN:=FXML.CreateElement('xs:schema');
  229. SN['id']:=Utf8Decode(DataName);
  230. SN['xmlns']:='';
  231. SN['xmlns:xs']:='http://www.w3.org/2001/XMLSchema';
  232. SN['xmlns:msdata']:= 'urn:schemas-microsoft-com:xml-msdata';
  233. FRoot.AppendChild(SN);
  234. // Add table list with 1 table.
  235. // Element
  236. N:=FXML.CreateElement('xs:element');
  237. SN.AppendChild(N);
  238. N['name']:=UTF8Decode(DataName);
  239. N['msdata:IsDataSet']:='true';
  240. N['msdata:UseCurrentLocale']:='true';
  241. // element is a complex type
  242. TLN:=FXML.CreateElement('xs:complexType');
  243. N.AppendChild(TLN);
  244. // Complex type is a choice (0..Unbounded] of records
  245. N:=FXML.CreateElement('xs:choice');
  246. TLN.AppendChild(N);
  247. N['minOccurs']:='0';
  248. N['maxOccurs']:='unbounded';
  249. // Each record is an element
  250. E:=FXML.CreateElement('xs:element');
  251. N.AppendChild(E);
  252. E['name']:=Utf8Decode(RowName);
  253. // Record is a complex type of fields
  254. N:=FXML.CreateElement('xs:complexType');
  255. E.AppendChild(N);
  256. // Fields are a sequence. To this sequence, the fields may be appended.
  257. Result:=FXML.CreateElement('xs:sequence');
  258. N.AppendChild(Result);
  259. end;
  260. Const
  261. XMLPropTypeNames : Array [TRestFieldType] of string = (
  262. 'unknown', { rtfUnknown }
  263. 'xs:int', { rftInteger }
  264. 'xs:int', { rftLargeInt}
  265. 'xs:double', { rftFloat }
  266. 'xs:dateTime', { rftDate }
  267. 'xs:dateTime', { rftTime }
  268. 'xs:dateTime', { rftDateTime }
  269. 'xs:string', { rftString }
  270. 'xs:boolean', { rftBoolean }
  271. 'xs:base64Binary' { rftBlob }
  272. );
  273. procedure TADOOutputStreamer.WriteMetadata(aFieldList: TRestFieldPairArray);
  274. Var
  275. FMetadata : TDOMElement;
  276. F : TDomElement;
  277. P : TREstFieldPair;
  278. I : integer;
  279. S : Utf8String;
  280. K : TRestFieldType;
  281. begin
  282. FMetadata:=CreateXSD;
  283. For I:=0 to Length(aFieldList)-1 do
  284. begin
  285. P:=aFieldList[i];
  286. K:=P.RestField.FieldType;
  287. S:=XMLPropTypeNames[K];
  288. F:=FXML.CreateElement('xs:element');
  289. F['name']:=Utf8Decode(P.Restfield.PublicName);
  290. F['type']:=Utf8decode(S);
  291. F['minOccurs']:='0';
  292. FMetaData.AppendChild(F);
  293. end;
  294. end;
  295. class function TADOOutputStreamer.GetContentType: String;
  296. begin
  297. Result:='text/xml';
  298. end;
  299. function TADOOutputStreamer.RequireMetadata: Boolean;
  300. begin
  301. Result:=True;
  302. end;
  303. procedure TADOOutputStreamer.CreateErrorContent(aCode: Integer; const aMessage: String);
  304. Var
  305. ErrorObj : TDomElement;
  306. begin
  307. ErrorObj:=FXML.CreateElement(UTF8Decode(GetString(rpErrorRoot)));
  308. ErrorObj['code']:=UTF8Decode(IntToStr(aCode));
  309. ErrorObj['message']:=UTF8Decode(aMessage);
  310. FRoot.AppendChild(ErrorObj);
  311. end;
  312. destructor TADOOutputStreamer.Destroy;
  313. begin
  314. FreeAndNil(FXML);
  315. inherited Destroy;
  316. end;
  317. procedure TADOOutputStreamer.InitStreaming;
  318. begin
  319. FXML:=TXMLDocument.Create;
  320. FXML.XMLStandalone:=True;
  321. if DataName='' then
  322. DataName:='Data';
  323. FRoot:=FXML.CreateElement('Data');
  324. FXML.AppendChild(FRoot);
  325. if RowName='' then
  326. RowName:='Row';
  327. end;
  328. Initialization
  329. TADOInputStreamer.RegisterStreamer('ado');
  330. TADOOutputStreamer.RegisterStreamer('ado');
  331. end.