<?php
/*
 * Класс таблицы для обмена данными в двоичном виде (по HTTP в zip)
 */

//Альтернатива unpack
function getBin($t,$v)
{
	$v=unpack($t, $v);
	return $v[1];
}

class TCField //Класс описания типа и названия столбцов
{	//Идентификаторы типов данных
	static $BD_UINT1 = 0; //1 байт без знаковый
	static $BD_UINT2 = 1; //2 байта без знаковый
	static $BD_UINT4 = 3; //4 байта без знаковый
	static $BD_INT1 = 10; //1 байт со знаковый
	static $BD_INT2 = 11; //2 байта со знаковый
	static $BD_INT4 = 13; //4 байта со знаковый
	static $BD_INT8 = 17; //8 байт со знаковый
	static $BD_FLOAT4 = 20; //4 байта
	static $BD_FLOAT8 = 22; //8 байт double
	static $BD_UTF8_1 = 100; //100 - utf8_1 string 1й байт размер строки в байтах
	static $BD_UTF8_2 = 101; //101 - utf8_2 string 1х 2 байта размер строки в байтах
	static $BD_UTF8_4 = 102; //101 - utf8_4 string 1х 4 байта размер строки в байтах
	static $BD_BLOB_2 = 141;
	static $BD_BLOB_4 = 143; //Двоичные данные uint4 количество байт

	public $name=''; //Название столбца
	public $type=-1; //Тип данных
	public $value=''; //Запакованые данные

	function TCField($name, $type)
	{
		//Преобразования для типов из PostgreSQL и MySQL
		if($type=='bool' || $type=='tinyint') { $type=TCField::$BD_UINT1; } else
		if($type=='int4' || $type=='int' || $type=='bigint') { $type=TCField::$BD_INT4; } else //bigint немного неправильно потому что это 64 бита но для андрод приложения не сделал
		if($type=='int8') { $type=TCField::$BD_INT8; } else
		if($type=='float4' || $type=='float') { $type=TCField::$BD_FLOAT4; } else
		if($type=='float8' || $type=='NUMBER') { $type=TCField::$BD_FLOAT8; } else
		if($type=='varchar' || $type=='VARCHAR2') { $type=TCField::$BD_UTF8_2; } else
		if($type=='text') { $type=TCField::$BD_UTF8_4; } else
		if($type=='bytea' || $type=='longblob') { $type=TCField::$BD_BLOB_4; } else
		if($type=='timestamptz') { $type=TCField::$BD_UTF8_1; } else
		if($type=='timestamp') { $type=TCField::$BD_UTF8_1; } else
		if($type=='date') { $type=TCField::$BD_UTF8_1; }

		$this->name=$name;
		$this->type=$type;
	}

	//Получить PDO тип
	function getPDOType()
	{
		if($this->type==TCField::$BD_UINT1) return PDO::PARAM_BOOL;
		if($this->type==TCField::$BD_INT4) return PDO::PARAM_INT;
		if($this->type==TCField::$BD_INT8) return PDO::PARAM_INT;
		if($this->type==TCField::$BD_FLOAT4) return PDO::PARAM_STR;
		if($this->type==TCField::$BD_FLOAT8) return PDO::PARAM_STR;
		if($this->type==TCField::$BD_UTF8_1) return PDO::PARAM_STR;
		if($this->type==TCField::$BD_UTF8_2) return PDO::PARAM_STR;
		if($this->type==TCField::$BD_UTF8_4) return PDO::PARAM_STR;
		if($this->type==TCField::$BD_BLOB_4) return PDO::PARAM_LOB;
		return PDO::PARAM_STR;
	}

	//Прочитать значение из файла в соответствии с типом
	function ReadValue($fileHandle)
	{
		if($this->type==TCField::$BD_UINT1)
		{	$this->value=fread($fileHandle, 1);
		}else
		if($this->type==TCField::$BD_UINT2)
		{	$this->value=fread($fileHandle, 2);
		}else
		if($this->type==TCField::$BD_UINT4)
		{	$this->value=fread($fileHandle, 4);
		}else
		if($this->type==TCField::$BD_INT1)
		{	$this->value=fread($fileHandle, 1);
		}else
		if($this->type==TCField::$BD_INT2)
		{	$this->value=fread($fileHandle, 2);
		}else
		if($this->type==TCField::$BD_INT4)
		{	$this->value=fread($fileHandle, 4);
		}else
		if($this->type==TCField::$BD_INT8)
		{	$this->value=fread($fileHandle, 8);
		}else
		if($this->type==TCField::$BD_FLOAT4)
		{	$this->value=fread($fileHandle, 4);
		}else
		if($this->type==TCField::$BD_FLOAT8)
		{	$this->value=fread($fileHandle, 8);
		}else
		if($this->type==TCField::$BD_UTF8_1)
		{	$s=getBin('C',fread($fileHandle, 1));
			if($s==0) $this->value='';
			else $this->value=fread($fileHandle, $s);
		}else
		if($this->type==TCField::$BD_UTF8_2)
		{	$s=getBin('S',fread($fileHandle, 2));
			if($s==0) $this->value='';
			else
			{	//В цикле так как из зип потока читало порциями
				$this->value='';
				while($s>strlen($this->value))
					$this->value.=fread($fileHandle, $s-strlen($this->value));
			}
		}else
		if($this->type==TCField::$BD_UTF8_4)
		{	$s=getBin('I',fread($fileHandle, 4));
			if($s==0) $this->value='';
			else
			{	//В цикле так как из зип потока читало порциями
				$this->value='';
				while($s>strlen($this->value))
					$this->value.=fread($fileHandle, $s-strlen($this->value));
			}
		}else
		if($this->type==TCField::$BD_BLOB_4)
		{	$s=getBin('I',fread($fileHandle, 4));
			if($s==0) $this->value='';
			else
			{	//В цикле так как из зип потока читало порциями
				$this->value='';
				while($s>strlen($this->value))
					$this->value.=fread($fileHandle, $s-strlen($this->value));
			}
		}
	}

	function pack($value)
	{
		if($value===NULL){ return NULL; }

		if($this->type==TCField::$BD_UINT1)
		{	return pack("C",$value);
		}else
		if($this->type==TCField::$BD_UINT2)
		{	return pack("S",$value);
		}else
		if($this->type==TCField::$BD_UINT4)
		{	return pack("I",$value);
		}else
		if($this->type==TCField::$BD_INT1)
		{	return pack("c",$value);
		}else
		if($this->type==TCField::$BD_INT2)
		{	return pack("s",$value);
		}else
		if($this->type==TCField::$BD_INT4)
		{	return pack("i",$value);
		}else
		if($this->type==TCField::$BD_INT8)
		{
			/*$str='0000000000000000000000000000000000000000000000000000000000000000'.decbin($value); //TODO decbin c 64 битами не работает на 32 битном php только 32 unpack('H*', '01010101');
			$str=substr($str,-64,64);
			for($ii=0;$ii<8;$ii++)//побайтно записываем все 8 байт
			{	$szRez=$szRez.pack("C",bindec(substr($str,-8,8)));
				$str=substr($str,0,-8);
			}*/
		}else
		if($this->type==TCField::$BD_FLOAT4)
		{
			$value=str_replace(',','.',$value); //Чтоб не зависело от настроек оракла
			return pack("f",$value);
		}else
		if($this->type==TCField::$BD_FLOAT8)
		{
			$value=str_replace(',','.',$value); //Чтоб не зависело от настроек оракла
			return pack("d",$value);
		}else
		if($this->type==TCField::$BD_UTF8_1)
		{	//$str=iconv('WINDOWS-1251', 'UTF-8', $value);
			return pack("C",strlen($value)).$value;
		}else
		if($this->type==TCField::$BD_UTF8_2)
		{	//$str=iconv('WINDOWS-1251', 'UTF-8', $value);
			return pack("S",strlen($value)).$value;
		}else
		if($this->type==TCField::$BD_UTF8_4)
		{	//$str=iconv('WINDOWS-1251', 'UTF-8', $value);
			return pack("I",strlen($value)).$value;
		}else
		if($this->type==TCField::$BD_BLOB_4)
		{	return pack("I",strlen($value)).$value;
		}
	}
	
	function setValue($value) //пакуем данные в соответствии с типом
	{
		$this->value=$this->pack($value);
	}
	//Распаковываем данные в соответствии с типом
	function getValue()
	{
		if($this->value===NULL) return NULL;

		if($this->type==TCField::$BD_UINT1)
		{	return getBin ("C", $this->value);
		}else
		if($this->type==TCField::$BD_UINT2)
		{	return getBin ("S", $this->value);
		}else
		if($this->type==TCField::$BD_UINT4)
		{	return getBin ("I", $this->value);
		}else
		if($this->type==TCField::$BD_INT1)
		{	return getBin ("c", $this->value);
		}else
		if($this->type==TCField::$BD_INT2)
		{	return getBin ("s", $this->value);
		}else
		if($this->type==TCField::$BD_INT4)
		{	return getBin ("i", $this->value);
		}else
		if($this->type==TCField::$BD_INT8)
		{	//return getBin ("S", $this->value);
		}else
		if($this->type==TCField::$BD_FLOAT4)
		{	return getBin ("f", $this->value);
		}else
		if($this->type==TCField::$BD_FLOAT8)
		{	return getBin ("d", $this->value);
		}else
		if($this->type==TCField::$BD_UTF8_1 || $this->type==TCField::$BD_UTF8_2 || $this->type==TCField::$BD_UTF8_4)
		{	return $this->value;
		}else
		if($this->type==TCField::$BD_BLOB_2 || $this->type==TCField::$BD_BLOB_4)
		{	return $this->value;
		}
		return '';
	}
}

class TCTable
{
	public $id=0; //Идентификатор таблицы
	public $name=''; //Название таблицы
	public $fields = array(); //Массив полей
	private $nc=0; //Байтов под NULL значения
	private $m_NULL; //NULL значения
	private $m_file;

	/**
	 * Конструктор
	 * @param string $name Название таблицы
	 * @param integer $id Идентификатор таблицы
	 */
	function TCTable($name,$id)
	{	$this->name=$name;
		$this->id=$id;
	}

	//Открыть таблицу по названию файла
	function OpenTableF($file)
	{
		if(file_exists($file))
		{
			$this->OpenTableH(fopen($file,'r'));
		}
	}

	//Открыть таблицу из HANDLE (файла)
	function OpenTableH($handle)
	{
		$this->m_file=$handle;

		if(getBin('S',fread($this->m_file, 2))!=65500) return false; //id файла
		if(getBin('S',fread($this->m_file, 2))!=1) return false; //Версия файла
		$this->id=getBin('I',fread($this->m_file, 4)); //ID таблицы или запроса (4 байта можно сделать 2)
		if(getBin('C',fread($this->m_file, 1))!=0) return  false; //Только плотные таблицы
		$this->name=fread($this->m_file, getBin('C',fread($this->m_file, 1))); //Название таблицы
		//Считываем столбцы
		$count=getBin('C',fread($this->m_file, 1)); //Количество столбцов
		for($i=0;$i<$count;$i++)
		{
			$field=new TCField (fread($this->m_file, getBin('C',fread($this->m_file, 1))),getBin('C',fread($this->m_file, 1)));
			$this->addField($field);
			//echo $field->name.'<br/>';
		}
		return true;
	}
	//Открыть таблицу из потока
	//OpenTable

	//Прочитать следующую запись из таблицы
	function ReadNextRecord()
	{
		if(feof($this->m_file)) return; //Неработает

		$this->m_NULL='';
		for($j=0;$j<$this->nc;$j++) //Побайтно
		{
			$v=fread($this->m_file, 1);
			if(strlen($v)==0) return; //Проверка конца файла
			$v=getBin('C',$v);
			$v=decbin($v);
			for($i=strlen($v);$i<8;$i++) $v='0'.$v;
			$this->m_NULL.=$v;
		}

		$this->clearRows();
		for($i=0;$i<count($this->fields);$i++)
		{
			if($this->m_NULL[$i]=="1")
			{
				$this->fields[$i]->ReadValue($this->m_file);
			}
		}
		return true;
	}

	//Добавить поле к таблице
	function addField($field)
	{	if($field!=NULL)
		{	$this->fields[]=$field;
			$this->nc=ceil(count($this->fields)/8.0); //Байтов под NULL
		}
	}
	//Получить заголовок плотной таблицы в виде двоичной строки
	function getHeader()
	{
		$szRez=''; //Данные из таблицы в двоичном формате
		$szRez=$szRez.pack("S",65500); //id файла (2 байта)
		$szRez=$szRez.pack("S",1); //Версия файла (2 байта)
		$szRez=$szRez.pack("I",$this->id); //ID таблицы или запроса (4 байта)
		$szRez=$szRez.pack("C",0); //Тип таблицы 0-"Плотная" 1-"Мягкая" (1 байт)
		$szRez=$szRez.pack("C",strlen($this->name)).$this->name; //UTF8_1 строка
		$szRez=$szRez.pack("C",count($this->fields)); //Колво столбцов (1 байт)
		//Записываем id типов столбцов
		for($i=0;$i<count($this->fields);$i++)
		{
			$szRez.=pack("C",strlen($this->fields[$i]->name)).$this->fields[$i]->name;
			$szRez.=pack("C",$this->fields[$i]->type);
		}
		return $szRez;
	}
	//Получить данные 1 записи в виде строки
	function getCol()
	{	$szRez='';
		//Запишем NULL значения побайтно (1-есть данные 0-нету данных)
		$str='';
		for($i=0;$i<$this->nc*8;$i++)
		{
			if($i>=count($this->fields)) $str.='0'; else
			if($this->fields[$i]->value===NULL) $str.='0'; else $str.='1';
			if(strlen($str)==8)
			{	$szRez=$szRez.pack("C",bindec($str));
				$str='';
			}
		}
		//Запишем сами данные в строку
		for($i=0;$i<count($this->fields);$i++)
		{
			$szRez.=$this->fields[$i]->value;
		}
		return $szRez;
	}

	//Row очистить запись
	function clearRows()
	{	for($i=0;$i<count($this->fields);$i++)
		{	$this->fields[$i]->value=null;
		}
	}
	//Получить обьект столбца по имени
	function getRowByName($name)
	{	for($i=0;$i<count($this->fields);$i++)
		{	if($this->fields[$i]->name==$name) return $this->fields[$i];
		}
	}
	//Получить объект столбца по номеру
	function getRowByNum($num)
	{	return $this->fields[$num];
	}
}
?>