Windows.SQL.Mod 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. (* OBERON System 3, Release 2.3.
  2. Copyright 1999 ETH Zürich Institute for Computer Systems,
  3. ETH Center, CH-8092 Zürich. e-mail: oberon@inf.ethz.ch.
  4. This module may be used under the conditions of the general Oberon
  5. System 3 license contract. The full text can be downloaded from
  6. "ftp://ftp.inf.ethz.ch/pub/software/Oberon/System3/license.txt;A"
  7. Under the license terms stated it is in particular (a) prohibited to modify
  8. the interface of this module in any way that disagrees with the style
  9. or content of the system and (b) requested to provide all conversions
  10. of the source code to another platform with the name OBERON. *)
  11. MODULE SQL; (** non-portable / source: Windows.SQL.Mod *) (** MAD **)
  12. IMPORT ODBC, Modules, KernelLog, SYSTEM, Files, Kernel, Heaps;
  13. CONST
  14. NTS = -3;
  15. DataAtExec = -2;
  16. BlockSize = 1024;
  17. (** result codes **)
  18. Error* = -1; (** error occured while executin last operation **)
  19. Success* = 0; (** last operation completed successfully **)
  20. SuccessWithInfo* = 1; (** last operation completed successfully, but information available with EnumErrors **)
  21. NeedData* = 99; (** execution of statement needs more data, eg not all paramters bound to statement **)
  22. NoDataFound* = 100; (** Fetch could not retrieve more data **)
  23. (** parameter types used for ParamType.inOut **)
  24. InParam* = 1; (** parameter used only for input, ie to put data into database **)
  25. InOutParam* = 2; (** parameter used both for input and output **)
  26. OutParam* = 4; (** parameter used only for output, ie. to retrieve data from database **)
  27. (** SQL data types **)
  28. SqlBitType* = -7; (** single bit binary data **)
  29. SqlTinyIntType* = -6; (** SHORTINT **)
  30. SqlBigIntType* = -5; (** 64 bit integer data **)
  31. SqlLongVarBinaryType* = -4; (** variable length binary data, maximum length is data source dependant **)
  32. SqlVarBinaryType* = -3; (** variable length binary data of maximum length n (1 <= n <= 255) **)
  33. SqlBinaryType* = -2; (** binary data of fixed length n (1<= n <=255) **)
  34. SqlLongVarCharType* = -1; (** variable length character data, maximum length is data source dependant **)
  35. SqlCharType* = 1; (** character string of fixed length n (1 <= n <= 254) **)
  36. SqlNumericType* = 2; (** signed exact numeric value with a precision p and scale s (1<=p<=15, 0<=s<=p) **)
  37. SqlDecimalType* = 3; (** signed exact numeric value with a precision p and scale s (1<=p<=15, 0<=s<=p) **)
  38. SqlLongIntType* = 4; (** LONGINT **)
  39. SqlIntType* = 5; (** INTEGER **)
  40. SqlFloatType* = 6; (** LONGREAL **)
  41. SqlRealType* = 7; (** REAL **)
  42. SqlLongRealType* = 8; (** LONGREAL **)
  43. SqlDateType* = 9; (** date data (yyyy-mm-dd) **)
  44. SqlTimeType* = 10; (** time data (hh:mm:ss) **)
  45. SqlTimeStampType* = 11; (** date/time data (yyyy-mm-dd hh:mm:ss[f..]) **)
  46. SqlVarCharType* = 12; (** variable length character string with maximum string length n (1 <= n <= 254) **)
  47. (** Oberon data types **)
  48. CharType* = 1; (* SqlCharType *)
  49. LongIntType* = 4; (* SqlLongIntType *)
  50. IntType* = 5; (* SqlIntType *)
  51. ShortIntType* = -6; (* SqlTinyIntType *)
  52. RealType* = 7; (* SqlRealType *)
  53. LongRealType* = 8; (* SqlLongRealType *)
  54. DateType* = 9; (* SqlDateType *)
  55. TimeType* = 10; (*SqlTimeType *)
  56. TimeStampType* = 11; (* SqlTimeStampType *)
  57. FileType* = -4; (* SqlLongVarBinaryType *)
  58. BinaryType* = -2; (* SqlBinaryType *)
  59. BooleanType* = -7; (* SqlBitType *)
  60. (** All these came from FreePascal *)
  61. FetchNext* = 1;
  62. FetchFirst* = 2;
  63. FetchLast* = 3;
  64. FetchPrevious* = 4;
  65. FetchAbsolute* = 5;
  66. FetchRelative* = 6;
  67. SQLAttrCursorScrollable = -(1);
  68. SQLNonScrollable = 0;
  69. SQLScrollable = 1;
  70. SQLAttrODBCCursors = 110;
  71. SQLCursorUseIfNeeded = 0;
  72. SQLCursorUseODBC = 1;
  73. SQLCursorUseDriver = 2;
  74. TYPE
  75. (** enumerate procedure used for EnumDataSources and EnumDrivers **)
  76. SourcesHandler* = PROCEDURE(name, desc: ARRAY OF CHAR);
  77. (** enumerate procedure used for EnumErrors **)
  78. ErrorProc* = PROCEDURE(state, msg: ARRAY OF CHAR; errorcode: LONGINT);
  79. (** database connection handle **)
  80. Connection* = POINTER TO ConnectionDesc;
  81. (** SQL statement handle **)
  82. Statement* = POINTER TO StatementDesc;
  83. (** row handle
  84. Whenever you prepare a SQL statement that delievers any data (SELECT statement) a Row is created. A Row contains
  85. a list of records which are extensions of a base type Field. Each such Field in the Row represents a column in the
  86. result set of the SQL statement, so they are used to access the data. The data in a result row only valid after
  87. executing the statement (Execute) and fetching data (Fetch).
  88. A Row must also be created if you want to execute a SQL statement which needs some parameters, ie. if you call
  89. a stored procedure or execute an INSERT statement with data at execution (for example"INSERT INTO person
  90. VALUES (?, ?)" where the question marks signal that the corresponding data is set in the parameter row). This
  91. parameter row must be created with the rocedure BindParamaters after a call to PrepareStataement and before
  92. you execute the statement with with procedure Execute. The data in a parameter row muts be set before executing
  93. the statement, and if a parameter is used to get it data the retrieved data is valid after executing the statement **)
  94. Row* = POINTER TO RowDesc;
  95. ErrBuff = POINTER TO ErrBuffDesc;
  96. ConnectionDesc = RECORD
  97. hdbc: ODBC.HDBC;
  98. closed: BOOLEAN;
  99. stmt: Statement;
  100. res*: INTEGER;
  101. END;
  102. StatementDesc = RECORD
  103. next: Statement;
  104. hstmt: ODBC.HSTMT;
  105. c: Connection;
  106. results, params: Row;
  107. firstExec, errBuffered: BOOLEAN;
  108. error: ErrBuff;
  109. res*: INTEGER;
  110. END;
  111. ErrBuffDesc = RECORD
  112. state: ARRAY 6 OF CHAR;
  113. msg: ARRAY 512 OF CHAR;
  114. native: LONGINT;
  115. next: ErrBuff
  116. END;
  117. (** base type of each field in a result set or a parameter list **)
  118. Field* = POINTER TO FieldDesc;
  119. FieldDesc* = RECORD
  120. next, prev: Field;
  121. dir: INTEGER; (* in/out/inout *)
  122. name*: ARRAY 32 OF CHAR; (** name of column **)
  123. len*: LONGINT; (** maximum number of characters needed to represent data, only valid for Fields in a result set
  124. (ie. not for parameters), and only between a call to PrepareStatement and Execute **)
  125. sqlType*: INTEGER; (** SQL type of column (needed because different SQL types are mapped into same
  126. field type) **)
  127. isNull*: BOOLEAN; (** get/set if field is NULL **)
  128. nullable*: BOOLEAN (** determine if field is nullable **)
  129. END;
  130. (** field extension to hold integer data **)
  131. IntField* = POINTER TO IntFieldDesc;
  132. IntFieldDesc* = RECORD(FieldDesc)
  133. i*: LONGINT
  134. END;
  135. (*ALEX 2005.10.20*)
  136. (** field extension to hold character data for numeric(n,m) fields **)
  137. NumericField* = POINTER TO NumericFieldDesc;
  138. NumericFieldDesc* = RECORD(FieldDesc)
  139. str*: ARRAY 256 OF CHAR
  140. END;
  141. (** field extension to hold character data (SQLCharType and SQLVarCharType) **)
  142. StringField* = POINTER TO StringFieldDesc;
  143. StringFieldDesc* = RECORD(FieldDesc)
  144. str*: ARRAY 256 OF CHAR
  145. END;
  146. (** field extension to hold floating point data (SQLFloatType, SQLRealType and SQLLongRealType) **)
  147. RealField* = POINTER TO RealFieldDesc;
  148. RealFieldDesc* = RECORD(FieldDesc)
  149. r*: LONGREAL
  150. END;
  151. (** field extension to hold date (SQLDateType) **)
  152. DateField* = POINTER TO DateFieldDesc;
  153. DateFieldDesc* = RECORD(FieldDesc)
  154. year*, month*, day*: INTEGER
  155. END;
  156. (** field extension to hold time (SQLTimeType) **)
  157. TimeField* = POINTER TO TimeFieldDesc;
  158. TimeFieldDesc* = RECORD(FieldDesc)
  159. hour*, minute*, second*: INTEGER
  160. END;
  161. (** field extension to hold time stamp (SQLTimeStampType) **)
  162. TimeStampField* = POINTER TO TimeStampFieldDesc;
  163. TimeStampFieldDesc* = RECORD(FieldDesc)
  164. year*, month*, day*, hour*, minute*, second*: INTEGER;
  165. fraction*: LONGINT
  166. END;
  167. (** field extension to hold long data like SQLLongVarCharType or SQLLongBinaryType **)
  168. FileField* = POINTER TO FileFieldDesc;
  169. FileFieldDesc* = RECORD(FieldDesc)
  170. f*: Files.File
  171. END;
  172. (** field extension to hold binary data **)
  173. BinaryField* = POINTER TO BinaryFieldDesc;
  174. BinaryFieldDesc* = RECORD(FieldDesc)
  175. b*: ARRAY 256 OF SYSTEM.BYTE
  176. END;
  177. (** field extension to hold boolean data **)
  178. BooleanField* = POINTER TO BooleanFieldDesc;
  179. BooleanFieldDesc* = RECORD(FieldDesc)
  180. b*: BOOLEAN
  181. END;
  182. SentinelField = POINTER TO SentinelFieldDesc;
  183. SentinelFieldDesc = RECORD(FieldDesc)
  184. END;
  185. (** handle for SQL statement results and parameters **)
  186. RowDesc* = RECORD
  187. dsc: Field;
  188. cols*: INTEGER
  189. END;
  190. (** parameter description record **)
  191. ParamType* = RECORD
  192. oberonType*, sqlType*, inOut*: INTEGER;
  193. name*: ARRAY 32 OF CHAR;
  194. END;
  195. VAR
  196. (** result code of last operation **)
  197. (*res*: INTEGER;*)
  198. (* ----------------------- Row handling ----------------------- *)
  199. PROCEDURE AllocRow(VAR row: Row);
  200. VAR sentinel: SentinelField;
  201. BEGIN
  202. NEW(row); NEW(sentinel); row.dsc:= sentinel;
  203. sentinel.next:= sentinel; sentinel.prev:= sentinel
  204. END AllocRow;
  205. PROCEDURE AppendField(r: Row; f: Field);
  206. BEGIN
  207. r.dsc.prev.next:= f; f.prev:= r.dsc.prev; f.next:= r.dsc; r.dsc.prev:= f
  208. END AppendField;
  209. (** set f to first field in row r **)
  210. PROCEDURE FirstField*(r: Row; VAR f: Field);
  211. BEGIN
  212. IF r.dsc.next = r.dsc THEN f:= NIL ELSE f:= r.dsc.next END
  213. END FirstField;
  214. (** get next field in row containing f, NIL if there are no more fields **)
  215. PROCEDURE NextField*(VAR f: Field);
  216. BEGIN
  217. IF f.next IS SentinelField THEN f:= NIL ELSE f:=f.next END
  218. END NextField;
  219. (** get previous field in row containing f, NIL if there is no previous field **)
  220. PROCEDURE PrevField*(VAR f: Field);
  221. BEGIN
  222. IF f.prev IS SentinelField THEN f:= NIL ELSE f:= f.prev END
  223. END PrevField;
  224. (** find field named name in row r **)
  225. PROCEDURE FindField*(r: Row; name: ARRAY OF CHAR; VAR f: Field);
  226. VAR cur: Field;
  227. BEGIN
  228. cur:= r.dsc.next;
  229. WHILE ~(cur IS SentinelField) DO
  230. IF cur.name = name THEN f:= cur; RETURN END;
  231. cur:= cur.next
  232. END;
  233. f:= NIL
  234. END FindField;
  235. (* -------------------------------------------------------------------------- *)
  236. PROCEDURE PrintError(state, msg: ARRAY OF CHAR; errorCode: LONGINT);
  237. BEGIN
  238. KernelLog.String(state); KernelLog.String(msg); KernelLog.Ln
  239. END PrintError;
  240. PROCEDURE DummyEnum(state, msg: ARRAY OF CHAR; code: LONGINT);
  241. END DummyEnum;
  242. PROCEDURE InsertError(stmt: Statement; state, msg: ARRAY OF CHAR; nativeCode: LONGINT);
  243. VAR err: ErrBuff;
  244. BEGIN
  245. NEW(err); stmt.errBuffered:= TRUE; err.next:= stmt.error; stmt.error:= err;
  246. COPY(state, err.state); COPY(msg, err.msg); err.native:= nativeCode
  247. END InsertError;
  248. (** enumerate all errors belonging to connection c and statement s. IF s = NIL and c # NIL then all errors for
  249. connection c are enumerated. If both s and c are NIL then all errors belonging to SQL and ODBC themselves
  250. are enumerated **)
  251. PROCEDURE EnumErrors*(c: Connection; s: Statement; enum: ErrorProc);
  252. VAR localErr: ErrBuff; errorState: ARRAY 6 OF CHAR; errorMsg: ARRAY 512 OF CHAR; nativeError: LONGINT;
  253. BEGIN
  254. IF s # NIL THEN
  255. IF s.errBuffered THEN
  256. localErr:= s.error;
  257. WHILE localErr # NIL DO
  258. enum(s.error.state, s.error.msg, s.error.native); localErr:= localErr.next
  259. END;
  260. s.errBuffered:= FALSE; s.error:= NIL
  261. END;
  262. ODBC.StatementError(s.hstmt, errorState, nativeError, errorMsg, s.res);
  263. WHILE s.res # 100 DO
  264. enum(errorState, errorMsg, nativeError);
  265. ODBC.StatementError(s.hstmt, errorState, nativeError, errorMsg, s.res);
  266. END;
  267. ODBC.ConnectionError(s.c.hdbc, errorState, nativeError, errorMsg, s.res);
  268. WHILE s.res # 100 DO
  269. enum(errorState, errorMsg, nativeError);
  270. ODBC.ConnectionError(s.c.hdbc, errorState, nativeError, errorMsg, s.res);
  271. END
  272. ELSIF c # NIL THEN
  273. ODBC.ConnectionError(c.hdbc, errorState, nativeError, errorMsg, c.res);
  274. WHILE c.res # 100 DO
  275. enum(errorState, errorMsg, nativeError);
  276. ODBC.ConnectionError(c.hdbc, errorState, nativeError, errorMsg, c.res);
  277. END
  278. END
  279. END EnumErrors;
  280. PROCEDURE FinalizeConnection(obj: ANY);
  281. VAR c: Connection;
  282. BEGIN
  283. c:= obj(Connection); KernelLog.String("SQL.FinalizeConnection: ");
  284. IF ~c.closed THEN
  285. KernelLog.String("closing connection"); KernelLog.Ln;
  286. ODBC.Commit(c.hdbc, c.res);
  287. ODBC.Disconnect(c.hdbc, c.res);
  288. ODBC.FreeConnect(c.hdbc, c.res)
  289. ELSE
  290. KernelLog.String("connection already closed"); KernelLog.Ln
  291. END
  292. END FinalizeConnection;
  293. PROCEDURE Terminate;
  294. BEGIN
  295. (* call garbage collector to finalize all registered connections *)
  296. KernelLog.String("SQL.Terminate: calling Kernel.GC"); KernelLog.Ln;
  297. Kernel.GC;
  298. KernelLog.String("SQL.Terminate: calling Kernel.GC a second time"); KernelLog.Ln;
  299. Kernel.GC
  300. END Terminate;
  301. (** open a connection to database source **)
  302. PROCEDURE Open*(source, user, passwd: ARRAY OF CHAR): Connection;
  303. VAR connect: ODBC.HDBC; connection: Connection;
  304. finalizerNode: Heaps.FinalizerNode;
  305. BEGIN
  306. NEW(connection); NEW(connection.hdbc); connection.stmt:= NIL; connection.closed:= FALSE;
  307. ODBC.AllocConnect(connection.hdbc, connection.res);
  308. ODBC.SetConnectAttr(connection.hdbc, SQLAttrODBCCursors, SQLCursorUseODBC, 0, connection.res);
  309. ODBC.Connect(connection.hdbc, source, user, passwd, connection.res);
  310. (*ALEX 2005.11.14*)
  311. NEW(finalizerNode); finalizerNode.finalizer := FinalizeConnection;
  312. Heaps.AddFinalizer(connection, finalizerNode);
  313. RETURN connection
  314. END Open;
  315. (** close a connection **)
  316. PROCEDURE Close*(c: Connection);
  317. VAR stmt: Statement;
  318. BEGIN
  319. (* Free all statements, disconnect and free connection *)
  320. stmt:= c.stmt; c.closed:= TRUE;
  321. (*
  322. WHILE stmt # NIL DO
  323. ODBC.FreeStmt(stmt.hstmt, 1); stmt:= stmt.next (* opt = 1: drop statement and all resources associated with it *)
  324. END;
  325. *)
  326. ODBC.Disconnect(c.hdbc, c.res);
  327. ODBC.FreeConnect(c.hdbc, c.res);
  328. END Close;
  329. (** bind parameter fields to statement s. The array types contains a description of each paramete:
  330. types[i].oberonType determines the type to be used in Obeorn, ie. what sort of Field (IntField, FielField, etc) should
  331. be added to paramRow
  332. types[i].sqlType determines the type the parameter uses in the data source (SqlInt, SqlLongVarBinary, etc)
  333. types[i].inOut determines for which operation the parameter is used: to put data into the DB -> InParam (parameter
  334. is used in an INSERT statement or in a stored procedure), to retrieve data from the DB -> OutParam (parameter is
  335. used in a stored procedure the get data), or to put and get data -> InOutParam (parameter is used in a stored
  336. procedure for input an output)
  337. types[i].name can be used to name the parameter, ie you can find the corresponding Field in paramRow with
  338. the procedure FindField
  339. A row containing a Field for each parameter is returned in paramRow **)
  340. PROCEDURE BindParameters*(s: Statement; types: ARRAY OF ParamType; numParams: INTEGER; VAR paramRow: Row);
  341. TYPE arr6 = ARRAY 6 OF CHAR; arr16 = ARRAY 16 OF CHAR;
  342. VAR i, parType: INTEGER; if: IntField; sf: StringField; rf: RealField; df: DateField; tf: TimeField; tsf: TimeStampField;
  343. ff: FileField; bf: BinaryField; boolf: BooleanField; buffer: ARRAY BlockSize OF CHAR;
  344. BEGIN
  345. AllocRow(s.params); s.params.cols:= numParams;
  346. FOR i:= 0 TO numParams-1 DO
  347. (* insert field into params row of statement *)
  348. (* Out.String("binding param "); Out.Int(i+1, 1); Out.Ln; *)
  349. CASE types[i].oberonType OF
  350. CharType:
  351. NEW(sf); AppendField(s.params, sf);
  352. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, types[i].oberonType, types[i].sqlType, 255, 0, sf.str, sf.len, s.res)
  353. (* sf.len will be set to NTS before executing the statement *)
  354. | LongIntType, IntType, ShortIntType:
  355. NEW(if); AppendField(s.params, if);
  356. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, types[i].oberonType, types[i].sqlType, 0, 0, if.i, if.len, s.res)
  357. | RealType, LongRealType:
  358. NEW(rf); AppendField(s.params, rf);
  359. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, types[i].oberonType, types[i].sqlType, 0, 0, rf.r, rf.len, s.res)
  360. | DateType:
  361. NEW(df); AppendField(s.params, df);
  362. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, types[i].oberonType, types[i].sqlType, 0, 0,
  363. SYSTEM.VAL(arr6, df.year), df.len, s.res)
  364. | TimeType:
  365. NEW(tf); AppendField(s.params, tf);
  366. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, types[i].oberonType, types[i].sqlType, 0, 0,
  367. SYSTEM.VAL(arr6, tf.hour), tf.len, s.res)
  368. | TimeStampType:
  369. NEW(tsf); AppendField(s.params, tsf);
  370. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, types[i].oberonType, types[i].sqlType, 0, 0,
  371. SYSTEM.VAL(arr16, tsf.year), tsf.len, s.res)
  372. | FileType:
  373. NEW(ff); ff.f:= Files.New(""); AppendField(s.params, ff);
  374. (* to be compatible with drivers which don't have convert functions, require that the data in file
  375. is already in right format *)
  376. IF (types[i].sqlType = SqlVarCharType) OR (types[i].sqlType = SqlLongVarCharType) THEN
  377. parType:= CharType
  378. ELSIF (types[i].sqlType = SqlVarBinaryType) OR (types[i].sqlType = SqlLongVarBinaryType) THEN
  379. parType:= BinaryType
  380. ELSIF (types[i].sqlType = SqlFloatType) THEN
  381. parType:= RealType
  382. ELSIF (types[i].sqlType = SqlNumericType) OR (types[i].sqlType = SqlDecimalType) THEN
  383. parType:= CharType
  384. ELSIF types[i].sqlType = SqlBigIntType THEN
  385. parType:= CharType
  386. ELSE
  387. parType:= types[i].sqlType
  388. END;
  389. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, parType, types[i].sqlType, (*BlockSize*) 0, 0, buffer, ff.len, s.res)
  390. (* ff.len will be set to DataAtExec before executing the statement *)
  391. | BinaryType:
  392. NEW(bf); AppendField(s.params, bf);
  393. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, types[i].oberonType, types[i].sqlType, 255, 0, bf.b, bf.len, s.res)
  394. | BooleanType:
  395. NEW(boolf); AppendField(s.params, boolf);
  396. ODBC.BindParameter(s.hstmt, i+1, types[i].inOut, types[i].oberonType, types[i].sqlType, 255, 0, boolf.b, boolf.len, s.res)
  397. ELSE HALT(99)
  398. END;
  399. s.params.dsc.prev.dir:= types[i].inOut; COPY(types[i].name, s.params.dsc.prev.name);
  400. IF s.res < 0 THEN s.params:= NIL; paramRow:= NIL; (* EnumErrors(s.c, s, PrintError); *) RETURN END
  401. END;
  402. paramRow:= s.params
  403. END BindParameters;
  404. (** prepares a SQL statement for execution. If sqlStatement returns any results (eg. a SELECT-statement) resultRow
  405. will point to row conataining a field for every element of the result set, else row is set to NIL. After PrepareStatement
  406. the record field len of each element of resultRow contains the maximum number of characters needed to represent
  407. the corresponding data. The value in this record field len will only be valid until a call to Execute, so if you need
  408. this data you must check it between the calls to PrepareStatement and to Execute. **)
  409. PROCEDURE PrepareStatement*(c: Connection; sqlStatement: ARRAY OF CHAR; VAR resultRow: Row): Statement;
  410. TYPE arr6 = ARRAY 6 OF CHAR; arr16 = ARRAY 16 OF CHAR;
  411. VAR stmt: Statement; cols, i, type, scale, oldres: INTEGER; name: ARRAY 256 OF CHAR; nullable: BOOLEAN;
  412. prec, nativeBuf: LONGINT; stateBuf: ARRAY 6 OF CHAR; msgBuf: ARRAY 512 OF CHAR;
  413. if: IntField; sf: StringField; rf: RealField; df: DateField; tf: TimeField; tsf: TimeStampField; cur: Field;
  414. bf: BinaryField; ff: FileField; boolf: BooleanField;
  415. nf: NumericField;
  416. BEGIN
  417. NEW(stmt); NEW(stmt.hstmt); ODBC.AllocStmt(c.hdbc, stmt.hstmt, stmt.res);
  418. stmt.next:= c.stmt; c.stmt:= stmt;
  419. stmt.c:= c; stmt.results:= NIL; stmt.params:= NIL; stmt.firstExec:= TRUE; stmt.errBuffered:= FALSE; resultRow:= NIL;
  420. (*ALEX 2005.12.06 - commented out because of sybase not suporting this property*)
  421. (*
  422. ODBC.SetStmtAttr(stmt.hstmt, SQLAttrCursorScrollable, SQLScrollable, 0, stmt.res);
  423. IF stmt.res # Success THEN
  424. KernelLog.String('SetStmtAttr:ODBC.res: '); KernelLog.Int(stmt.res,0); KernelLog.Ln;
  425. EnumErrors(c, stmt, PrintError)
  426. END;
  427. *)
  428. ODBC.Prepare(stmt.hstmt, sqlStatement, stmt.res);
  429. IF stmt.res < 0 THEN
  430. RETURN stmt
  431. ELSE
  432. (* As the Prepare function is not guaranteed to do the same work on all the different ODBC database drivers
  433. it is not always sufficient calling ODBC.Prepare to get all needed information about the result set. So we also
  434. need to make a call to ODBC.Execute. Afterwards a call to ODBC.NumResultCols and ODBC.DescribeCol
  435. should work with every ODBC database (except MicroSoft included some other features) *)
  436. ODBC.Execute(stmt.hstmt, stmt.res);
  437. IF stmt.res < 0 THEN
  438. (* check why error occured: if parameters are missing (state = 07001) just ignore error, else buffer error and
  439. exit prepare *)
  440. (* Out.String("ODBC.res = "); Out.Int(ODBC.res, 1); Out.Ln; *)
  441. oldres:= stmt.res; ODBC.StatementError(stmt.hstmt, stateBuf, nativeBuf, msgBuf, stmt.res);
  442. (* Out.String("Error in PrepareStatement: "); Out.Ln; Out.String(stateBuf); Out.String(msgBuf); Out.Ln; *)
  443. IF stateBuf = "07001" THEN
  444. (* ignore error, but read remaining errors of statement *)
  445. (* Out.String("error ignored, continuing PrepareStatement"); Out.Ln; *)
  446. EnumErrors(c, stmt, DummyEnum); stmt.errBuffered:= FALSE; stmt.firstExec:= FALSE
  447. ELSE
  448. (* 'real' error *)
  449. (* Out.String("aborting PrepareStatement"); Out.Ln; *)
  450. stmt.errBuffered:= TRUE; NEW(stmt.error);
  451. COPY(stateBuf, stmt.error.state); COPY(msgBuf, stmt.error.msg); stmt.error.native:= nativeBuf;
  452. stmt.firstExec:= FALSE; stmt.res:= oldres; RETURN stmt
  453. END
  454. END;
  455. ODBC.NumResultCols(stmt.hstmt, cols, stmt.res);
  456. IF cols > 0 THEN
  457. AllocRow(resultRow); resultRow.cols:= cols;
  458. (* Out.String("Anzahl ResultCols: "); Out.Int(cols, 5); Out.Ln; *)
  459. FOR i:= 1 TO cols DO
  460. ODBC.DescribeCol(stmt.hstmt, i, name, type, prec, scale, nullable, stmt.res);
  461. (* Out.String(name); Out.Char(9X); Out.Int(type, 5); Out.Char(9X); Out.Int(prec, 5); Out.Char(9X);
  462. Out.Int(scale, 5); Out.Char(9X); IF nullable THEN Out.String("nullable") ELSE Out.String("not nullable") END;
  463. Out.Ln; *)
  464. CASE type OF
  465. 1, 12: NEW(sf); AppendField(resultRow, sf); sf.len:= prec;
  466. ODBC.BindCol(stmt.hstmt, i, CharType, sf.str, sf.len, stmt.res);
  467. | 2, 3: NEW(nf); AppendField(resultRow, nf); nf.len:= prec + 2; (* prec digits, sign, decimal point *)
  468. ODBC.BindCol(stmt.hstmt, i, CharType, nf.str, nf.len, stmt.res);
  469. (*ALEX 2005.10.20 modified from StringField to NumericField*)
  470. | 4, 5, -6: NEW(if); AppendField(resultRow, if);
  471. IF type = 4 THEN if.len:= 11 ELSIF type = 5 THEN if.len:= 6 ELSE if.len:= 4 END;
  472. ODBC.BindCol(stmt.hstmt, i, LongIntType, if.i, if.len, stmt.res);
  473. | 6, 7, 8: NEW(rf); AppendField(resultRow, rf);
  474. IF type = 7 THEN rf.len:= 13 ELSE rf.len:= 22 END;
  475. ODBC.BindCol(stmt.hstmt, i, LongRealType, rf.r, rf.len, stmt.res);
  476. | 9: NEW(df); AppendField(resultRow, df); df.len:= prec;
  477. ODBC.BindCol(stmt.hstmt, i, DateType, SYSTEM.VAL(arr6, df.year), df.len, stmt.res);
  478. | 10: NEW(tf); AppendField(resultRow, tf); tf.len:= prec;
  479. ODBC.BindCol(stmt.hstmt, i, TimeType, SYSTEM.VAL(arr6, tf.hour), tf.len, stmt.res);
  480. | 11: NEW(tsf); AppendField(resultRow, tsf); IF scale > 0 THEN tsf.len:= 20+scale ELSE tsf.len:= 19 END;
  481. ODBC.BindCol(stmt.hstmt, i, TimeStampType, SYSTEM.VAL(arr16, tsf.year), tsf.len, stmt.res);
  482. | -2, -3: NEW(bf); AppendField(resultRow, bf); bf.len:= prec;
  483. ODBC.BindCol(stmt.hstmt, i, LongIntType, bf.b, bf.len, stmt.res);
  484. | -1, -4: NEW(ff); ff.f:= Files.New(""); AppendField(resultRow, ff); ff.len:= prec;
  485. (* don't bind this column, but get the data with ODBC.GetData *)
  486. (* ODBC.BindCol(stmt.hstmt, i, LongIntType, ff.f, ff.len); *)
  487. | -5: NEW(sf); AppendField(resultRow, sf); sf.len:= 20;
  488. ODBC.BindCol(stmt.hstmt, i, CharType, sf.str, sf.len, stmt.res);
  489. | -7: NEW(boolf); AppendField(resultRow, boolf); boolf.len:= 5;
  490. ODBC.BindCol(stmt.hstmt, i, LongIntType, boolf.b, boolf.len, stmt.res);
  491. ELSE HALT(99)
  492. END;
  493. resultRow.dsc.prev.sqlType:= type; COPY(name, resultRow.dsc.prev.name);
  494. resultRow.dsc.prev.nullable:= nullable;
  495. IF stmt.res < 0 THEN
  496. resultRow:= NIL; RETURN stmt
  497. END
  498. END; (* FOR *)
  499. stmt.results:= resultRow;
  500. (* cur:= resultRow.dsc.next; FOR i:= 1 TO resultRow.cols DO Out.String(cur.name); Out.Ln; cur:= cur.next END; *)
  501. END (* IF *)
  502. END;
  503. RETURN stmt
  504. END PrepareStatement;
  505. (** execute a previously prepared statement. If the statement delievers any data it can now be retrieved using procedure
  506. Fetch.
  507. IMPORTANT: a statement which does not need parameters and does not return any data (for example
  508. "DROP TABLE MyTable") will take effect even if you don't call Execute for this statement handle **)
  509. PROCEDURE Execute*(s: Statement);
  510. VAR parDesc, cur: Field; buffer: ARRAY BlockSize OF CHAR; r: Files.Rider; cnt: LONGINT;
  511. BEGIN
  512. IF s.firstExec THEN
  513. (* Out.String("discarding Execute"); Out.Ln; *)
  514. s.firstExec:= FALSE; RETURN
  515. END;
  516. (* Out.String("doing Execute"); Out.Ln; *)
  517. cur:= s.params.dsc.next;
  518. WHILE ~(cur IS SentinelField) DO
  519. IF cur.isNull THEN cur.len:= -1
  520. ELSIF cur IS StringField THEN cur.len:= NTS
  521. ELSIF cur IS FileField THEN cur.len:= DataAtExec
  522. ELSE cur.len:= 0
  523. END;
  524. cur:= cur.next
  525. END;
  526. ODBC.Execute(s.hstmt, s.res);
  527. cur:= s.params.dsc.next;
  528. (* must also put data with ODBC.PutData if there are any FileFields *)
  529. WHILE s.res = NeedData DO
  530. ODBC.ParamData(s.hstmt, parDesc^, s.res);
  531. IF s.res = NeedData THEN
  532. (* Out.String("needing data"); Out.Ln; *)
  533. WHILE ~(cur IS SentinelField) & ~(cur IS FileField) DO cur:= cur.next END;
  534. IF cur IS SentinelField THEN
  535. KernelLog.String("Warning: Field used for PutData is not a FileField"); KernelLog.Ln; HALT(99)
  536. ELSIF cur IS FileField THEN
  537. cnt:= 0; cur(FileField).f.Set(r, 0);
  538. REPEAT INC(cnt);
  539. cur(FileField).f.ReadBytes(r, buffer, 0, BlockSize); ODBC.PutData(s.hstmt, buffer, BlockSize-r.res, s.res);
  540. IF s.res < 0 THEN RETURN END;
  541. (* Out.Int(cnt, 5); Out.Int(BlockSize-r.res, 5); Out.Ln; Out.String(buffer); *)
  542. UNTIL r.eof;
  543. cur:= cur.next;
  544. (* Out.Ln *)
  545. END
  546. ELSE
  547. (* Out.String("don't need more data, ODBC.res = "); Out.Int(ODBC.res, 1); Out.Ln *)
  548. END
  549. END; (* WHILE *)
  550. cur:= s.params.dsc.next;
  551. WHILE ~(cur IS SentinelField) DO
  552. IF cur.len = -1 THEN cur.isNull:= TRUE ELSE cur.isNull:= FALSE END;
  553. cur:= cur.next
  554. END;
  555. END Execute;
  556. (** returns the number of rows affected by the execution of statement s. This is not the number of rows which are
  557. delievered by a SELECT Statement but rather the number of rows affected by an UPDATE or DELETE statement.
  558. If you want the number of rows in the result set of a SELECT statement use "SELECT COUNT( * ) .." or increment
  559. a counter variable after each call to Fetch **)
  560. PROCEDURE RowCount*(s: Statement; VAR rows: LONGINT);
  561. BEGIN
  562. ODBC.RowCount(s.hstmt, rows, s.res);
  563. END RowCount;
  564. (** fetch the next result row for statement s (of course statement s must have been executed before you can call
  565. the procedure Fetch) **)
  566. PROCEDURE Fetch*(s: Statement);
  567. VAR cur: Field; col: INTEGER; resSize, actSize: LONGINT; buffer: ARRAY BlockSize OF CHAR; r: Files.Rider;
  568. BEGIN
  569. ODBC.Fetch(s.hstmt, s.res);
  570. IF (s.res < 0) OR (s.res = 100) THEN RETURN END;
  571. (* now get all unbound columns with ODBC.GetData *)
  572. FirstField(s.results, cur); col:= 1;
  573. WHILE ~(cur IS SentinelField) DO
  574. IF cur IS FileField THEN
  575. cur(FileField).f := Files.New( "" ); (* Dan 12.10.04 *)
  576. cur(FileField).f.Set(r, 0);
  577. REPEAT
  578. ODBC.GetData(s.hstmt, col, -2, buffer, resSize, s.res);
  579. IF s.res < 0 THEN
  580. (* Out.String("Error while fetching data from res col "); Out.Int(col, 1); Out.Ln; *)
  581. (* EnumErrors(s.c, s, PrintError); *) RETURN
  582. END;
  583. IF resSize > LEN(buffer) THEN actSize:= LEN(buffer) ELSE actSize:= resSize END;
  584. (* Out.Int(actSize, 5); Out.Ln; *)
  585. IF actSize > 0 THEN cur(FileField).f.WriteBytes(r, buffer, 0, actSize) END; (*ALEX 2006.02.15 added the IF*)
  586. UNTIL s.res # 1;
  587. (* Out.String("Filesize: "); Out.Int(Files.Length(cur(FileField).f), 1); Out.Ln *)
  588. ELSE
  589. END;
  590. INC(col); cur:= cur.next
  591. END;
  592. cur:= s.results.dsc.next;
  593. WHILE ~(cur IS SentinelField) DO
  594. IF cur.len = -1 THEN cur.isNull:= TRUE ELSE cur.isNull:= FALSE END;
  595. cur:= cur.next
  596. END;
  597. END Fetch;
  598. (** JSS: fetch the result row for statement s (s must have been executed before you can call
  599. the procedure ExtendedFetch), based on fetchType and rowToFetch **)
  600. PROCEDURE FetchExtended*(s: Statement; fetchType: INTEGER; rowToFetch: LONGINT; VAR numFetchedRows: LONGINT; VAR rowStatus: INTEGER );
  601. VAR
  602. cur: Field;
  603. col: INTEGER;
  604. resSize,
  605. actSize: LONGINT;
  606. (*buffer: ARRAY BlockSize OF SYSTEM.BYTE; *)
  607. buffer: ARRAY BlockSize OF CHAR;
  608. r: Files.Rider;
  609. BEGIN
  610. ODBC.ExtendedFetch(s.hstmt, fetchType, rowToFetch, numFetchedRows, rowStatus, s.res);
  611. IF (s.res < 0) OR (s.res = 100) THEN RETURN END;
  612. (* now get all unbound columns with ODBC.GetData *)
  613. FirstField(s.results, cur); col:= 1;
  614. WHILE ~(cur IS SentinelField) DO
  615. IF cur IS FileField THEN
  616. cur(FileField).f.Set(r, 0);
  617. REPEAT
  618. ODBC.GetData(s.hstmt, col, -2, buffer, resSize, s.res);
  619. IF s.res < 0 THEN
  620. (* Out.String("Error while fetching data from res col "); Out.Int(col, 1); Out.Ln; *)
  621. (* EnumErrors(s.c, s, PrintError); *) RETURN
  622. END;
  623. IF resSize > LEN(buffer) THEN actSize:= LEN(buffer) ELSE actSize:= resSize END;
  624. (* Out.Int(actSize, 5); Out.Ln; *)
  625. IF actSize > 0 THEN cur(FileField).f.WriteBytes(r, buffer, 0, actSize) END; (*ALEX 2006.02.15 added the IF*)
  626. UNTIL s.res # 1;
  627. (* Out.String("Filesize: "); Out.Int(Files.Length(cur(FileField).f), 1); Out.Ln *)
  628. ELSE
  629. END;
  630. INC(col); cur:= cur.next
  631. END;
  632. cur:= s.results.dsc.next;
  633. WHILE ~(cur IS SentinelField) DO
  634. IF cur.len = -1 THEN cur.isNull:= TRUE ELSE cur.isNull:= FALSE END;
  635. cur:= cur.next
  636. END;
  637. END FetchExtended;
  638. (** commits all statements for connection c (works only if supported by the database) **)
  639. PROCEDURE Commit*(c: Connection);
  640. BEGIN
  641. ODBC.Commit(c.hdbc, c.res)
  642. END Commit;
  643. (** rolls back all statements executed since last Commit for connection c (works only if supported by the database) **)
  644. PROCEDURE Rollback*(c: Connection);
  645. BEGIN
  646. ODBC.Rollback(c.hdbc, c.res)
  647. END Rollback;
  648. (** enumerates all data sources **)
  649. PROCEDURE EnumDataSources*(enum: SourcesHandler; VAR res: INTEGER);
  650. VAR name, desc: ARRAY 256 OF CHAR;
  651. BEGIN
  652. ODBC.DataSources(2, name, desc, res);
  653. IF res < 0 THEN RETURN END;
  654. WHILE res = 0 DO
  655. enum(name, desc);
  656. ODBC.DataSources(1, name, desc, res);
  657. IF res < 0 THEN RETURN END;
  658. END;
  659. END EnumDataSources;
  660. (** enumerates all installed ODBC database drivers **)
  661. PROCEDURE EnumDrivers*(enum: SourcesHandler; VAR res: INTEGER);
  662. VAR name, desc: ARRAY 256 OF CHAR;
  663. BEGIN
  664. ODBC.Drivers(2, name, desc, res);
  665. IF res < 0 THEN RETURN END;
  666. WHILE res = 0 DO
  667. enum(name, desc);
  668. ODBC.Drivers(1, name, desc, res);
  669. IF res < 0 THEN RETURN END;
  670. END;
  671. END EnumDrivers;
  672. (** prepares statement to retrieve all tables accessible within connection c
  673. result row: field name field type
  674. TABLE_QUALIFIER StringField
  675. TABLE_OWNER StringField
  676. TABLE_NAME StringField
  677. TABLE_TYPE StringField ("TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY"
  678. "LOCAL TEMPORARY", "ALIAS", "SYNONYM", or data-source specific)
  679. REMARKS StringField
  680. **)
  681. PROCEDURE Tables*(c: Connection; VAR row: Row): Statement;
  682. VAR stmt: Statement; i, cols, type, scale: INTEGER; if: IntField; sf: StringField; rf: RealField; cur: Field;
  683. name: ARRAY 256 OF CHAR; prec: LONGINT; nullable: BOOLEAN;
  684. BEGIN
  685. NEW(stmt); NEW(stmt.hstmt); ODBC.AllocStmt(c.hdbc, stmt.hstmt, stmt.res); stmt.c:= c; row:= NIL; stmt.firstExec:= TRUE;
  686. ODBC.Tables(stmt.hstmt, ODBC.nullString, ODBC.nullString, ODBC.nullString, ODBC.nullString, stmt.res);
  687. IF stmt.res < 0 THEN
  688. row:= NIL; RETURN stmt
  689. ELSE
  690. ODBC.NumResultCols(stmt.hstmt, cols, stmt.res);
  691. AllocRow(row); row.cols:= cols; stmt.results:= row;
  692. FOR i:= 1 TO cols DO
  693. ODBC.DescribeCol(stmt.hstmt, i, name, type, prec, scale, nullable, stmt.res);
  694. CASE type OF
  695. 1, 12: NEW(sf); AppendField(row, sf);
  696. ODBC.BindCol(stmt.hstmt, i, CharType, sf.str, sf.len, stmt.res);
  697. ELSE HALT(99)
  698. END;
  699. row.dsc.prev.sqlType:= type; COPY(name, row.dsc.prev.name); row.dsc.prev.nullable:= nullable;
  700. IF stmt.res < 0 THEN
  701. row:= NIL; RETURN stmt
  702. END
  703. END
  704. END;
  705. RETURN stmt
  706. END Tables;
  707. (** retrieve the name of a SQL data type as it is used in the data source connected to by Connection c.
  708. For example some DBs call a 4 byte integer "LONG", and others call it "INT4". **)
  709. PROCEDURE GetTypeName*(c: Connection; sqlType: INTEGER; VAR typeName: ARRAY OF CHAR; VAR res: INTEGER);
  710. VAR stmt: Statement; len: LONGINT;
  711. BEGIN
  712. NEW(stmt); NEW(stmt.hstmt); ODBC.AllocStmt(c.hdbc, stmt.hstmt, stmt.res);
  713. typeName[0]:= 0X;
  714. ODBC.GetTypeInfo(stmt.hstmt, sqlType, stmt.res);
  715. IF stmt.res < 0 THEN
  716. res:= stmt.res; RETURN
  717. END;
  718. ODBC.BindCol(stmt.hstmt, 1, CharType, typeName, len, stmt.res);
  719. ODBC.Fetch(stmt.hstmt, stmt.res);
  720. res:= stmt.res
  721. END GetTypeName;
  722. (*ALEX 2006.03.22 added this function*)
  723. (**closes a statement and removes it from the connection statements list*)
  724. PROCEDURE CloseStatement*(s: Statement);
  725. VAR prevStmt, stmt: Statement;
  726. BEGIN
  727. ODBC.FreeStmt(s.hstmt, 1, s.res); (* opt = 1: drop statement and all resources associated with it *)
  728. stmt := s.c.stmt;
  729. WHILE (stmt # NIL) & (stmt # s) DO
  730. prevStmt := stmt; stmt:= stmt.next
  731. END;
  732. IF prevStmt # NIL THEN prevStmt.next := s.next ELSE s.c.stmt := s.next END;
  733. END CloseStatement;
  734. BEGIN
  735. Modules.InstallTermHandler(Terminate)
  736. END SQL.
  737. (**
  738. Remarks:
  739. Before you can execute a SQL statement you need to open a connection to the data source. Use procedure
  740. Open to get a connection. When you no longer need the connection you can close it with Close.
  741. In the following the basic steps used in commonly routines are described. It is assumed that there is already
  742. an open connection
  743. 1. A simple SQL statement which needs no input and delievers no output:
  744. example: "INSERT INTO addresses VALUES ('Markus', 'Dätwyler')"
  745. - Prepare the statement with PrepareStatement. As the statement retrieves no data, resultRow is NIL.
  746. - Execute the statement with procedure Execute
  747. - The number of rows affected by the execution of this statement can be get with procedure RowCount.
  748. 2. A SQL statement with retrieves data:
  749. example: "SELECT * FROM addresses WHERE name = 'Dätwyler' "
  750. - Prepare the statement with PrepareStatement. A result row containig a field for each column in the table
  751. addresses is generated.
  752. - Execute the statement.
  753. - Retrieve data from the result set with the procedure Fetch. Each call to Fetch delievers the next row in the
  754. result set. When there is no more data to get when calling Fetch the variable res will be set to NoDataFound.
  755. After fetching a row the fields containing the data can be accessed using resultRow generated by
  756. PrepareStatement and the procedures FirstField, NextField, PrevField and FindField.
  757. 3. A SQL statement which needs input:
  758. example: "INSERT INTO addresses VALUES (?, ?)"
  759. - Prepare the statement with PrepareStatement. As the statement retrieves no data, resultRow is NIL.
  760. - Create a row to hold the input data of the parameters with procedure BindParameters. paramRow
  761. will now contain this row.
  762. - Set the values of the parameters. To access the fields representing the parameters use paramRow and the
  763. procedures FirstField, NextField, PrevField and FindField.
  764. - Execute the statement.
  765. - The number of rows affected by the execution of this statement can be get with procedure RowCount.
  766. 4. A SQL statement which needs input and retrieves data:
  767. example: "SELECT * FROM addresses WHERE name LIKE ?"
  768. - Prepare the statement with procedure PrepareStatement. resultRow will be created and contain a field
  769. for each column in the result set.
  770. - Create a row to hold the parameter values with BindParameters.
  771. - Set the values of the parameters. Access the parameters using paramRow and FirstField, NextField,
  772. PrevField and FindField.
  773. - Execute the statement.
  774. - Fetch the data from the result set.
  775. **)