03.select.pas 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. Program select;
  2. {
  3. * PROGRAM: Object oriented API samples.
  4. * MODULE: 03.select.pas
  5. * DESCRIPTION:
  6. * A sample of running SELECT statement without parameters.
  7. * Prints string fields in a table, coercing VARCHAR to CHAR.
  8. * Learns how to coerce output data in prepared statement
  9. * and execute it.
  10. *
  11. * Example for the following interfaces:
  12. *
  13. * IStatement - SQL statement execution
  14. * IMessageMetadata - describe input and output data format
  15. * IResultSet - fetch data returned by statement after execution
  16. *
  17. * Run something like this to build the program :
  18. *
  19. * fpc -Fu./common -Fu/opt/firebird/include/firebird -FUlib 03.select.pas
  20. *
  21. *
  22. * The contents of this file are subject to the Initial
  23. * Developer's Public License Version 1.0 (the "License");
  24. * you may not use this file except in compliance with the
  25. * License. You may obtain a copy of the License at
  26. * https://www.ibphoenix.com/about/firebird/idpl.
  27. *
  28. * Software distributed under the License is distributed AS IS,
  29. * WITHOUT WARRANTY OF ANY KIND, either express or implied.
  30. * See the License for the specific language governing rights
  31. * and limitations under the License.
  32. *
  33. * The Original Code was created by Paul Reeves
  34. * for the Firebird Open Source RDBMS project.
  35. * Most of the code for GetOutput was taken from Denis
  36. * Simonov's UDR-Book project.
  37. *
  38. * Copyright (c) 2020 Paul Reeves <[email protected]>
  39. * and all contributors signed below.
  40. *
  41. * All Rights Reserved.
  42. * Contributor(s): ______________________________________. }
  43. {$mode Delphi}{$H+}
  44. Uses {$IFDEF UNIX} {$IFDEF UseCThreads}
  45. cthreads
  46. , {$ENDIF} {$ENDIF}
  47. SysUtils
  48. , Firebird
  49. , strutils
  50. , FbCharsets
  51. ;
  52. // Record to store received metadata
  53. Type
  54. TField = Record
  55. fieldname: String;
  56. fieldtype: Cardinal;
  57. fieldlength: Integer;
  58. offset: Integer;
  59. sqlnullind: Wordbool;
  60. charset: TFBCharSet;
  61. charLength: Integer;
  62. fieldvalue: String;
  63. End;
  64. Var
  65. // master and status are required for all access to the API.
  66. // This is main interface of firebird, and the only one
  67. // for getting which there is special function in our API
  68. master: IMaster;
  69. // Status is used to return error descriptions to user
  70. status: IStatus;
  71. // Provides some miscellaneous utilities.
  72. util: IUtil;
  73. // Provider is needed to start to work with database (or service)
  74. prov: IProvider;
  75. // Attachment and Transaction contain methods to work with
  76. // database attachment and transaction
  77. att: IAttachment;
  78. tra: ITransaction;
  79. tpb: IXpbBuilder;
  80. // to prepare an sql statement
  81. stmt: IStatement;
  82. // We geain access to the result set with a cursor
  83. curs: IResultSet;
  84. // Retrieve info about metadata of a statement
  85. meta: IMessageMetadata;
  86. builder: IMetadataBuilder;
  87. // Store the meta data of each field in the result set
  88. fields: Array Of TField;
  89. // Store the titles of each field in the result set
  90. title: String = '';
  91. // msg is a pointer to each row in the result set.
  92. msg: Pointer;
  93. msgLen: Cardinal;
  94. counter: Integer;
  95. Const
  96. // Firebird types
  97. SQL_VARYING = 448; // VARCHAR
  98. SQL_TEXT = 452; // CHAR
  99. Procedure PrintError(AMaster: IMaster; AStatus: IStatus);
  100. Var
  101. maxMessage: Integer;
  102. outMessage: PAnsiChar;
  103. Begin
  104. maxMessage := 256;
  105. outMessage := StrAlloc(maxMessage);
  106. AMaster.getUtilInterface.formatStatus(outMessage, maxMessage, AStatus);
  107. writeln(outMessage);
  108. StrDispose(outMessage);
  109. End;
  110. Function GetOutput(AStatus: IStatus; ABuffer: PByte; AMeta: IMessageMetadata; AUtil: IUtil;
  111. AFieldsArray: Array Of TField): UnicodeString;
  112. Var
  113. i: Integer;
  114. NullFlag: Wordbool;
  115. pData: PByte;
  116. CharBuffer: TBytes;
  117. StringValue: UnicodeString;
  118. current_field: TField;
  119. Begin
  120. Result := '';
  121. For i := 0 To length(AFieldsArray) - 1 Do Begin
  122. current_field := AfieldsArray[i];
  123. With current_field Do Begin
  124. NullFlag := PWordBool(ABuffer + AMeta.getNullOffset(AStatus, i))^;
  125. If NullFlag Then Begin
  126. StringValue := 'NULL';
  127. continue;
  128. End;
  129. // get a pointer to the field data
  130. pData := ABuffer + AMeta.getOffset(AStatus, i);
  131. pData := ABuffer + offset;
  132. Case fieldType Of
  133. SQL_VARYING:
  134. Begin
  135. SetLength(CharBuffer, fieldLength);
  136. // For VARCHAR, the first 2 bytes are the length
  137. charLength := PSmallint(pData)^;
  138. // For VARCHAR, the first 2 bytes are the length in bytes
  139. // so we copy it to the buffer starting at 3 bytes
  140. Move((pData + 2)^, CharBuffer[0], fieldLength);
  141. StringValue := charset.GetString(CharBuffer, 0, charLength);
  142. End;
  143. Else
  144. StringValue := ' Fieldtype not handled.';
  145. End; // case fieldType of
  146. If Result = '' Then
  147. Result := Result + UnicodeString(PadRight(UTF8Encode(StringValue), fieldLength))
  148. Else
  149. Result := Result + ' ' + UnicodeString(PadRight(UTF8Encode(StringValue), fieldLength));
  150. End; // end with current_field
  151. End; // for i := 0 to length(AFieldsArray) - 1 do begin
  152. End; // function GetOutput
  153. Begin
  154. master := fb_get_master_interface;
  155. status := master.getStatus;
  156. // Here we get access to the helper utility interfaces
  157. // no errors can occur - this function will always succeed
  158. util := master.getUtilInterface;
  159. // the main dispatcher is returned by a call to IMaster
  160. // no errors can occur - this function will always succeed
  161. prov := master.getDispatcher;
  162. Try
  163. Try
  164. // attach to employee db
  165. // We assume that ISC_USER and ISC_PASSWORD env vars are set. Otherwise,
  166. // see code in 01.create for an example of setting the un/pw via the dpb.
  167. att := prov.attachDatabase(status, 'employee', 0, nil);
  168. writeln('Attached to database employee.fdb');
  169. // start read only transaction
  170. tpb := util.getXpbBuilder(status, IXpbBuilder.TPB, nil, 0);
  171. tpb.insertTag(status, isc_tpb_read_committed);
  172. tpb.insertTag(status, isc_tpb_no_rec_version);
  173. tpb.insertTag(status, isc_tpb_wait);
  174. tpb.insertTag(status, isc_tpb_read);
  175. // start transaction
  176. tra := att.startTransaction(status, tpb.getBufferLength( status ), tpb.getBuffer( status ));
  177. // prepare statement
  178. stmt := att.prepare(status, tra, 0, 'Select last_name, first_name, phone_ext from phone_list ' +
  179. 'where location = ''Monterey'' order by last_name, first_name', 3,
  180. IStatement.PREPARE_PREFETCH_METADATA);
  181. // get list of columns
  182. meta := stmt.getOutputMetadata(status);
  183. builder := meta.getBuilder(status);
  184. SetLength(fields, meta.getCount(status));
  185. // parse columns list & coerce datatype(s)
  186. For counter := 0 To length(fields) - 1 Do Begin
  187. If ((meta.getType(status, counter) = (SQL_VARYING Or SQL_TEXT))) Then
  188. builder.setType(status, counter, SQL_TEXT);
  189. fields[counter].fieldname := meta.getField(status, counter);
  190. End;
  191. // release automatically created metadata
  192. // metadata is not database object, therefore no specific call to close it
  193. meta.Release;
  194. // get metadata with coerced datatypes
  195. meta := builder.getMetadata(status);
  196. // builder is no longer needed
  197. builder.Release;
  198. builder := nil;
  199. // now get field info
  200. For counter := 0 To length(fields) - 1 Do Begin
  201. If fields[counter].fieldname <> '' Then Begin
  202. fields[counter].fieldlength := meta.getLength(status, counter);
  203. fields[counter].offset := meta.getOffset(status, counter);
  204. fields[counter].fieldType := meta.getType(status, counter) And Not 1;
  205. Case fields[counter].fieldType Of
  206. SQL_TEXT, SQL_VARYING:
  207. fields[counter].charset := TFBCharSet(meta.getCharSet(status, counter));
  208. Else
  209. ;
  210. End;
  211. // Set the title line for later use.
  212. If title = '' Then
  213. title := title + fields[counter].fieldname.PadRight(fields[counter].fieldlength)
  214. Else
  215. title := title + ' ' + fields[counter].fieldname.PadRight(fields[counter].fieldlength);
  216. End;
  217. End;
  218. // open cursor
  219. curs := stmt.openCursor(status, tra, nil, nil, meta, 0);
  220. // allocate output buffer
  221. msgLen := meta.getMessageLength(status);
  222. msg := AllocMem(msgLen);
  223. counter := 0;
  224. While curs.fetchNext(status, msg) = IStatus.RESULT_OK Do Begin
  225. If ((counter Mod 10) = 0) Then Begin
  226. writeln('');
  227. writeln(title);
  228. End;
  229. Inc(counter);
  230. WriteLn(GetOutput(status, msg, meta, util, fields));
  231. End;
  232. // What is correct way to close and release?
  233. // close interfaces
  234. curs.Close(status);
  235. stmt.Free(status);
  236. meta.Release();
  237. tra.commit(status);
  238. att.detach(status);
  239. Except
  240. on e: FbException Do
  241. PrintError(master, e.getStatus);
  242. End;
  243. Finally
  244. If assigned(meta) Then
  245. meta.Release;
  246. If assigned(builder) Then
  247. builder.Release;
  248. If assigned(curs) Then
  249. curs.Release;
  250. If assigned(stmt) Then
  251. stmt.Release;
  252. If assigned(tra) Then
  253. tra.Release;
  254. If assigned(att) Then
  255. att.Release;
  256. If assigned(tpb) Then
  257. tpb.dispose;
  258. prov.Release;
  259. status.dispose;
  260. End;
  261. End.