// Мирошниченко Кирилл aka spock
// #163860836
// e-mail: spock@km.ru
//
#include "stdafx.h"
#include "sp_cooledb.h"
#include "DBPropSet.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//_____________________________________________________________________________
//
COLEDBFldsInf::COLEDBFldsInf():m_nType1C(CType(0))
{
	m_Index		 = 0;
	m_strName	 = _T("");
	m_bIsType1C	 = false;
	m_bIsLong	 = false;
}

//_____________________________________________________________________________
//
enum
{
	funcExecuteStatement, 
	procClose, 
	funcExecute, 
	procDebug, 
	procSetTextParam, 
	procPutObjectList, 
	procSetTempTablesDir, 
	procSetExecTimeOut, 
	lastMethod
};

extern CDataBase7* pDataBase7;
extern CMetaDataCont* pMetaDataCont;
extern CBkEndUI* pBkEndUI;

class CObjID COLEDBCommand::ObjID;
class CParamDefs COLEDBCommand::defFnNames;

extern char szMNPErrorPrefix[];

//_____________________________________________________________________________
//
static ULONG AddOffset(ULONG nCurrent, ULONG nAdd)
{
	struct foobar
	{
		char foo;
		long bar;
	};
	
	ULONG nAlign = offsetof(foobar, bar);
	
	return nCurrent + nAdd + (nAdd % nAlign);
}

//_____________________________________________________________________________
//
COLEDBCommand::COLEDBCommand(CComPtr<IDBCreateCommand> pIDBCreateCommand) : CBLContext(), m_IsDebugMode(false)
{
	HRESULT hr;
	CString strErr;

	m_bCreated	 = true;
	::UuidCreateNil(&m_GUID);
	m_szTempTablesDir.Empty();

	m_ExecTimeout = -1;

	hr = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown**)&m_pICommandText);
	if (FAILED(hr))
	{
		strErr = "FAILED! IDBCreateCommand::CreateCommand()";
		GetErrorDescription(strErr);
		CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
		return;
	}

	m_TmpTblsList.RemoveAll();
	
	if (defFnNames.Empty())
	{
		defFnNames.SetPoolSize(lastMethod, 0);
		defFnNames.AddParam("ExecuteStatement", "ВыполнитьИнструкцию", funcExecuteStatement, 1, 2); // OUT:[ТЗ], IN:[Инструкция, ВидТЗ]
		defFnNames.AddParam("Close", "Закрыть", procClose, 0, 0);
		defFnNames.AddParam("Execute", "Выполнить", funcExecute, 1, 1); // OUT:[0-bad, 1-ok], IN:[Инструкция]
		defFnNames.AddParam("Debug", "Отладка", procDebug, 0, 1); // IN:[0-без отладки запроса, 1-с отладкой запроса]
		defFnNames.AddParam("SetTextParam", "УстановитьТекстовыйПараметр", procSetTextParam, 0, 2);
		defFnNames.AddParam("PutObjectList", "УложитьСписокОбъектов", procPutObjectList, 0, 3);
		defFnNames.AddParam("SetTempTablesDir", "УстановитьКаталогВремТаблиц", procSetTempTablesDir, 0, 1);
		defFnNames.AddParam("SetExecTimeOut", "УстановитьТаймаутВыполнения", procSetExecTimeOut, 0, 1);
	}
}

//_____________________________________________________________________________
//
COLEDBCommand::~COLEDBCommand()
{
	RemoveAllTmpTables();

	if(m_pICommandText)
	{
		m_pICommandText.Release();
	}

	m_bCreated = false;
}

//_____________________________________________________________________________
//
int COLEDBCommand::CallAsFunc(int iMethNum, class CValue& rValue, class CValue** ppValue)
{
	USES_CONVERSION;

	long ret = 0L;
	HRESULT hr;
	CString strErr;

	switch (iMethNum)
	{
		case funcExecute:
			{
				if (!m_bCreated)
				{
					strErr = "Использование метода без создания команды!";
					CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);

					rValue = 0L;
					return 0L;
				}

				CString strSQL(ppValue[0]->GetString());
				
				hr = COLEDBCommand::ParseQuery(strSQL);
				if (FAILED(hr))
				{
					rValue = 0L;
					return 0L;
				}
				
				long nRowsAffected = 0;
				
				hr = COLEDBCommand::Execute(strSQL, &nRowsAffected, NULL);
				if (FAILED(hr))
				{
					rValue = 0L;
					return 0L;
				}

				rValue	 = nRowsAffected;
				ret		 = 1L;
			}
			break;
		
		case funcExecuteStatement:
			{
				
				// Возвращаемое значение - Таблица значений.
				// Создаем сразу...
				//
				rValue.CreateObject("ТаблицаЗначений");

				if (!m_bCreated)
				{
					strErr = "Использование метода без создания команды!";
					CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);

					return 0L;
				}

				CString strSQL(ppValue[0]->GetString());
				
				hr = COLEDBCommand::ParseQuery(strSQL);
				if (FAILED(hr))
				{
					return 0L;
				}
				
				long nRowsAffected = 0;

				CComPtr<IRowset> pIRowset;
				
				hr = COLEDBCommand::Execute(strSQL, &nRowsAffected, (IUnknown**)&pIRowset);
				if (FAILED(hr))
				{
					return 0L;
				}

				// Если после исполнения запроса получаем пустой rowset, значит дальше нет смысла продолжать
				// возвращаем пустую ТЗ. А вообще это не ошибка. Например, такое может быть, если выполнить 
				// этим методом drop table XXX - результатом будет только RowsAffected
				//
				//if (pIRowset == NULL || nRowsAffected < 0)
				if (pIRowset == NULL)
				{
					// В случае с SQLOLEDB после запроса select * from XXX через переменную
					// nRowsAffected возвращается значение -1.
					// Если же использовать провайдер VFPOLEDB, то после такого же запроса 
					// получаем количество выбранных строк. 
					// Пока не знаю в чем фишка... идея есть, но нужно пошукать 
					// Проверку на <0 убираем.
					//
					return 1L;
				}

				ULONG cCols					 = 0;
				ULONG cbMaxRowSize			 = 0;
				DBBINDING* pDBBindings		 = NULL;
				COLEDBFldsInf* pColumnsInfo	 = NULL;
				DBOBJECT* pDBObject			 = NULL;
				bool HasKindField			 = false; // флаг - если в выборке есть хоть одно поле вида "ХХХ_вид" или "XXX_kind", то выставляем в значение true

				hr = COLEDBCommand::Binding(pIRowset, &cCols, &pDBBindings, &pColumnsInfo, &pDBObject, &cbMaxRowSize, HasKindField);
				if (FAILED(hr))
				{
					return 0L;
				}

				HACCESSOR hAccessor = NULL;
				CComPtr<IAccessor> pIAccessor;

				hr = COLEDBCommand::CreateAccessor(pIAccessor, pIRowset, &hAccessor, cCols, pDBBindings);
				if (FAILED(hr))
				{
					return 0L;
				}

				CValueTable* pVT = CValue2VT(rValue);
				//CVTExtended* pVT = CValue2VTExt(rValue);

				// Инфо по колонкам
				//
				CString strFormat;
				for (ULONG nCol = 0; nCol < cCols; nCol++)
				{
					CString szName(pColumnsInfo[nCol].m_strName);
					CType ColType(pColumnsInfo[nCol].m_nType1C);

					// Пока выяснил, что если типизируем поле как Перечисление (без указания вида),
					// то стоит создавать колонку нетипизированную (так делает сама 1с).
					// Про другие типы ничего пока не знаю
					//
					if((ENUM_TYPE_1C == ColType.GetTypeCode()) && (0 == ColType.GetTypeID()))
						pVT->AddColumn(szName, CType(0), szName, 0, strFormat, 0);
					else
						pVT->AddColumn(szName, ColType, szName, 0, strFormat, 0);
				}

				BYTE* pRowValues = new BYTE[cbMaxRowSize];
				ULONG cRowsObtained = 0;

				HROW m_hRow[NUMROWS_CHUNK];
				HROW* phRow = &m_hRow[0];

				ULONG cRowNum = 0;
				CValue v;
				CMetaDataWork::TypeStringValue eType;

				wchar_t* wstrVal;
				CString strTemp;
				DBDATE* dbdateVal;
				DBTIMESTAMP* dbdtVal;
				CNumeric strNum;
				ULONG dwStatus;
				
				ISequentialStream* pISequentialStream;
				
				BYTE* pRBuffer = new BYTE[BLOCK_SIZE];
				ULONG cRead;

				for(;;)
				{
					hr = pIRowset->GetNextRows(NULL, 0, NUMROWS_CHUNK, &cRowsObtained, &phRow);
					if (FAILED(hr))
					{
						strErr = "FAILED! IAccessor::GetNextRows()";
						COLEDBCommand::GetErrorDescription(strErr);
						CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
						
						return 0L;
					}
					
					if (cRowsObtained == 0)
					{
						break;
					}

					for (ULONG iRow = 0; iRow < cRowsObtained; iRow++)
					{
						hr = pIRowset->GetData(m_hRow[iRow], hAccessor, pRowValues);
						if (FAILED(hr))
						{
							strErr = "FAILED! IAccessor::GetData()";
							COLEDBCommand::GetErrorDescription(strErr);
							CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
							
							return 0L;
						}

						pVT->NewRow(cRowNum);

						for (ULONG nCol = 0; nCol < cCols; nCol++)
						{
							v.Reset();

							if (pColumnsInfo[nCol].m_bIsLong)
							{
								pISequentialStream = *((ISequentialStream**)(pRowValues + pDBBindings[nCol].obValue));

								strTemp = "";

								do
								{
									pISequentialStream->Read(pRBuffer, BLOCK_SIZE, &cRead);

									if (cRead > 0)
									{
										strTemp += (const char *)pRBuffer;
									}
								} while (cRead >= BLOCK_SIZE);

								pISequentialStream->Release();

								v.SetType(pColumnsInfo[nCol].m_nType1C);
								v = strTemp;
							}
							else
							{
								CType rt	 = pColumnsInfo[nCol].m_nType1C;
								dwStatus	 = (ULONG)pRowValues[pDBBindings[nCol].obStatus];
								
								switch(pColumnsInfo[nCol].m_wType)
								{
									case DBTYPE_NUMERIC:
									case DBTYPE_I2:
									case DBTYPE_I4:
									case DBTYPE_R4:
									case DBTYPE_R8:
									case DBTYPE_CY:
									case DBTYPE_DECIMAL:
									case DBTYPE_UI1:
									case DBTYPE_I1:
									case DBTYPE_UI2:
									case DBTYPE_UI4:
									case DBTYPE_I8:
									case DBTYPE_UI8:
									{
										// spock: 2005-12-15
										// Про обработку NULL-значений я и забыл :(
										// и про числовые забыл вообще
										// БАГ: если есть значения NULL. то провайдер возвращает предыдущее значение
										// баг с числовыми нашел DmitrO
										// А я растяпа забыл про числовые, когда правил 2005-11-13
										//
										if (DBSTATUS_S_ISNULL == dwStatus)
										{
											v.SetType(pColumnsInfo[nCol].m_nType1C);
											v = 0L;
											break;
										}

										v.SetType(pColumnsInfo[nCol].m_nType1C);
										v = strNum.FromString((const char*)&pRowValues[pDBBindings[nCol].obValue], NULL);
										break;
									}

									case DBTYPE_BOOL:
									{
										char* strVal = (char*)&pRowValues[pDBBindings[nCol].obValue];
										
										// spock: 2005-11-13
										// Про обработку NULL-значений я и забыл :(
										// Обрабатываем просто: есть NULL, значит просто прописываем
										// ноль в ТЗ
										//
										if (DBSTATUS_S_ISNULL == dwStatus)
										{
											v.SetType(pColumnsInfo[nCol].m_nType1C);
											v = 0L;
											break;
										}
										
										strTemp.Format("%s", strVal);
										v.SetType(pColumnsInfo[nCol].m_nType1C);

										if (strTemp.Compare("False") == 0)
											v = 0L;
										else
											v = 1L;

										break;
									}
									
									case DBTYPE_DBDATE:
									{
										dbdateVal = (DBDATE*)&pRowValues[pDBBindings[nCol].obValue];

										// spock: 2005-11-13
										// Про обработку NULL-значений я и забыл :(
										// Обрабатываем просто: есть NULL, значит просто прописываем
										// пустую дату в ТЗ
										//
										if (DBSTATUS_S_ISNULL == dwStatus)
										{
											CDate dateVal(0, 0, 0);
											v = dateVal;
											break;
										}

										v.SetType(pColumnsInfo[nCol].m_nType1C);

										if (dbdateVal->year == 1753 && 
											dbdateVal->month == 1 && 
											dbdateVal->day == 1)
										{
											// скваль 2к
											//
											CDate dateVal(0, 0, 0);
											v = dateVal;
										}
										else if (dbdateVal->year == 1899 && 
											dbdateVal->month == 12 && 
											dbdateVal->day == 30)
										{
											// фоксовый драйвер
											//
											CDate dateVal(0, 0, 0);
											v = dateVal;
										}
										else
										{
											CDate dateVal(dbdateVal->year, dbdateVal->month, dbdateVal->day);
											v = dateVal;
										}
										break;
									}
									
									case DBTYPE_DBTIMESTAMP:
									{
										dbdtVal = (DBTIMESTAMP*)&pRowValues[pDBBindings[nCol].obValue];

										// spock: 2005-11-13
										// Про обработку NULL-значений я и забыл :(
										// Обрабатываем просто: есть NULL, значит просто прописываем
										// пустую дату в ТЗ
										//
										if (DBSTATUS_S_ISNULL == dwStatus)
										{
											CDate dateVal(0, 0, 0);
											v = dateVal;
											break;
										}

										v.SetType(pColumnsInfo[nCol].m_nType1C);

										if (dbdtVal->year == 1753 && 
											dbdtVal->month == 1 && 
											dbdtVal->day == 1)
										{
											// скваль 2к
											//
											CDate dateVal(0, 0, 0);
											v = dateVal;
										}
										else if (dbdtVal->year == 1899 && 
											dbdtVal->month == 12 && 
											dbdtVal->day == 30)
										{
											// фоксовый драйвер
											//
											CDate dateVal(0, 0, 0);
											v = dateVal;
										}
										else
										{
											CDate dateVal(dbdtVal->year, dbdtVal->month, dbdtVal->day);
											v = dateVal;
										}
										break;
									}
									
									case DBTYPE_WSTR:
									{
										wstrVal = (wchar_t*)&pRowValues[pDBBindings[nCol].obValue];

										// spock: 2005-11-13
										// Про обработку NULL-значений я и забыл :(
										// Обрабатываем просто: есть NULL, значит просто прописываем
										// пустое значение в ТЗ
										//
										if (DBSTATUS_S_ISNULL == dwStatus)
										{
											v.SetType(pColumnsInfo[nCol].m_nType1C);
											v = "";
											break;
										}

										v.SetType(pColumnsInfo[nCol].m_nType1C);
										v = OLE2T(wstrVal);
										break;
									}
									
									default:
									{
										// spock: 2005-11-13
										// Про обработку NULL-значений я и забыл :(
										// Обрабатываем просто: есть NULL, значит просто прописываем
										// пустое значение в ТЗ
										//
										if(DBSTATUS_S_ISNULL == dwStatus)
										{
											v = "";
											break;
										}

										if(true == pColumnsInfo[nCol].m_bIsType1C)
										{
											CString strVal;
											CType LocalType(0);
											
											if(0 == rt.GetTypeID())
											{
												int nTypeCode = rt.GetTypeCode();

												switch(nTypeCode)
												{
													case ENUM_TYPE_1C:
													case REFERENCE_TYPE_1C:
													case CALENDAR_TYPE_1C:
													case ACCOUNT_TYPE_1C:
														{
															strVal	 = (char*)&pRowValues[pDBBindings[nCol].obValue];
															eType	 = CMetaDataWork::LongString;

															LocalType = rt;
														}
														break;
													
													case DOCUMENT_TYPE_1C:
														{
															ULONG nKindCol = 0;
															
															if(HasKindField)
																
																nKindCol = FindKindCol(pColumnsInfo, cCols, pColumnsInfo[nCol].m_strName);

															if(!nKindCol)
															{
																strVal	 = (char*)&pRowValues[pDBBindings[nCol].obValue];
																eType	 = CMetaDataWork::LongString;
															}
															else
															{
																CString strValLoc	 = (char*)&pRowValues[pDBBindings[nCol].obValue];
																CString strKindVal	 = (char*)&pRowValues[pDBBindings[nKindCol - 1].obValue];

																strVal	 = strKindVal + strValLoc;
																eType	 = CMetaDataWork::LongString;
															}
															LocalType = rt;
														}
														break;
													
													default:
														strVal	 = (char*)&pRowValues[pDBBindings[nCol].obValue];
														eType	 = CMetaDataWork::ShortString;

														LocalType = rt;
														break;
												}
											}
											else
											{
												strVal	 = (char*)&pRowValues[pDBBindings[nCol].obValue];
												eType	 = CMetaDataWork::ShortString;

												LocalType = rt;
											}

											CMetaDataWork::Make1C_ValueFromString(v, LocalType, strVal, eType);
										}
										else
										{
											char* strVal = (char*)&pRowValues[pDBBindings[nCol].obValue];

											v.SetType(pColumnsInfo[nCol].m_nType1C);
											v = strVal;
										}
										break;
									}
								};
							}

							pVT->SetValue(v, nCol, cRowNum);
						}

						++cRowNum;
					}

					hr = pIRowset->ReleaseRows(cRowsObtained, m_hRow, NULL, NULL, NULL);
					if (FAILED(hr))
					{
						strErr = "FAILED! IAccessor::GetData()";
						COLEDBCommand::GetErrorDescription(strErr);
						CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);

						return 0L;
					}
				}

				pIAccessor->ReleaseAccessor(hAccessor, NULL);
				
				delete[] pColumnsInfo;
				delete[] pRowValues;
				delete[] pDBBindings;
				delete[] pRBuffer;
				delete[] pDBObject;

				return 1L;
			}
			break;
		
		default:
			{
				strErr.Format("Нет обработчика метода под номером: %d)", iMethNum);
				CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
			}
	};

	return ret;
}

//_____________________________________________________________________________
//
int COLEDBCommand::CallAsProc(int iMethNum, class CValue** ppValue)
{
	int ret = 0;
	CString strErr;
	HRESULT hr;
	
	switch(iMethNum)
	{
		case procSetExecTimeOut:
			{
				m_ExecTimeout = ppValue[0]->GetNumeric();
				return 1L;
			}
			break;
		
		case procSetTempTablesDir:
			{
				m_szTempTablesDir.Empty();
				m_szTempTablesDir = ppValue[0]->GetString();

				if (m_szTempTablesDir.IsEmpty())
				{
					strErr.Format("Введена пустая строка!");
					CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
					return 0;
				}

				if (m_szTempTablesDir.Right(1) != "\\")
					m_szTempTablesDir += "\\";

				if (m_szTempTablesDir.Find(' ') > 0)
				{
					strErr.Format("Введена строка с пробелом!");
					CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
					return 0;
				}
			}
			break;
		
		case procPutObjectList:
			{
				hr = PutObjectsList(ppValue);
				if (FAILED(hr))
				{
					ret = 0;
				}
				
				ret = 1;
			}
			break;
		
		case procClose:
			{
				RemoveAllTmpTables();

				if(m_pICommandText)
					m_pICommandText.Release();
				
				m_bCreated	 = false;
				ret			 = 1;
			}
			break;

		case procDebug:
			{
				m_IsDebugMode = (0 == ppValue[0]->GetNumeric()) ? false : true;
			}
			break;
		
		case procSetTextParam:
			{
				m_MetaNameParser.SetParameter(ppValue[0]->GetString(), *ppValue[1]);
			}
			break;
		
		default:
			{
				strErr.Format("Нет обработчика метода под номером: %d)", iMethNum);
				CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
			}
	};
	
	return ret;
}

//_____________________________________________________________________________
//
int COLEDBCommand::GetParamDefValue(int iMethodNum, int iParamNum, class CValue* pDefValue) const
{
	int ret = 0;

	switch (iMethodNum)
	{
		case procPutObjectList:
			{
				if (iParamNum == 2)
				{
					*pDefValue	 = "";
					ret			 = 1;
				}
			}
			break;
		
		case funcExecute:
			{
				*pDefValue	 = "";
				ret			 = 1;
			}
			break;
		
		case funcExecuteStatement:
			{
				switch(iParamNum)
				{
					case 0:
						{
							*pDefValue	 = "";
							ret			 = 1;
						}
						break;
					case 1:
						{
							*pDefValue	 = CNumeric(0);
							ret			 = 1;
						}
						break;
				};
			}
			break;
	};

	return ret;
}

//_____________________________________________________________________________
//
int COLEDBCommand::FindMethod(char const * lpMethodName) const
{
	return defFnNames.GetIndexByName(lpMethodName);
}

//_____________________________________________________________________________
//
char const * COLEDBCommand::GetMethodName(int iMethodNum, int iMethodAlias) const
{
	return defFnNames[iMethodNum].Names[iMethodAlias];
}

//_____________________________________________________________________________
//
int COLEDBCommand::GetNMethods(void) const
{
	return defFnNames.Size();
}

//_____________________________________________________________________________
//
int COLEDBCommand::HasRetVal(int iMethodNum) const
{
	return defFnNames[iMethodNum].HasReturnValue;
}

//_____________________________________________________________________________
//
int COLEDBCommand::GetNParams(int iMethodNum) const
{
	return defFnNames[iMethodNum].NumberOfParams;
}

//_____________________________________________________________________________
//
char const * COLEDBCommand::GetCode(void) const
{
	return 0;
}

//_____________________________________________________________________________
//
int COLEDBCommand::GetDestroyUnRefd(void) const
{
	return 1;
}

//_____________________________________________________________________________
//
class CObjID COLEDBCommand::GetID(void) const
{
	return ObjID;
}

//_____________________________________________________________________________
//
long COLEDBCommand::GetTypeID(void) const
{
	return 100L;
}

//_____________________________________________________________________________
//
char const * COLEDBCommand::GetTypeString(void) const
{
	return "OLEDBCommand";
}

//_____________________________________________________________________________
//
class CType COLEDBCommand::GetValueType(void) const
{
	CType tType(100);

	return tType;
}

//_____________________________________________________________________________
//
int COLEDBCommand::IsExactValue(void) const
{
	return 0;
}

//_____________________________________________________________________________
//
int COLEDBCommand::IsOleContext(void) const
{
	return 0;
}

//_____________________________________________________________________________
//
int COLEDBCommand::IsPropReadable(int iPropNum) const
{
	return 1;
}

//_____________________________________________________________________________
//
int COLEDBCommand::IsPropWritable(int iPropNum) const
{
	return 1;
}

//_____________________________________________________________________________
//
int COLEDBCommand::IsSerializable(void)
{
	return 0;
}

//_____________________________________________________________________________
//
int COLEDBCommand::SaveToString(class CString& csStr)
{
	csStr = "";
	return 1;
}

//_____________________________________________________________________________
//
int COLEDBCommand::GetNProps(void) const
{
	return 0;
}

//_____________________________________________________________________________
//
char const * COLEDBCommand::GetPropName(int A, int B) const
{
	return NULL;
}

//_____________________________________________________________________________
//
int COLEDBCommand::GetPropVal(int iPropNum, class CValue& rValue) const
{
	return -1;
}

//_____________________________________________________________________________
//
int COLEDBCommand::SetPropVal(int iPropNum, class CValue const & vValue)
{
	return -1;
}

//_____________________________________________________________________________
//
int COLEDBCommand::FindProp(char const * Name) const
{
	return -1;
}

//_____________________________________________________________________________
//
void COLEDBCommand::GetErrorDescription(CString& strError) const
{
	HRESULT hr;
	
	IErrorInfo* pIErrorInfo;
	hr = ::GetErrorInfo(0, &pIErrorInfo);
	if (SUCCEEDED(hr))
	{
		BSTR errDescr;
		pIErrorInfo->GetDescription(&errDescr);

		strError += ": ";
		strError += errDescr;
		
		::SysFreeString(errDescr);
	}
	pIErrorInfo->Release();
}

//_____________________________________________________________________________
//
HRESULT COLEDBCommand::Binding(CComPtr<IRowset>& pIRowset, ULONG* pnCols, DBBINDING** ppDBBindings, COLEDBFldsInf** ppColumnsInfo1C, DBOBJECT** ppDBObject, ULONG* pcMaxRowSize, bool& HasKindField) const
{
	// OUT: [S_OK, S_FALSE]
	//
	USES_CONVERSION;

	HRESULT hr;
	CString strErr;

	CComPtr<IColumnsInfo> pIColumnsInfo;

	hr = pIRowset->QueryInterface(&pIColumnsInfo);
	if (FAILED(hr))
	{
		strErr = "FAILED! IRowset::QueryInterface()";
		COLEDBCommand::GetErrorDescription(strErr);
		CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);

		return S_FALSE;
	}

	ULONG nCols = 0;
	BSTR ppColumnStrings;
	DBCOLUMNINFO* pColumnsInfo;

	hr = pIColumnsInfo->GetColumnInfo(&nCols, &pColumnsInfo, &ppColumnStrings);
	if (FAILED(hr))
	{
		::SysFreeString(ppColumnStrings);

		strErr = "FAILED! IColumnsInfo::GetColumnInfo()";
		COLEDBCommand::GetErrorDescription(strErr);
		CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);

		return S_FALSE;
	}

	ULONG nDataOffset = 0;
	ULONG nLengthOffset;
	ULONG nStatusOffset;
	DBBINDING* pDBBindings		 = new DBBINDING[nCols];
	COLEDBFldsInf* pColInfo1C	 = new COLEDBFldsInf[nCols];
	DBOBJECT* pDBObject			 = new DBOBJECT[nCols];

	for (ULONG nCol = 0; nCol < nCols; nCol++)
	{
		//
		// нужно различать BLOB колонки от обычных.
		// MSDN говорит, что "text" and "ntext" (its for mssql) - 
		// это DBCOLUMNFLAGS_ISLONG = true or Maximum column size > 4,000 characters
		//

		// заполняем мою структуру
		//
		pColInfo1C[nCol].m_Index	 = nCol;
		pColInfo1C[nCol].m_strName	 = OLE2T(pColumnsInfo[nCol].pwszName);
		pColInfo1C[nCol].m_wType	 = pColumnsInfo[nCol].wType;
		pColInfo1C[nCol].m_nType1C	 = GetType1C(&pColInfo1C[nCol]);

		// Для небольшой оптимизации: чтобы не искать при любом виде запроса колонку с названием "XXX_вид"/"XXX_kind"
		//
		if((pColInfo1C[nCol].m_strName.Find("_kind") >= 0) || (pColInfo1C[nCol].m_strName.Find("_вид") >= 0))
			HasKindField = true;

		if (pColumnsInfo[nCol].dwFlags & DBCOLUMNFLAGS_ISLONG)
		{
			pColInfo1C[nCol].m_bIsLong	 = true;

			pDBObject[nCol].dwFlags		 = STGM_READ;
			pDBObject[nCol].iid			 = IID_ISequentialStream;

			pDBBindings[nCol].pObject	 = &pDBObject[nCol];
			pDBBindings[nCol].wType		 = DBTYPE_IUNKNOWN;
			pDBBindings[nCol].cbMaxLen	 = 0;
		}
		else
		{
			pColInfo1C[nCol].m_bIsLong	 = false;
			
			pDBBindings[nCol].pObject	 = NULL;

			switch (pColumnsInfo[nCol].wType)
			{
				case DBTYPE_STR:
				case DBTYPE_WSTR:
					{
						pDBBindings[nCol].wType		 = pColumnsInfo[nCol].wType;
						pDBBindings[nCol].cbMaxLen	 = pColumnsInfo[nCol].ulColumnSize + sizeof(char);
					}
					break;
				
				case DBTYPE_DBDATE:
				case DBTYPE_DBTIMESTAMP:
					{
						pDBBindings[nCol].wType		 = pColumnsInfo[nCol].wType;
						pDBBindings[nCol].cbMaxLen	 = pColumnsInfo[nCol].ulColumnSize;
					}
					break;
				
				default:
					{
						pDBBindings[nCol].wType		 = DBTYPE_STR;
						pDBBindings[nCol].cbMaxLen	 = 100;
					}
					break;
			};

			nLengthOffset	 = AddOffset(nDataOffset, pDBBindings[nCol].cbMaxLen);
			nStatusOffset	 = AddOffset(nLengthOffset, sizeof(ULONG));
		}

		pDBBindings[nCol].iOrdinal	 = pColumnsInfo[nCol].iOrdinal;
		pDBBindings[nCol].obValue	 = nDataOffset;
		pDBBindings[nCol].obLength	 = nLengthOffset;
		pDBBindings[nCol].obStatus	 = nStatusOffset;
		pDBBindings[nCol].pTypeInfo	 = NULL;
		pDBBindings[nCol].pBindExt	 = NULL;
		pDBBindings[nCol].dwPart	 = DBPART_VALUE | DBPART_STATUS;
		pDBBindings[nCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
		pDBBindings[nCol].eParamIO	 = DBPARAMIO_NOTPARAM;
		pDBBindings[nCol].dwFlags	 = 0;
		pDBBindings[nCol].bPrecision = pColumnsInfo[nCol].bPrecision;
		pDBBindings[nCol].bScale	 = pColumnsInfo[nCol].bScale;

		nDataOffset = AddOffset(nStatusOffset, sizeof(DBSTATUS));

		switch (pColInfo1C[nCol].m_nType1C.GetTypeCode())
		{
			case ENUM_TYPE_1C:
			case REFERENCE_TYPE_1C:
			case DOCUMENT_TYPE_1C:
			case CALENDAR_TYPE_1C:
			case CALCULATIONKIND_TYPE_1C:
			case ACCOUNT_TYPE_1C:
			case SUBCONTOKIND_TYPE_1C:
			case CHART_OF_ACC_TYPE_1C:
				pColInfo1C[nCol].m_bIsType1C = true;
				break;
			
			default:
				pColInfo1C[nCol].m_bIsType1C = false;
				break;
		}
	}
	
	*pnCols			 = nCols;
	*ppDBBindings	 = pDBBindings;
	*ppColumnsInfo1C = pColInfo1C;
	*pcMaxRowSize	 = nDataOffset;
	*ppDBObject		 = pDBObject;
	
	::SysFreeString(ppColumnStrings);

	return S_OK;
}

//_____________________________________________________________________________
//
CType COLEDBCommand::GetType1C(COLEDBFldsInf* fInfo) const
{
	if (pDataBase7->GetDataSourceType() == DATA_SOURCE_TYPE_DBF)
	{
		int nPos = -1;
		CType rt(0);
		CString strName = fInfo->m_strName;
		
		nPos = strName.Find(TypeNamePrefix);
		if (nPos >= 0)
		{
			CString Type1C(strName.operator LPCTSTR() + nPos + (sizeof(TypeNamePrefix) - 1));
			Type1C.Replace('_', '.');

			CMetaDataWork::SetTypeAndKind(rt, Type1C);

			strName				 = strName.Left(nPos);
			fInfo->m_strName	 = strName;
			
			return rt;
		}
	}

	switch(fInfo->m_wType)
	{
		case DBTYPE_STR:
		case DBTYPE_WSTR:
		case DBTYPE_DBTIME:
			return CType(2);
            break;

		case DBTYPE_NUMERIC:
		case DBTYPE_I2:
		case DBTYPE_I4:
		case DBTYPE_R4:
		case DBTYPE_R8:
		case DBTYPE_CY:
		case DBTYPE_DECIMAL:
		case DBTYPE_UI1:
		case DBTYPE_BOOL:
		case DBTYPE_I1:
		case DBTYPE_UI2:
		case DBTYPE_UI4:
		case DBTYPE_I8:
		case DBTYPE_UI8:
			return CType(1); // DmitrO говорит, что тип Число с Длина(0).Точность(0) - нормально
            break;

		case DBTYPE_DBDATE:
		case DBTYPE_DBTIMESTAMP:
			return CType(3);
			break;
		
		default:
			return CType(0);
			break;
	};
}

//_____________________________________________________________________________
//
HRESULT COLEDBCommand::ParseQuery(CString& strQuery)
{
	// OUT: [S_OK, S_FALSE]
	//
	CString strErr;

	strQuery.TrimLeft();
	strQuery.TrimRight();

	if (strQuery.IsEmpty())
	{
		strErr = "A SQL statement is empty!";
		CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
		
		return S_FALSE;
	}

	m_MetaNameParser.SetQueryText(strQuery);
	
	try
	{
		m_MetaNameParser.Parse();
	}
	catch (CMNPException* MNPException)
	{
		strErr = szMNPErrorPrefix + MNPException->GetErrorDescr();
		CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);

		MNPException->Delete();

		return S_FALSE;
	};

	strQuery = m_MetaNameParser.GetQueryText();

	if (m_IsDebugMode)
		pBkEndUI->DoMessageLine(strQuery, mmNone);
	
	return S_OK;
}

//_____________________________________________________________________________
//
HRESULT COLEDBCommand::Execute(const CString& strSQL, long* pnRowsAffected, IUnknown** pIRowset, bool quiet) const
{
	USES_CONVERSION;
	
	HRESULT hr;
	CString strErr;

	hr = m_pICommandText->SetCommandText(DBGUID_DEFAULT, T2OLE(strSQL));
	if (FAILED(hr))
	{
		if (quiet)
			return S_FALSE;
		
		strErr = "FAILED! ICommandText::SetCommandText()";
		COLEDBCommand::GetErrorDescription(strErr);
		CBLModule::RaiseExtRuntimeError(strErr, 0);
		
		return S_FALSE;
	}

	if (m_ExecTimeout >= 0)
	{
		CComPtr<ICommandProperties> spCommProperties;

		hr = m_pICommandText->QueryInterface(&spCommProperties);
		if (FAILED(hr))
		{
			strErr = "FAILED! ICommandProperties::QueryInterface()";
			COLEDBCommand::GetErrorDescription(strErr);
			CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
			
			return S_FALSE;
		}

		CDBPropSet PropSet(DBPROPSET_ROWSET);
		PropSet.AddProperty(DBPROP_COMMANDTIMEOUT, m_ExecTimeout);
		
		hr = spCommProperties->SetProperties(1, &PropSet);
		if (FAILED(hr))
		{
			strErr = "FAILED! ICommandProperties::SetProperties(): Не удалось установить property";
			CBLModule::RaiseExtRuntimeError(strErr, mmRedErr);
			
			return S_FALSE;
		}
	}

	if (!pIRowset)
		hr = m_pICommandText->Execute(NULL, IID_NULL, NULL, pnRowsAffected, NULL);
	else
		hr = m_pICommandText->Execute(NULL, IID_IRowset, NULL, pnRowsAffected, pIRowset);

	if (FAILED(hr))
	{

		if (quiet)
			return S_FALSE;

		strErr = "FAILED! ICommandText::Execute()";
		COLEDBCommand::GetErrorDescription(strErr);
		CBLModule::RaiseExtRuntimeError(strErr, 0);
		strErr.FreeExtra();
		
		return S_FALSE;
	}

	return S_OK;
}

//_____________________________________________________________________________
//
HRESULT COLEDBCommand::CreateAccessor(CComPtr<IAccessor>& pIAccessor, CComPtr<IRowset>& pIRowset, HACCESSOR* phAccessor, ULONG& cCols, DBBINDING* pDBBindings) const
{
	// OUT: [S_FALSE, S_OK]
	//
	HRESULT hr;
	CString strErr;

	hr = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor);
	if (FAILED(hr))
	{
		strErr = "FAILED! IRowset::QueryInterface()";
		COLEDBCommand::GetErrorDescription(strErr);
		CBLModule::RaiseExtRuntimeError(strErr, 0);

		return S_FALSE;
	}
	
	DBBINDSTATUS* pDBBindStatus = new DBBINDSTATUS[cCols];

	hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cCols, pDBBindings, 0, phAccessor, pDBBindStatus);
	if (FAILED(hr))
	{
		strErr = "FAILED! IAccessor::CreateAccessor()";
		COLEDBCommand::GetErrorDescription(strErr);
		CBLModule::RaiseExtRuntimeError(strErr, 0);
		
		return S_FALSE;
	}
	
	delete[] pDBBindStatus;
	
	return S_OK;
}

//_____________________________________________________________________________
//
HRESULT COLEDBCommand::PutObjectsList(CValue** ppValue)
{
	CString strErr;
	HRESULT hr;

	// Сразу позаботимся, чтобы параметр был пустым
	//
	CValue* pTableName = ppValue[1];
	pTableName->Reset();

	// Сгенерируем новый GUID
	//
	::UuidCreate(&m_GUID);
	
	unsigned char* strGUID;
	::UuidToString(&m_GUID, &strGUID);
	
	// Получим текстовое значение GUID
	//
	CString szTableName = strGUID;
	szTableName = m_szTempTablesDir + szTableName;
	::RpcStringFree(&strGUID);

	CString strSQL;
	
	// Создаем временную таблицу
	//
	strSQL.Format("CREATE TABLE %s (VAL C(9), ISFOLDER N(1,0))", szTableName);

	long nRowsAffected = 0;
	hr = COLEDBCommand::Execute(strSQL, &nRowsAffected, NULL);
	if (FAILED(hr))
	{
		strErr.Format("Не удалось создать временную таблицу!");
		CBLModule::RaiseExtRuntimeError(strErr, 0);

		return S_FALSE;
	}

	// Заполняем список: временные таблицы
	//
	m_TmpTblsList.AddTail(szTableName);

	CString RefName = ppValue[2]->GetString();

	bool ToDeleteFldr = false;

	if (ppValue[0]->GetTypeCode() == AGREGATE_TYPE_1C)
	{
		// Если передан список значений
		//
		CBLContext* pValCont = ppValue[0]->GetContext();

		if (pValCont && !strcmp(pValCont->GetRuntimeClass()->m_lpszClassName, "CValueListContext"))
		{
			CPtrArray* pVL = *(CPtrArray**) (((char*)pValCont) + 0x30);
			
			int nlistSize = pVL->GetSize();
			for(int i = 0; i < nlistSize; i++)
			{
				CValue* pValItem = (CValue*)pVL->GetAt(i);

				hr = PutAggregateObjects(pValItem, &szTableName, &RefName);
				if (FAILED(hr))
					return S_FALSE;
			}

			if (nlistSize > 0)
				ToDeleteFldr = true;
		}
		else
		{
			strErr.Format("Недопустимое значение первого аргумента метода!");
			CBLModule::RaiseExtRuntimeError(strErr, 0);
			return S_FALSE;
		}
	}
	else if (ppValue[0]->GetTypeCode() > DATE_TYPE_1C)
	{
		// Если передан агрегатный объект
		//
		CValue* pAggregate	 = ppValue[0];

		hr = PutAggregateObjects(pAggregate, &szTableName, &RefName);
		if (FAILED(hr))
			return S_FALSE;

		ToDeleteFldr = true;
	}

	if (ToDeleteFldr)
	{
		// Теперь нужно из временной таблицы удалить строки - группы (isfolder=1)
		//
		strSQL = "DELETE FROM %tmptblnm WHERE ISFOLDER = 1";
		strSQL.Replace("%tmptblnm", szTableName);

		nRowsAffected = 0;
		hr = COLEDBCommand::Execute(strSQL, &nRowsAffected, NULL);
		if (FAILED(hr))
		{
			strErr.Format("Не удалось удалить группы из временной таблицы!");
			CBLModule::RaiseExtRuntimeError(strErr, 0);
			
			return S_FALSE;
		}

		strSQL = "USE";

		nRowsAffected = 0;
		hr = COLEDBCommand::Execute(strSQL, &nRowsAffected, NULL);
		if (FAILED(hr))
		{
			strErr.Format("Не удалось сменить контекст базы данных!");
			CBLModule::RaiseExtRuntimeError(strErr, 0);
			
			return S_FALSE;
		}
	}

	// Все, теперь можно вернуть имя временной таблицы
	//
	*pTableName	 = szTableName.operator LPCTSTR();
	
	return S_OK;
	}

//_____________________________________________________________________________
//
HRESULT COLEDBCommand::PutAggregateObjects(CValue* pValue, const CString* pszTableName, const CString* RefName)
{
	CString strErr;
	CString strSQL;
	HRESULT hr;

	CSbCntTypeDef* pSbCntTypeDef = NULL;

	if (!RefName->IsEmpty())
	{
		pSbCntTypeDef = pMetaDataCont->GetSTypeDef(*RefName);
		
		if (!pSbCntTypeDef)
		{
			strErr.Format("Недопустимое значение третьего пераметра метода!");
			CBLModule::RaiseExtRuntimeError(strErr, 0);
			
			return S_FALSE;
		}
	}

	int nLevelsCount = 1;
	int nIsFldr		 = 2;
	
	if (!pValue->IsEmpty() && !RefName->IsEmpty())
	{
		CValue arValue;
		arValue.Reset();

		pValue->LinkContext(FALSE);
		CBLContext* pValCont = pValue->GetContext();
		pValCont->CallAsFunc(pValCont->FindMethod("IsGroup"), arValue, NULL);
		nIsFldr = 0 == arValue.GetNumeric() ? 2 : 1;

		nLevelsCount = pSbCntTypeDef->GetLevelsLimit();
	}

	CString ObjStr = "'";
	ObjStr += CMetaDataWork::GetObjDBString(*pValue, CMetaDataWork::ShortString);
	ObjStr += "'";

	strSQL.Empty();
	strSQL.Format("INSERT INTO %s (VAL, ISFOLDER) VALUES (%s, %d)", (*pszTableName), ObjStr, nIsFldr);

	long nRowsAffected = 0;
	hr = COLEDBCommand::Execute(strSQL, &nRowsAffected, NULL);
	if (FAILED(hr))
	{
		strErr.Format("Не удалось добавить корневую строку!");
		CBLModule::RaiseExtRuntimeError(strErr, 0);

		return S_FALSE;
	}

	// Если не передали вид справочника, то дальше не выполняем
	//
	if (RefName->IsEmpty() || nLevelsCount < 2)
	{
		return S_OK;
	}

	strSQL.Empty();

	strSQL = "INSERT INTO %tmptblnm (VAL, ISFOLDER)\r\n\
			SELECT SC.ID as VAL, SC.ISFOLDER as ISFOLDER\r\n\
			FROM %sctbl as SC\r\n\
			WHERE\r\n\
			(SC.PARENTID IN (SELECT T.VAL FROM %tmptblnm as T))\r\n\
			AND (SC.ID NOT IN (SELECT TT.VAL FROM %tmptblnm as TT))";

	strSQL.Replace("%tmptblnm", (*pszTableName));
	
	CString RefTable;
	RefTable.Format("sc%d", pSbCntTypeDef->GetID());
	strSQL.Replace("%sctbl", RefTable);

	// Нужно заполнять временную таблицу итеративно, проход - спуск ниже уровнем
	//
	do
	{
		nRowsAffected = 0;
		hr = COLEDBCommand::Execute(strSQL, &nRowsAffected, NULL);
		if (FAILED(hr))
		{
			strErr.Format("Не удалось добавить строки!");
			CBLModule::RaiseExtRuntimeError(strErr, 0);
			
			return S_FALSE;
		}
	}while (nRowsAffected > 0 && !FAILED(hr));
	
	return S_OK;
}

//_____________________________________________________________________________
//
void COLEDBCommand::RemoveTmpTable(CString& strTmpTblName)
{
	// Значит уже закрыли команду, вообще такого не должно быть
	// но лучше перебздеть, чем недобздеть
	//
	if(!m_pICommandText)
		return;

	CString strErr;
	HRESULT hr;

	CString strSQL("DROP TABLE ");
	strSQL += strTmpTblName;

	long nRowsAffected = 0;
	hr = COLEDBCommand::Execute(strSQL, &nRowsAffected, NULL, true);
}

//_____________________________________________________________________________
//
void COLEDBCommand::RemoveAllTmpTables(void)
{
	int nCountTmps = m_TmpTblsList.GetCount();
	if (nCountTmps > 0)
	{
		for(int i = 0; i < nCountTmps; ++i)
			RemoveTmpTable(m_TmpTblsList.GetAt(m_TmpTblsList.FindIndex(i)));

		m_TmpTblsList.RemoveAll();
	}
}

//_____________________________________________________________________________
//
ULONG COLEDBCommand::FindKindCol(const COLEDBFldsInf* pColumnsInfo, const ULONG& cMaxCols, const CString& strColName) const
{
	CString tmpNameRus(strColName);
	tmpNameRus += "_вид";
	
	CString tmpNameEng(strColName);
	tmpNameEng += "_kind";

	for(ULONG cCol = 0; cCol < cMaxCols; cCol++)
	{
		int nRus = tmpNameRus.Compare(pColumnsInfo[cCol].m_strName);
		int nEng = tmpNameEng.Compare(pColumnsInfo[cCol].m_strName);
		
		if((0 == nRus) || (0 == nEng))
			return cCol + 1;
	}

	return 0;
}