// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include <e32test.h>
#include <e32math.h>
#include <bautils.h>
#include <sqldb.h>
#include "SqlResourceTester.h"

///////////////////////////////////////////////////////////////////////////////////////

#define UNUSED_VAR(a) (a) = (a)

RTest TheTest(_L("t_sqlload test"));

_LIT(KTestDir, "c:\\test\\");
_LIT(KTestDbName1, "c:\\test\\t_sqlload_1.db");
_LIT(KTestDbName2, "c:\\test\\t_sqlload_2.db");
_LIT(KTestDbName3, "c:\\test\\t_sqlload_3.db");

//Test thread count
const TInt KTestThreadCnt = 4;

//Test database names
const TPtrC KTestDbNames[] =
	{
	KTestDbName1(),
	KTestDbName2(),
	KTestDbName3()
	};

//Test database count
const TInt KTestDbCnt = sizeof(KTestDbNames) / sizeof(KTestDbNames[0]);

//Test duration
const TInt KTestDuration = 120;//seconds

//Test record count
const TInt KRecordCnt = 100;
//Record count which will be used in the test SQL queries
const TInt KQueriedRecordCnt = 40;
//Every SQL query will be processed (stepped) in KTestStepCnt steps.
const TInt KTestStepCnt = 4;
//RSqlStatement object count which will be used in the tests
const TInt KStatementCnt = 10;
//Max allowed alive RSqlStatement objects per thread
const TInt KMaxStatementPerThread = 30;
//Binary data length
const TInt KBinDataLen = 2003;

///////////////////////////////////////////////////////////////////////////////////////

void DeleteTestFiles()
	{
	RSqlDatabase::Delete(KTestDbName3);
	RSqlDatabase::Delete(KTestDbName2);
	RSqlDatabase::Delete(KTestDbName1);
	}

///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
//Test macros and functions
void Check1(TInt aValue, TInt aLine, TBool aPrintThreadName = EFalse)
	{
	if(!aValue)
		{
		DeleteTestFiles();
		if(aPrintThreadName)
			{
			RThread th;
			TName name = th.Name();
			RDebug::Print(_L("*** Thread %S, Line %d\r\n"), &name, aLine);
			}
		else
			{
			RDebug::Print(_L("*** Line %d\r\n"), aLine);
			}
		TheTest(EFalse, aLine);
		}
	}
void Check2(TInt aValue, TInt aExpected, TInt aLine, TBool aPrintThreadName = EFalse)
	{
	if(aValue != aExpected)
		{
		DeleteTestFiles();
		if(aPrintThreadName)
			{
			RThread th;
			TName name = th.Name();
			RDebug::Print(_L("*** Thread %S, Line %d Expected error: %d, got: %d\r\n"), &name, aLine, aExpected, aValue);
			}
		else
			{
			RDebug::Print(_L("*** Line %d, Expected error: %d, got: %d\r\n"), aLine, aExpected, aValue);
			}
		TheTest(EFalse, aLine);
		}
	}
#define TEST(arg) ::Check1((arg), __LINE__)
#define TEST2(aValue, aExpected) ::Check2(aValue, aExpected, __LINE__)
#define TTEST(arg) ::Check1((arg), __LINE__, ETrue)
#define TTEST2(aValue, aExpected) ::Check2(aValue, aExpected, __LINE__, ETrue)

///////////////////////////////////////////////////////////////////////////////////////

void CreateTestDir()
    {
    RFs fs;
	TInt err = fs.Connect();
	TEST2(err, KErrNone);

	err = fs.MkDir(KTestDir);
	TEST(err == KErrNone || err == KErrAlreadyExists);

	fs.Close();
	}

///////////////////////////////////////////////////////////////////////////////////////

void CreateTestDatabases()
	{
	HBufC8* recData = HBufC8::New(KBinDataLen * 2 + 50);//"* 2" - hex values for the INSERT SQL statement
	TEST(recData != NULL);
	TPtr8 sql = recData->Des();

	for(TInt dbIdx=0;dbIdx<KTestDbCnt;++dbIdx)
		{
		//Create test database
		RSqlDatabase db;
		TInt err = db.Create(KTestDbNames[dbIdx]);
		TEST2(err, KErrNone);

		//Create test table
		_LIT8(KCreateSql, "CREATE TABLE A(F1 INTEGER, F2 BLOB)");
		err = db.Exec(KCreateSql);
		TEST(err >= 0);

		//Insert records in the test table
		for(TInt recIdx=1;recIdx<=KRecordCnt;++recIdx)
			{
			_LIT8(KInsertSql, "INSERT INTO A(F1, F2) VALUES(");
			sql.Copy(KInsertSql);
			sql.AppendNum((TInt64)recIdx);
			sql.Append(_L(", X'"));
			for(TInt k=0;k<KBinDataLen;++k)
				{
				sql.AppendFormat(_L8("%02X"), recIdx);
				}
			sql.Append(_L("')"));
			err = db.Exec(sql);
			TEST2(err, 1);
			}

		db.Close();
		}

	delete recData;
	}

//Structure used by the test thread function for orginizing its set of test data.
struct TSqlStatement
	{
	RSqlStatement	iObj;
	TBool 			iAlive;			//Non-zero if iObj is alive
	TInt			iCurIndex;		//The number of the current record in the set controlled by iObj statement
	TInt			iEndIndex;		//The last record number in the set controlled by iObj statement
	TInt			iCount;			//Records count in the set controlled by iObj statement
	};
	
typedef RArray<TSqlStatement> RSqlStatementArray;	

//Inits the random numbers generator.
//Opens one of the test databases.
void PreTest(RSqlDatabase& aDb, TInt64& aSeed, TName& aThreadName)
	{
	RThread currThread;
	
	//Init the random numbers generator
	TTime now;
	now.UniversalTime();
	aSeed = now.Int64() + currThread.Id();

	//Open one of the test databases
	const TInt KDbIndex = Math::Rand(aSeed) % KTestDbCnt;

	aThreadName = currThread.Name();
	RDebug::Print(_L("=== Thread %S, database %S\r\n"), &aThreadName, &KTestDbNames[KDbIndex]);

	TInt err = aDb.Open(KTestDbNames[KDbIndex]);
	TTEST2(err, KErrNone);
	}

//Creates N statements, where 0 < N < KStatementCnt
TInt CreateStatements(RSqlDatabase& aDb, TInt64& aSeed, RSqlStatementArray& aStmtArray)
	{
	TInt stmtCount = Math::Rand(aSeed) % KStatementCnt;
	if(stmtCount == 0)
		{
		stmtCount = 1;
		}
	for(TInt i=0;i<stmtCount;++i)
		{
		TSqlStatement stmt;
		stmt.iAlive = EFalse;
		stmt.iCount = KQueriedRecordCnt;
		stmt.iCurIndex = Math::Rand(aSeed) % KRecordCnt;
		if(stmt.iCurIndex == 0)
			{
			stmt.iCurIndex = 1;
			}
		if(stmt.iCurIndex > (KRecordCnt - KQueriedRecordCnt))
			{
			stmt.iCurIndex = KRecordCnt - KQueriedRecordCnt;
			}
		stmt.iEndIndex = stmt.iCurIndex + KQueriedRecordCnt;
		TBuf8<100> sql;
		sql.Copy(_L8("SELECT * FROM A WHERE F1 >= "));
		sql.AppendNum(stmt.iCurIndex);
		sql.Append(_L8(" AND F1 < "));
		sql.AppendNum(stmt.iEndIndex);
		TInt err = stmt.iObj.Prepare(aDb, sql);
		TTEST2(err, KErrNone);
		stmt.iAlive = ETrue;
		err = aStmtArray.Append(stmt);
		TTEST2(err, KErrNone);
		}
	return stmtCount;		
	}
	
//For each alive statement object - do (TSqlStatement::iCount / KTestStepCnt)
//RSqlStatement::Next() calls. If the Next() call reaches the end - close the statement object.
TInt ProcessStatements(RSqlStatementArray& aStmtArray)
	{
	const TInt KTotalStmtCount = aStmtArray.Count();
	TInt alive = 0;
	TInt completed = 0;
	for(TInt k=0;k<KTotalStmtCount;++k)
		{
		TSqlStatement& stmt = aStmtArray[k];
		if(stmt.iAlive)
			{
			++alive;
			TInt endIndex = stmt.iCurIndex + stmt.iCount / KTestStepCnt;
			if(endIndex <= stmt.iEndIndex)
				{
				while(stmt.iCurIndex < endIndex)
					{
					TInt err = stmt.iObj.Next();
					TTEST2(err, KSqlAtRow);
					//test column values
					TInt val1 = stmt.iObj.ColumnInt(0);
					TTEST(val1 == stmt.iCurIndex);
					RSqlColumnReadStream strm;
					err = strm.ColumnBinary(stmt.iObj, 1);
					TTEST2(err, KErrNone);
					for(TInt ii=0;ii<KBinDataLen;++ii)
						{
						TUint8 byte = 0;
						TRAP(err, byte = strm.ReadUint8L());
						TTEST2(err, KErrNone);
						TTEST(byte == (TUint8)val1);
						}
					strm.Close();
					++stmt.iCurIndex;
					}
				}
			if(stmt.iCurIndex >= stmt.iEndIndex)
				{
				stmt.iObj.Close();
				stmt.iAlive = EFalse;
				++completed;
				}
			}
		}
	return completed;
	}

//Close up to N statements, where 0 < N < KStatementCnt
TInt CloseStatements(RSqlStatementArray& aStmtArray, TInt64& aSeed)
	{
	TInt stmtCount = Math::Rand(aSeed) % KStatementCnt;
	if(stmtCount == 0)
		{
		stmtCount = 1;
		}
	const TInt KTotalStmtCount = aStmtArray.Count();
	TInt closed = 0;
	for(TInt j=0;j<stmtCount;++j)
		{
		const TInt KIdx = Math::Rand(aSeed) % KTotalStmtCount;
		TInt idx = KIdx;
		while((idx = (++idx % KTotalStmtCount)) != KIdx)
			{
			if(aStmtArray[idx].iAlive)
				{
				aStmtArray[idx].iObj.Close();
				aStmtArray[idx].iAlive = EFalse;
				++closed;
				break;
				}
			}
		}
	return closed;
	}

//Counts the alive statements
TInt AliveStatementsCount(RSqlStatementArray& aStmtArray)
	{
	TInt aliveCnt = 0;
	const TInt KTotalStmtCount = aStmtArray.Count();
	for(TInt l=0;l<KTotalStmtCount;++l)
		{
		if(aStmtArray[l].iAlive)
			{
			++aliveCnt;
			}
		}
	return aliveCnt;
	}
	
//Close all alive statements
void CloseAllStatements(RSqlStatementArray& aStmtArray)
	{
	const TInt KTotalStmtCount = aStmtArray.Count();
	for(TInt i=0;i<KTotalStmtCount;++i)
		{
		if(aStmtArray[i].iAlive)
			{
			aStmtArray[i].iObj.Close();
			}
		}
	TTEST2(TSqlResourceTester::Count(), 0);
	}

//Removes the already closed statements and compresses the array
void RemoveDeadStatements(RSqlStatementArray& aStmtArray)
	{
	for(TInt i=aStmtArray.Count()-1;i>=0;--i)
		{
		if(!aStmtArray[i].iAlive)
			{
			aStmtArray.Remove(i);
			}
		}
	aStmtArray.Compress();		
	}

//Close statement objects, statements array and the database object
TInt PostTest(RSqlDatabase& aDb, RSqlStatementArray& aStmtArray)
	{
	TInt statementsAlive = AliveStatementsCount(aStmtArray);
	CloseAllStatements(aStmtArray);
	aStmtArray.Close();
	aDb.Close();
	return statementsAlive;
	}

//Test thread function
//The thread function works with a set of TSqlStatement objects
//The test consists of 4 steps:
//Step 1: the test thread creates m TSqlStatement objects, 0 < m < KStatementCnt.
//		  With each of the created TSqlStatement objects the test thread prepares SELECT SQL query
//        "SELECT * FROM A WHERE F1 >= K1 AND F1 < K2", where K1 is random generated number, such that:
//        0 < K1 < (KRecordCnt - KQueriedRecordCnt)
//        K2 = K1 + KQueriedRecordCnt
//		  All just created TSqlStatement objects are marked as alive.
//Step 2: For each alive TSqlStatement object the test thread calls iObj.Next() method KTestStepCnt times,
//        KTestStepCnt < KQueriedRecordCnt.
//        The column values are retrieved and checked.
//Step 3: the test thread closes n TSqlStatement objects, 0 < n < KStatementCnt.
//Step 4: the test thread counts how many alive TSqlStatement objects are there.
//        If this count > KMaxStatementPerThread then the test thread closes all alive TSqlStatement objects
//		  to avoid OOM errors during the test.
//
//		  Each test thread does steps 1..4 for a period of KTestDuration seconds.
//		  At the end all TSqlStatement objects are closed.
//
//		  The idea of the test is to load the SQL server creating several amount of statement and stream objects
//		  and see that it is working stable and without problems.
TInt ThreadFunc(void*)
	{
	__UHEAP_MARK;

	CTrapCleanup* tc = CTrapCleanup::New();
	TTEST(tc != NULL);

	TInt64 seed = 0;
	RSqlDatabase db;
	TName threadName;
	RSqlStatementArray statements;
	
	//Init the random numbers generator, opens the database
	PreTest(db, seed, threadName);

	//Main test loop
	TInt iteration = 0;
	TTime currTime;
	currTime.UniversalTime();
	TTime endTime = currTime + TTimeIntervalSeconds(KTestDuration);
	while(currTime < endTime)
		{
		++iteration;
		///////////////////////////////////////////////////////////////////////
		TInt statementsAliveBegin = statements.Count();
		//Step 1: Create N statements, where 0 < N < KStatementCnt
		TInt statementsCreated = CreateStatements(db, seed, statements);
		///////////////////////////////////////////////////////////////////////
		//Step 2: For each alive statement object - do (TSqlStatement::iCount / KTestStepCnt)
		//        RSqlStatement::Next() calls. If the Next() call reaches the end - close the statement object.
		TInt statementsCompleted = ProcessStatements(statements);
		///////////////////////////////////////////////////////////////////////
		//Step 3: Close up to N statements, where 0 < N < KStatementCnt
		TInt statementsClosed = CloseStatements(statements, seed);
		///////////////////////////////////////////////////////////////////////
		//Step 4: If the alive statement count is more than KMaxStatementPerThread, then close them all
		TInt statementsAliveEnd = AliveStatementsCount(statements);
		if(statementsAliveEnd > KMaxStatementPerThread)
			{
			RDebug::Print(_L("!!! Thread %S, iteration %d, alive %d, close all\r\n"), &threadName, iteration, statementsAliveEnd);
			CloseAllStatements(statements);
			statementsAliveEnd = 0;
			}
		///////////////////////////////////////////////////////////////////////
		RemoveDeadStatements(statements);
		RDebug::Print(_L("=== Thread %S, iteration % 4d, begin: % 3d, created % 2d, closed % 2d, completed % 2d, end % 3d, \r\n"),
							&threadName, iteration, statementsAliveBegin, 
													statementsCreated, statementsClosed, statementsCompleted, 
													statementsAliveEnd);
		currTime.UniversalTime();
		}

	//Close statement objects and the database object
	TInt statementsAlive = PostTest(db, statements);

	delete tc;

	__UHEAP_MARKEND;

	RDebug::Print(_L("=== Thread %S exit, still alive %d\r\n"), &threadName, statementsAlive);

	return KErrNone;
	}

void CreateTestThreads(RThread aThreads[], TRequestStatus aStatuses[], TInt aMaxCount)
	{
	_LIT(KThreadName, "TstThr");
	for(TInt i=0;i<aMaxCount;++i)
		{
		TBuf<20> threadName(KThreadName);
		threadName.AppendNum((TInt64)(i + 1));
		TEST2(aThreads[i].Create(threadName, &ThreadFunc, 0x2000, 0x1000, 0x10000, NULL, EOwnerProcess), KErrNone);
		aThreads[i].Logon(aStatuses[i]);
		TEST2(aStatuses[i].Int(), KRequestPending);
		}
	}

void ResumeTestThreads(RThread aThreads[], TInt aMaxCount)
	{
	for(TInt i=0;i<aMaxCount;++i)
		{
		aThreads[i].Resume();
		}
	}


void CloseTestThreads(RThread aThreads[], TRequestStatus aStatuses[], TInt aMaxCount)
	{
	for(TInt i=0;i<aMaxCount;++i)
		{
		User::WaitForRequest(aStatuses[i]);
		TEST(aThreads[i].ExitType() != EExitPanic);
		aThreads[i].Close();
		}
	}

/**
@SYMTestCaseID			SYSLIB-SQL-CT-1627-0001
@SYMTestCaseDesc		SQL server load test. The test creates KTestThreadCnt threads, KTestDbCnt test databases and
						inserts in each of them KRecordCnt test records.
						Pre-test step: each test thread randomly chooses and opens one of the test databases.
						Then, each of the test threads is doing the following 4 test steps:
						Step 1: the test thread creates m TSqlStatement objects, 0 < m < KStatementCnt.
						With each of the created TSqlStatement objects the test thread prepares SELECT SQL query
						"SELECT * FROM A WHERE F1 >= K1 AND F1 < K2", where K1 is random generated number, such that:
						0 < K1 < (KRecordCnt - KQueriedRecordCnt)
						K2 = K1 + KQueriedRecordCnt
						All just created TSqlStatement objects are marked as alive.
						Step 2: For each alive TSqlStatement object the test thread calls iObj.Next() method KTestStepCnt times,
						KTestStepCnt < KQueriedRecordCnt.
						The column values are retrieved and checked.
						Step 3: the test thread closes n TSqlStatement objects, 0 < n < KStatementCnt.
						Step 4: the test thread counts how many alive TSqlStatement objects are there.
						If this count > KMaxStatementPerThread then the test thread closes all alive TSqlStatement objects
						to avoid OOM errors during the test.

						Each test thread does steps 1..4 for a period of KTestDuration seconds.
						At the end all TSqlStatement objects are closed.

						The idea of the test is to load the SQL server creating several amount of statement and stream objects
						and see that it is working stable and without problems.
@SYMTestPriority		High
@SYMTestActions			SQL server load test
@SYMTestExpectedResults Test must not fail
@SYMREQ					REQ5792
                        REQ5793
*/
void SqlLoadTest()
	{
	CreateTestDatabases();

	RThread threads[KTestThreadCnt];
	TRequestStatus statuses[KTestThreadCnt];

	CreateTestThreads(threads, statuses, KTestThreadCnt);

	ResumeTestThreads(threads, KTestThreadCnt);

	User::After(2000000);

	CloseTestThreads(threads, statuses, KTestThreadCnt);
	}

void DoTests()
	{
	TheTest.Start(_L(" @SYMTestCaseID:SYSLIB-SQL-CT-1627-0001 SQL server load test "));
	SqlLoadTest();
	}

TInt E32Main()
	{
	TheTest.Title();

	CTrapCleanup* tc = CTrapCleanup::New();

	__UHEAP_MARK;

	CreateTestDir();
	DeleteTestFiles();
	DoTests();
	DeleteTestFiles();

	__UHEAP_MARKEND;

	TheTest.End();
	TheTest.Close();

	delete tc;

	User::Heap().Check();
	return KErrNone;
	}
