text: //---------------------------------------------------------------------------
  //	Greenplum Database
  //	Copyright (C) 2011 EMC Corp.
  //
  //	@filename:
  //		CTestUtils.cpp
  //
  //	@doc:
  //		Implementation of test utility functions
  //---------------------------------------------------------------------------
  
  #include "gpos/base.h"
  #include "gpos/common/CAutoP.h"
  #include "gpos/error/CMessage.h"
  #include "gpos/io/COstreamString.h"
  #include "gpos/memory/CAutoMemoryPool.h"
  #include "gpos/string/CWStringDynamic.h"
  #include "gpos/task/CAutoTraceFlag.h"
  #include "gpos/test/CUnittest.h"
  
  #include "gpopt/base/CConstraintInterval.h"
  #include "gpopt/base/CCTEMap.h"
  #include "gpopt/base/CColRefSetIter.h"
  #include "gpopt/base/CDefaultComparator.h"
  #include "gpopt/base/CDistributionSpecHashed.h"
  #include "gpopt/eval/CConstExprEvaluatorDefault.h"
  #include "gpopt/eval/IConstExprEvaluator.h"
  #include "gpopt/operators/ops.h"
  #include "gpopt/operators/CScalarSubqueryAll.h"
  #include "gpopt/operators/CScalarSubqueryAny.h"
  #include "gpopt/xforms/CSubqueryHandler.h"
  
  #include "naucrates/md/CMDIdGPDB.h"
  #include "naucrates/md/CMDProviderMemory.h"
  #include "naucrates/md/IMDTypeBool.h"
  #include "naucrates/md/CMDTypeGenericGPDB.h"
  #include "naucrates/md/IMDTypeInt2.h"
  #include "naucrates/md/IMDTypeInt4.h"
  #include "naucrates/md/IMDTypeInt8.h"
  #include "naucrates/md/IMDScalarOp.h"
  #include "naucrates/base/CDatumInt2GPDB.h"
  #include "naucrates/base/CDatumInt4GPDB.h"
  #include "naucrates/base/CDatumInt8GPDB.h"
  #include "naucrates/base/CDatumBoolGPDB.h"
  
  #include "naucrates/dxl/operators/CDXLDatumGeneric.h"
  #include "naucrates/dxl/operators/CDXLDatumStatsDoubleMappable.h"
  #include "naucrates/dxl/operators/CDXLDatumStatsLintMappable.h"
  
  #include "naucrates/statistics/CStatsPredUtils.h"
  
  
  #include "gpopt/exception.h"
  #include "gpopt/base/CUtils.h"
  #include "gpopt/base/CAutoOptCtxt.h"
  #include "gpopt/base/CColRefSetIter.h"
  #include "gpopt/base/CPrintPrefix.h"
  #include "gpopt/engine/CEngine.h"
  #include "gpopt/engine/CEnumeratorConfig.h"
  #include "gpopt/engine/CStatisticsConfig.h"
  #include "gpopt/optimizer/COptimizerConfig.h"
  #include "gpopt/metadata/CTableDescriptor.h"
  #include "gpopt/mdcache/CMDCache.h"
  #include "gpopt/minidump/CMinidumperUtils.h"
  #include "gpopt/operators/ops.h"
  #include "gpopt/xforms/CXformUtils.h"
  #include "gpopt/translate/CTranslatorDXLToExpr.h"
  #include "gpopt/translate/CTranslatorExprToDXL.h"
  
  #include "naucrates/base/CQueryToDXLResult.h"
  #include "naucrates/dxl/CDXLUtils.h"
  
  #include "unittest/base.h"
  #include "unittest/gpopt/CSubqueryTestUtils.h"
  #include "unittest/gpopt/CTestUtils.h"
  
  #include <fstream>
  
  #define GPOPT_SEGMENT_COUNT 2 // number segments for testing
  
  using namespace gpopt;
  
  // static variable initialization
  // default source system id
  CSystemId 
  CTestUtils::m_sysidDefault(IMDId::EmdidGPDB, GPOS_WSZ_STR_LENGTH("GPDB"));
  
  
  // XSD path
  const CHAR *
  CTestUtils::m_szXSDPath = "http://greenplum.com/dxl/2010/12/ dxl.xsd";
  
  // metadata file
  const CHAR *
  CTestUtils::m_szMDFileName = "../data/dxl/metadata/md.xml";
  
  // provider file
  CMDProviderMemory *
  CTestUtils::m_pmdpf = NULL;
  
  // local memory pool
  CMemoryPool *
  CTestUtils::m_mp = NULL;
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::CTestSetup::PmdpSetupFileBasedProvider
  //
  //	@doc:
  //		set up a file based provider
  //
  //---------------------------------------------------------------------------
  CMDProviderMemory *
  CTestUtils::CTestSetup::PmdpSetupFileBasedProvider()
  {
  	CMDProviderMemory *pmdp = CTestUtils::m_pmdpf;
  	pmdp->AddRef();
  	return pmdp;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::CTestSetup::CTestSetup
  //
  //	@doc:
  //		ctor
  //
  //---------------------------------------------------------------------------
  CTestUtils::CTestSetup::CTestSetup()
  	:
  	m_amp(),
  	m_mda(m_amp.Pmp(), CMDCache::Pcache(), CTestUtils::m_sysidDefault, PmdpSetupFileBasedProvider()),
  	// install opt context in TLS
  	m_aoc
  		(
  		m_amp.Pmp(),
  		&m_mda,
  		NULL,  /* pceeval */
  		CTestUtils::GetCostModel(m_amp.Pmp())
  		)
  {
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::InitProviderFile
  //
  //	@doc:
  //		Initialize provider file;
  //		called before unittests start
  //
  //---------------------------------------------------------------------------
  void
  CTestUtils::InitProviderFile
  	(
  	CMemoryPool *mp
  	)
  {
  	GPOS_ASSERT(NULL == m_mp);
  	GPOS_ASSERT(NULL != mp);
  
  	m_mp = mp;
  	m_pmdpf = GPOS_NEW(m_mp) CMDProviderMemory(m_mp, m_szMDFileName);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::DestroyMDProvider
  //
  //	@doc:
  //		Destroy metadata file;
  //		called after all unittests complete
  //
  //---------------------------------------------------------------------------
  void
  CTestUtils::DestroyMDProvider()
  {
  	GPOS_ASSERT(NULL != m_mp);
  
  	CRefCount::SafeRelease(m_pmdpf);
  
  	// release local memory pool
  	CMemoryPoolManager::GetMemoryPoolMgr()->Destroy(m_mp);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PtabdescPlainWithColNameFormat
  //
  //	@doc:
  //		Generate a plain table descriptor, where the column names are generated
  //		using a format string containing %d
  //
  //---------------------------------------------------------------------------
  
  
  CTableDescriptor *
  CTestUtils::PtabdescPlainWithColNameFormat
  	(
  	CMemoryPool *mp,
  	ULONG num_cols,
  	IMDId *mdid,
  	const WCHAR *wszColNameFormat,
  	const CName &nameTable,
  	BOOL is_nullable // define nullable columns
  	)
  {
  	GPOS_ASSERT(0 < num_cols);
  
  	CMDAccessor *md_accessor = COptCtxt::PoctxtFromTLS()->Pmda();
  
  	const IMDTypeInt4 *pmdtypeint4 = md_accessor->PtMDType<IMDTypeInt4>(CTestUtils::m_sysidDefault);
  	CWStringDynamic *str_name = GPOS_NEW(mp) CWStringDynamic(mp);
  	CTableDescriptor *ptabdesc = GPOS_NEW(mp) CTableDescriptor
  											(
  											mp, 
  											mdid, 
  											nameTable, 
  											false, // convert_hash_to_random
  											IMDRelation::EreldistrRandom,
  											IMDRelation::ErelstorageHeap, 
  											0 // ulExecuteAsUser
  											);
  
  	for (ULONG i = 0; i < num_cols; i++)
  	{
  		str_name->Reset();
  		str_name->AppendFormat(wszColNameFormat, i);
  
  		// create a shallow constant string to embed in a name
  		CWStringConst strName(str_name->GetBuffer());
  		CName nameColumnInt(&strName);
  
  		CColumnDescriptor *pcoldescInt = GPOS_NEW(mp) CColumnDescriptor(mp, pmdtypeint4, default_type_modifier, nameColumnInt, i + 1, is_nullable);
  		ptabdesc->AddColumn(pcoldescInt);
  	}
  
  	GPOS_DELETE(str_name);
  
  	return ptabdesc;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PtabdescPlain
  //
  //	@doc:
  //		Generate a plain table descriptor
  //
  //---------------------------------------------------------------------------
  CTableDescriptor *
  CTestUtils::PtabdescPlain
  	(
  	CMemoryPool *mp,
  	ULONG num_cols,
  	IMDId *mdid,
  	const CName &nameTable,
  	BOOL is_nullable // define nullable columns
  	)
  {
  	return PtabdescPlainWithColNameFormat(
  			mp, num_cols, mdid, GPOS_WSZ_LIT("column_%04d"), nameTable, is_nullable);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PtabdescCreate
  //
  //	@doc:
  //		Generate a table descriptor
  //
  //---------------------------------------------------------------------------
  CTableDescriptor *
  CTestUtils::PtabdescCreate
  	(
  	CMemoryPool *mp,
  	ULONG num_cols,
  	IMDId *mdid,
  	const CName &nameTable,
  	BOOL fPartitioned
  	)
  {
  	CTableDescriptor *ptabdesc = PtabdescPlain(mp, num_cols, mdid, nameTable);
  
  	if (fPartitioned)
  	{
  		ptabdesc->AddPartitionColumn(0);
  	}
  	
  	// create a keyset containing the first column
  	CBitSet *pbs = GPOS_NEW(mp) CBitSet(mp, num_cols);
  	pbs->ExchangeSet(0);
  #ifdef GPOS_DEBUG
  	BOOL fSuccess =
  #endif // GPOS_DEBUG
  		ptabdesc->FAddKeySet(pbs);
  	GPOS_ASSERT(fSuccess);
  
  	return ptabdesc;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGet
  //
  //	@doc:
  //		Generate a get expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGet
  	(
  	CMemoryPool *mp,
  	CTableDescriptor *ptabdesc,
  	const CWStringConst *pstrTableAlias
  	)
  {
  	GPOS_ASSERT(NULL != ptabdesc);
  
  	CLogicalGet *pop = GPOS_NEW(mp) CLogicalGet
  						(
  						 mp,
  						 GPOS_NEW(mp) CName(mp, CName(pstrTableAlias)),
  						 ptabdesc
  						 );
  
  	CExpression *result = GPOS_NEW(mp) CExpression
  							(
  							mp,
  							pop
  							);
  
  	CColRefArray *arr = pop->PdrgpcrOutput();
  	for (ULONG ul = 0; ul < arr->Size(); ul++)
  	{
  		CColRef *ref = (*arr)[ul];
  		ref->MarkAsUsed();
  	}
  
  	return result;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGetNullable
  //
  //	@doc:
  //		Generate a get expression over table with nullable columns
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGetNullable
  	(
  	CMemoryPool *mp,
  	OID oidTable,
  	const CWStringConst *str_table_name,
  	const CWStringConst *pstrTableAlias
  	)
  {
  	CWStringConst strName(str_table_name->GetBuffer());
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(oidTable, 1, 1);
  	CTableDescriptor *ptabdesc = CTestUtils::PtabdescPlain(mp, 3, mdid, CName(&strName), true /*is_nullable*/);
  	CWStringConst strAlias(pstrTableAlias->GetBuffer());
  
  	return PexprLogicalGet(mp, ptabdesc, &strAlias);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGet
  //
  //	@doc:
  //		Generate a get expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGet
  	(
  	CMemoryPool *mp,
  	CWStringConst *str_table_name,
  	CWStringConst *pstrTableAlias,
  	ULONG ulTableId
  	)
  {
  	CTableDescriptor *ptabdesc = PtabdescCreate
  									(
  									mp,
  									GPOPT_TEST_REL_WIDTH,
  									GPOS_NEW(mp) CMDIdGPDB(ulTableId, 1, 1),
  									CName(str_table_name)
  									);
  
  	CWStringConst strAlias(pstrTableAlias->GetBuffer());
  	return PexprLogicalGet(mp, ptabdesc, &strAlias);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGet
  //
  //	@doc:
  //		Generate a randomized get expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGet
  	(
  	CMemoryPool *mp
  	)
  {
  	CWStringConst strName(GPOS_WSZ_LIT("BaseTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID, 1, 1);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, 3, mdid, CName(&strName));
  	CWStringConst strAlias(GPOS_WSZ_LIT("BaseTableAlias"));
  
  	return PexprLogicalGet(mp, ptabdesc, &strAlias);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalExternalGet
  //
  //	@doc:
  //		Generate a randomized external get expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalExternalGet
  	(
  	CMemoryPool *mp
  	)
  {
  	CWStringConst strName(GPOS_WSZ_LIT("ExternalTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID, 1, 1);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, 3, mdid, CName(&strName));
  	CWStringConst strAlias(GPOS_WSZ_LIT("ExternalTableAlias"));
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalExternalGet
  								(
  								mp,
  								GPOS_NEW(mp) CName(mp, &strAlias),
  								ptabdesc
  								)
  					);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGetPartitioned
  //
  //	@doc:
  //		Generate a randomized get expression for a partitioned table
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGetPartitioned
  	(
  	CMemoryPool *mp
  	)
  {
  	ULONG ulAttributes = 2;
  	CWStringConst strName(GPOS_WSZ_LIT("PartTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID_PARTITIONED);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, ulAttributes, mdid, CName(&strName), true /*fPartitioned*/);
  	CWStringConst strAlias(GPOS_WSZ_LIT("PartTableAlias"));
  
  	return PexprLogicalGet(mp, ptabdesc, &strAlias);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalDynamicGetWithIndexes
  //
  //	@doc:
  //		Generate a randomized get expression for a partitioned table
  //		with indexes
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalDynamicGetWithIndexes
  	(
  	CMemoryPool *mp
  	)
  {
  	ULONG ulAttributes = 2;
  	CWStringConst strName(GPOS_WSZ_LIT("P1"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID_PARTITIONED_WITH_INDEXES);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, ulAttributes, mdid, CName(&strName), true /*fPartitioned*/);
  	CWStringConst strAlias(GPOS_WSZ_LIT("P1Alias"));
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalDynamicGet
  								(
  								mp,
  								GPOS_NEW(mp) CName(mp, CName(&strAlias)),
  								ptabdesc,
  								0 // ulPartIndex
  								)
  					);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelect
  //
  //	@doc:
  //		Generate a Select expression with a random equality predicate
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelect
  	(
  	CMemoryPool *mp,
  	CExpression *pexpr
  	)
  {
  	GPOS_ASSERT(NULL != pexpr);
  
  	// get any two columns
  	CColRefSet *pcrs = pexpr->DeriveOutputColumns();
  	CColRef *pcrLeft =  pcrs->PcrAny();
  	CColRef *pcrRight = pcrs->PcrAny();
  	CExpression *pexprPredicate = CUtils::PexprScalarEqCmp(mp, pcrLeft, pcrRight);
  
  	return CUtils::PexprLogicalSelect(mp, pexpr, pexprPredicate);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelect
  //
  //	@doc:
  //		Generate select expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelect
  	(
  	CMemoryPool *mp,
  	CWStringConst *str_table_name,
  	CWStringConst *pstrTableAlias,
  	ULONG ulTableId
  	)
  {
  	CExpression *pexprGet = PexprLogicalGet(mp, str_table_name, pstrTableAlias, ulTableId);
  	return PexprLogicalSelect(mp, pexprGet);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelect
  //
  //	@doc:
  //		Generate randomized Select expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelect
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalSelect(mp, PexprLogicalGet(mp));
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  // 		CTestUtils::PexprLogicalSelectWithContradiction
  //
  //	@doc:
  //		Generate a select expression with a contradiction
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithContradiction
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexpr = PexprLogicalSelect(mp, PexprLogicalGet(mp));
  	// get any column
  	CColRefSet *pcrs = pexpr->DeriveOutputColumns();
  	CColRef *colref =  pcrs->PcrAny();
  
  	CExpression *pexprConstFirst = CUtils::PexprScalarConstInt4(mp, 3 /*val*/);
  	CExpression *pexprPredFirst = CUtils::PexprScalarEqCmp(mp, colref, pexprConstFirst);
  
  	CExpression *pexprConstSecond = CUtils::PexprScalarConstInt4(mp, 5 /*val*/);
  	CExpression *pexprPredSecond = CUtils::PexprScalarEqCmp(mp, colref, pexprConstSecond);
  
  	CExpression *pexprPredicate = CPredicateUtils::PexprConjunction(mp, pexprPredFirst, pexprPredSecond);
  	pexprPredFirst->Release();
  	pexprPredSecond->Release();
  
  	return CUtils::PexprLogicalSelect(mp, pexpr, pexprPredicate);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectPartitioned
  //
  //	@doc:
  //		Generate a randomized select expression over a partitioned table
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectPartitioned
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprGet = PexprLogicalGetPartitioned(mp);
  
  	// extract first partition key
  	CLogicalGet *popGet = CLogicalGet::PopConvert(pexprGet->Pop());
  	const CColRef2dArray *pdrgpdrgpcr = popGet->PdrgpdrgpcrPartColumns();
  
  	GPOS_ASSERT(pdrgpdrgpcr != NULL);
  	GPOS_ASSERT(0 < pdrgpdrgpcr->Size());
  	CColRefArray *colref_array = (*pdrgpdrgpcr)[0];
  	GPOS_ASSERT(1 == colref_array->Size());
  	CColRef *pcrPartKey = (*colref_array)[0];
  
  	// construct a comparison pk = 5
  	INT val = 5;
  	CExpression *pexprScalar = CUtils::PexprScalarEqCmp
  										(
  										mp,
  										CUtils::PexprScalarIdent(mp, pcrPartKey),
  										CUtils::PexprScalarConstInt4(mp, val)
  										);
  
  	return CUtils::PexprLogicalSelect
  						(
  						mp,
  						pexprGet,
  						pexprScalar
  						);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalAssert
  //
  //	@doc:
  //		Generate randomized Assert expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalAssert
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprGet = PexprLogicalGet(mp);
  	
  	// get any two columns
  	CColRefSet *pcrs = pexprGet->DeriveOutputColumns();
  	CColRef *pcrLeft =  pcrs->PcrAny();
  	CColRef *pcrRight = pcrs->PcrAny();
  	CExpression *pexprPredicate = CUtils::PexprScalarEqCmp(mp, pcrLeft, pcrRight);
  	
  	CWStringConst *pstrErrMsg = CXformUtils::PstrErrorMessage(mp, gpos::CException::ExmaSQL, gpos::CException::ExmiSQLTest, GPOS_WSZ_LIT("Test msg"));
  	CExpression *pexprAssertConstraint = GPOS_NEW(mp) CExpression
  											(
  											mp,
  											GPOS_NEW(mp) CScalarAssertConstraint(mp, pstrErrMsg),
  											pexprPredicate
  											);
  	CExpression *pexprAssertPredicate = GPOS_NEW(mp) CExpression
  												(
  												mp,
  												GPOS_NEW(mp) CScalarAssertConstraintList(mp),
  												pexprAssertConstraint
  												);
  		
  	CLogicalAssert *popAssert = 
  			GPOS_NEW(mp) CLogicalAssert
  						(
  						mp, 
  						GPOS_NEW(mp) CException(gpos::CException::ExmaSQL, gpos::CException::ExmiSQLTest)						
  						);
  	
  	return GPOS_NEW(mp) CExpression(mp, popAssert, pexprGet, pexprAssertPredicate);
  }
  
  //---------------------------------------------------------------------------
  //		CTestUtils::Pexpr3WayJoinPartitioned
  //
  //	@doc:
  //		Generate a 3-way join including a partitioned table
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::Pexpr3WayJoinPartitioned
  	(
  	CMemoryPool *mp
  	)
  {
  	return
  		PexprLogicalJoin<CLogicalInnerJoin>(mp, PexprLogicalGet(mp), PexprJoinPartitionedOuter<CLogicalInnerJoin>(mp));
  
  }
  
  //---------------------------------------------------------------------------
  //		CTestUtils::Pexpr4WayJoinPartitioned
  //
  //	@doc:
  //		Generate a 4-way join including a partitioned table
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::Pexpr4WayJoinPartitioned
  	(
  	CMemoryPool *mp
  	)
  {
  	return
  		PexprLogicalJoin<CLogicalInnerJoin>(mp, PexprLogicalGet(mp), Pexpr3WayJoinPartitioned(mp));
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithNestedAnd
  //
  //	@doc:
  //		Generate a random select expression with nested AND tree
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithNestedAnd
  	(
  	CMemoryPool *mp
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  
  	CExpression *pexprGet = PexprLogicalGet(mp);
  	CExpression *pexprPred = PexprScalarNestedPreds(mp, pexprGet, CScalarBoolOp::EboolopAnd);
  
  	return CUtils::PexprLogicalSelect(mp, pexprGet, pexprPred);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithNestedOr
  //
  //	@doc:
  //		Generate a random select expression with nested OR tree
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithNestedOr
  	(
  	CMemoryPool *mp
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  
  	CExpression *pexprGet = PexprLogicalGet(mp);
  	CExpression *pexprPred = PexprScalarNestedPreds(mp, pexprGet, CScalarBoolOp::EboolopOr);
  
  	return CUtils::PexprLogicalSelect(mp, pexprGet, pexprPred);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithEvenNestedNot
  //
  //	@doc:
  //		Generate a random select expression with an even number of
  //		nested NOT nodes
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithEvenNestedNot
  	(
  	CMemoryPool *mp
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  
  	CExpression *pexprGet = PexprLogicalGet(mp);
  	CExpression *pexprPred = PexprScalarNestedPreds(mp, pexprGet, CScalarBoolOp::EboolopNot);
  
  	return CUtils::PexprLogicalSelect(mp, pexprGet, pexprPred);
  } 
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprScIdentCmpScIdent
  //
  //	@doc:
  //		Generate a scalar expression comparing scalar identifiers
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprScIdentCmpScIdent
  	(
  	CMemoryPool *mp,
  	CExpression *pexprLeft,
  	CExpression *pexprRight,
  	IMDType::ECmpType cmp_type
  	)
  {
  	GPOS_ASSERT(NULL != pexprLeft);
  	GPOS_ASSERT(NULL != pexprRight);
  	GPOS_ASSERT(cmp_type <= IMDType::EcmptOther);
  
  	CColRefSet *pcrsLeft = pexprLeft->DeriveOutputColumns();
  	CColRef *pcrLeft =  pcrsLeft->PcrAny();
  
  	CColRefSet *pcrsRight = pexprRight->DeriveOutputColumns();
  	CColRef *pcrRight =  pcrsRight->PcrAny();
  
  	CExpression *pexprPred = CUtils::PexprScalarCmp(mp, pcrLeft, pcrRight, cmp_type);
  
  	return pexprPred;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprScIdentCmpConst
  //
  //	@doc:
  //		Generate a scalar expression comparing scalar identifier to a constant
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprScIdentCmpConst
  	(
  	CMemoryPool *mp,
  	CExpression *pexpr,
  	IMDType::ECmpType cmp_type,
  	ULONG ulVal
  	)
  {
  	GPOS_ASSERT(NULL != pexpr);
  
  	CColRefSet *pcrs = pexpr->DeriveOutputColumns();
  	CColRef *pcrLeft =  pcrs->PcrAny();
  	CExpression *pexprUl = CUtils::PexprScalarConstInt4(mp, ulVal);
  
  	CExpression *pexprPred = CUtils::PexprScalarCmp(mp, pcrLeft, pexprUl, cmp_type);
  
  	return pexprPred;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectCmpToConst
  //
  //	@doc:
  //		Generate a Select expression with an equality predicate on the first
  //		column and a constant
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectCmpToConst
  	(
  	CMemoryPool *mp
  	)
  {
  	// generate a get expression
  	CExpression *pexpr = PexprLogicalGet(mp);
  	CExpression *pexprPred = PexprScIdentCmpConst(mp, pexpr, IMDType::EcmptEq /* cmp_type */, 10 /* ulVal */);
  
  	return CUtils::PexprLogicalSelect(mp, pexpr, pexprPred);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectArrayCmp
  //
  //	@doc:
  //		Generate a Select expression with an array compare
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectArrayCmp
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalSelectArrayCmp(mp, CScalarArrayCmp::EarrcmpAny, IMDType::EcmptEq);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectArrayCmp
  //
  //	@doc:
  //		Generate a Select expression with an array compare. Takes an enum for
  //		the type of array comparison (ANY or ALL) and an enum for the comparator
  //		type (=, !=, <, etc).
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectArrayCmp
  	(
  	CMemoryPool *mp,
  	CScalarArrayCmp::EArrCmpType earrcmptype,
  	IMDType::ECmpType ecmptype
  	)
  {
  	const ULONG ulArraySize = 5;
  	IntPtrArray *pdrgpiVals = GPOS_NEW(mp) IntPtrArray(mp);
  	for (ULONG val = 0; val < ulArraySize; val++)
  	{
  		pdrgpiVals->Append(GPOS_NEW(mp) INT(val));
  	}
  	CExpression *pexprSelect = PexprLogicalSelectArrayCmp(mp, earrcmptype, ecmptype, pdrgpiVals);
  	pdrgpiVals->Release();
  	return pexprSelect;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectArrayCmp
  //
  //	@doc:
  //		Generate a Select expression with an array compare. Takes an enum for
  //		the type of array comparison (ANY or ALL) and an enum for the comparator
  //		type (=, !=, <, etc). The array will be populated with the given integer
  //		values.
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectArrayCmp
  	(
  	CMemoryPool *mp,
  	CScalarArrayCmp::EArrCmpType earrcmptype,
  	IMDType::ECmpType ecmptype,
  	const IntPtrArray *pdrgpiVals
  	)
  {
  	GPOS_ASSERT(CScalarArrayCmp::EarrcmpSentinel > earrcmptype);
  	GPOS_ASSERT(IMDType::EcmptOther > ecmptype);
  
  	// generate a get expression
  	CExpression *pexprGet = PexprLogicalGet(mp);
  
  	// get the first column
  	CColRefSet *pcrs = pexprGet->DeriveOutputColumns();
  	CColRef *colref =  pcrs->PcrAny();
  
  	// construct an array of integers
  	CExpressionArray *pdrgpexprArrayElems = GPOS_NEW(mp) CExpressionArray(mp);
  	
  	const ULONG ulValsLength = pdrgpiVals->Size();
  	for (ULONG ul = 0; ul < ulValsLength; ul++)
  	{
  		CExpression *pexprArrayElem = CUtils::PexprScalarConstInt4(mp, *(*pdrgpiVals)[ul]);
  		pdrgpexprArrayElems->Append(pexprArrayElem);
  	}
  
  	return CUtils::PexprLogicalSelect
  				(
  				mp,
  				pexprGet,
  				CUtils::PexprScalarArrayCmp(mp, earrcmptype, ecmptype, pdrgpexprArrayElems, colref)
  				);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithOddNestedNot
  //
  //	@doc:
  //		Generate a random select expression with an odd number of
  //		nested NOT nodes
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithOddNestedNot
  	(
  	CMemoryPool *mp
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  
  	CExpression *pexprGet = PexprLogicalGet(mp);
  	CExpression *pexprPred = PexprScalarNestedPreds(mp, pexprGet, CScalarBoolOp::EboolopNot);
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexpr->Append(pexprPred);
  	CExpression *pexprNot =  CUtils::PexprScalarBoolOp(mp, CScalarBoolOp::EboolopNot, pdrgpexpr);
  
  	return CUtils::PexprLogicalSelect(mp, pexprGet, pexprNot);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithNestedAndOrNot
  //
  //	@doc:
  //		Generate a random select expression with nested AND-OR-NOT predicate
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithNestedAndOrNot
  	(
  	CMemoryPool *mp
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  
  	CExpression *pexprGet = PexprLogicalGet(mp);
  	CExpression *pexprPredAnd = PexprScalarNestedPreds(mp, pexprGet, CScalarBoolOp::EboolopAnd);
  	CExpression *pexprPredOr = PexprScalarNestedPreds(mp, pexprGet, CScalarBoolOp::EboolopOr);
  	CExpression *pexprPredNot = PexprScalarNestedPreds(mp, pexprGet, CScalarBoolOp::EboolopNot);
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexpr->Append(pexprPredAnd);
  	pdrgpexpr->Append(pexprPredOr);
  	pdrgpexpr->Append(pexprPredNot);
  	CExpression *pexprOr =  CUtils::PexprScalarBoolOp(mp, CScalarBoolOp::EboolopOr, pdrgpexpr);
  	CExpression *pexprPred =  CUtils::PexprNegate(mp, pexprOr);
  
  	return CUtils::PexprLogicalSelect(mp, pexprGet, pexprPred);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSubqueryWithConstTableGet
  //
  //	@doc:
  //		Generate Select expression with Any subquery predicate over a const
  //		table get
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSubqueryWithConstTableGet
  	(
  	CMemoryPool *mp,
  	COperator::EOperatorId op_id
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  	GPOS_ASSERT(COperator::EopScalarSubqueryAny == op_id ||
  				COperator::EopScalarSubqueryAll == op_id);
  
  	CWStringConst strNameR(GPOS_WSZ_LIT("Rel1"));
  	CMDIdGPDB *pmdidR = GPOS_NEW(mp) CMDIdGPDB(GPOPT_TEST_REL_OID1, 1, 1);
  	CTableDescriptor *ptabdescR = PtabdescCreate(mp, 3 /*num_cols*/, pmdidR, CName(&strNameR));
  
  	CExpression *pexprOuter = PexprLogicalGet(mp, ptabdescR, &strNameR);
  	CExpression *pexprConstTableGet = PexprConstTableGet(mp, 3 /* ulElements */);
  
  	// get random columns from inner expression
  	CColRefSet *pcrs = pexprConstTableGet->DeriveOutputColumns();
  	const CColRef *pcrInner = pcrs->PcrAny();
  
  	// get random columns from outer expression
  	pcrs = pexprOuter->DeriveOutputColumns();
  	const CColRef *pcrOuter = pcrs->PcrAny();
  
  	const CWStringConst *str = GPOS_NEW(mp) CWStringConst(GPOS_WSZ_LIT("="));
  
  	CExpression *pexprSubquery = NULL;
  	if (COperator::EopScalarSubqueryAny == op_id)
  	{
  		// construct ANY subquery expression
  		pexprSubquery = GPOS_NEW(mp) CExpression
  									(
  									mp,
  									GPOS_NEW(mp) CScalarSubqueryAny(mp, GPOS_NEW(mp) CMDIdGPDB(GPDB_INT4_EQ_OP), str, pcrInner),
  									pexprConstTableGet,
  									CUtils::PexprScalarIdent(mp, pcrOuter)
  									);
  	}
  	else
  	{
  		// construct ALL subquery expression
  		pexprSubquery = GPOS_NEW(mp) CExpression
  									(
  									mp,
  									GPOS_NEW(mp) CScalarSubqueryAll(mp, GPOS_NEW(mp) CMDIdGPDB(GPDB_INT4_EQ_OP), str, pcrInner),
  									pexprConstTableGet,
  									CUtils::PexprScalarIdent(mp, pcrOuter)
  									);
  
  	}
  
  	return CUtils::PexprLogicalSelect(mp, pexprOuter, pexprSubquery);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithConstAnySubquery
  //
  //	@doc:
  //		Generate a random select expression with constant ANY subquery
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithConstAnySubquery
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalSubqueryWithConstTableGet(mp, COperator::EopScalarSubqueryAny);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithConstAllSubquery
  //
  //	@doc:
  //		Generate a random select expression with constant ALL subquery
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithConstAllSubquery
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalSubqueryWithConstTableGet(mp, COperator::EopScalarSubqueryAll);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectCorrelated
  //
  //	@doc:
  //		Generate correlated select; wrapper
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectCorrelated
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprOuter = PexprLogicalGet(mp);
  	CColRefSet *outer_refs = pexprOuter->DeriveOutputColumns();
  
  	CExpression *pexpr = PexprLogicalSelectCorrelated(mp, outer_refs, 8);
  
  	pexprOuter->Release();
  
  	return pexpr;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectOnOuterJoin
  //
  //	@doc:
  //		Generate a select on top of outer join expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectOnOuterJoin
  	(
  	CMemoryPool *mp
  	)
  {
  	// generate a pair of get expressions
  	CExpression *pexprOuter = NULL;
  	CExpression *pexprInner = NULL;
  	CSubqueryTestUtils::GenerateGetExpressions(mp, &pexprOuter, &pexprInner);
  
  	const CColRef *pcrOuter = pexprOuter->DeriveOutputColumns()->PcrAny();
  	const CColRef *pcrInner = pexprInner->DeriveOutputColumns()->PcrAny();
  	CExpression *pexprOuterJoinPred = CUtils::PexprScalarEqCmp(mp, pcrOuter, pcrInner);
  	CExpression *pexprOuterJoin =
  			GPOS_NEW(mp) CExpression
  				(
  				mp,
  				GPOS_NEW(mp) CLogicalLeftOuterJoin(mp),
  				pexprOuter,
  				pexprInner,
  				pexprOuterJoinPred
  				);
  	CExpression *pexprPred = CUtils::PexprScalarEqCmp(mp, pcrInner, CUtils::PexprScalarConstInt4(mp, 5 /*val*/));
  
  	return CUtils::PexprLogicalSelect(mp, pexprOuterJoin, pexprPred);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectCorrelated
  //
  //	@doc:
  //		Generate correlated select, different predicates depending on nesting
  //		level: correlated, exclusively external, non-correlated
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectCorrelated
  	(
  	CMemoryPool *mp,
  	CColRefSet *outer_refs,
  	ULONG ulLevel
  	)
  {
  	GPOS_CHECK_STACK_SIZE;
  
  	if (0 == ulLevel)
  	{
  		return PexprLogicalGet(mp);
  	}
  
  	CExpression *pexpr = PexprLogicalSelectCorrelated(mp, outer_refs, ulLevel - 1);
  
  	CColRefSet *pcrs = pexpr->DeriveOutputColumns();
  	GPOS_ASSERT(outer_refs->IsDisjoint(pcrs));
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  
  	switch (ulLevel % 6)
  	{
  		case 4:
  			// outer only
  			EqualityPredicate(mp, outer_refs, outer_refs, pdrgpexpr);
  
  			// inner only
  			EqualityPredicate(mp, pcrs, pcrs, pdrgpexpr);
  
  			// regular correlation
  			EqualityPredicate(mp, pcrs, outer_refs, pdrgpexpr);
  			break;
  
  		case 3:
  			// outer only
  			EqualityPredicate(mp, outer_refs, outer_refs, pdrgpexpr);
  			break;
  
  		case 2:
  			// inner only
  			EqualityPredicate(mp, pcrs, pcrs, pdrgpexpr);
  
  			// regular correlation
  			EqualityPredicate(mp, pcrs, outer_refs, pdrgpexpr);
  			break;
  
  		case 1:
  			// inner only
  			EqualityPredicate(mp, pcrs, pcrs, pdrgpexpr);
  			break;
  
  		case 0:
  			// regular correlation
  			EqualityPredicate(mp, pcrs, outer_refs, pdrgpexpr);
  			break;
  	}
  
  	CExpression *pexprCorrelation = CPredicateUtils::PexprConjunction(mp, pdrgpexpr);
  
  	// assemble select
  	CExpression *pexprResult =
  			GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CLogicalSelect(mp),
  					pexpr,
  					pexprCorrelation);
  
  	return pexprResult;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalProject
  //
  //	@doc:
  //		Generate a Project expression that re-maps a single column
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalProject
  	(
  	CMemoryPool *mp,
  	CExpression *pexpr,
  	CColRef *colref,
  	CColRef *new_colref
  	)
  {
  	GPOS_ASSERT(NULL != pexpr);
  	GPOS_ASSERT(NULL != colref);
  	GPOS_ASSERT(NULL != new_colref);
  
  	return GPOS_NEW(mp) CExpression
  			(
  			mp,
  			GPOS_NEW(mp) CLogicalProject(mp),
  			pexpr,
  			GPOS_NEW(mp) CExpression
  				(
  				mp,
  				GPOS_NEW(mp) CScalarProjectList(mp),
  				GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CScalarProjectElement(mp, new_colref),
  					GPOS_NEW(mp) CExpression
  						(
  						mp,
  						GPOS_NEW(mp) CScalarIdent(mp, colref)
  						)
  					)
  				)
  			);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalProject
  //
  //	@doc:
  //		Generate a randomized project expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalProject
  	(
  	CMemoryPool *mp
  	)
  {
  	// generate a get expression
  	CExpression *pexpr = PexprLogicalGet(mp);
  
  	// get output columns
  	CColRefSet *pcrs = pexpr->DeriveOutputColumns();
  
  	CColumnFactory *col_factory = COptCtxt::PoctxtFromTLS()->Pcf();
  	CColRef *colref =  pcrs->PcrAny();
  
  	// create new column to which we will an existing one remap to
  	CColRef *new_colref = col_factory->PcrCreate(colref);
  
  	return PexprLogicalProject(mp, pexpr, colref, new_colref);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalProjectGbAggCorrelated
  //
  //	@doc:
  //		Generate correlated Project over GbAgg
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalProjectGbAggCorrelated
  	(
  	CMemoryPool *mp
  	)
  {
  	// create a GbAgg expression
  	CExpression *pexprGbAgg = PexprLogicalGbAggCorrelated(mp);
  
  	CExpression *pexprPrjList = GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CScalarProjectList(mp));
  	CExpression *pexprProject =
  		GPOS_NEW(mp) CExpression(mp,
  							 GPOS_NEW(mp) CLogicalProject(mp),
  							 pexprGbAgg,
  							 pexprPrjList);
  
  	return pexprProject;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalJoinCorrelated
  //
  //	@doc:
  //		Generate correlated join
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalJoinCorrelated
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprLeft = PexprLogicalSelectCorrelated(mp);
  	CExpression *pexprRight = PexprLogicalSelectCorrelated(mp);
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexpr->Append(pexprLeft);
  	pdrgpexpr->Append(pexprRight);
  
  	CColRefSet *pcrsLeft = pexprLeft->DeriveOutputColumns();
  	CColRefSet *pcrsRight = pexprRight->DeriveOutputColumns();
  
  	EqualityPredicate(mp, pcrsLeft, pcrsRight, pdrgpexpr);
  
  	return GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CLogicalInnerJoin(mp), pdrgpexpr);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalJoinCorrelated
  //
  //	@doc:
  //		Generate join with a partitioned and indexed inner child
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalJoinWithPartitionedAndIndexedInnerChild
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprLeft = PexprLogicalGet(mp);
  	CExpression *pexprRight = PexprLogicalDynamicGetWithIndexes(mp);
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexpr->Append(pexprLeft);
  	pdrgpexpr->Append(pexprRight);
  
  	CColRefSet *pcrsLeft = pexprLeft->DeriveOutputColumns();
  	CColRefSet *pcrsRight = pexprRight->DeriveOutputColumns();
  
  	EqualityPredicate(mp, pcrsLeft, pcrsRight, pdrgpexpr);
  
  	return GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CLogicalInnerJoin(mp), pdrgpexpr);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalNAryJoin
  //
  //	@doc:
  //		Generate randomized n-ary join expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalNAryJoin
  	(
  	CMemoryPool *mp,
  	CExpressionArray *pdrgpexpr
  	)
  {
  	GPOS_ASSERT(NULL != pdrgpexpr);
  	GPOS_ASSERT(2 < pdrgpexpr->Size());
  
  	return GPOS_NEW(mp) CExpression
  			(
  			mp,
  			GPOS_NEW(mp) CLogicalNAryJoin(mp),
  			pdrgpexpr
  			);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalNAryJoin
  //
  //	@doc:
  //		Generate randomized n-ary join expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalNAryJoin
  	(
  	CMemoryPool *mp
  	)
  {
  	const ULONG  ulRels = 4;
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	CExpressionArray *pdrgpexprConjuncts = GPOS_NEW(mp) CExpressionArray(mp);
  
  	CExpression *pexprInit = PexprLogicalGet(mp);
  	pdrgpexpr->Append(pexprInit);
  
  	// build pairwise joins, extract predicate and input, then discard join
  	for (ULONG ul = 0; ul < ulRels; ul++)
  	{
  		CExpression *pexprNext = PexprLogicalGet(mp);
  		pdrgpexpr->Append(pexprNext);
  
  		pexprInit->AddRef();
  		pexprNext->AddRef();
  
  		CExpression *pexprJoin = PexprLogicalJoin<CLogicalInnerJoin>(mp, pexprInit, pexprNext);
  
  		CExpression *pexprPredicate = (*pexprJoin)[2];
  
  		pexprPredicate->AddRef();
  		pdrgpexprConjuncts->Append(pexprPredicate);
  
  		pexprJoin->Release();
  	}
  
  	// add predicate built of conjuncts to the input array
  	pdrgpexpr->Append(CUtils::PexprScalarBoolOp(mp, CScalarBoolOp::EboolopAnd, pdrgpexprConjuncts));
  
  	return PexprLogicalNAryJoin(mp, pdrgpexpr);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLeftOuterJoinOnNAryJoin
  //
  //	@doc:
  //		Generate left outer join on top of n-ary join expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLeftOuterJoinOnNAryJoin
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprNAryJoin = PexprLogicalNAryJoin(mp);
  	CExpression *pexprLOJInnerChild = PexprLogicalGet(mp);
  
  	// get a random column from LOJ inner child output
  	CColRef *pcrLeft1 = pexprLOJInnerChild->DeriveOutputColumns()->PcrAny();
  
  	// get a random column from NAry join second child
  	CColRef *pcrLeft2 = (*pexprNAryJoin)[1]->DeriveOutputColumns()->PcrAny();
  
  	// get a random column from NAry join output
  	CColRef *pcrRight = pexprNAryJoin->DeriveOutputColumns()->PcrAny();
  
  	CExpression *pexprPred1 = CUtils::PexprScalarEqCmp(mp, pcrLeft1, pcrRight);
  	CExpression *pexprPred2 = CUtils::PexprScalarEqCmp(mp, pcrLeft2, pcrRight);
  	CExpression *pexprPred = CPredicateUtils::PexprConjunction(mp, pexprPred1, pexprPred2);
  	pexprPred1->Release();
  	pexprPred2->Release();
  
  	return GPOS_NEW(mp) CExpression (mp, GPOS_NEW(mp) CLogicalLeftOuterJoin(mp), pexprNAryJoin, pexprLOJInnerChild, pexprPred);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprNAryJoinOnLeftOuterJoin
  //
  //	@doc:
  //		Generate n-ary join expression on top of left outer join
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprNAryJoinOnLeftOuterJoin
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprNAryJoin = PexprLogicalNAryJoin(mp);
  	CColRef *pcrNAryJoin = (*pexprNAryJoin)[0]->DeriveOutputColumns()->PcrAny();
  	CExpression *pexprScalar = (*pexprNAryJoin)[pexprNAryJoin->Arity() - 1];
  
  	// copy NAry-Join children
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	CUtils::AddRefAppend<CExpression>(pdrgpexpr, pexprNAryJoin->PdrgPexpr());
  
  	// generate LOJ expression
  	CExpression *pexprLOJOuterChild = PexprLogicalGet(mp);
  	CExpression *pexprLOJInnerChild = PexprLogicalGet(mp);
  
  	// get a random column from LOJ outer child output
  	CColRef *pcrLeft = pexprLOJOuterChild->DeriveOutputColumns()->PcrAny();
  
  	// get a random column from LOJ inner child output
  	CColRef *pcrRight = pexprLOJInnerChild->DeriveOutputColumns()->PcrAny();
  
  	CExpression *pexprPred = CUtils::PexprScalarEqCmp(mp, pcrLeft, pcrRight);
  	CExpression *pexprLOJ = GPOS_NEW(mp) CExpression (mp, GPOS_NEW(mp) CLogicalLeftOuterJoin(mp), pexprLOJOuterChild, pexprLOJInnerChild, pexprPred);
  
  	// replace NAry-Join scalar predicate with LOJ expression
  	pdrgpexpr->Replace(pdrgpexpr->Size() - 1, pexprLOJ);
  
  	// create new scalar predicate for NAry join
  	CExpression *pexprNewPred = CUtils::PexprScalarEqCmp(mp, pcrNAryJoin, pcrRight);
  	pdrgpexpr->Append(CPredicateUtils::PexprConjunction(mp, pexprScalar, pexprNewPred));
  	pexprNAryJoin->Release();
  	pexprNewPred->Release();
  
  	return GPOS_NEW(mp) CExpression (mp, GPOS_NEW(mp) CLogicalNAryJoin(mp), pdrgpexpr);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalLimit
  //
  //	@doc:
  //		Generate a limit expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalLimit
  	(
  	CMemoryPool *mp,
  	CExpression *pexpr,
  	LINT iStart,
  	LINT iRows,
  	BOOL fGlobal,
  	BOOL fHasCount
  	)
  {
  	GPOS_ASSERT(NULL != pexpr);
  	GPOS_ASSERT(0 <= iStart);
  	GPOS_ASSERT(0 <= iRows);
  
  	COrderSpec *pos = GPOS_NEW(mp) COrderSpec(mp);
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalLimit(mp, pos, fGlobal, fHasCount, false /*fTopLimitUnderDML*/),
  					pexpr,
  					CUtils::PexprScalarConstInt8(mp, iStart),
  					CUtils::PexprScalarConstInt8(mp, iRows)
  					);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalLimit
  //
  //	@doc:
  //		Generate a randomized limit expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalLimit
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalLimit
  				(
  				mp,
  				PexprLogicalGet(mp),
  				0, // iStart
  				100, // iRows
  				true, // fGlobal
  				true // fHasCount
  				);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGbAggWithSum
  //
  //	@doc:
  //		Generate a randomized aggregate expression with sum agg function
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGbAggWithSum
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprGb = PexprLogicalGbAgg(mp);
  
  	// get a random column from Gb child
  	CColRef *colref =
  		(*pexprGb)[0]->DeriveOutputColumns()->PcrAny();
  
  	// generate a SUM expression
  	CExpression *pexprProjElem = PexprPrjElemWithSum(mp, colref);
  	CExpression *pexprPrjList =
  		GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CScalarProjectList(mp), pexprProjElem);
  
  	(*pexprGb)[0]->AddRef();
  	CColRefArray *colref_array = CLogicalGbAgg::PopConvert(pexprGb->Pop())->Pdrgpcr();
  	colref_array->AddRef();
  	CExpression *pexprGbWithSum = CUtils::PexprLogicalGbAggGlobal(mp, colref_array, (*pexprGb)[0], pexprPrjList);
  
  	// release old Gb expression
  	pexprGb->Release();
  
  	return pexprGbWithSum;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGbAggWithInput
  //
  //	@doc:
  //		Generate a randomized aggregate expression
  //
  //---------------------------------------------------------------------------
  CExpression *CTestUtils::PexprLogicalGbAggWithInput
  	(
  	CMemoryPool *mp,
  	CExpression *pexprInput
  	)
  {
  	// get the first few columns
  	CColRefSet *pcrs = pexprInput->DeriveOutputColumns();
  
  	ULONG num_cols = std::min(3u, pcrs->Size());
  
  	CColRefArray *colref_array = GPOS_NEW(mp) CColRefArray(mp);
  	CColRefSetIter crsi(*pcrs);
  	for (ULONG i = 0; i < num_cols; i++)
  	{
  		(void) crsi.Advance();
  		colref_array->Append(crsi.Pcr());
  	}
  
  	CExpression *pexprList = GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CScalarProjectList(mp));
  	return CUtils::PexprLogicalGbAggGlobal(mp, colref_array, pexprInput, pexprList);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGbAgg
  //
  //	@doc:
  //		Generate a randomized aggregate expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGbAgg
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalGbAggWithInput(mp, PexprLogicalGet(mp));
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprPrjElemWithSum
  //
  //	@doc:
  //		Generate a project element with sum agg function
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprPrjElemWithSum
  	(
  	CMemoryPool *mp,
  	CColRef *colref
  	)
  {
  	CMDAccessor *md_accessor = COptCtxt::PoctxtFromTLS()->Pmda();
  
  	// generate a SUM expression
  	CMDIdGPDB *pmdidSumAgg = GPOS_NEW(mp) CMDIdGPDB(GPDB_INT4_SUM_AGG);
  	CWStringConst *pstrAggFunc = GPOS_NEW(mp) CWStringConst(GPOS_WSZ_LIT("sum"));
  	CExpression *pexprScalarAgg = CUtils::PexprAggFunc
  									(
  									mp,
  									pmdidSumAgg,
  									pstrAggFunc,
  									colref,
  									false /*is_distinct*/,
  									EaggfuncstageGlobal /*eaggfuncstage*/,
  									false /*fSplit*/
  									);
  
  	// map a computed column to SUM expression
  	CScalar *pop = CScalar::PopConvert(pexprScalarAgg->Pop());
  	IMDId *mdid_type = pop->MdidType();
  	const IMDType *pmdtype = md_accessor->RetrieveType(mdid_type);
  	CWStringConst str(GPOS_WSZ_LIT("sum_col"));
  	CName name(mp, &str);
  	CColRef *pcrComputed = COptCtxt::PoctxtFromTLS()->Pcf()->PcrCreate(pmdtype, pop->TypeModifier(), name);
  
  	return CUtils::PexprScalarProjectElement(mp, pcrComputed, pexprScalarAgg);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGbAggOverJoin
  //
  //	@doc:
  //		Generate a randomized group by over join expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGbAggOverJoin
  	(
  	CMemoryPool *mp
  	)
  {
  	// generate a join expression
  	CExpression *pexprJoin = PexprLogicalJoin<CLogicalInnerJoin>(mp);
  
  	// include one grouping column
  	CColRefArray *colref_array = GPOS_NEW(mp) CColRefArray(mp);
  	CColRef *colref = pexprJoin->DeriveOutputColumns()->PcrAny();
  	colref_array->Append(colref);
  
  	return CUtils::PexprLogicalGbAggGlobal
  				(
  				mp,
  				colref_array,
  				pexprJoin,
  				GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CScalarProjectList(mp))
  				);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGbAggDedupOverInnerJoin
  //
  //	@doc:
  //		Generate a dedup group by over inner join expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGbAggDedupOverInnerJoin
  	(
  	CMemoryPool *mp
  	)
  {
  	// generate a join expression
  	CExpression *pexprJoin = PexprLogicalJoin<CLogicalInnerJoin>(mp);
  
  	// get join's outer child
  	CExpression *pexprOuter = (*pexprJoin)[0];
  	CColRefSet *pcrs = pexprOuter->DeriveOutputColumns();
  
  	// grouping columns: all columns from outer child
  	CColRefArray *pdrgpcrGrp = pcrs->Pdrgpcr(mp);
  
  	// outer child keys: get a random column from its output columns
  	CColRef *colref = pcrs->PcrAny();
  	CColRefArray *pdrgpcrKeys = GPOS_NEW(mp) CColRefArray(mp);
  	pdrgpcrKeys->Append(colref);
  
  	return GPOS_NEW(mp) CExpression
  				(
  				mp,
  				GPOS_NEW(mp) CLogicalGbAggDeduplicate(mp, pdrgpcrGrp, COperator::EgbaggtypeGlobal  /*egbaggtype*/, pdrgpcrKeys),
  				pexprJoin,
  				GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CScalarProjectList(mp))
  				);
  	}
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalGbAggCorrelated
  //
  //	@doc:
  //		Generate correlated GbAgg
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalGbAggCorrelated
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexpr = PexprLogicalSelectCorrelated(mp);
  
  	// include one grouping column
  	CColRefArray *colref_array = GPOS_NEW(mp) CColRefArray(mp);
  	CColRef *colref = pexpr->DeriveOutputColumns()->PcrAny();
  	colref_array->Append(colref);
  
  	CExpression *pexprList = GPOS_NEW(mp) CExpression(mp, GPOS_NEW(mp) CScalarProjectList(mp));
  	return CUtils::PexprLogicalGbAggGlobal(mp, colref_array, pexpr, pexprList);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprConstTableGet
  //
  //	@doc:
  //		Generate logical const table get expression with one column and 
  //		the given number of elements
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprConstTableGet
  	(
  	CMemoryPool *mp, 
  	ULONG ulElements
  	)
  {
  	GPOS_ASSERT(0 < ulElements);
  	
  	CMDAccessor *md_accessor = COptCtxt::PoctxtFromTLS()->Pmda();
  
  	const IMDTypeInt4 *pmdtypeint4 = md_accessor->PtMDType<IMDTypeInt4>(CTestUtils::m_sysidDefault);
  
  	// create an integer column descriptor
  	CWStringConst strName(GPOS_WSZ_LIT("A"));
  	CName name(&strName);
  	CColumnDescriptor *pcoldescInt = GPOS_NEW(mp) CColumnDescriptor
  													(
  													mp,
  													pmdtypeint4,
  													default_type_modifier,
  													name,
  													1 /* attno */,
  													false /*IsNullable*/
  													);
  
  	CColumnDescriptorArray *pdrgpcoldesc = GPOS_NEW(mp) CColumnDescriptorArray(mp);
  	pdrgpcoldesc->Append(pcoldescInt);
  
  	// generate values
  	IDatum2dArray *pdrgpdrgpdatum = GPOS_NEW(mp) IDatum2dArray(mp);
  	
  	for (ULONG ul = 0; ul < ulElements; ul++)
  	{
  		IDatumInt4 *datum = pmdtypeint4->CreateInt4Datum(mp, ul, false /* is_null */);
  		IDatumArray *pdrgpdatum = GPOS_NEW(mp) IDatumArray(mp);
  		pdrgpdatum->Append((IDatum *) datum);
  		pdrgpdrgpdatum->Append(pdrgpdatum);
  	}
  	CLogicalConstTableGet *popConstTableGet = GPOS_NEW(mp) CLogicalConstTableGet(mp, pdrgpcoldesc, pdrgpdrgpdatum);
  	
  	return GPOS_NEW(mp) CExpression(mp, popConstTableGet);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprConstTableGet5
  //
  //	@doc:
  //		Generate logical const table get expression with one column and 5 elements
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprConstTableGet5
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprConstTableGet(mp, 5 /* ulElements */);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalInsert
  //
  //	@doc:
  //		Generate logical insert expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalInsert
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprCTG = PexprConstTableGet(mp, 1 /* ulElements */);
  
  	CColRefSet *pcrs = pexprCTG->DeriveOutputColumns();
  	CColRefArray *colref_array = pcrs->Pdrgpcr(mp);
  
  	CWStringConst strName(GPOS_WSZ_LIT("BaseTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID, 1, 1);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, 1, mdid, CName(&strName));
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalInsert(mp, ptabdesc, colref_array),
  					pexprCTG
  					);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalDelete
  //
  //	@doc:
  //		Generate logical delete expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalDelete
  	(
  	CMemoryPool *mp
  	)
  {
  	CWStringConst strName(GPOS_WSZ_LIT("BaseTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID, 1, 1);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, 1, mdid, CName(&strName));
  	CWStringConst strAlias(GPOS_WSZ_LIT("BaseTableAlias"));
  
  	CExpression *pexprGet = PexprLogicalGet(mp, ptabdesc, &strAlias);
  
  	CColRefSet *pcrs = pexprGet->DeriveOutputColumns();
  	CColRefArray *colref_array = pcrs->Pdrgpcr(mp);
  
  	CColRef *colref = pcrs->PcrFirst();
  
  	ptabdesc->AddRef();
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalDelete(mp, ptabdesc, colref_array, colref, colref),
  					pexprGet
  					);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalUpdate
  //
  //	@doc:
  //		Generate logical update expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalUpdate
  	(
  	CMemoryPool *mp
  	)
  {
  	CWStringConst strName(GPOS_WSZ_LIT("BaseTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID, 1, 1);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, 1, mdid, CName(&strName));
  	CWStringConst strAlias(GPOS_WSZ_LIT("BaseTableAlias"));
  
  	CExpression *pexprGet = PexprLogicalGet(mp, ptabdesc, &strAlias);
  
  	CColRefSet *pcrs = pexprGet->DeriveOutputColumns();
  	CColRefArray *pdrgpcrDelete = pcrs->Pdrgpcr(mp);
  	CColRefArray *pdrgpcrInsert = pcrs->Pdrgpcr(mp);
  
  	CColRef *colref = pcrs->PcrFirst();
  
  	ptabdesc->AddRef();
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalUpdate(mp, ptabdesc, pdrgpcrDelete, pdrgpcrInsert, colref, colref, NULL /*pcrTupleOid*/),
  					pexprGet
  					);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalDynamicGet
  //
  //	@doc:
  //		Generate a dynamic get expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalDynamicGet
  	(
  	CMemoryPool *mp,
  	CTableDescriptor *ptabdesc,
  	const CWStringConst *pstrTableAlias,
  	ULONG ulPartIndex
  	)
  {
  	GPOS_ASSERT(NULL != ptabdesc);
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalDynamicGet
  								(
  								mp,
  								GPOS_NEW(mp) CName(mp, CName(pstrTableAlias)),
  								ptabdesc,
  								ulPartIndex
  								)
  					);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalDynamicGet
  //
  //	@doc:
  //		Generate a dynamic get expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalDynamicGet
  	(
  	CMemoryPool *mp
  	)
  {
  	CWStringConst strName(GPOS_WSZ_LIT("PartTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID_PARTITIONED, 1, 1);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, 3, mdid, CName(&strName), true /*fPartitioned*/);
  	CWStringConst strAlias(GPOS_WSZ_LIT("PartTableAlias"));
  
  	return PexprLogicalDynamicGet(mp, ptabdesc, &strAlias, GPOPT_TEST_PART_INDEX);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithEqPredicateOverDynamicGet
  //
  //	@doc:
  //		Generate a select over dynamic get expression with a predicate on the
  //		partition key 
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithEqPredicateOverDynamicGet
  	(
  	CMemoryPool *mp
  	)
  {
  	CWStringConst strName(GPOS_WSZ_LIT("PartTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID_PARTITIONED, 1, 1);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, 3, mdid, CName(&strName), true /*fPartitioned*/);
  	CWStringConst strAlias(GPOS_WSZ_LIT("PartTableAlias"));
  
  	CExpression *pexprDynamicGet = PexprLogicalDynamicGet(mp, ptabdesc, &strAlias, GPOPT_TEST_PART_INDEX);
  	
  	// construct scalar comparison
  	CLogicalDynamicGet *popDynamicGet = CLogicalDynamicGet::PopConvert(pexprDynamicGet->Pop());
  	CColRef2dArray *pdrgpdrgpcr = popDynamicGet->PdrgpdrgpcrPart();
  	CColRef *colref = CUtils::PcrExtractPartKey(pdrgpdrgpcr, 0 /*ulLevel*/);
  	CExpression *pexprScalarIdent = CUtils::PexprScalarIdent(mp, colref);
  	
  	CExpression *pexprConst = CUtils::PexprScalarConstInt4(mp, 5 /*val*/);
  	CExpression *pexprScalar = CUtils::PexprScalarEqCmp(mp, pexprScalarIdent, pexprConst);
  	
  	return CUtils::PexprLogicalSelect(mp, pexprDynamicGet, pexprScalar);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSelectWithLTPredicateOverDynamicGet
  //
  //	@doc:
  //		Generate a select over dynamic get expression with a predicate on the
  //		partition key 
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSelectWithLTPredicateOverDynamicGet
  	(
  	CMemoryPool *mp
  	)
  {
  	CWStringConst strName(GPOS_WSZ_LIT("PartTable"));
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(GPOPT_MDCACHE_TEST_OID_PARTITIONED, 1, 1);
  	CTableDescriptor *ptabdesc = PtabdescCreate(mp, 3, mdid, CName(&strName), true /*fPartitioned*/);
  	CWStringConst strAlias(GPOS_WSZ_LIT("PartTableAlias"));
  
  	CExpression *pexprDynamicGet = PexprLogicalDynamicGet(mp, ptabdesc, &strAlias, GPOPT_TEST_PART_INDEX);
  	
  	// construct scalar comparison
  	CLogicalDynamicGet *popDynamicGet = CLogicalDynamicGet::PopConvert(pexprDynamicGet->Pop());
  	CColRef2dArray *pdrgpdrgpcr = popDynamicGet->PdrgpdrgpcrPart();
  	CColRef *colref = CUtils::PcrExtractPartKey(pdrgpdrgpcr, 0 /*ulLevel*/);
  	CExpression *pexprScalarIdent = CUtils::PexprScalarIdent(mp, colref);
  	
  	CExpression *pexprConst = CUtils::PexprScalarConstInt4(mp, 5 /*val*/);
  	CExpression *pexprScalar = CUtils::PexprScalarCmp(mp, pexprScalarIdent, pexprConst, IMDType::EcmptL);
  	
  	return CUtils::PexprLogicalSelect(mp, pexprDynamicGet, pexprScalar);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalTVFTwoArgs
  //
  //	@doc:
  //		Generate a logical TVF expression with 2 constant arguments
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalTVFTwoArgs
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalTVF(mp, 2);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalTVFThreeArgs
  //
  //	@doc:
  //		Generate a logical TVF expression with 3 constant arguments
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalTVFThreeArgs
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalTVF(mp, 3);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalTVFNoArgs
  //
  //	@doc:
  //		Generate a logical TVF expression with no arguments
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalTVFNoArgs
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalTVF(mp, 0);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalTVF
  //
  //	@doc:
  //		Generate a TVF expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalTVF
  	(
  	CMemoryPool *mp,
  	ULONG ulArgs
  	)
  {
  	CMDAccessor *md_accessor = COptCtxt::PoctxtFromTLS()->Pmda();
  
  	const WCHAR *wszFuncName = GPOS_WSZ_LIT("generate_series");
  
  	IMDId *mdid = GPOS_NEW(mp) CMDIdGPDB(GPDB_INT8_GENERATE_SERIES);
  	CWStringConst *str_func_name = GPOS_NEW(mp) CWStringConst(mp, wszFuncName);
  
  	const IMDTypeInt8 *pmdtypeint8 = md_accessor->PtMDType<IMDTypeInt8>(CTestUtils::m_sysidDefault);
  
  	// create an integer column descriptor
  	CWStringConst strName(GPOS_WSZ_LIT("generate_series"));
  	CName name(&strName);
  
  	CColumnDescriptor *pcoldescInt = GPOS_NEW(mp) CColumnDescriptor(mp, pmdtypeint8, default_type_modifier, name, 1 /* attno */, false /*IsNullable*/);
  
  	CColumnDescriptorArray *pdrgpcoldesc = GPOS_NEW(mp) CColumnDescriptorArray(mp);
  	pdrgpcoldesc->Append(pcoldescInt);
  
  	IMDId *mdid_return_type = pmdtypeint8->MDId();
  	mdid_return_type->AddRef();
  
  	CLogicalTVF *popTVF = GPOS_NEW(mp) CLogicalTVF
  										(
  										mp,
  										mdid,
  										mdid_return_type,
  										str_func_name,
  										pdrgpcoldesc
  										);
  
  	if (0 == ulArgs)
  	{
  		return GPOS_NEW(mp) CExpression(mp, popTVF);
  	}
  
  	CExpressionArray *pdrgpexprArgs = GPOS_NEW(mp) CExpressionArray(mp);
  
  	for (ULONG ul = 0; ul < ulArgs; ul++)
  	{
  		ULONG ulArg = 1;
  		IDatum *datum = (IDatum *) pmdtypeint8->CreateInt8Datum(mp, ulArg, false /* is_null */);
  		CScalarConst *popConst = GPOS_NEW(mp) CScalarConst(mp, datum);
  		pdrgpexprArgs->Append(GPOS_NEW(mp) CExpression(mp, popConst));
  	}
  
  	return GPOS_NEW(mp) CExpression(mp, popTVF, pdrgpexprArgs);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalCTEProducerOverSelect
  //
  //	@doc:
  //		Generate a CTE producer on top of a select
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalCTEProducerOverSelect
  	(
  	CMemoryPool *mp,
  	ULONG ulCTEId
  	)
  {
  	CExpression *pexprSelect = CTestUtils::PexprLogicalSelect(mp);
  	CColRefArray *colref_array = pexprSelect->DeriveOutputColumns()->Pdrgpcr(mp);
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalCTEProducer(mp, ulCTEId, colref_array),
  					pexprSelect
  					);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalCTEProducerOverSelect
  //
  //	@doc:
  //		Generate a CTE producer on top of a select
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalCTEProducerOverSelect
  	(
  	CMemoryPool *mp
  	)
  {
  	return PexprLogicalCTEProducerOverSelect(mp, 0);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprCTETree
  //
  //	@doc:
  //		Generate an expression with CTE producer and consumer
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprCTETree
  	(
  	CMemoryPool *mp
  	)
  {
  	ULONG ulCTEId = 0;
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  
  	CExpression *pexprProducer = PexprLogicalCTEProducerOverSelect(mp, ulCTEId);
  	COptCtxt::PoctxtFromTLS()->Pcteinfo()->AddCTEProducer(pexprProducer);
  
  	pdrgpexpr->Append(pexprProducer);
  
  	CColRefArray *pdrgpcrProducer = CLogicalCTEProducer::PopConvert(pexprProducer->Pop())->Pdrgpcr();
  	CColRefArray *pdrgpcrConsumer = CUtils::PdrgpcrCopy(mp, pdrgpcrProducer);
  
  	CExpression *pexprConsumer =
  			GPOS_NEW(mp) CExpression
  						(
  						mp,
  						GPOS_NEW(mp) CLogicalCTEConsumer(mp, ulCTEId, pdrgpcrConsumer)
  						);
  
  	pdrgpexpr->Append(pexprConsumer);
  
  	return PexprLogicalSequence(mp, pdrgpexpr);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSequence
  //
  //	@doc:
  //		Generate a dynamic get expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSequence
  	(
  	CMemoryPool *mp,
  	CExpressionArray *pdrgpexpr
  	)
  {
  	GPOS_ASSERT(NULL != pdrgpexpr);
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CLogicalSequence(mp),
  					pdrgpexpr
  					);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSequence
  //
  //	@doc:
  //		Generate a logical sequence expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSequence
  	(
  	CMemoryPool *mp
  	)
  {
  	const ULONG  ulRels = 2;
  	const ULONG  ulCTGElements = 2;
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  
  	// build an array of get expressions
  	for (ULONG ul = 0; ul < ulRels; ul++)
  	{
  		CExpression *pexpr = PexprLogicalGet(mp);
  		pdrgpexpr->Append(pexpr);
  	}
  
  	CExpression *pexprCTG = PexprConstTableGet(mp, ulCTGElements);
  	pdrgpexpr->Append(pexprCTG);
  
  	CExpression *pexprDynamicGet = PexprLogicalDynamicGet(mp);
  	pdrgpexpr->Append(pexprDynamicGet);
  
  	return PexprLogicalSequence(mp, pdrgpexpr);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalUnion
  //
  //	@doc:
  //		Generate tree of unions recursively
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalUnion
  	(
  	CMemoryPool *mp,
  	ULONG ulDepth
  	)
  {
  	// stack check in recursion
  	GPOS_CHECK_STACK_SIZE;
  
  	CExpression *pexpr = NULL;
  
  	if (0 == ulDepth)
  	{
  		// terminal case, generate table
  		pexpr = PexprLogicalGet(mp);
  	}
  	else
  	{
  		// recursive case, generate union w/ 3 children
  		CExpressionArray *pdrgpexprInput = GPOS_NEW(mp) CExpressionArray(mp, 3);
  		CColRef2dArray *pdrgpdrgpcrInput = GPOS_NEW(mp) CColRef2dArray(mp);
  
  		for (ULONG i = 0; i < 3; i++)
  		{
  			CExpression *pexprInput = PexprLogicalUnion(mp, ulDepth -1);
  			COperator *pop = pexprInput->Pop();
  			CColRefArray *colref_array = NULL;
  
  			if (pop->Eopid() == COperator::EopLogicalGet)
  			{
  				CLogicalGet *popGet =  CLogicalGet::PopConvert(pop);
  				colref_array = popGet->PdrgpcrOutput();
  			}
  			else
  			{
  				GPOS_ASSERT(COperator::EopLogicalUnion == pop->Eopid());
  				CLogicalUnion *popUnion = CLogicalUnion::PopConvert(pop);
  				colref_array = popUnion->PdrgpcrOutput();
  			}
  			pdrgpexprInput->Append(pexprInput);
  			GPOS_ASSERT(NULL != colref_array);
  
  			colref_array->AddRef();
  			pdrgpdrgpcrInput->Append(colref_array);
  		}
  
  		// remap columns of first input
  		CColRefArray *pdrgpcrInput = (*pdrgpdrgpcrInput)[0];
  		ULONG num_cols = pdrgpcrInput->Size();
  		CColRefArray *pdrgpcrOutput = GPOS_NEW(mp) CColRefArray(mp, num_cols);
  
  		for (ULONG j = 0; j < num_cols; j++)
  		{
  			pdrgpcrOutput->Append((*pdrgpcrInput)[j]);
  		}
  
  		pexpr = GPOS_NEW(mp) CExpression
  							(
  							mp,
  							GPOS_NEW(mp) CLogicalUnion(mp, pdrgpcrOutput, pdrgpdrgpcrInput),
  							pdrgpexprInput
  							);
  	}
  
  	return pexpr;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalSequenceProject
  //
  //	@doc:
  //		Generate a logical sequence project expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalSequenceProject
  	(
  	CMemoryPool *mp,
  	OID oidFunc,
  	CExpression *pexprInput
  	)
  {
  	CMDAccessor *md_accessor = COptCtxt::PoctxtFromTLS()->Pmda();
  	CColumnFactory *col_factory = COptCtxt::PoctxtFromTLS()->Pcf();
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexpr->Append(CUtils::PexprScalarIdent(mp, pexprInput->DeriveOutputColumns()->PcrAny()));
  	COrderSpecArray *pdrgpos = GPOS_NEW(mp) COrderSpecArray(mp);
  	CWindowFrameArray *pdrgwf = GPOS_NEW(mp) CWindowFrameArray(mp);
  
  	CLogicalSequenceProject *popSeqProj =
  			GPOS_NEW(mp) CLogicalSequenceProject
  				(
  				mp,
  				GPOS_NEW(mp) CDistributionSpecHashed(pdrgpexpr, true /*fNullsCollocated*/),
  				pdrgpos,
  				pdrgwf
  				);
  
  	IMDId *mdid = GPOS_NEW(mp) CMDIdGPDB(oidFunc);
  	const IMDFunction *pmdfunc = md_accessor->RetrieveFunc(mdid);
  
  	IMDId *mdid_return_type = pmdfunc->GetResultTypeMdid();
  	mdid_return_type->AddRef();
  
  	CExpression *pexprWinFunc =
  			GPOS_NEW(mp) CExpression
  				(
  				mp,
  				GPOS_NEW(mp) CScalarWindowFunc
  							(
  							mp,
  							mdid,
  							mdid_return_type,
  							GPOS_NEW(mp) CWStringConst(mp, pmdfunc->Mdname().GetMDName()->GetBuffer()),
  							CScalarWindowFunc::EwsImmediate,
  							false /*is_distinct*/,
  							false /*is_star_arg*/,
  							false /*is_simple_agg*/
  							)
  				);
  	// window function call is not a cast and so does not need a type modifier
  	CColRef *pcrComputed = col_factory->PcrCreate(md_accessor->RetrieveType(pmdfunc->GetResultTypeMdid()), default_type_modifier);
  
  	CExpression *pexprPrjList =
  		GPOS_NEW(mp) CExpression
  			(
  			mp,
  			GPOS_NEW(mp) CScalarProjectList(mp),
  			CUtils::PexprScalarProjectElement(mp, pcrComputed, pexprWinFunc)
  			);
  
  	return GPOS_NEW(mp) CExpression(mp, popSeqProj, pexprInput, pexprPrjList);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprOneWindowFunction
  //
  //	@doc:
  //		Generate a random expression with one window function
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprOneWindowFunction
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprGet = PexprLogicalGet(mp);
  
  	OID row_number_oid = COptCtxt::PoctxtFromTLS()->GetOptimizerConfig()->GetWindowOids()->OidRowNumber();
  
  	return PexprLogicalSequenceProject(mp, row_number_oid, pexprGet);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprTwoWindowFunctions
  //
  //	@doc:
  //		Generate a random expression with two window functions
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprTwoWindowFunctions
  	(
  	CMemoryPool *mp
  	)
  {
  	CExpression *pexprWinFunc = PexprOneWindowFunction(mp);
  
  	OID rank_oid = COptCtxt::PoctxtFromTLS()->GetOptimizerConfig()->GetWindowOids()->OidRank();
  
  	return PexprLogicalSequenceProject(mp, rank_oid, pexprWinFunc);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PdrgpexprJoins
  //
  //	@doc:
  //		Generate an array of join expressions for the given relation names;
  //		the first generated expression is a Select of the first relation;
  //		every subsequent expression is a Join between a new relation and the
  //		previous join expression
  //
  //---------------------------------------------------------------------------
  CExpressionJoinsArray *
  CTestUtils::PdrgpexprJoins
  	(
  	CMemoryPool *mp,
  	CWStringConst *pstrRel, // array of relation names
  	ULONG *pulRel, // array of relation oids
  	ULONG ulRels, // number of relations
  	BOOL fCrossProduct
  	)
  {
  	CExpressionJoinsArray *pdrgpexpr = GPOS_NEW(mp) CExpressionJoinsArray(mp);
  	for (ULONG i = 0; i < ulRels; i++)
  	{
  		CExpression *pexpr = NULL;
  		if (fCrossProduct)
  		{
  			pexpr = PexprLogicalGet(mp, &pstrRel[i], &pstrRel[i], pulRel[i]);
  		}
  		else
  		{
  			pexpr = PexprLogicalSelect(mp, &pstrRel[i], &pstrRel[i], pulRel[i]);
  		}
  
  
  		if (0 == i)
  		{
  			// the first join is set to a Select/Get of the first relation
  			pdrgpexpr->Append(pexpr);
  		}
  		else
  		{
  			CExpression *pexprJoin = NULL;
  			if (fCrossProduct)
  			{
  				// create a cross product
  				pexprJoin = CUtils::PexprLogicalJoin<CLogicalInnerJoin>
  					(
  					mp,
  					(*pdrgpexpr)[i - 1],
  					pexpr,
  					CPredicateUtils::PexprConjunction(mp, NULL) // generate a constant True
  				);
  			}
  			else
  			{
  				// otherwise, we create a new join out of the last created
  				// join and the current Select expression
  				pexprJoin = PexprLogicalJoin<CLogicalInnerJoin>(mp, (*pdrgpexpr)[i - 1], pexpr);
  			}
  
  			pdrgpexpr->Append(pexprJoin);
  		}
  
  		GPOS_CHECK_ABORT;
  	}
  
  	return pdrgpexpr;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprLogicalNAryJoin
  //
  //	@doc:
  //		Generate an n-ary join expression using given array of relation names
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprLogicalNAryJoin
  	(
  	CMemoryPool *mp,
  	CWStringConst *pstrRel,
  	ULONG *pulRel,
  	ULONG ulRels,
  	BOOL fCrossProduct
  	)
  {
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	for (ULONG ul = 0; ul < ulRels; ul++)
  	{
  		CExpression *pexpr = PexprLogicalGet(mp, &pstrRel[ul], &pstrRel[ul], pulRel[ul]);
  		pdrgpexpr->Append(pexpr);
  	}
  
  	CExpressionArray *pdrgpexprPred = NULL;
  	if (!fCrossProduct)
  	{
  		pdrgpexprPred = GPOS_NEW(mp) CExpressionArray(mp);
  		for (ULONG ul = 0; ul < ulRels - 1; ul++)
  		{
  			CExpression *pexprLeft = (*pdrgpexpr)[ul];
  			CExpression *pexprRight = (*pdrgpexpr)[ul + 1];
  
  			// get any two columns; one from each side
  			CColRef *pcrLeft = pexprLeft->DeriveOutputColumns()->PcrAny();
  			CColRef *pcrRight = pexprRight->DeriveOutputColumns()->PcrAny();
  			pdrgpexprPred->Append(CUtils::PexprScalarEqCmp(mp, pcrLeft, pcrRight));
  		}
  	}
  	pdrgpexpr->Append(CPredicateUtils::PexprConjunction(mp, pdrgpexprPred));
  
  	return PexprLogicalNAryJoin(mp, pdrgpexpr);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PqcGenerate
  //
  //	@doc:
  //		Generate a dummy context from an array of column references and
  //		empty sort columns for testing
  //
  //---------------------------------------------------------------------------
  CQueryContext *
  CTestUtils::PqcGenerate
  	(
  	CMemoryPool *mp,
  	CExpression *pexpr,
  	CColRefArray *colref_array
  	)
  {
  	// generate required columns
  	CColRefSet *pcrs = GPOS_NEW(mp) CColRefSet(mp);
  	pcrs->Include(colref_array);
  
  	COrderSpec *pos = GPOS_NEW(mp) COrderSpec(mp);
  	CDistributionSpec *pds =
  		GPOS_NEW(mp) CDistributionSpecSingleton(CDistributionSpecSingleton::EstMaster);
  
  	CRewindabilitySpec *prs = GPOS_NEW(mp) CRewindabilitySpec(CRewindabilitySpec::ErtNone, CRewindabilitySpec::EmhtNoMotion);
  
  	CEnfdOrder *peo = GPOS_NEW(mp) CEnfdOrder(pos, CEnfdOrder::EomSatisfy);
  
  	// we require exact matching on distribution since final query results must be sent to master
  	CEnfdDistribution *ped = GPOS_NEW(mp) CEnfdDistribution(pds, CEnfdDistribution::EdmExact);
  
  	CEnfdRewindability *per = GPOS_NEW(mp) CEnfdRewindability(prs, CEnfdRewindability::ErmSatisfy);
  		
  	CCTEReq *pcter = COptCtxt::PoctxtFromTLS()->Pcteinfo()->PcterProducers(mp);
  
  	CReqdPropPlan *prpp = GPOS_NEW(mp) CReqdPropPlan(pcrs, peo, ped, per, pcter);
  
  	colref_array->AddRef();
  	CMDNameArray *pdrgpmdname = GPOS_NEW(mp) CMDNameArray(mp);
  	const ULONG length = colref_array->Size();
  	for (ULONG ul = 0; ul < length; ul++)
  	{
  		CColRef *colref = (*colref_array)[ul];
  		CMDName *mdname = GPOS_NEW(mp) CMDName(mp, colref->Name().Pstr());
  		pdrgpmdname->Append(mdname);
  	}
  
  	return GPOS_NEW(mp) CQueryContext(mp, pexpr, prpp, colref_array, pdrgpmdname, true /*fDeriveStats*/);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PqcGenerate
  //
  //	@doc:
  //		Generate a dummy context with a subset of required column, one
  //		sort column and one distribution column
  //
  //---------------------------------------------------------------------------
  CQueryContext *
  CTestUtils::PqcGenerate
  	(
  	CMemoryPool *mp,
  	CExpression *pexpr
  	)
  {
  	CColRefSet *pcrs = GPOS_NEW(mp) CColRefSet(mp);
  	pcrs->Include(pexpr->DeriveOutputColumns());
  
  	// keep a subset of columns
  	CColRefSet *pcrsOutput = GPOS_NEW(mp) CColRefSet(mp);
  	CColRefSetIter crsi(*pcrs);
  	while (crsi.Advance())
  	{
  		CColRef *colref = crsi.Pcr();
  		if (1 != colref->Id() % GPOPT_TEST_REL_WIDTH)
  		{
  			pcrsOutput->Include(colref);
  		}
  	}
  	pcrs->Release();
  
  	// construct an ordered array of the output columns
  	CColRefArray *colref_array = GPOS_NEW(mp) CColRefArray(mp);
  	CColRefSetIter crsiOutput(*pcrsOutput);
  	while (crsiOutput.Advance())
  	{
  		CColRef *colref = crsiOutput.Pcr();
  		colref_array->Append(colref);
  	}
  
  	// generate a sort order
  	COrderSpec *pos = GPOS_NEW(mp) COrderSpec(mp);
  	pos->Append(GPOS_NEW(mp) CMDIdGPDB(GPDB_INT4_LT_OP), pcrsOutput->PcrAny(), COrderSpec::EntFirst);
  
  	CDistributionSpec *pds =
  		GPOS_NEW(mp) CDistributionSpecSingleton(CDistributionSpecSingleton::EstMaster);
  
  	CRewindabilitySpec *prs = GPOS_NEW(mp) CRewindabilitySpec(CRewindabilitySpec::ErtNone, CRewindabilitySpec::EmhtNoMotion);
  
  	CEnfdOrder *peo = GPOS_NEW(mp) CEnfdOrder(pos, CEnfdOrder::EomSatisfy);
  
  	// we require exact matching on distribution since final query results must be sent to master
  	CEnfdDistribution *ped = GPOS_NEW(mp) CEnfdDistribution(pds, CEnfdDistribution::EdmExact);
  
  	CEnfdRewindability *per = GPOS_NEW(mp) CEnfdRewindability(prs, CEnfdRewindability::ErmSatisfy);
  
  	CCTEReq *pcter = COptCtxt::PoctxtFromTLS()->Pcteinfo()->PcterProducers(mp);
  
  	CReqdPropPlan *prpp = GPOS_NEW(mp) CReqdPropPlan(pcrsOutput, peo, ped, per, pcter);
  
  	CMDNameArray *pdrgpmdname = GPOS_NEW(mp) CMDNameArray(mp);
  	const ULONG length = colref_array->Size();
  	for (ULONG ul = 0; ul < length; ul++)
  	{
  		CColRef *colref = (*colref_array)[ul];
  		CMDName *mdname = GPOS_NEW(mp) CMDName(mp, colref->Name().Pstr());
  		pdrgpmdname->Append(mdname);
  	}
  
  	return GPOS_NEW(mp) CQueryContext(mp, pexpr, prpp, colref_array, pdrgpmdname, true /*fDeriveStats*/);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprScalarNestedPreds
  //
  //	@doc:
  //		Helper for generating a nested AND/OR/NOT tree
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprScalarNestedPreds
  	(
  	CMemoryPool *mp,
  	CExpression *pexpr,
  	CScalarBoolOp::EBoolOperator eboolop
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  	GPOS_ASSERT(NULL != pexpr);
  	GPOS_ASSERT(CScalarBoolOp::EboolopAnd == eboolop ||
  				CScalarBoolOp::EboolopOr == eboolop ||
  				CScalarBoolOp::EboolopNot == eboolop);
  
  	CColRefSet *pcrs = pexpr->DeriveOutputColumns();
  	CColRef *pcrLeft =  pcrs->PcrAny();
  	CExpression *pexprConstActual = CUtils::PexprScalarConstInt4(mp, 3 /*val*/);
  
  	CExpression *pexprPredActual = CUtils::PexprScalarEqCmp(mp, pcrLeft, pexprConstActual);
  	CExpression *pexprPredExpected = NULL;
  
  	if (CScalarBoolOp::EboolopNot != eboolop)
  	{
  		CExpression *pexprConstExpected = CUtils::PexprScalarConstInt4(mp, 5 /*val*/);
  		pexprPredExpected = CUtils::PexprScalarEqCmp(mp, pcrLeft, pexprConstExpected);
  	}
  
  	CExpressionArray *pdrgpexprActual = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexprActual->Append(pexprPredActual);
  
  	if (CScalarBoolOp::EboolopNot != eboolop)
  	{
  		pdrgpexprActual->Append(pexprPredExpected);
  	}
  
  	CExpression *pexprBoolOp = CUtils::PexprScalarBoolOp(mp, eboolop , pdrgpexprActual);
  	CExpressionArray *pdrgpexprExpected = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexprExpected->Append(pexprBoolOp);
  
  	if (CScalarBoolOp::EboolopNot != eboolop)
  	{
  		pexprPredExpected->AddRef();
  		pdrgpexprExpected->Append(pexprPredExpected);
  	}
  
  	return CUtils::PexprScalarBoolOp(mp, eboolop , pdrgpexprExpected);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprFindFirstExpressionWithOpId
  //
  //	@doc:
  //		DFS of expression tree to find and return a pointer to the expression
  //		containing the given operator type. NULL if not found
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprFindFirstExpressionWithOpId
  	(
  	CExpression *pexpr,
  	COperator::EOperatorId op_id
  	)
  {
  	GPOS_ASSERT(NULL != pexpr);
  	if (op_id == pexpr->Pop()->Eopid())
  	{
  		return pexpr;
  	}
  
  	ULONG arity = pexpr->Arity();
  	for (ULONG ul = 0; ul < arity; ul++)
  	{
  		CExpression *pexprFound = PexprFindFirstExpressionWithOpId((*pexpr)[ul], op_id);
  		if (NULL != pexprFound)
  		{
  			return pexprFound;
  		}
  	}
  
  	return NULL;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EqualityPredicate
  //
  //	@doc:
  //		Generate equality predicate given column sets
  //
  //---------------------------------------------------------------------------
  void
  CTestUtils::EqualityPredicate
  	(
  	CMemoryPool *mp,
  	CColRefSet *pcrsLeft,
  	CColRefSet *pcrsRight,
  	CExpressionArray *pdrgpexpr
  	)
  {
  	CColRef *pcrLeft = pcrsLeft->PcrAny();
  	CColRef *pcrRight = pcrsRight->PcrAny();
  
  	// generate correlated predicate
  	CExpression *pexprCorrelation = CUtils::PexprScalarEqCmp(mp, pcrLeft, pcrRight);
  
  	pdrgpexpr->Append(pexprCorrelation);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PpointInt2
  //
  //	@doc:
  //		Create an INT2 point
  //
  //---------------------------------------------------------------------------
  CPoint *
  CTestUtils::PpointInt2
  	(
  	CMemoryPool *mp,
  	INT i
  	)
  {
  	CDatumInt2GPDB *datum = GPOS_NEW(mp) CDatumInt2GPDB(m_sysidDefault, (SINT) i);
  	CPoint *point = GPOS_NEW(mp) CPoint(datum);
  	return point;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PpointInt4
  //
  //	@doc:
  //		Create an INT4 point
  //
  //---------------------------------------------------------------------------
  CPoint *
  CTestUtils::PpointInt4
  	(
  	CMemoryPool *mp,
  	INT i
  	)
  {
  	CDatumInt4GPDB *datum = GPOS_NEW(mp) CDatumInt4GPDB(m_sysidDefault, i);
  	CPoint *point = GPOS_NEW(mp) CPoint(datum);
  	return point;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PpointInt4NullVal
  //
  //	@doc:
  //		Create an INT4 point with null value
  //
  //---------------------------------------------------------------------------
  CPoint *
  CTestUtils::PpointInt4NullVal
  	(
  	CMemoryPool *mp
  	)
  {
  	CDatumInt4GPDB *datum = GPOS_NEW(mp) CDatumInt4GPDB(m_sysidDefault, 0, true /* is_null */);
  	CPoint *point = GPOS_NEW(mp) CPoint(datum);
  	return point;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PpointInt8
  //
  //	@doc:
  //		Create an INT8 point
  //
  //---------------------------------------------------------------------------
  CPoint *
  CTestUtils::PpointInt8
  	(
  	CMemoryPool *mp,
  	INT i
  	)
  {
  	CDatumInt8GPDB *datum = GPOS_NEW(mp) CDatumInt8GPDB(m_sysidDefault, (LINT) i);
  	CPoint *point = GPOS_NEW(mp) CPoint(datum);
  	return point;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PpointBool
  //
  //	@doc:
  //		Create a point
  //
  //---------------------------------------------------------------------------
  CPoint *
  CTestUtils::PpointBool
  	(
  	CMemoryPool *mp,
  	BOOL value
  	)
  {
  	CDatumBoolGPDB *datum = GPOS_NEW(mp) CDatumBoolGPDB(m_sysidDefault, value);
  	CPoint *point = GPOS_NEW(mp) CPoint(datum);
  	return point;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprReadQuery
  //
  //	@doc:
  //		Return query logical expression from minidump
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprReadQuery
  	(
  	CMemoryPool *mp,
  	const CHAR *szQueryFileName
  	)
  {
  	CHAR *szQueryDXL = CDXLUtils::Read(mp, szQueryFileName);
  
  	// parse the DXL query tree from the given DXL document
  	CQueryToDXLResult *ptroutput = CDXLUtils::ParseQueryToQueryDXLTree(mp, szQueryDXL, NULL);
  
  	// get md accessor
  	CMDAccessor *md_accessor = COptCtxt::PoctxtFromTLS()->Pmda();
  	GPOS_ASSERT(NULL != md_accessor);
  
  	// translate DXL tree into CExpression
  	CTranslatorDXLToExpr trdxl2expr(mp, md_accessor);
  	CExpression *pexprQuery =
  			trdxl2expr.PexprTranslateQuery
  						(
  						ptroutput->CreateDXLNode(),
  						ptroutput->GetOutputColumnsDXLArray(),
  						ptroutput->GetCTEProducerDXLArray()
  						);
  
  	GPOS_DELETE(ptroutput);
  	GPOS_DELETE_ARRAY(szQueryDXL);
  
  	return pexprQuery;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresTranslate
  //
  //	@doc:
  //		Rehydrate a query from the given file, optimize it, translate back to
  //		DXL and compare the resulting plan to the given DXL document
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresTranslate
  	(
  	CMemoryPool *mp,
  	const CHAR *szQueryFileName,
  	const CHAR *szPlanFileName,
  	BOOL fIgnoreMismatch
  	)
  {
  	// debug print
  	CWStringDynamic str(mp);
  	COstreamString oss(&str);
  
  	GPOS_TRACE(str.GetBuffer());
  
  	// read the dxl document
  	CHAR *szQueryDXL = CDXLUtils::Read(mp, szQueryFileName);
  
  	// parse the DXL query tree from the given DXL document
  	CQueryToDXLResult *ptroutput = CDXLUtils::ParseQueryToQueryDXLTree(mp, szQueryDXL, NULL);
  
  	// get md accessor
  	CMDAccessor *md_accessor = COptCtxt::PoctxtFromTLS()->Pmda();
  	GPOS_ASSERT(NULL != md_accessor);
  
  	// translate DXL tree into CExpression
  	CTranslatorDXLToExpr ptrdxl2expr(mp, md_accessor);
  	CExpression *pexprQuery = ptrdxl2expr.PexprTranslateQuery
  											(
  											ptroutput->CreateDXLNode(),
  											ptroutput->GetOutputColumnsDXLArray(),
  											ptroutput->GetCTEProducerDXLArray()
  											);
  
  	CQueryContext *pqc = CQueryContext::PqcGenerate
  												(
  												mp,
  												pexprQuery,
  												ptrdxl2expr.PdrgpulOutputColRefs(),
  												ptrdxl2expr.Pdrgpmdname(),
  												true /*fDeriveStats*/
  												);
  
  #ifdef GPOS_DEBUG
  	pqc->OsPrint(oss);
  #endif //GPOS_DEBUG
  
  	gpopt::CEngine eng(mp);
  	eng.Init(pqc, NULL /*search_stage_array*/);
  
  #ifdef GPOS_DEBUG
  	eng.RecursiveOptimize();
  #else
  	eng.Optimize();
  #endif //GPOS_DEBUG
  
  	gpopt::CExpression *pexprPlan = eng.PexprExtractPlan();
  	GPOS_ASSERT(NULL != pexprPlan);
  
  	(void) pexprPlan->PrppCompute(mp, pqc->Prpp());
  	pexprPlan->OsPrint(oss);
  
  	// translate plan back to DXL
  	CTranslatorExprToDXL ptrexpr2dxl(mp, md_accessor, PdrgpiSegments(mp));
  	CDXLNode *pdxlnPlan = ptrexpr2dxl.PdxlnTranslate(pexprPlan, pqc->PdrgPcr(), pqc->Pdrgpmdname());
  	GPOS_ASSERT(NULL != pdxlnPlan);
  
  	COptimizerConfig *optimizer_config = COptCtxt::PoctxtFromTLS()->GetOptimizerConfig();
  
  	CWStringDynamic strTranslatedPlan(mp);
  	COstreamString osTranslatedPlan(&strTranslatedPlan);
  
  	CDXLUtils::SerializePlan(mp, osTranslatedPlan, pdxlnPlan, optimizer_config->GetEnumeratorCfg()->GetPlanId(), optimizer_config->GetEnumeratorCfg()->GetPlanSpaceSize(), true /*serialize_header_footer*/, true /*indentation*/);
  
  	GPOS_TRACE(str.GetBuffer());
  	GPOS_RESULT eres = GPOS_OK;
  	if (NULL != szPlanFileName)
  	{
  		// parse the DXL plan tree from the given DXL file
  		CHAR *szExpectedPlan = CDXLUtils::Read(mp, szPlanFileName);
  
  		CWStringDynamic strExpectedPlan(mp);
  		strExpectedPlan.AppendFormat(GPOS_WSZ_LIT("%s"), szExpectedPlan);
  
  		eres = EresCompare(oss, &strTranslatedPlan, &strExpectedPlan, fIgnoreMismatch);
  		GPOS_DELETE_ARRAY(szExpectedPlan);
  	}
  
  	// cleanup
  	pexprQuery->Release();
  	pexprPlan->Release();
  	pdxlnPlan->Release();
  	GPOS_DELETE_ARRAY(szQueryDXL);
  	GPOS_DELETE(ptroutput);
  	GPOS_DELETE(pqc);
  	return eres;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresCompare
  //
  //	@doc:
  //		Compare expected and actual output
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresCompare
  	(
  	IOstream &os,
  	CWStringDynamic *pstrActual,
  	CWStringDynamic *pstrExpected,
  	BOOL fIgnoreMismatch
  	)
  {
  	if (!pstrExpected->Equals(pstrActual))
  	{
  		os << "Output does not match expected DXL document" << std::endl;
  		os << "Actual: " << std::endl;
  		os << pstrActual->GetBuffer() << std::endl;
  
  		os << "Expected: " << std::endl;
  		os << pstrExpected->GetBuffer() << std::endl;
  
  		if (fIgnoreMismatch)
  		{
  			return GPOS_OK;
  		}
  		return GPOS_FAILED;
  	}
  	else
  	{
  		os << "Output matches expected DXL document" << std::endl;
  		return GPOS_OK;
  	}
  }
  
  CHAR*
  CTestUtils::ExtractFilenameFromPath
  	(
  	 CHAR* file_path
  	)
  {
  	CHAR *filename = NULL;
  	CHAR *token = strtok(file_path, "/");
  
  	while (token != NULL)
  	{
  		filename = token;
  		token = strtok(NULL, "/");
  	}
  
  	return filename;
  }
  
  void
  CTestUtils::CreateExpectedAndActualFile
  	(
  	CMemoryPool *mp,
  	const CHAR *file_name,
  	CWStringDynamic *strExpected,
  	CWStringDynamic *strActual)
  {
  	CHAR *filepath = GPOS_NEW_ARRAY(mp, CHAR, strlen(file_name));
  	clib::Strncpy(filepath, file_name, strlen(file_name));
  	filepath[strlen(file_name) - 1] = '\0';
  	CWStringDynamic expected_file(mp);
  	CWStringDynamic actual_file(mp);
  	CHAR *basename = ExtractFilenameFromPath(filepath);
  	
  	expected_file.AppendFormat(GPOS_WSZ_LIT("../server/%s_expected"), basename);
  	actual_file.AppendFormat(GPOS_WSZ_LIT("../server/%s_actual"), basename);
  	
  	CAutoP<std::wofstream> wos_expected;
  	CAutoP<COstreamBasic> os_expected;
  	
  	CAutoP<std::wofstream> wos_actual;
  	CAutoP<COstreamBasic> os_actual;
  	
  	wos_expected = GPOS_NEW(mp) std::wofstream(CUtils::CreateMultiByteCharStringFromWCString(m_mp, const_cast<WCHAR *>(expected_file.GetBuffer())));
  	os_expected = GPOS_NEW(mp) COstreamBasic(wos_expected.Value());
  	
  	wos_actual = GPOS_NEW(mp) std::wofstream(CUtils::CreateMultiByteCharStringFromWCString(m_mp, const_cast<WCHAR *>(actual_file.GetBuffer())));
  	os_actual = GPOS_NEW(mp) COstreamBasic(wos_actual.Value());
  
  	*os_expected << strExpected->GetBuffer() << std::endl;
  	*os_actual << strActual->GetBuffer() << std::endl;
  	
  	GPOS_DELETE_ARRAY(filepath);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::FPlanMatch
  //
  //	@doc:
  //		Match given two plans using string comparison
  //
  //---------------------------------------------------------------------------
  BOOL
  CTestUtils::FPlanMatch
  	(
  	CMemoryPool *mp,
  	IOstream &os,
  	const CDXLNode *pdxlnActual,
  	ULLONG ullPlanIdActual,
  	ULLONG ullPlanSpaceSizeActual,
  	const CDXLNode *pdxlnExpected,
  	ULLONG ullPlanIdExpected,
  	ULLONG ullPlanSpaceSizeExpected,
  	const CHAR* file_name
  	)
  {
  	
  	if (NULL == pdxlnActual && NULL == pdxlnExpected)
  	{
  		CAutoTrace at(mp);
  		at.Os() << "Both plans are NULL." << std::endl;
  
  		return true;
  	}
  
  	if (NULL != pdxlnActual && NULL == pdxlnExpected)
  	{
  		CAutoTrace at(mp);
  		at.Os() << "Plan comparison *** FAILED ***" << std::endl;
  		at.Os() << "Expected plan is NULL. Actual: " << std::endl;
  		CDXLUtils::SerializePlan(mp, at.Os(), pdxlnActual, ullPlanIdActual, ullPlanSpaceSizeActual, false /*serialize_document_header_footer*/, true /*indentation*/);
  		at.Os() << std::endl;
  
  		return false;
  	}
  
  	if (NULL == pdxlnActual && NULL != pdxlnExpected)
  	{
  		CAutoTrace at(mp);
  		at.Os() << "Plan comparison *** FAILED ***" << std::endl;
  		at.Os()  << "Actual plan is NULL. Expected: " << std::endl;
  		CDXLUtils::SerializePlan(mp, at.Os(), pdxlnExpected, ullPlanIdExpected, ullPlanSpaceSizeExpected, false /*serialize_document_header_footer*/, true /*indentation*/);
  		at.Os()  << std::endl;
  
  		return false;
  	}		
  		
  	GPOS_ASSERT(NULL != pdxlnActual);
  	GPOS_ASSERT(NULL != pdxlnExpected);
  	
  	// plan id's and space sizes are already compared before this point,
  	// overwrite PlanId's and space sizes with zeros to pass string comparison on plan body
  	CWStringDynamic strActual(mp);
  	COstreamString osActual(&strActual);
  	CDXLUtils::SerializePlan(mp, osActual, pdxlnActual, 0 /*ullPlanIdActual*/, 0 /*ullPlanSpaceSizeActual*/, false /*serialize_document_header_footer*/, true /*indentation*/);
  	GPOS_CHECK_ABORT;
  
  	CWStringDynamic strExpected(mp);
  	COstreamString osExpected(&strExpected);
  	CDXLUtils::SerializePlan(mp, osExpected, pdxlnExpected, 0 /*ullPlanIdExpected*/, 0 /*ullPlanSpaceSizeExpected*/, false /*serialize_document_header_footer*/, true /*indentation*/);
  	GPOS_CHECK_ABORT;
  
  	BOOL result = strActual.Equals(&strExpected);
  
  	if (!result)
  	{
  		// serialize plans again to restore id's and space size before printing error message
  		CWStringDynamic strActual(mp);
  		COstreamString osActual(&strActual);
  		CDXLUtils::SerializePlan(mp, osActual, pdxlnActual, ullPlanIdActual, ullPlanSpaceSizeActual, false /*serialize_document_header_footer*/, true /*indentation*/);
  		GPOS_CHECK_ABORT;
  
  		CWStringDynamic strExpected(mp);
  		COstreamString osExpected(&strExpected);
  		CDXLUtils::SerializePlan(mp, osExpected, pdxlnExpected, ullPlanIdExpected, ullPlanSpaceSizeExpected, false /*serialize_document_header_footer*/, true /*indentation*/);
  		GPOS_CHECK_ABORT;
  
  		{
  			CAutoTrace at(mp);
  
  			at.Os() << "Plan comparison *** FAILED ***" << std::endl;
  			at.Os() << "Actual: " << std::endl;
  			at.Os()  << strActual.GetBuffer() << std::endl;
  		}
  
  		os << "Expected: " << std::endl;
  		os << strExpected.GetBuffer() << std::endl;
  		
  		CreateExpectedAndActualFile(mp, file_name, &strExpected, &strActual);
  	}
  	
  	GPOS_CHECK_ABORT;
  	return result;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::FPlanCompare
  //
  //	@doc:
  //		Compare two plans based on their string representation
  //
  //---------------------------------------------------------------------------
  BOOL
  CTestUtils::FPlanCompare
  	(
  	CMemoryPool *mp,
  	IOstream &os,
  	const CDXLNode *pdxlnActual,
  	ULLONG ullPlanIdActual,
  	ULLONG ullPlanSpaceSizeActual,
  	const CDXLNode *pdxlnExpected,
  	ULLONG ullPlanIdExpected,
  	ULLONG ullPlanSpaceSizeExpected,
  	BOOL fMatchPlans,
  	INT iCmpSpaceSize,
  	const CHAR* file_name
  	)
  {
  	BOOL fPlanSpaceUnchanged = true;
  
  	if (!fMatchPlans)
  	{
  		return true;
  	}
  
  	CAutoTrace at(mp);
  
  	if (ullPlanIdActual != ullPlanIdExpected)
  	{
  		at.Os()
  		<< "Plan Id mismatch." << std::endl
  		<< "\tActual Id: " << ullPlanIdActual << std::endl
  		<< "\tExpected Id: " << ullPlanIdExpected << std::endl;
  
  		return false;
  	}
  
  	// check plan space size required comparison
  	if (
  		(0 == iCmpSpaceSize && ullPlanSpaceSizeActual != ullPlanSpaceSizeExpected) ||	// required comparison is equality
  		(-1 == iCmpSpaceSize && ullPlanSpaceSizeActual > ullPlanSpaceSizeExpected) ||	// required comparison is (Actual <= Expected)
  		(1 == iCmpSpaceSize && ullPlanSpaceSizeActual < ullPlanSpaceSizeExpected)  // required comparison is (Actual >= Expected)
  		)
  	{
  		at.Os()
  				<< "Plan space size comparison *** FAILED ***" << std::endl
  				<< "Required comparison: " << iCmpSpaceSize << std::endl
  				<< "\tActual size: " << ullPlanSpaceSizeActual << std::endl
  				<< "\tExpected size: " << ullPlanSpaceSizeExpected << std::endl;
  
  		fPlanSpaceUnchanged = false;
  	}
  
  	// perform deep matching on plan bodies
  	return FPlanMatch(mp, os, pdxlnActual, ullPlanIdActual, ullPlanSpaceSizeActual, pdxlnExpected, ullPlanIdExpected, ullPlanSpaceSizeExpected, file_name) && fPlanSpaceUnchanged;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PdrgpiSegments
  //
  //	@doc:
  //		Helper function for creating an array of segment ids for the target system
  //
  //---------------------------------------------------------------------------
  IntPtrArray *
  CTestUtils::PdrgpiSegments
  	(
  	CMemoryPool *mp
  	)
  {
  	IntPtrArray *pdrgpiSegments = GPOS_NEW(mp) IntPtrArray(mp);
  	const ULONG ulSegments = GPOPT_SEGMENT_COUNT;
  	GPOS_ASSERT(0 < ulSegments);
  
  	for (ULONG ul = 0; ul < ulSegments; ul++)
  	{
  		pdrgpiSegments->Append(GPOS_NEW(mp) INT(ul));
  	}
  	return pdrgpiSegments;
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::SzMinidumpFileName
  //
  //	@doc:
  //		Generate minidump file name from passed file name
  //
  //---------------------------------------------------------------------------
  CHAR *
  CTestUtils::SzMinidumpFileName
  	(
  	CMemoryPool *mp,
  	const CHAR *file_name
  	)
  {
  	GPOS_ASSERT(NULL != file_name);
  
  	if (!GPOS_FTRACE(EopttraceEnableSpacePruning))
  	{
  		return const_cast<CHAR *>(file_name);
  	}
  
  	CWStringDynamic *pstrMinidumpFileName = GPOS_NEW(mp) CWStringDynamic(mp);
  	COstreamString oss(pstrMinidumpFileName);
  	oss << file_name << "-space-pruned";
  
  	// convert wide char to regular char
  	const WCHAR *wsz = pstrMinidumpFileName->GetBuffer();
  	const ULONG ulInputLength = GPOS_WSZ_LENGTH(wsz);
  	const ULONG ulWCHARSize = GPOS_SIZEOF(WCHAR);
  	const ULONG ulMaxLength = (ulInputLength + 1) * ulWCHARSize;
  	CHAR *sz = GPOS_NEW_ARRAY(mp, CHAR, ulMaxLength);
  	gpos::clib::Wcstombs(sz, const_cast<WCHAR *>(wsz), ulMaxLength);
  	sz[ulMaxLength - 1] = '\0';
  
  	GPOS_DELETE(pstrMinidumpFileName);
  
  	return sz;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresRunMinidump
  //
  //	@doc:
  //		Run one minidump-based test using passed MD Accessor
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresRunMinidump
  	(
  	CMemoryPool *mp,
  	CMDAccessor *md_accessor,
  	const CHAR *file_name,
  	ULONG *pulTestCounter,
  	ULONG ulSessionId,
  	ULONG ulCmdId,
  	BOOL fMatchPlans,
  	INT iCmpSpaceSize,
  	IConstExprEvaluator *pceeval
  	)
  {
  	GPOS_ASSERT(NULL != md_accessor);
  
  	GPOS_RESULT eres = GPOS_OK;
  
  	{
  		CAutoTrace at(mp);
  		at.Os() << "executing " << file_name;
  	}
  
  	// load dump file
  	CDXLMinidump *pdxlmd = CMinidumperUtils::PdxlmdLoad(mp, file_name);
  	GPOS_CHECK_ABORT;
  
  	COptimizerConfig *optimizer_config = pdxlmd->GetOptimizerConfig();
  
  	if (NULL == optimizer_config)
  	{
  		optimizer_config = COptimizerConfig::PoconfDefault(mp);
  	}
  	else
  	{
  		optimizer_config->AddRef();
  	}
  
  	ULONG ulSegments = UlSegments(optimizer_config);
  
  	// allow sampler to throw invalid plan exception
  	optimizer_config->GetEnumeratorCfg()->SetSampleValidPlans(false /*fSampleValidPlans*/);
  
  	CDXLNode *pdxlnPlan = NULL;
  
  	CHAR *szMinidumpFileName = SzMinidumpFileName(mp, file_name);
  
  	pdxlnPlan = CMinidumperUtils::PdxlnExecuteMinidump
  					(
  					mp,
  					md_accessor,
  					pdxlmd,
  					szMinidumpFileName,
  					ulSegments,
  					ulSessionId,
  					ulCmdId,
  					optimizer_config,
  					pceeval
  					);
  
  	if (szMinidumpFileName != file_name)
  	{
  		// a new name was generated
  		GPOS_DELETE_ARRAY(szMinidumpFileName);
  	}
  
  	GPOS_CHECK_ABORT;
  
  
  	{
  		CAutoTrace at(mp);
  		if (!CTestUtils::FPlanCompare
  			(
  			mp,
  			at.Os(),
  			pdxlnPlan,
  			optimizer_config->GetEnumeratorCfg()->GetPlanId(),
  			optimizer_config->GetEnumeratorCfg()->GetPlanSpaceSize(),
  			pdxlmd->PdxlnPlan(),
  			pdxlmd->GetPlanId(),
  			pdxlmd->GetPlanSpaceSize(),
  			fMatchPlans,
  			iCmpSpaceSize,
  			file_name
  			)
  		)
  		{
  			eres = GPOS_FAILED;
  		}
      }
  
  	GPOS_CHECK_ABORT;
  
  	{
  		CAutoTrace at(mp);
  		at.Os() << std::endl;
  	}
  
  	// cleanup
  	GPOS_DELETE(pdxlmd);
  	pdxlnPlan->Release();
  	optimizer_config->Release();
  
  	(*pulTestCounter)++;
  	return eres;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresRunMinidumps
  //
  //	@doc:
  //		Run minidump-based tests
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresRunMinidumps
  	(
  	CMemoryPool *, // pmpInput,
  	const CHAR *rgszFileNames[],
  	ULONG ulTests,
  	ULONG *pulTestCounter,
  	ULONG ulSessionId,
  	ULONG ulCmdId,
  	BOOL fMatchPlans,
  	BOOL fTestSpacePruning,
  	const CHAR *, // szMDFilePath,
  	IConstExprEvaluator *pceeval
  	)
  {
  	GPOS_RESULT eres = GPOS_OK;
  	BOOL fSuccess = true;
  
  	for (ULONG ul = *pulTestCounter; ul < ulTests; ul++)
  	{
  		// each test uses a new memory pool to keep total memory consumption low
  		CAutoMemoryPool amp;
  		CMemoryPool *mp = amp.Pmp();
  
  		if (fMatchPlans)
  		{
  			{
  				CAutoTrace at(mp);
  				at.Os() << "IsRunning test with EXHAUSTIVE SEARCH:";
  			}
  
  			eres = EresRunMinidumpsUsingOneMDFile
  				(
  				mp,
  				rgszFileNames[ul],
  				&rgszFileNames[ul],
  				pulTestCounter,
  				ulSessionId,
  				ulCmdId,
  				fMatchPlans,
  				0, // iCmpSpaceSize
  				pceeval
  				);
  
  			if (GPOS_FAILED == eres)
  			{
  				fSuccess = false;
  			}
  		}
  
  		if (GPOS_OK == eres && fTestSpacePruning)
  		{
  
  			{
  				CAutoTrace at(mp);
  				at.Os() << "IsRunning test with BRANCH-AND-BOUND SEARCH:";
  			}
  
  			// enable space pruning
  			CAutoTraceFlag atf(EopttraceEnableSpacePruning, true /*value*/);
  
  			eres = EresRunMinidumpsUsingOneMDFile
  				(
  				mp,
  				rgszFileNames[ul],
  				&rgszFileNames[ul],
  				pulTestCounter,
  				ulSessionId,
  				ulCmdId,
  				fMatchPlans,
  				-1, // iCmpSpaceSize
  				pceeval
  				);
  
  			if (GPOS_FAILED == eres)
  			{
  				fSuccess = false;
  			}
  		}
  	}
  
  	*pulTestCounter = 0;
  
  	// return GPOS_OK if all the minidump tests passed.
  	// the minidump test runner, EresRunMinidump(), only returns
  	// GPOS_FAILED in case of a failure, hence remaining error codes need
  	// not be handled here
  	return fSuccess ? GPOS_OK : GPOS_FAILED;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresRunMinidumpsUsingOneMDFile
  //
  //	@doc:
  //		Run all minidumps based on one metadata file
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresRunMinidumpsUsingOneMDFile
  	(
  	CMemoryPool *mp,
  	const CHAR *szMDFilePath,
  	const CHAR *rgszFileNames[],
  	ULONG *pulTestCounter,
  	ULONG ulSessionId,
  	ULONG ulCmdId,
  	BOOL fMatchPlans,
  	INT iCmpSpaceSize,
  	IConstExprEvaluator *pceeval
  	)
  {
  	GPOS_ASSERT(NULL != rgszFileNames);
  	GPOS_ASSERT(NULL != szMDFilePath);
  
  	// reset metadata cache
  	CMDCache::Reset();
  
  	// load metadata file
  	CDXLMinidump *pdxlmd = CMinidumperUtils::PdxlmdLoad(mp, szMDFilePath);
  	GPOS_CHECK_ABORT;
  
  	// set up MD providers
  	CMDProviderMemory *pmdp = GPOS_NEW(mp) CMDProviderMemory(mp, szMDFilePath);
  	GPOS_CHECK_ABORT;
  
  	const CSystemIdArray *pdrgpsysid = pdxlmd->GetSysidPtrArray();
  	CMDProviderArray *pdrgpmdp = GPOS_NEW(mp) CMDProviderArray(mp);
  	pdrgpmdp->Append(pmdp);
  
  	for (ULONG ul = 1; ul < pdrgpsysid->Size(); ul++)
  	{
  		pmdp->AddRef();
  		pdrgpmdp->Append(pmdp);
  	}
  
  	GPOS_RESULT eres = GPOS_OK;
  
  	{	// scope for MD accessor
  		CMDAccessor mda(mp, CMDCache::Pcache(), pdrgpsysid, pdrgpmdp);
  
  		eres = EresRunMinidump(mp, &mda, rgszFileNames[0], pulTestCounter, ulSessionId, ulCmdId, fMatchPlans, iCmpSpaceSize, pceeval);
  
  		*pulTestCounter = 0;
  	}
  
  	// cleanup
  	pdrgpmdp->Release();
  	GPOS_DELETE(pdxlmd);
  	GPOS_CHECK_ABORT;
  
  	return eres;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresSamplePlans
  //
  //	@doc:
  //		Test plan sampling
  //		to extract attribute 'X' value from xml file:
  //		xpath distr.xml //dxl:Value/@X | grep 'X=' | sed 's/\"//g' | sed 's/X=//g' | tr ' ' '\n'
  //
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresSamplePlans
  	(
  	const CHAR *rgszFileNames[],
  	ULONG ulTests,
  	ULONG *pulTestCounter,
  	ULONG ulSessionId,
  	ULONG ulCmdId
  	)
  {
  	GPOS_RESULT eres = GPOS_OK;
  
  	for (ULONG ul = *pulTestCounter; ul < ulTests; ul++)
  	{
  		// each test uses a new memory pool to keep total memory consumption low
  		CAutoMemoryPool amp;
  		CMemoryPool *mp = amp.Pmp();
  
  		// reset metadata cache
  		CMDCache::Reset();
  
  		CAutoTraceFlag atf1(EopttraceEnumeratePlans, true);
  		CAutoTraceFlag atf2(EopttraceSamplePlans, true);
  
  		// load dump file
  		CDXLMinidump *pdxlmd = CMinidumperUtils::PdxlmdLoad(mp, rgszFileNames[ul]);
  		GPOS_CHECK_ABORT;
  
  		// set up MD providers
  		CMDProviderMemory *pmdp = GPOS_NEW(mp) CMDProviderMemory(mp, rgszFileNames[ul]);
  		GPOS_CHECK_ABORT;
  
  		const CSystemIdArray *pdrgpsysid = pdxlmd->GetSysidPtrArray();
  		CMDProviderArray *pdrgpmdp = GPOS_NEW(mp) CMDProviderArray(mp);
  		pdrgpmdp->Append(pmdp);
  
  		for (ULONG ulSys = 1; ulSys < pdrgpsysid->Size(); ulSys++)
  		{
  			pmdp->AddRef();
  			pdrgpmdp->Append(pmdp);
  		}
  
  		COptimizerConfig *optimizer_config = pdxlmd->GetOptimizerConfig();
  			
  		if (NULL == optimizer_config)
  		{
  			optimizer_config = GPOS_NEW(mp) COptimizerConfig
  								(
  								GPOS_NEW(mp) CEnumeratorConfig(mp, 0 /*plan_id*/, 1000 /*ullSamples*/),
  								CStatisticsConfig::PstatsconfDefault(mp),
  								CCTEConfig::PcteconfDefault(mp),
  								ICostModel::PcmDefault(mp),
  								CHint::PhintDefault(mp),
  								CWindowOids::GetWindowOids(mp)
  								);
  		}
  		else
  		{
  			optimizer_config->AddRef();
  		}
  
  		ULONG ulSegments = UlSegments(optimizer_config);
  
  		// allow sampler to throw invalid plan exception
  		optimizer_config->GetEnumeratorCfg()->SetSampleValidPlans(false /*fSampleValidPlans*/);
  
  		{
  			// scope for MD accessor
  			CMDAccessor mda(mp, CMDCache::Pcache(), pdrgpsysid, pdrgpmdp);
  
  			CDXLNode *pdxlnPlan = CMinidumperUtils::PdxlnExecuteMinidump
  							(
  							mp,
  							&mda,
  							pdxlmd,
  							rgszFileNames[ul],
  							ulSegments,
  							ulSessionId,
  							ulCmdId,
  							optimizer_config,
  							NULL // pceeval
  							);
  
  			GPOS_CHECK_ABORT;
  
  			{
  				CAutoTrace at(mp);
  
  				at.Os() << "Generated " <<  optimizer_config->GetEnumeratorCfg()->UlCreatedSamples() <<" samples ... " << std::endl;
  
  				// print ids of sampled plans
  				CWStringDynamic *str = CDXLUtils::SerializeSamplePlans(mp, optimizer_config->GetEnumeratorCfg(), true /*fIdent*/);
  				at.Os() << str->GetBuffer();
  				GPOS_DELETE(str);
  
  				// print fitted cost distribution
  				at.Os() << "Cost Distribution: " << std::endl;
  				const ULONG size = optimizer_config->GetEnumeratorCfg()->UlCostDistrSize();
  				for (ULONG ul = 0; ul < size; ul++)
  				{
  					at.Os() << optimizer_config->GetEnumeratorCfg()->DCostDistrX(ul) << "\t" << optimizer_config->GetEnumeratorCfg()->DCostDistrY(ul) << std::endl;
  				}
  
  				// print serialized cost distribution
  				str = CDXLUtils::SerializeCostDistr(mp, optimizer_config->GetEnumeratorCfg(), true /*fIdent*/);
  
  				at.Os() << str->GetBuffer();
  				GPOS_DELETE(str);
  
  			}
  
  			// cleanup
  			GPOS_DELETE(pdxlmd);
  			pdxlnPlan->Release();
  
  		} // end of MDAccessor scope
  
  
  		// cleanup
  		optimizer_config->Release();
  		pdrgpmdp->Release();
  
  
  		(*pulTestCounter)++;
  	}
  
  	*pulTestCounter = 0;
  	return eres;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresCheckPlans
  //
  //	@doc:
  //		Check all enumerated plans using given PlanChecker function
  //
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresCheckPlans
  	(
  	const CHAR *rgszFileNames[],
  	ULONG ulTests,
  	ULONG *pulTestCounter,
  	ULONG ulSessionId,
  	ULONG ulCmdId,
  	FnPlanChecker *pfpc
  	)
  {
  	GPOS_ASSERT(NULL != pfpc);
  
  	GPOS_RESULT eres = GPOS_OK;
  
  	for (ULONG ul = *pulTestCounter; ul < ulTests; ul++)
  	{
  		// each test uses a new memory pool to keep total memory consumption low
  		CAutoMemoryPool amp;
  		CMemoryPool *mp = amp.Pmp();
  
  		// reset metadata cache
  		CMDCache::Reset();
  
  		CAutoTraceFlag atf1(EopttraceEnumeratePlans, true);
  
  		// load dump file
  		CDXLMinidump *pdxlmd = CMinidumperUtils::PdxlmdLoad(mp, rgszFileNames[ul]);
  		GPOS_CHECK_ABORT;
  
  		// set up MD providers
  		CMDProviderMemory *pmdp = GPOS_NEW(mp) CMDProviderMemory(mp, rgszFileNames[ul]);
  		GPOS_CHECK_ABORT;
  
  		const CSystemIdArray *pdrgpsysid = pdxlmd->GetSysidPtrArray();
  		CMDProviderArray *pdrgpmdp = GPOS_NEW(mp) CMDProviderArray(mp);
  		pdrgpmdp->Append(pmdp);
  
  		for (ULONG ulSys = 1; ulSys < pdrgpsysid->Size(); ulSys++)
  		{
  			pmdp->AddRef();
  			pdrgpmdp->Append(pmdp);
  		}
  
  		COptimizerConfig *optimizer_config = pdxlmd->GetOptimizerConfig();
  
  		if (NULL == optimizer_config)
  		{
  			optimizer_config = GPOS_NEW(mp) COptimizerConfig
  								(
  								GPOS_NEW(mp) CEnumeratorConfig(mp, 0 /*plan_id*/, 1000 /*ullSamples*/),
  								CStatisticsConfig::PstatsconfDefault(mp),
  								CCTEConfig::PcteconfDefault(mp),
  								ICostModel::PcmDefault(mp),
  								CHint::PhintDefault(mp),
  								CWindowOids::GetWindowOids(mp)
  								);
  		}
  		else
  		{
  			optimizer_config->AddRef();
  		}
  
  		ULONG ulSegments = UlSegments(optimizer_config);
  
  		// set plan checker
  		optimizer_config->GetEnumeratorCfg()->SetPlanChecker(pfpc);
  
  		// allow sampler to throw invalid plan exception
  		optimizer_config->GetEnumeratorCfg()->SetSampleValidPlans(false /*fSampleValidPlans*/);
  
  		{
  			// scope for MD accessor
  			CMDAccessor mda(mp, CMDCache::Pcache(), pdrgpsysid, pdrgpmdp);
  
  			CDXLNode *pdxlnPlan = CMinidumperUtils::PdxlnExecuteMinidump
  							(
  							mp,
  							&mda,
  							pdxlmd,
  							rgszFileNames[ul],
  							ulSegments,
  							ulSessionId,
  							ulCmdId,
  							optimizer_config,
  							NULL // pceeval
  							);
  
  			GPOS_CHECK_ABORT;
  
  			// cleanup
  			GPOS_DELETE(pdxlmd);
  			pdxlnPlan->Release();
  
  		} // end of MDAcessor scope
  
  		optimizer_config->Release();
  		pdrgpmdp->Release();
  
  		(*pulTestCounter)++;
  	}
  
  	*pulTestCounter = 0;
  	return eres;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::UlSegments
  //
  //	@doc:
  //		Return the number of segments, default return GPOPT_TEST_SEGMENTS
  //
  //---------------------------------------------------------------------------
  ULONG
  CTestUtils::UlSegments
  	(
  	COptimizerConfig *optimizer_config
  	)
  {
  	GPOS_ASSERT(NULL != optimizer_config);
  	ULONG ulSegments = GPOPT_TEST_SEGMENTS;
  	if (NULL != optimizer_config->GetCostModel())
  	{
  		ULONG ulSegs = optimizer_config->GetCostModel()->UlHosts();
  		if (ulSegments < ulSegs)
  		{
  			ulSegments = ulSegs;
  		}
  	}
  
  	return ulSegments;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresCheckOptimizedPlan
  //
  //	@doc:
  //		Check the optimized plan using given DXLPlanChecker function. Does
  //		not take ownership of the given pdrgpcp. The cost model configured
  //		in the minidumps must be the calibrated one.
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresCheckOptimizedPlan
  	(
  	const CHAR *rgszFileNames[],
  	ULONG ulTests,
  	ULONG *pulTestCounter,
  	ULONG ulSessionId,
  	ULONG ulCmdId,
  	FnDXLPlanChecker *pfdpc,
  	ICostModelParamsArray *pdrgpcp
  	)
  {
  	GPOS_ASSERT(NULL != pfdpc);
  
  	GPOS_RESULT eres = GPOS_OK;
  
  	for (ULONG ul = *pulTestCounter; ul < ulTests; ul++)
  	{
  		// each test uses a new memory pool to keep total memory consumption low
  		CAutoMemoryPool amp;
  		CMemoryPool *mp = amp.Pmp();
  
  		// reset metadata cache
  		CMDCache::Reset();
  
  		CAutoTraceFlag atf1(EopttraceEnableSpacePruning, true /*value*/);
  
  		// load dump file
  		CDXLMinidump *pdxlmd = CMinidumperUtils::PdxlmdLoad(mp, rgszFileNames[ul]);
  		GPOS_CHECK_ABORT;
  
  		// set up MD providers
  		CMDProviderMemory *pmdp = GPOS_NEW(mp) CMDProviderMemory(mp, rgszFileNames[ul]);
  		GPOS_CHECK_ABORT;
  
  		const CSystemIdArray *pdrgpsysid = pdxlmd->GetSysidPtrArray();
  		CMDProviderArray *pdrgpmdp = GPOS_NEW(mp) CMDProviderArray(mp);
  		pdrgpmdp->Append(pmdp);
  
  		for (ULONG ulSys = 1; ulSys < pdrgpsysid->Size(); ulSys++)
  		{
  			pmdp->AddRef();
  			pdrgpmdp->Append(pmdp);
  		}
  
  		COptimizerConfig *optimizer_config = pdxlmd->GetOptimizerConfig();
  		GPOS_ASSERT(NULL != optimizer_config);
  
  		if (NULL != pdrgpcp)
  		{
  			optimizer_config->GetCostModel()->SetParams(pdrgpcp);
  		}
  		optimizer_config->AddRef();
  		ULONG ulSegments = UlSegments(optimizer_config);
  
  		// allow sampler to throw invalid plan exception
  		optimizer_config->GetEnumeratorCfg()->SetSampleValidPlans(false /*fSampleValidPlans*/);
  
  		{
  			// scope for MD accessor
  			CMDAccessor mda(mp, CMDCache::Pcache(), pdrgpsysid, pdrgpmdp);
  
  			CDXLNode *pdxlnPlan = CMinidumperUtils::PdxlnExecuteMinidump
  							(
  							mp,
  							&mda,
  							pdxlmd,
  							rgszFileNames[ul],
  							ulSegments,
  							ulSessionId,
  							ulCmdId,
  							optimizer_config,
  							NULL // pceeval
  							);
  			if (!pfdpc(pdxlnPlan))
  			{
  				eres = GPOS_FAILED;
  				{
  					CAutoTrace at(mp);
  					at.Os() << "Failed check for minidump " << rgszFileNames[ul] << std::endl;
  					CDXLUtils::SerializePlan
  						(
  						mp,
  						at.Os(),
  						pdxlnPlan,
  						0, // plan_id
  						0, // plan_space_size
  						true, // serialize_header_footer
  						true // indentation
  						);
  					at.Os() << std::endl;
  				}
  			}
  
  			GPOS_CHECK_ABORT;
  
  			// cleanup
  			GPOS_DELETE(pdxlmd);
  			pdxlnPlan->Release();
  
  		} // end of MDAcessor scope
  
  		optimizer_config->Release();
  		pdrgpmdp->Release();
  
  		(*pulTestCounter)++;
  	}
  
  	*pulTestCounter = 0;
  	return eres;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::CreateGenericDatum
  //
  //	@doc:
  //		Create a datum with a given type, encoded value and int value.
  //
  //---------------------------------------------------------------------------
  IDatum *
  CTestUtils::CreateGenericDatum
  	(
  	CMemoryPool *mp,
  	CMDAccessor *md_accessor,
  	IMDId *mdid_type,
  	CWStringDynamic *pstrEncodedValue,
  	LINT value
  	)
  {
  	GPOS_ASSERT(NULL != md_accessor);
  
  	GPOS_ASSERT(!mdid_type->Equals(&CMDIdGPDB::m_mdid_numeric));
  	const IMDType *pmdtype = md_accessor->RetrieveType(mdid_type);
  	ULONG ulbaSize = 0;
  	BYTE *data = CDXLUtils::DecodeByteArrayFromString(mp, pstrEncodedValue, &ulbaSize);
  
  	CDXLDatumGeneric *dxl_datum = NULL;
  	if (CMDTypeGenericGPDB::IsTimeRelatedType(mdid_type))
  	{
  		dxl_datum = GPOS_NEW(mp) CDXLDatumStatsDoubleMappable(mp, mdid_type, default_type_modifier, false /*is_const_null*/, data, ulbaSize, CDouble(value));
  	}
  	else if (pmdtype->IsTextRelated())
  	{
  		dxl_datum = GPOS_NEW(mp) CDXLDatumStatsLintMappable(mp, mdid_type, default_type_modifier, false /*is_const_null*/, data, ulbaSize, value);
  	}
  	else
  	{
  		dxl_datum = GPOS_NEW(mp) CDXLDatumGeneric(mp, mdid_type, default_type_modifier, false /*is_const_null*/, data, ulbaSize);
  	}
  
  	IDatum *datum = pmdtype->GetDatumForDXLDatum(mp, dxl_datum);
  	dxl_datum->Release();
  
  	return datum;
  }
  
  //---------------------------------------------------------------------------
  //      @function:
  //              CConstraintTest::PciGenericInterval
  //
  //      @doc:
  //              Create an interval for generic data types.
  //              Does not take ownership of any argument.
  //              Caller takes ownership of returned pointer.
  //
  //---------------------------------------------------------------------------
  CConstraintInterval *
  CTestUtils::PciGenericInterval
          (
          CMemoryPool *mp,
          CMDAccessor *md_accessor,
          const CMDIdGPDB &mdidType,
          CColRef *colref,
          CWStringDynamic *pstrLower,
          LINT lLower,
          CRange::ERangeInclusion eriLeft,
          CWStringDynamic *pstrUpper,
          LINT lUpper,
          CRange::ERangeInclusion eriRight
          )
  {
  	GPOS_ASSERT(NULL != md_accessor);
  
  	IDatum *pdatumLower =
  			CTestUtils::CreateGenericDatum(mp, md_accessor, GPOS_NEW(mp) CMDIdGPDB(mdidType), pstrLower, lLower);
  	IDatum *pdatumUpper =
  			CTestUtils::CreateGenericDatum(mp, md_accessor, GPOS_NEW(mp) CMDIdGPDB(mdidType), pstrUpper, lUpper);
  
  	CRangeArray *pdrgprng = GPOS_NEW(mp) CRangeArray(mp);
  	CMDIdGPDB *mdid = GPOS_NEW(mp) CMDIdGPDB(CMDIdGPDB::m_mdid_date);
  	CRange *prange = GPOS_NEW(mp) CRange
  				(
  				mdid,
  				COptCtxt::PoctxtFromTLS()->Pcomp(),
  				pdatumLower,
  				eriLeft,
  				pdatumUpper,
  				eriRight
  				);
  	pdrgprng->Append(prange);
  
  	return GPOS_NEW(mp) CConstraintInterval(mp, colref, pdrgprng, false /*is_null*/);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprScalarCmpIdentToConstant
  //
  //	@doc:
  //		Helper for generating a scalar compare identifier to a constant
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprScalarCmpIdentToConstant
  	(
  	CMemoryPool *mp,
  	CExpression *pexpr
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  	GPOS_ASSERT(NULL != pexpr);
  
  	CColRefSet *pcrs = pexpr->DeriveOutputColumns();
  	CColRef *pcrAny =  pcrs->PcrAny();
  	CExpression *pexprConst =  CUtils::PexprScalarConstInt4(mp, 10 /* val */);
  
  	return CUtils::PexprScalarEqCmp(mp, pcrAny, pexprConst);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprExistsSubquery
  //
  //	@doc:
  //		Helper for generating an exists subquery
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprExistsSubquery
  	(
  	CMemoryPool *mp,
  	CExpression *pexprOuter
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  	GPOS_ASSERT(NULL != pexprOuter);
  
  	CExpression *pexprInner = CTestUtils::PexprLogicalGet(mp);
  
  	return CSubqueryTestUtils::PexprSubqueryExistential
  			(
  			mp,
  			COperator::EopScalarSubqueryExists,
  			pexprOuter,
  			pexprInner,
  			false /* fCorrelated */
  			);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexpSubqueryAll
  //
  //	@doc:
  //		Helper for generating an ALL subquery
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexpSubqueryAll
  	(
  	CMemoryPool *mp,
  	CExpression *pexprOuter
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  	GPOS_ASSERT(NULL != pexprOuter);
  
  	CColRefSet *outer_refs = pexprOuter->DeriveOutputColumns();
  	const CColRef *pcrOuter = outer_refs->PcrAny();
  
  
  	CExpression *pexprInner = CTestUtils::PexprLogicalGet(mp);
  	CColRefSet *pcrsInner = pexprInner->DeriveOutputColumns();
  	const CColRef *pcrInner = pcrsInner->PcrAny();
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CScalarSubqueryAll
  								(
  								mp,
  								GPOS_NEW(mp) CMDIdGPDB(GPDB_INT4_EQ_OP),
  								GPOS_NEW(mp) CWStringConst(GPOS_WSZ_LIT("=")),
  								pcrInner
  								),
  					pexprInner,
  					CUtils::PexprScalarIdent(mp, pcrOuter)
  					);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexpSubqueryAny
  //
  //	@doc:
  //		Helper for generating an ANY subquery
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexpSubqueryAny
  	(
  	CMemoryPool *mp,
  	CExpression *pexprOuter
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  	GPOS_ASSERT(NULL != pexprOuter);
  
  	CColRefSet *outer_refs = pexprOuter->DeriveOutputColumns();
  	const CColRef *pcrOuter = outer_refs->PcrAny();
  
  
  	CExpression *pexprInner = CTestUtils::PexprLogicalGet(mp);
  	CColRefSet *pcrsInner = pexprInner->DeriveOutputColumns();
  	const CColRef *pcrInner = pcrsInner->PcrAny();
  
  	return GPOS_NEW(mp) CExpression
  					(
  					mp,
  					GPOS_NEW(mp) CScalarSubqueryAny
  								(
  								mp,
  								GPOS_NEW(mp) CMDIdGPDB(GPDB_INT4_EQ_OP),
  								GPOS_NEW(mp) CWStringConst(GPOS_WSZ_LIT("=")),
  								pcrInner
  								),
  								pexprInner,
  					CUtils::PexprScalarIdent(mp, pcrOuter)
  					);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprNotExistsSubquery
  //
  //	@doc:
  //		Helper for generating a not exists subquery
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprNotExistsSubquery
  	(
  	CMemoryPool *mp,
  	CExpression *pexprOuter
  	)
  {
  	GPOS_ASSERT(NULL != mp);
  	GPOS_ASSERT(NULL != pexprOuter);
  
  	CExpression *pexprInner = CTestUtils::PexprLogicalGet(mp);
  
  	return CSubqueryTestUtils::PexprSubqueryExistential
  			(
  			mp,
  			COperator::EopScalarSubqueryNotExists,
  			pexprOuter,
  			pexprInner,
  			false /* fCorrelated */
  			);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::FHasOp
  //
  //	@doc:
  //		 Recursively traverses the subtree rooted at the given expression, and
  //  	 return the first subexpression it encounters that has the given id
  //
  //---------------------------------------------------------------------------
  const CExpression *
  CTestUtils::PexprFirst
  	(
  	const CExpression *pexpr,
  	const COperator::EOperatorId op_id
  	)
  {
  	GPOS_CHECK_STACK_SIZE;
  	GPOS_ASSERT(NULL != pexpr);
  
  	if (pexpr->Pop()->Eopid() == op_id)
  	{
  		return pexpr;
  	}
  
  	// recursively check children
  	const ULONG arity = pexpr->Arity();
  	for (ULONG ul = 0; ul < arity; ul++)
  	{
  		const CExpression *pexprFirst = PexprFirst((*pexpr)[ul], op_id);
  		if (NULL != pexprFirst)
  		{
  			return pexprFirst;
  		}
  
  	}
  
  	return NULL;
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprAnd
  //
  //	@doc:
  //		Generate a scalar AND expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprAnd
  	(
  	CMemoryPool *mp,
  	CExpression *pexprActual,
  	CExpression *pexprExpected
  	)
  {
  	GPOS_ASSERT(NULL != pexprActual);
  	GPOS_ASSERT(NULL != pexprExpected);
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexpr->Append(pexprActual);
  	pdrgpexpr->Append(pexprExpected);
  
  	return CUtils::PexprScalarBoolOp(mp, CScalarBoolOp::EboolopAnd, pdrgpexpr);
  }
  
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::PexprOr
  //
  //	@doc:
  //		Generate a scalar OR expression
  //
  //---------------------------------------------------------------------------
  CExpression *
  CTestUtils::PexprOr
  	(
  	CMemoryPool *mp,
  	CExpression *pexprActual,
  	CExpression *pexprExpected
  	)
  {
  	GPOS_ASSERT(NULL != pexprActual);
  	GPOS_ASSERT(NULL != pexprExpected);
  
  	CExpressionArray *pdrgpexpr = GPOS_NEW(mp) CExpressionArray(mp);
  	pdrgpexpr->Append(pexprActual);
  	pdrgpexpr->Append(pexprExpected);
  
  	return CUtils::PexprScalarBoolOp(mp, CScalarBoolOp::EboolopOr, pdrgpexpr);
  }
  
  //---------------------------------------------------------------------------
  //	@function:
  //		CTestUtils::EresUnittest_RunTests
  //
  //	@doc:
  //		Run  Minidump-based tests in the given array of files
  //
  //---------------------------------------------------------------------------
  GPOS_RESULT
  CTestUtils::EresUnittest_RunTests
  	(
  	const CHAR **rgszFileNames,
  	ULONG *pulTestCounter,
  	ULONG ulTests
  	)
  {
  	BOOL fMatchPlans = false;
  	BOOL fTestSpacePruning = false;
  	fMatchPlans = true;
  	fTestSpacePruning = true;
  	// enable (Redistribute, Broadcast) hash join plans
  	CAutoTraceFlag atf1(EopttraceEnableRedistributeBroadcastHashJoin, true /*value*/);
  
  	// enable plan enumeration only if we match plans
  	CAutoTraceFlag atf2(EopttraceEnumeratePlans, fMatchPlans);
  
  	// enable stats derivation for DPE
  	CAutoTraceFlag atf3(EopttraceDeriveStatsForDPE, true /*value*/);
  
  	// prefer MDQA
  	CAutoTraceFlag atf5(EopttraceForceExpandedMDQAs, true);
  
  	GPOS_RESULT eres = EresUnittest_RunTestsWithoutAdditionalTraceFlags
  						(
  						rgszFileNames,
  						pulTestCounter,
  						ulTests,
  						fMatchPlans,
  						fTestSpacePruning
  						);
  	return eres;
  }
  
  
  GPOS_RESULT
  CTestUtils::EresUnittest_RunTestsWithoutAdditionalTraceFlags
  	(
  	const CHAR **rgszFileNames,
  	ULONG *pulTestCounter,
  	ULONG ulTests,
  	BOOL fMatchPlans,
  	BOOL fTestSpacePruning
  	)
  {
  	CAutoMemoryPool amp;
  	CMemoryPool *mp = amp.Pmp();
  
  
  	GPOS_RESULT eres =
  			CTestUtils::EresRunMinidumps
  						(
  						mp,
  						rgszFileNames,
  						ulTests,
  						pulTestCounter,
  						1, // ulSessionId
  						1,  // ulCmdId
  						fMatchPlans,
  						fTestSpacePruning
  						);
  
  	return eres;
  }
  
  
  // Create Equivalence Class based on the breakpoints
  CColRefSetArray *
  CTestUtils::createEquivalenceClasses(CMemoryPool *mp, CColRefSet *pcrs, INT setBoundary[]) {
  	INT i = 0;
  	ULONG bpIndex = 0;
  
  	CColRefSetArray *pdrgcrs = GPOS_NEW(mp) CColRefSetArray(mp);
  
  	CColRefSetIter crsi(*pcrs);
  	CColRefSet *pcrsLoop = GPOS_NEW(mp) CColRefSet(mp);
  
  	while (crsi.Advance())
  	{
  		if (i == setBoundary[bpIndex]) {
  			pdrgcrs->Append(pcrsLoop);
  			CColRefSet *pcrsLoop1 = GPOS_NEW(mp) CColRefSet(mp);
  			pcrsLoop = pcrsLoop1;
  			bpIndex++;
  		}
  
  		CColRef *colref = crsi.Pcr();
  		pcrsLoop->Include(colref);
  		i++;
  	}
  	pdrgcrs->Append(pcrsLoop);
  	return pdrgcrs;
  }
  // EOF
  