1<?php
2/**
3 * ADOdb driver for ADS.
4 *
5 * NOTE: This driver requires the Advantage PHP client libraries, which
6 * can be downloaded for free via:
7 * @link http://devzone.advantagedatabase.com/dz/content.aspx?key=20
8 *
9 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
10 *
11 * @package ADOdb
12 * @link https://adodb.org Project's web site and documentation
13 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
14 *
15 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
16 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
17 * any later version. This means you can use it in proprietary products.
18 * See the LICENSE.md file distributed with this source code for details.
19 * @license BSD-3-Clause
20 * @license LGPL-2.1-or-later
21 *
22 * @copyright 2000-2013 John Lim
23 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
24 */
25
26/*
27DELPHI FOR PHP USERS:
28      The following steps can be taken to utilize this driver from the
29      CodeGear Delphi for PHP product:
30        1 - See note above, download and install the Advantage PHP client.
31        2 - Copy the following files to the Delphi for PHP\X.X\php\ext directory:
32              ace32.dll
33              axcws32.dll
34              adsloc32.dll
35              php_advantage.dll (rename the existing php_advantage.dll.5.x.x file)
36        3 - Add the following line to the Delphi for PHP\X.X\php\php.ini.template file:
37              extension=php_advantage.dll
38        4 - To use: enter "ads" as the DriverName on a connection component, and set
39            a Host property similar to "DataDirectory=c:\". See the Advantage PHP
40            help file topic for ads_connect for details on connection path options
41            and formatting.
42        5 - (optional) - Modify the Delphi for PHP\X.X\vcl\packages\database.packages.php
43            file and add ads to the list of strings returned when registering the
44            Database object's DriverName property.
45
46*/
47
48// security - hide paths
49if (!defined('ADODB_DIR')) {
50	die();
51}
52
53define("_ADODB_ADS_LAYER", 2);
54
55/*--------------------------------------------------------------------------------------
56--------------------------------------------------------------------------------------*/
57
58
59class ADODB_ads extends ADOConnection
60{
61	var $databaseType = "ads";
62	var $fmt = "'m-d-Y'";
63	var $fmtTimeStamp = "'Y-m-d H:i:s'";
64	var $concat_operator = '';
65	var $replaceQuote = "''"; // string to use to replace quotes
66	var $dataProvider = "ads";
67	var $hasAffectedRows = true;
68	var $binmode = ODBC_BINMODE_RETURN;
69	var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive
70	// breaking backward-compat
71	//var $longreadlen = 8000; // default number of chars to return for a Blob/Long field
72	var $_bindInputArray = false;
73	var $curmode = SQL_CUR_USE_DRIVER; // See sqlext.h, SQL_CUR_DEFAULT == SQL_CUR_USE_DRIVER == 2L
74	var $_genSeqSQL = "create table %s (id integer)";
75	var $_autocommit = true;
76	var $_lastAffectedRows = 0;
77	var $uCaseTables = true; // for meta* functions, uppercase table names
78
79	function __construct()
80	{
81	}
82
83	// returns true or false
84	function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
85	{
86		if (!function_exists('ads_connect')) {
87			return null;
88		}
89
90		if ($this->debug && $argDatabasename && $this->databaseType != 'vfp') {
91			ADOConnection::outp("For Advantage Connect(), $argDatabasename is not used. Place dsn in 1st parameter.");
92		}
93		$last_php_error = $this->resetLastError();
94		if ($this->curmode === false) {
95			$this->_connectionID = ads_connect($argDSN, $argUsername, $argPassword);
96		} else {
97			$this->_connectionID = ads_connect($argDSN, $argUsername, $argPassword, $this->curmode);
98		}
99		$this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
100		if (isset($this->connectStmt)) {
101			$this->Execute($this->connectStmt);
102		}
103
104		return $this->_connectionID != false;
105	}
106
107	// returns true or false
108	function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
109	{
110		if (!function_exists('ads_connect')) {
111			return null;
112		}
113
114		$last_php_error = $this->resetLastError();
115		$this->_errorMsg = '';
116		if ($this->debug && $argDatabasename) {
117			ADOConnection::outp("For PConnect(), $argDatabasename is not used. Place dsn in 1st parameter.");
118		}
119		//  print "dsn=$argDSN u=$argUsername p=$argPassword<br>"; flush();
120		if ($this->curmode === false) {
121			$this->_connectionID = ads_connect($argDSN, $argUsername, $argPassword);
122		} else {
123			$this->_connectionID = ads_pconnect($argDSN, $argUsername, $argPassword, $this->curmode);
124		}
125
126		$this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
127		if ($this->_connectionID && $this->autoRollback) {
128			@ads_rollback($this->_connectionID);
129		}
130		if (isset($this->connectStmt)) {
131			$this->Execute($this->connectStmt);
132		}
133
134		return $this->_connectionID != false;
135	}
136
137	// returns the Server version and Description
138	function ServerInfo()
139	{
140
141		if (!empty($this->host)) {
142			$stmt = $this->Prepare('EXECUTE PROCEDURE sp_mgGetInstallInfo()');
143			$res = $this->Execute($stmt);
144			if (!$res) {
145				print $this->ErrorMsg();
146			} else {
147				$ret["version"] = $res->fields[3];
148				$ret["description"] = "Advantage Database Server";
149				return $ret;
150			}
151		} else {
152			return ADOConnection::ServerInfo();
153		}
154	}
155
156
157	// returns true or false
158	function CreateSequence($seqname = 'adodbseq', $start = 1)
159	{
160		$res = $this->Execute("CREATE TABLE $seqname ( ID autoinc( 1 ) ) IN DATABASE");
161		if (!$res) {
162			print $this->ErrorMsg();
163			return false;
164		} else {
165			return true;
166		}
167
168	}
169
170	// returns true or false
171	function DropSequence($seqname = 'adodbseq')
172	{
173		$res = $this->Execute("DROP TABLE $seqname");
174		if (!$res) {
175			print $this->ErrorMsg();
176			return false;
177		} else {
178			return true;
179		}
180	}
181
182
183	// returns the generated ID or false
184	// checks if the table already exists, else creates the table and inserts a record into the table
185	// and gets the ID number of the last inserted record.
186	function GenID($seqname = 'adodbseq', $start = 1)
187	{
188		$go = $this->Execute("select * from $seqname");
189		if (!$go) {
190			$res = $this->Execute("CREATE TABLE $seqname ( ID autoinc( 1 ) ) IN DATABASE");
191			if (!$res) {
192				print $this->ErrorMsg();
193				return false;
194			}
195		}
196		$res = $this->Execute("INSERT INTO $seqname VALUES( DEFAULT )");
197		if (!$res) {
198			print $this->ErrorMsg();
199			return false;
200		} else {
201			$gen = $this->Execute("SELECT LastAutoInc( STATEMENT ) FROM system.iota");
202			$ret = $gen->fields[0];
203			return $ret;
204		}
205
206	}
207
208
209	function ErrorMsg()
210	{
211		if ($this->_errorMsg !== false) {
212			return $this->_errorMsg;
213		}
214		if (empty($this->_connectionID)) {
215			return @ads_errormsg();
216		}
217		return @ads_errormsg($this->_connectionID);
218	}
219
220
221	function ErrorNo()
222	{
223		if ($this->_errorCode !== false) {
224			// bug in 4.0.6, error number can be corrupted string (should be 6 digits)
225			return (strlen($this->_errorCode) <= 2) ? 0 : $this->_errorCode;
226		}
227
228		if (empty($this->_connectionID)) {
229			$e = @ads_error();
230		} else {
231			$e = @ads_error($this->_connectionID);
232		}
233
234		// bug in 4.0.6, error number can be corrupted string (should be 6 digits)
235		// so we check and patch
236		if (strlen($e) <= 2) {
237			return 0;
238		}
239		return $e;
240	}
241
242
243	function BeginTrans()
244	{
245		if (!$this->hasTransactions) {
246			return false;
247		}
248		if ($this->transOff) {
249			return true;
250		}
251		$this->transCnt += 1;
252		$this->_autocommit = false;
253		return ads_autocommit($this->_connectionID, false);
254	}
255
256	function CommitTrans($ok = true)
257	{
258		if ($this->transOff) {
259			return true;
260		}
261		if (!$ok) {
262			return $this->RollbackTrans();
263		}
264		if ($this->transCnt) {
265			$this->transCnt -= 1;
266		}
267		$this->_autocommit = true;
268		$ret = ads_commit($this->_connectionID);
269		ads_autocommit($this->_connectionID, true);
270		return $ret;
271	}
272
273	function RollbackTrans()
274	{
275		if ($this->transOff) {
276			return true;
277		}
278		if ($this->transCnt) {
279			$this->transCnt -= 1;
280		}
281		$this->_autocommit = true;
282		$ret = ads_rollback($this->_connectionID);
283		ads_autocommit($this->_connectionID, true);
284		return $ret;
285	}
286
287
288	// Returns tables,Views or both on successful execution. Returns
289	// tables by default on successful execution.
290	function &MetaTables($ttype = false, $showSchema = false, $mask = false)
291	{
292		$recordSet1 = $this->Execute("select * from system.tables");
293		if (!$recordSet1) {
294			print $this->ErrorMsg();
295			return false;
296		}
297		$recordSet2 = $this->Execute("select * from system.views");
298		if (!$recordSet2) {
299			print $this->ErrorMsg();
300			return false;
301		}
302		$i = 0;
303		while (!$recordSet1->EOF) {
304			$arr["$i"] = $recordSet1->fields[0];
305			$recordSet1->MoveNext();
306			$i = $i + 1;
307		}
308		if ($ttype == 'FALSE') {
309			while (!$recordSet2->EOF) {
310				$arr["$i"] = $recordSet2->fields[0];
311				$recordSet2->MoveNext();
312				$i = $i + 1;
313			}
314			return $arr;
315		} elseif ($ttype == 'VIEWS') {
316			while (!$recordSet2->EOF) {
317				$arrV["$i"] = $recordSet2->fields[0];
318				$recordSet2->MoveNext();
319				$i = $i + 1;
320			}
321			return $arrV;
322		} else {
323			return $arr;
324		}
325
326	}
327
328	function &MetaPrimaryKeys($table, $owner = false)
329	{
330		$recordSet = $this->Execute("select table_primary_key from system.tables where name='$table'");
331		if (!$recordSet) {
332			print $this->ErrorMsg();
333			return false;
334		}
335		$i = 0;
336		while (!$recordSet->EOF) {
337			$arr["$i"] = $recordSet->fields[0];
338			$recordSet->MoveNext();
339			$i = $i + 1;
340		}
341		return $arr;
342	}
343
344	/*
345	See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcdatetime_data_type_changes.asp
346	/ SQL data type codes /
347	#define SQL_UNKNOWN_TYPE  0
348	#define SQL_CHAR      1
349	#define SQL_NUMERIC    2
350	#define SQL_DECIMAL    3
351	#define SQL_INTEGER    4
352	#define SQL_SMALLINT    5
353	#define SQL_FLOAT      6
354	#define SQL_REAL      7
355	#define SQL_DOUBLE      8
356	#if (ODBCVER >= 0x0300)
357	#define SQL_DATETIME    9
358	#endif
359	#define SQL_VARCHAR   12
360
361
362	/ One-parameter shortcuts for date/time data types /
363	#if (ODBCVER >= 0x0300)
364	#define SQL_TYPE_DATE   91
365	#define SQL_TYPE_TIME   92
366	#define SQL_TYPE_TIMESTAMP 93
367
368	#define SQL_UNICODE                             (-95)
369	#define SQL_UNICODE_VARCHAR                     (-96)
370	#define SQL_UNICODE_LONGVARCHAR                 (-97)
371	*/
372	function ODBCTypes($t)
373	{
374		switch ((integer)$t) {
375			case 1:
376			case 12:
377			case 0:
378			case -95:
379			case -96:
380				return 'C';
381			case -97:
382			case -1: //text
383				return 'X';
384			case -4: //image
385				return 'B';
386
387			case 9:
388			case 91:
389				return 'D';
390
391			case 10:
392			case 11:
393			case 92:
394			case 93:
395				return 'T';
396
397			case 4:
398			case 5:
399			case -6:
400				return 'I';
401
402			case -11: // uniqidentifier
403				return 'R';
404			case -7: //bit
405				return 'L';
406
407			default:
408				return 'N';
409		}
410	}
411
412	function &MetaColumns($table, $normalize = true)
413	{
414		global $ADODB_FETCH_MODE;
415
416		$false = false;
417		if ($this->uCaseTables) {
418			$table = strtoupper($table);
419		}
420		$schema = '';
421		$this->_findschema($table, $schema);
422
423		$savem = $ADODB_FETCH_MODE;
424		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
425
426		/*if (false) { // after testing, confirmed that the following does not work because of a bug
427		  $qid2 = ads_tables($this->_connectionID);
428		  $rs = new ADORecordSet_ads($qid2);
429		  $ADODB_FETCH_MODE = $savem;
430		  if (!$rs) return false;
431		  $rs->_fetch();
432
433		  while (!$rs->EOF) {
434			if ($table == strtoupper($rs->fields[2])) {
435			  $q = $rs->fields[0];
436			  $o = $rs->fields[1];
437			  break;
438			}
439			$rs->MoveNext();
440		  }
441		  $rs->Close();
442
443		  $qid = ads_columns($this->_connectionID,$q,$o,strtoupper($table),'%');
444		} */
445
446		switch ($this->databaseType) {
447			case 'access':
448			case 'vfp':
449				$qid = ads_columns($this->_connectionID);#,'%','',strtoupper($table),'%');
450				break;
451
452
453			case 'db2':
454				$colname = "%";
455				$qid = ads_columns($this->_connectionID, "", $schema, $table, $colname);
456				break;
457
458			default:
459				$qid = @ads_columns($this->_connectionID, '%', '%', strtoupper($table), '%');
460				if (empty($qid)) {
461					$qid = ads_columns($this->_connectionID);
462				}
463				break;
464		}
465		if (empty($qid)) {
466			return $false;
467		}
468
469		$rs = new ADORecordSet_ads($qid);
470		$ADODB_FETCH_MODE = $savem;
471
472		if (!$rs) {
473			return $false;
474		}
475		$rs->_fetch();
476
477		$retarr = array();
478
479		/*
480		$rs->fields indices
481		0 TABLE_QUALIFIER
482		1 TABLE_SCHEM
483		2 TABLE_NAME
484		3 COLUMN_NAME
485		4 DATA_TYPE
486		5 TYPE_NAME
487		6 PRECISION
488		7 LENGTH
489		8 SCALE
490		9 RADIX
491		10 NULLABLE
492		11 REMARKS
493		*/
494		while (!$rs->EOF) {
495			//  adodb_pr($rs->fields);
496			if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) {
497				$fld = new ADOFieldObject();
498				$fld->name = $rs->fields[3];
499				$fld->type = $this->ODBCTypes($rs->fields[4]);
500
501				// ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp
502				// access uses precision to store length for char/varchar
503				if ($fld->type == 'C' or $fld->type == 'X') {
504					if ($this->databaseType == 'access') {
505						$fld->max_length = $rs->fields[6];
506					} else {
507						if ($rs->fields[4] <= -95) // UNICODE
508						{
509							$fld->max_length = $rs->fields[7] / 2;
510						} else {
511							$fld->max_length = $rs->fields[7];
512						}
513					}
514				} else {
515					$fld->max_length = $rs->fields[7];
516				}
517				$fld->not_null = !empty($rs->fields[10]);
518				$fld->scale = $rs->fields[8];
519				$retarr[strtoupper($fld->name)] = $fld;
520			} else {
521				if (sizeof($retarr) > 0) {
522					break;
523				}
524			}
525			$rs->MoveNext();
526		}
527		$rs->Close(); //-- crashes 4.03pl1 -- why?
528
529		if (empty($retarr)) {
530			$retarr = false;
531		}
532		return $retarr;
533	}
534
535	// Returns an array of columns names for a given table
536	function &MetaColumnNames($table, $numIndexes = false, $useattnum = false)
537	{
538		$recordSet = $this->Execute("select name from system.columns where parent='$table'");
539		if (!$recordSet) {
540			print $this->ErrorMsg();
541			return false;
542		} else {
543			$i = 0;
544			while (!$recordSet->EOF) {
545				$arr["FIELD$i"] = $recordSet->fields[0];
546				$recordSet->MoveNext();
547				$i = $i + 1;
548			}
549			return $arr;
550		}
551	}
552
553
554	function Prepare($sql)
555	{
556		if (!$this->_bindInputArray) {
557			return $sql;
558		} // no binding
559		$stmt = ads_prepare($this->_connectionID, $sql);
560		if (!$stmt) {
561			// we don't know whether odbc driver is parsing prepared stmts, so just return sql
562			return $sql;
563		}
564		return array($sql, $stmt, false);
565	}
566
567	/* returns queryID or false */
568	function _query($sql, $inputarr = false)
569	{
570		$last_php_error = $this->resetLastError();
571		$this->_errorMsg = '';
572
573		if ($inputarr) {
574			if (is_array($sql)) {
575				$stmtid = $sql[1];
576			} else {
577				$stmtid = ads_prepare($this->_connectionID, $sql);
578
579				if ($stmtid == false) {
580					$this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
581					return false;
582				}
583			}
584
585			if (!ads_execute($stmtid, $inputarr)) {
586				//@ads_free_result($stmtid);
587				$this->_errorMsg = ads_errormsg();
588				$this->_errorCode = ads_error();
589				return false;
590			}
591
592		} else {
593			if (is_array($sql)) {
594				$stmtid = $sql[1];
595				if (!ads_execute($stmtid)) {
596					//@ads_free_result($stmtid);
597					$this->_errorMsg = ads_errormsg();
598					$this->_errorCode = ads_error();
599					return false;
600				}
601			} else {
602
603				$stmtid = ads_exec($this->_connectionID, $sql);
604
605			}
606		}
607
608		$this->_lastAffectedRows = 0;
609
610		if ($stmtid) {
611
612			if (@ads_num_fields($stmtid) == 0) {
613				$this->_lastAffectedRows = ads_num_rows($stmtid);
614				$stmtid = true;
615
616			} else {
617
618				$this->_lastAffectedRows = 0;
619				ads_binmode($stmtid, $this->binmode);
620				ads_longreadlen($stmtid, $this->maxblobsize);
621
622			}
623
624			$this->_errorMsg = '';
625			$this->_errorCode = 0;
626		} else {
627			$this->_errorMsg = ads_errormsg();
628			$this->_errorCode = ads_error();
629		}
630
631		return $stmtid;
632
633	}
634
635	/*
636	  Insert a null into the blob field of the table first.
637	  Then use UpdateBlob to store the blob.
638
639	  Usage:
640
641	  $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
642	  $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
643	 */
644	function UpdateBlob($table, $column, $val, $where, $blobtype = 'BLOB')
645	{
646		$last_php_error = $this->resetLastError();
647		$sql = "UPDATE $table SET $column=? WHERE $where";
648		$stmtid = ads_prepare($this->_connectionID, $sql);
649		if ($stmtid == false) {
650			$this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
651			return false;
652		}
653		if (!ads_execute($stmtid, array($val), array(SQL_BINARY))) {
654			$this->_errorMsg = ads_errormsg();
655			$this->_errorCode = ads_error();
656			return false;
657		}
658		return true;
659	}
660
661	// returns true or false
662	function _close()
663	{
664		$ret = @ads_close($this->_connectionID);
665		$this->_connectionID = false;
666		return $ret;
667	}
668
669	function _affectedrows()
670	{
671		return $this->_lastAffectedRows;
672	}
673
674}
675
676/*--------------------------------------------------------------------------------------
677   Class Name: Recordset
678--------------------------------------------------------------------------------------*/
679
680class ADORecordSet_ads extends ADORecordSet
681{
682
683	var $bind = false;
684	var $databaseType = "ads";
685	var $dataProvider = "ads";
686	var $useFetchArray;
687
688	function __construct($id, $mode = false)
689	{
690		if ($mode === false) {
691			global $ADODB_FETCH_MODE;
692			$mode = $ADODB_FETCH_MODE;
693		}
694		$this->fetchMode = $mode;
695
696		$this->_queryID = $id;
697
698		// the following is required for mysql odbc driver in 4.3.1 -- why?
699		$this->EOF = false;
700		$this->_currentRow = -1;
701		//parent::__construct($id);
702	}
703
704
705	// returns the field object
706	function &FetchField($fieldOffset = -1)
707	{
708
709		$off = $fieldOffset + 1; // offsets begin at 1
710
711		$o = new ADOFieldObject();
712		$o->name = @ads_field_name($this->_queryID, $off);
713		$o->type = @ads_field_type($this->_queryID, $off);
714		$o->max_length = @ads_field_len($this->_queryID, $off);
715		if (ADODB_ASSOC_CASE == 0) {
716			$o->name = strtolower($o->name);
717		} else {
718			if (ADODB_ASSOC_CASE == 1) {
719				$o->name = strtoupper($o->name);
720			}
721		}
722		return $o;
723	}
724
725	/* Use associative array to get fields array */
726	function Fields($colname)
727	{
728		if ($this->fetchMode & ADODB_FETCH_ASSOC) {
729			return $this->fields[$colname];
730		}
731		if (!$this->bind) {
732			$this->bind = array();
733			for ($i = 0; $i < $this->_numOfFields; $i++) {
734				$o = $this->FetchField($i);
735				$this->bind[strtoupper($o->name)] = $i;
736			}
737		}
738
739		return $this->fields[$this->bind[strtoupper($colname)]];
740	}
741
742
743	function _initrs()
744	{
745		global $ADODB_COUNTRECS;
746		$this->_numOfRows = ($ADODB_COUNTRECS) ? @ads_num_rows($this->_queryID) : -1;
747		$this->_numOfFields = @ads_num_fields($this->_queryID);
748		// some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0
749		if ($this->_numOfRows == 0) {
750			$this->_numOfRows = -1;
751		}
752		//$this->useFetchArray = $this->connection->useFetchArray;
753	}
754
755	function _seek($row)
756	{
757		return false;
758	}
759
760	// speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated
761	function &GetArrayLimit($nrows, $offset = -1)
762	{
763		if ($offset <= 0) {
764			$rs =& $this->GetArray($nrows);
765			return $rs;
766		}
767		$savem = $this->fetchMode;
768		$this->fetchMode = ADODB_FETCH_NUM;
769		$this->Move($offset);
770		$this->fetchMode = $savem;
771
772		if ($this->fetchMode & ADODB_FETCH_ASSOC) {
773			$this->fields =& $this->GetRowAssoc();
774		}
775
776		$results = array();
777		$cnt = 0;
778		while (!$this->EOF && $nrows != $cnt) {
779			$results[$cnt++] = $this->fields;
780			$this->MoveNext();
781		}
782
783		return $results;
784	}
785
786
787	function MoveNext()
788	{
789		if ($this->_numOfRows != 0 && !$this->EOF) {
790			$this->_currentRow++;
791			if ($this->_fetch()) {
792				return true;
793			}
794		}
795		$this->fields = false;
796		$this->EOF = true;
797		return false;
798	}
799
800	function _fetch()
801	{
802		$this->fields = false;
803		$rez = @ads_fetch_into($this->_queryID, $this->fields);
804		if ($rez) {
805			if ($this->fetchMode & ADODB_FETCH_ASSOC) {
806				$this->fields =& $this->GetRowAssoc();
807			}
808			return true;
809		}
810		return false;
811	}
812
813	function _close()
814	{
815		return @ads_free_result($this->_queryID);
816	}
817
818}
819