<?php
/**
 * Portable version of Oracle oci8 driver
 *
 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
 *
 * Portable version of oci8 driver, to make it more similar to other database
 * drivers. The main differences are
 * 1. that the OCI_ASSOC names are in lowercase instead of uppercase.
 * 2. bind variables are mapped using ? instead of :<bindvar>
 *
 * @package ADOdb
 * @link https://adodb.org Project's web site and documentation
 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
 *
 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
 * any later version. This means you can use it in proprietary products.
 * See the LICENSE.md file distributed with this source code for details.
 * @license BSD-3-Clause
 * @license LGPL-2.1-or-later
 *
 * @copyright 2000-2013 John Lim
 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
 */

// security - hide paths
if (!defined('ADODB_DIR')) die();

include_once(ADODB_DIR.'/drivers/adodb-oci8.inc.php');

class ADODB_oci8po extends ADODB_oci8 {
	var $databaseType = 'oci8po';
	var $dataProvider = 'oci8';
	var $metaTablesSQL = <<<ENDSQL
		SELECT lower(table_name), table_type
		FROM user_catalog
		WHERE table_type IN ('TABLE', 'VIEW') AND table_name NOT LIKE 'BIN\$%'
		ENDSQL; // bin$ tables are recycle bin tables
	var $metaColumnsSQL = <<<ENDSQL
		SELECT lower(column_name), data_type, data_length, data_scale, data_precision, nullable, data_default
		FROM user_tab_columns
		WHERE table_name = '%s'
		ORDER BY column_id
		ENDSQL;

	function Param($name,$type='C')
	{
		return '?';
	}

	function Prepare($sql,$cursor=false)
	{
		$sqlarr = explode('?',$sql);
		$sql = $sqlarr[0];
		for ($i = 1, $max = sizeof($sqlarr); $i < $max; $i++) {
			$sql .=  ':'.($i-1) . $sqlarr[$i];
		}
		return ADODB_oci8::Prepare($sql,$cursor);
	}

	function Execute($sql,$inputarr=false)
	{
		return ADOConnection::Execute($sql,$inputarr);
	}

	/**
	 * The optimizations performed by ADODB_oci8::SelectLimit() are not
	 * compatible with the oci8po driver, so we rely on the slower method
	 * from the base class.
	 * We can't properly handle prepared statements either due to preprocessing
	 * of query parameters, so we treat them as regular SQL statements.
	 */
	function SelectLimit($sql, $nrows=-1, $offset=-1, $inputarr=false, $secs2cache=0)
	{
		if(is_array($sql)) {
//			$sql = $sql[0];
		}
		return ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
	}

	/**
	 * Execute a query.
	 *
	 * Emulate handling of parameters ? ?, replacing with :bind0 :bind1
	 *
	 * @param string|array $sql        Query to execute.
	 * @param array        $inputarr   An optional array of parameters.
	 *
	 * @return mixed|bool Query identifier or true if execution successful, false if failed.
	 */
	function _query($sql,$inputarr=false)
	{
		if (is_array($inputarr)) {
			$i = 0;
			if (is_array($sql)) {
				foreach($inputarr as $v) {
					$arr['bind'.$i++] = $v;
				}
			} else {
				$sql = $this->extractBinds($sql,$inputarr);
			}
		}
		return ADODB_oci8::_query($sql,$inputarr);
	}

	/**
	* Replaces compatibility bind markers with oracle ones and returns a
	* valid sql statement
	*
	* This replaces a regexp based section of code that has been subject
	* to numerous tweaks, as more extreme test cases have appeared. This
	* is now done this like this to help maintainability and avoid the
	* need to rely on regexp experienced maintainers
	*
	* @param	string		$sql		The sql statement
	* @param	string[]	$inputarr	The bind array
	*
	* @return	string	The modified statement
	*/
	private function extractBinds($sql,$inputarr)
	{
		$inString  = false;
		$escaped   = 0;
		$sqlLength = strlen($sql) - 1;
		$newSql    = '';
		$bindCount = 0;

		/*
		* inputarr is the passed in bind list, which is associative, but
		* we only want the keys here
		*/
		$inputKeys = array_keys($inputarr);

		for ($i=0;$i<=$sqlLength;$i++)
		{
			/*
			* find the next character of the string
			*/
			$c = $sql[$i];

			if ($c == "'" && !$inString && $escaped==0)
				/*
				* Found the start of a string inside the statement
				*/
				$inString = true;
			elseif ($c == "\\" && $escaped==0)
				/*
				* The next character will be escaped
				*/
				$escaped = 1;
			elseif ($c == "'" && $inString && $escaped==0)
				/*
				* We found the end of the string
				*/
				$inString = false;

			if ($escaped == 2)
				$escaped = 0;

			if ($escaped==0 && !$inString && $c == '?')
				/*
				* We found a bind symbol, replace it with the oracle equivalent
				*/
				$newSql .= ':' . $inputKeys[$bindCount++];
			else
				/*
				* Add the current character the pile
				*/
				$newSql .= $c;

			if ($escaped == 1)
				/*
				* We have just found an escape character, make sure we ignore the
				* next one that comes along, it might be a ' character
				*/
				$escaped = 2;
		}

		return $newSql;

	}
}

/*--------------------------------------------------------------------------------------
		 Class Name: Recordset
--------------------------------------------------------------------------------------*/

class ADORecordset_oci8po extends ADORecordset_oci8 {

	var $databaseType = 'oci8po';

	function Fields($colname)
	{
		if ($this->fetchMode & OCI_ASSOC) return $this->fields[$colname];

		if (!$this->bind) {
			$this->bind = array();
			for ($i=0; $i < $this->_numOfFields; $i++) {
				$o = $this->FetchField($i);
				$this->bind[strtoupper($o->name)] = $i;
			}
		}
		 return $this->fields[$this->bind[strtoupper($colname)]];
	}

	// lowercase field names...
	function _FetchField($fieldOffset = -1)
	{
		$fld = new ADOFieldObject;
		$fieldOffset += 1;
		$fld->name = oci_field_name($this->_queryID, $fieldOffset);
		if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER) {
			$fld->name = strtolower($fld->name);
		}
		$fld->type = oci_field_type($this->_queryID, $fieldOffset);
		$fld->max_length = oci_field_size($this->_queryID, $fieldOffset);
		if ($fld->type == 'NUMBER') {
			$sc = oci_field_scale($this->_queryID, $fieldOffset);
			if ($sc == 0) {
				$fld->type = 'INT';
			}
		}
		return $fld;
	}

	// 10% speedup to move MoveNext to child class
	function MoveNext()
	{
		$ret = @oci_fetch_array($this->_queryID,$this->fetchMode);
		if($ret !== false) {
		global $ADODB_ANSI_PADDING_OFF;
			$this->fields = $ret;
			$this->_currentRow++;
			$this->_updatefields();

			if (!empty($ADODB_ANSI_PADDING_OFF)) {
				foreach($this->fields as $k => $v) {
					if (is_string($v)) $this->fields[$k] = rtrim($v);
				}
			}
			return true;
		}
		if (!$this->EOF) {
			$this->EOF = true;
			$this->_currentRow++;
		}
		return false;
	}

	function GetArrayLimit($nrows,$offset=-1)
	{
		if ($offset <= 0) {
			$arr = $this->GetArray($nrows);
			return $arr;
		}
		for ($i=1; $i < $offset; $i++)
			if (!@oci_fetch($this->_queryID)) {
				$arr = array();
				return $arr;
			}
		$ret = @oci_fetch_array($this->_queryID,$this->fetchMode);
		if ($ret === false) {
			$arr = array();
			return $arr;
		}
		$this->fields = $ret;
		$this->_updatefields();
		$results = array();
		$cnt = 0;
		while (!$this->EOF && $nrows != $cnt) {
			$results[$cnt++] = $this->fields;
			$this->MoveNext();
		}

		return $results;
	}

	function _fetch()
	{
		global $ADODB_ANSI_PADDING_OFF;

		$ret = @oci_fetch_array($this->_queryID,$this->fetchMode);
		if ($ret) {
			$this->fields = $ret;
			$this->_updatefields();

			if (!empty($ADODB_ANSI_PADDING_OFF)) {
				foreach($this->fields as $k => $v) {
					if (is_string($v)) $this->fields[$k] = rtrim($v);
				}
			}
		}
		return $ret !== false;
	}

}
