PageRenderTime 30ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/core/db/DbConnection.php

http://zoop.googlecode.com/
PHP | 811 lines | 481 code | 119 blank | 211 comment | 52 complexity | 5b2d81ee6299163458a058c2b218c98d MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. <?php
  2. abstract class DbConnection
  3. {
  4. protected $params;
  5. private $queryParams;
  6. private $types;
  7. /**
  8. * Actual connection to the database
  9. *
  10. * @var unknown_type
  11. */
  12. private $conn;
  13. private $echo;
  14. function __construct($params, $connectionName)
  15. {
  16. $this->params = $params;
  17. $this->validateParams($connectionName);
  18. $echo = false;
  19. $this->init();
  20. }
  21. //
  22. // Begin configuration funtions
  23. //
  24. private function validateParams($connectionName)
  25. {
  26. // handle the required fields
  27. $missing = array();
  28. foreach($this->getRequireds() as $thisRequired)
  29. {
  30. if(!isset($this->params[$thisRequired]))
  31. $missing[] = $thisRequired;
  32. }
  33. if(!empty($missing))
  34. throw new ConfigException('db', $missing, "for connection $connectionName");
  35. // handle the defaults
  36. foreach($this->getDefaults() as $name => $value)
  37. {
  38. if(!isset($this->params[$name]))
  39. $this->params[$name] = $value;
  40. }
  41. }
  42. protected function init()
  43. {
  44. }
  45. protected function getRequireds()
  46. {
  47. return array();
  48. }
  49. protected function getDefaults()
  50. {
  51. return array();
  52. }
  53. //
  54. // End configuration funtions
  55. //
  56. //
  57. // Begin misc funtions
  58. //
  59. public function echoOn()
  60. {
  61. $this->echo = true;
  62. }
  63. public function echoOff()
  64. {
  65. $this->echo = false;
  66. }
  67. function escapeString($string)
  68. {
  69. trigger_error("escapeString must be defined in each individual database driver");
  70. }
  71. function escapeIdentifier($string)
  72. {
  73. return '"' . $string . '"';
  74. }
  75. //
  76. // End misc funtions
  77. //
  78. //
  79. // Begin Schema functions
  80. //
  81. public function alterSchema($sql)
  82. {
  83. return $this->_query($sql);
  84. }
  85. public function getSchema()
  86. {
  87. return new DbSchema($this);
  88. }
  89. abstract public function tableExists($name);
  90. abstract public function getTableNames();
  91. abstract public function getTableFieldInfo($tableName);
  92. /**
  93. * Executes the passed in SQL statement on the database and returns a result set
  94. *
  95. * @param string $sql SQL statement to execute
  96. * @return DbResultSet object
  97. */
  98. abstract public function _query($sql);
  99. //
  100. // End Schema functions
  101. //
  102. //
  103. // Begin transaction funtions
  104. //
  105. public function beginTransaction()
  106. {
  107. $this->_query('begin');
  108. }
  109. public function commitTransaction()
  110. {
  111. $this->_query('commit');
  112. }
  113. public function rollbackTransaction()
  114. {
  115. $this->_query('rollback');
  116. }
  117. //
  118. // End transaction funtions
  119. //
  120. //
  121. // Begin query funtions
  122. //
  123. /**
  124. * Escapes and inserts parameters into the the SQL statement and executes the statement.
  125. *
  126. * Syntax for parameters:
  127. * - :<varname>:string -- the variable is escaped as a string and replaces this entry
  128. * - :<varname>:int -- the variable is escaped as a number and replaces this entry
  129. * - :<varname>:keyword -- the variable replaces this entry with no change
  130. * - :<varname>:identifier -- the variable is escaped as a SQL identifier (table, field name, etc) and replaces this entry
  131. * - :<varname> -- if no type is specified, variable is treated as a string.
  132. *
  133. * @param string $sql SQL statement to execute
  134. * @param assoc_array $params $variable => $value array of parameters to substitute into the SQL statement
  135. * @return DbResultSet Resultset object
  136. */
  137. public function query($sql, $params)
  138. {
  139. // do all of the variable replacements
  140. $this->queryParams = array();
  141. foreach($params as $key => $value)
  142. {
  143. $parts = explode(':', $key);
  144. $this->queryParams[$parts[0]] = $value;
  145. if(isset($parts[1]))
  146. $this->types[$parts[0]] = $parts[1];
  147. }
  148. // TODO: indication here how to escape options in here
  149. // $sql = preg_replace_callback("/:([[:alpha:]_\d]+):([[:alpha:]_]+)|[^:]:([[:alpha:]_\d]+)/", array($this, 'queryCallback'), $sql);
  150. $sql = preg_replace_callback("/:([[:alpha:]_\d]+):([[:alpha:]_]+)|:([[:alpha:]_\d]+)/", array($this, 'queryCallback'), $sql);
  151. if($this->echo)
  152. {
  153. if(php_sapi_name() == "cli")
  154. echo $sql . "\n";
  155. else
  156. echo $sql . '<br>';
  157. }
  158. // actually do the query
  159. return $this->_query($sql);
  160. }
  161. /**
  162. * Private callback function passed to preg_replace_callback for doing the variable substitutions
  163. * This method handles all escaping of strings and identifiers based on the matched list.
  164. * Syntax:
  165. * - :<varname>:string -- the variable is escaped as a string and replaces this entry
  166. * - :<varname>:int -- the variable is escaped as a number and replaces this entry
  167. * - :<varname>:keyword -- the variable replaces this entry with no change
  168. * - :<varname>:identifier -- the variable is escaped as a SQL identifier (table, field name, etc) and replaces this entry
  169. * - :<varname> -- if no type is specified, variable is treated as a string.
  170. *
  171. * @param array $matches array of matches provided by preg_replace_callback
  172. * @return string String to replace the matched string with
  173. */
  174. private function queryCallback($matches)
  175. {
  176. if(isset($matches[3]))
  177. $matches[1] = $matches[3];
  178. $name = $matches[1];
  179. if(is_null($this->queryParams[$name]))
  180. return 'NULL';
  181. if($matches[2])
  182. $type = $matches[2];
  183. if(isset($type) && isset($this->types[$name]))
  184. assert($type == $this->types[$name]);
  185. $type = isset($type) ? $type : (isset($this->types[$name]) ? $this->types[$name] : 'string');
  186. switch($type)
  187. {
  188. case 'string':
  189. $replaceString = $this->escapeString($this->queryParams[$name]);
  190. break;
  191. case 'int':
  192. $replaceString = (int)$this->queryParams[$name];
  193. break;
  194. case 'keyword':
  195. $replaceString = $this->queryParams[$name];
  196. break;
  197. case 'identifier':
  198. $replaceString = $this->escapeIdentifier($this->queryParams[$name]);
  199. break;
  200. case 'inInts':
  201. assert(is_array($this->queryParams[$name]));
  202. foreach($this->queryParams[$name] as $key => $int)
  203. {
  204. $this->queryParams[$name][$key] = (int)$int;
  205. }
  206. $replaceString = implode(', ', $this->queryParams[$name]);
  207. break;
  208. case 'inStrings':
  209. assert(is_array($this->queryParams[$name]));
  210. foreach($this->queryParams[$name] as $key => $string)
  211. {
  212. $this->queryParams[$name][$key] = $this->escapeString($string);
  213. }
  214. $replaceString = implode(', ', $this->queryParams[$name]);
  215. break;
  216. default:
  217. trigger_error("unknown param type: " . $type);
  218. break;
  219. }
  220. return $replaceString;
  221. }
  222. //
  223. // end query funtions
  224. //
  225. //
  226. // Begin fetch funtions
  227. //
  228. public function fetchCell($sql, $params)
  229. {
  230. $row = $this->fetchRow($sql, $params);
  231. if(!$row)
  232. return NULL;
  233. return current($row);
  234. }
  235. public function fetchRow($sql, $params)
  236. {
  237. $res = $this->query($sql, $params);
  238. $num = $res->numRows();
  239. if($num > 1)
  240. trigger_error("1 row expected: $num returned");
  241. if($num == 0)
  242. return false;
  243. return $res->current();
  244. }
  245. public function fetchRows($sql, $params)
  246. {
  247. $rows = array();
  248. $res = $this->query($sql, $params);
  249. if(!$res->valid())
  250. return array();
  251. for($row = $res->current(); $res->valid(); $row = $res->next())
  252. {
  253. $rows[] = $row;
  254. }
  255. return $rows;
  256. }
  257. public function fetchColumn($sql, $params)
  258. {
  259. $rows = array();
  260. $res = $this->query($sql, $params);
  261. if(!$res->valid())
  262. return array();
  263. for($row = $res->current(); $res->valid(); $row = $res->next())
  264. {
  265. $rows[] = current($row);
  266. }
  267. return $rows;
  268. }
  269. /**
  270. * Creates a simple nested array structure grouping the values of the $valueField column by the values of the columns specified in the $keyFields array.
  271. *
  272. * For example, if your query returns a list of books and you'd like to group the titles by subject and isbn number, let $keyFields = array("subject", "isbn") and $valueField = "title".
  273. * The format thus created will be $var[$subject][$isbn] = $title;
  274. *
  275. * @param string $sql SQL query with parameters in the format ":variablename" or ":variablename:datatype"
  276. * @param array $keyFields array of fields to group the results by
  277. * @param array $valueField name of the field containing the value to be grouped
  278. * @param array($key=>$value) $params ($key => value) array of parameters to substitute into the SQL query. If you are not passing parameters in, params should be an empty array()
  279. * @return associative array structure grouped by the values in $mapFields
  280. */
  281. public function fetchSimpleMap($sql, $keyFields, $valueField, $params)
  282. {
  283. $map = array();
  284. $res = $this->query($sql, $params);
  285. for($row = $res->current(); $res->valid(); $row = $res->next())
  286. {
  287. $cur = &$map;
  288. if(is_array($keyFields))
  289. {
  290. foreach($keyFields as $key)
  291. {
  292. $cur = &$cur[$row[$key]];
  293. $lastKey = $row[$key];
  294. }
  295. }
  296. else
  297. {
  298. $cur = &$cur[$row[$keyFields]];
  299. $lastKey = $row[$keyFields];
  300. }
  301. if(isset($cur) && !empty($lastKey))
  302. trigger_error("db::fetchSimpleMap : duplicate key in query: \n $sql \n");
  303. $cur = $row[ $valueField ];
  304. }
  305. return $map;
  306. }
  307. /**
  308. * Returns a nested array, grouped by the fields (or field) listed in $mapFields
  309. *
  310. * For example, if mapFields = array("person_id", "book_id"), and the resultset returns
  311. * a list of all the chapters of all the books of all the people, this will group the
  312. * records by person and by book, keeping each row in an array under
  313. * $var[$person_id][$book_id]
  314. *
  315. * @param string $sql SQL query with parameters in the format ":variablename" or ":variablename:datatype"
  316. * @param array $mapFields array of fields to group the results by
  317. * @param array($key=>$value) $params ($key => value) array of parameters to substitute into the SQL query. If you are not passing parameters in, params should be an empty array()
  318. * @return associative array structure grouped by the values in $mapFields
  319. */
  320. public function fetchMap($sql, $mapFields, $params)
  321. {
  322. $map = array();
  323. $res = $this->query($sql, $params);
  324. for($row = $res->current(); $res->valid(); $row = $res->next())
  325. {
  326. if(is_array($mapFields))
  327. {
  328. $cur = &$map;
  329. foreach($mapFields as $val)
  330. {
  331. $curKey = $row[$val];
  332. if(!isset($cur[$curKey]))
  333. $cur[$curKey] = array();
  334. $cur = &$cur[$curKey];
  335. }
  336. if(count($cur))
  337. trigger_error("db::fetchSimpleMap : duplicate key $curKey (would silently destroy data) in query: \n $inQuery \n");
  338. $cur = $row;
  339. }
  340. else
  341. {
  342. $mapKey = $row[$mapFields];
  343. $map[$mapKey] = $row;
  344. }
  345. }
  346. return $map;
  347. }
  348. public function fetchComplexMap($sql, $mapFields, $params)
  349. {
  350. $map = array();
  351. $res = $this->query($sql, $params);
  352. for($row = $res->current(); $res->valid(); $row = $res->next())
  353. {
  354. if(is_array($mapFields))
  355. {
  356. $cur = &$map;
  357. foreach($mapFields as $val)
  358. {
  359. $curKey = $row[$val];
  360. if(!isset($cur[$curKey]))
  361. $cur[$curKey] = array();
  362. $cur = &$cur[$curKey];
  363. }
  364. $cur[] = $row;
  365. }
  366. else
  367. {
  368. $mapKey = $row[$mapFields];
  369. $map[$mapKey][] = $row;
  370. }
  371. }
  372. return $map;
  373. }
  374. //
  375. // End fetch functions
  376. //
  377. //
  378. // Begin insert functions
  379. //
  380. public function insertRow($sql, $params, $serial = true)
  381. {
  382. $this->query($sql, $params);
  383. if($serial)
  384. return $this->getLastInsertId();
  385. else
  386. return false;
  387. }
  388. public function insertRows($sql, $params, $serial = false)
  389. {
  390. $ids = array();
  391. foreach($params as $thisRow)
  392. {
  393. $id = $this->insertRow($sql, $thisRow, $serial);
  394. if($serial)
  395. $ids[] = $id;
  396. }
  397. }
  398. /**
  399. * Automatically insert the values of an ($key=>$value) array into a database using the $key as the field name
  400. *
  401. * @param string $tableName Table into which the insert should be performed
  402. * @param array $values ($key => $value) array in the format ($fieldName => $fieldValue)
  403. * @param boolean $serial True if the last inserted ID should be returned
  404. * @return mixed ID of the inserted row or false if $serial == false
  405. */
  406. public function insertArray($tableName, $values, $serial = true)
  407. {
  408. $insertInfo = DbConnection::generateInsertInfo($tableName, $values);
  409. return $this->insertRow($insertInfo['sql'], $insertInfo['params'], $serial);
  410. }
  411. //
  412. // End insert functions
  413. //
  414. //
  415. // Begin update functions
  416. //
  417. /**
  418. * Executes the given update SQL statement. This method throws an error if multiple rows are updated.
  419. *
  420. * @param string $sql SQL UPDATE statement
  421. * @param assoc_array $params $param => $value array of parameters to escape and substitute into the query
  422. */
  423. public function updateRow($sql, $params)
  424. {
  425. $affected = $this->updateRows($sql, $params);
  426. if($affected == 0 || $affected > 1)
  427. trigger_error("attempting to update one row, $affected altered");
  428. }
  429. /**
  430. * Executes the given update SQL statement
  431. *
  432. * @param string $sql SQL UPDATE statement
  433. * @param assoc_array $params $param => $value array of parameters to escape and substitute into the query
  434. */
  435. public function updateRows($sql, $params)
  436. {
  437. $res = $this->query($sql, $params);
  438. return $res->affectedRows();
  439. }
  440. //
  441. // End update functions
  442. //
  443. //
  444. // Begin delete functions
  445. //
  446. public function deleteRow($sql, $params)
  447. {
  448. // it just so happens that they actually need to do the exact same thing
  449. $this->updateRow($sql, $params);
  450. }
  451. public function deleteRows($sql, $params)
  452. {
  453. // it just so happens that they actually need to do the exact same thing
  454. return $this->updateRows($sql, $params);
  455. }
  456. //
  457. // End delete functions
  458. //
  459. //
  460. // Begin combo functions
  461. //
  462. /**
  463. * Automatically generates a select statement using the given information
  464. *
  465. * @param string $tableName name of the table to select from
  466. * @param array $fieldsNames array of fields to retreive
  467. * @param assoc_array $conditions $field => $value array of what to check (at this time only the "=" operator is supported)
  468. * @param assoc_array $params $var => $value array of optional special parameters. Currently "lock" and "orderby" (set to an array of field names) are supported.
  469. * @return string SQL select statement
  470. */
  471. static function generateSelectInfo($tableName, $fieldsNames, $conditions = NULL, $params = NULL)
  472. {
  473. // echo_r($params);
  474. $selectParams = array();
  475. // create the field clause
  476. // This first option will allow a "*" or a comma separated list of fields in case an array was not passed in
  477. if(is_string($fieldsNames))
  478. $fieldClause = $fieldsNames;
  479. else
  480. {
  481. $fieldList = array();
  482. foreach ($fieldsNames as $fieldName)
  483. {
  484. $fieldList[] = ":fld_$fieldName:identifier";
  485. $selectParams["fld_$fieldName"] = $fieldName;
  486. }
  487. $fieldClause = implode(', ', $fieldList);
  488. }
  489. // create the condition clause
  490. if($conditions)
  491. {
  492. $conditionParts = array();
  493. foreach($conditions as $fieldName => $value)
  494. {
  495. if(is_array($value))
  496. {
  497. $valueParts = array();
  498. foreach($value as $thisKey => $thisValue)
  499. {
  500. $partName = "{$fieldName}_{$thisKey}";
  501. $valueParts[] = ":$partName";
  502. $selectParams[$partName] = $thisValue;
  503. }
  504. $valueString = implode(', ', $valueParts);
  505. $conditionParts[] = ":fld_$fieldName:identifier in ($valueString)";
  506. }
  507. else
  508. $conditionParts[] = ":fld_$fieldName:identifier = :$fieldName";
  509. $selectParams[$fieldName] = $value;
  510. $selectParams["fld_$fieldName"] = $fieldName;
  511. }
  512. $conditionClause = 'WHERE ' . implode(' AND ', $conditionParts);
  513. }
  514. else
  515. $conditionClause = '';
  516. // create the lock clause
  517. if(isset($params['lock']) && $params['lock'])
  518. $lockClause = 'for update';
  519. else
  520. $lockClause = '';
  521. // create the order by clause
  522. if(isset($params['orderby']) && $params['orderby'])
  523. {
  524. $orderByClause = 'order by ';
  525. if(is_array($params['orderby']))
  526. $orderByClause .= implode(', ', $params['orderby']);
  527. else
  528. $orderByClause .= $params['orderby'];
  529. }
  530. else
  531. $orderByClause = '';
  532. // create the limit
  533. if(isset($params['limit']) && $params['limit'])
  534. {
  535. $limitClause = 'limit ' . $params['limit'];
  536. }
  537. else
  538. $limitClause = '';
  539. // now put it all together
  540. $selectSql = "SELECT $fieldClause FROM :tableName:identifier $conditionClause $orderByClause $limitClause $lockClause";
  541. $selectParams['tableName'] = $tableName;
  542. return array('sql' => $selectSql, 'params' => $selectParams);
  543. }
  544. /**
  545. * Automatically generate an UPDATE SQL statement for the given table based on the passed in conditions and values. Currently only supports direct equalities for conditions.
  546. *
  547. * @param string $tableName Name of the table to update
  548. * @param assoc_array $conditions $field => $value array of conditions. Currently only supports the "=" operator.
  549. * @param assoc_array $values $field => $value array of fields to update. The update statement will be generated to update the fields supplied to the supplied values.
  550. * @return string SQL statement to update the table
  551. */
  552. static function generateUpdateInfo($tableName, $conditions, $values)
  553. {
  554. $updateParams = array();
  555. // create the condition part
  556. $conditionParts = array();
  557. foreach($conditions as $fieldName => $value)
  558. {
  559. $conditionParts[] = ":fld_$fieldName:identifier = :$fieldName";
  560. $updateParams[$fieldName] = $value;
  561. $updateParams["fld_$fieldName"] = $fieldName;
  562. }
  563. $conditionClause = implode(' AND ', $conditionParts);
  564. // create the set part
  565. $setParts = array();
  566. foreach($values as $fieldName => $value)
  567. {
  568. $fieldNameParts = explode(':', $fieldName);
  569. $realFieldName = $fieldNameParts[0];
  570. $setParts[] = ":fld_$realFieldName:identifier = :$fieldName";
  571. $updateParams[$realFieldName] = $value;
  572. $updateParams["fld_$realFieldName"] = $realFieldName;
  573. }
  574. $setClause = implode(', ', $setParts);
  575. // now put it all together
  576. $updateSql = "UPDATE :tableName:keyword SET $setClause WHERE $conditionClause";
  577. $updateParams['tableName'] = $tableName;
  578. return array('sql' => $updateSql, 'params' => $updateParams);
  579. }
  580. /**
  581. * Automatically generate a statement to delete records from a table based on certain conditions. Currently only the "=" operator is supported for conditions.
  582. *
  583. * @param string $tableName Name of the table to delete from
  584. * @param assoc_array $conditions $field => $value array of conditions to match for the delete statement
  585. * @return string SQL statement to delete records from the table
  586. */
  587. static function generateDeleteInfo($tableName, $conditions)
  588. {
  589. $deleteParams = array();
  590. // create the condition part
  591. $conditionParts = array();
  592. foreach($conditions as $fieldName => $value)
  593. {
  594. $conditionParts[] = ":fld_$fieldName:identifier = :$fieldName";
  595. $deleteParams[$fieldName] = $value;
  596. $deleteParams["fld_$fieldName"] = $fieldName;
  597. }
  598. $conditionClause = implode(' AND ', $conditionParts);
  599. $updateSql = "DELETE FROM :tableName:keyword WHERE $conditionClause";
  600. $deleteParams['tableName'] = $tableName;
  601. return array('sql' => $updateSql, 'params' => $deleteParams);
  602. }
  603. /**
  604. * Generate an insert statement from an associative ($key => $value) array
  605. *
  606. * @param string $tableName Table into which the insert should be performed
  607. * @param array $values ($key => $value) array in the format ($fieldName => $fieldValue)
  608. * @return string SQL statement to perform the insert
  609. */
  610. static function generateInsertInfo($tableName, $values)
  611. {
  612. $insertParams = array();
  613. if($values)
  614. {
  615. // create the set part
  616. $fieldParts = array();
  617. $valuesParts = array();
  618. foreach($values as $fieldName => $value)
  619. {
  620. $fieldNameParts = explode(':', $fieldName);
  621. $realFieldName = $fieldNameParts[0];
  622. $fieldParts[] = ':fld_' . $realFieldName . ":identifier" ;
  623. $valuesParts[] = ':' . $fieldName;
  624. $insertParams[$realFieldName] = $value;
  625. $insertParams["fld_" . $realFieldName] = $realFieldName;
  626. }
  627. $fieldClause = implode(', ', $fieldParts);
  628. $valuesClause = implode(', ', $valuesParts);
  629. // now put it all together
  630. $insertSql = "INSERT INTO :tableName:identifier ($fieldClause) VALUES ($valuesClause)";
  631. $insertParams['tableName'] = $tableName;
  632. }
  633. else
  634. {
  635. $insertSql = "INSERT INTO :tableName:identifier DEFAULT VALUES";
  636. $insertParams['tableName'] = $tableName;
  637. }
  638. return array('sql' => $insertSql, 'params' => $insertParams);
  639. }
  640. public function upsertRow($tableName, $conditions, $values)
  641. {
  642. // generate the update query info
  643. $updateInfo = self::generateUpdateInfo($tableName, $conditions, $values);
  644. // update the row if it's there
  645. $num = $this->updateRows($updateInfo['sql'], $updateInfo['params']);
  646. if($num > 1)
  647. trigger_error("one row expected, $num rows updated");
  648. if(!$num)
  649. {
  650. // generate the insert query info
  651. $insertInfo = self::generateInsertInfo($tableName, array_merge($conditions, $values));
  652. // if it wasn't there insert it
  653. $this->insertRow($insertInfo['sql'], $insertInfo['params'], false);
  654. //
  655. // I think the only thing that can go wrong here is we get a race condition where another process inserts the row
  656. // after we do the update but before we do the insert. In that case an error will be thrown here that needs to be handled.
  657. // Also after catching the error we should still try to execute the update statement. Of course if the thread causing the insert
  658. // error then rolls back we might need to do an insert. Hmmm... this could get tricky.
  659. //
  660. }
  661. }
  662. public function selsertRow($tableName, $fieldNames, $conditions, $defaults = NULL, $lock = 0)
  663. {
  664. // generate the sqlect query info
  665. $selectInfo = self::generateSelectInfo($tableName, $fieldNames, $conditions, $lock);
  666. // select the row if it's there
  667. $row = $this->fetchRow($selectInfo['sql'], $selectInfo['params']);
  668. if(!$row)
  669. {
  670. // generate the insert query info
  671. $allInsertFields = $defaults ? array_merge($conditions, $defaults) : $conditions;
  672. $insertInfo = self::generateInsertInfo($tableName, array_merge($conditions, $allInsertFields));
  673. // if it wasn't there insert it
  674. $this->insertRow($insertInfo['sql'], $insertInfo['params'], false);
  675. // you may have a race condition here
  676. // if another thread inserted it first then we need to supress the error in PHP4
  677. // we should probably try to use exceptions in PHP5
  678. $row = $this->fetchRow($selectInfo['sql'], $selectInfo['params']);
  679. if(!$row)
  680. {
  681. // I'm pretty sure that this should actually never happen
  682. trigger_error("error upsert: this shouldn't ever happen. If it does figure out why and fix this function");
  683. }
  684. }
  685. return $row;
  686. }
  687. //
  688. // End combo functions
  689. //
  690. }