* test_benchmark_in1000.prg - compara 3 variante pentru citirea din ISTORIC_CODURI_FISCALE
* pe loturi mari de coduri fiscale, ca inlocuitor pentru IN (<lista>) inline care pica pe
* ORA-01795 peste 1000 elemente (CitesteIstoricPentruCoduri, validare.prg:2698).
*   A - transe de max 1000 legate cu OR in aceeasi interogare
*   B - o interogare per transa de 1000, acumulate in acelasi cursor rezultat
*   C - tabela temporara globala (GTT) populata cu INSERT ALL, IN (subquery) pe GTT
* Fiecare varianta ruleaza de 3 ori per lot (1000/2000/5000 coduri), se retine mediana.
* Nu modifica validare.prg. Obiectele Oracle create (GTT de test) se sterg la final.
*
* Rulare: vfp9.exe -A -T "...\test_benchmark_in1000.prg" [watermark]

LPARAMETERS tcWatermark

SET SAFETY OFF
SET TALK OFF
SET DELETED ON
SET EXACT ON
SET CENTURY ON
SET DATE DMY
SET NULLDISPLAY TO ''
CLOSE DATABASES

PUBLIC gcTestLog, pdDataCache, pnZileCache
gcTestLog = "D:\ROA\ROACONT\COMUN\utile\Teste\cache_anaf\test_benchmark_in1000_log.txt"
STRTOFILE("START " + TTOC(DATETIME()) + CHR(13) + CHR(10), gcTestLog)

ON ERROR DO ErrBench WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
ON SHUTDOWN QUIT

DO D:\ROA\ROACONT\COMUN\utile\Teste\test_init_env_auto.prg WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
IF gnHandle <= 0
	DO WrLogB WITH 'EROARE: conectare Oracle esuata - GATA'
	QUIT
ENDIF
DO WrLogB WITH 'mediu OK, schema=' + gcS

PUBLIC gnErorriSQL, gcUltimaEroareSQL
gnErorriSQL = 0
gcUltimaEroareSQL = ''
LOCAL loExecNou
loExecNou = CREATEOBJECT('oexec_fara_dialog')
loExecNou.nHandle = gnHandle
goExecutor = loExecNou
DO WrLogB WITH 'executor inlocuit (fara dialog modal la eroare SQL)'

pdDataCache = DATE()
pnZileCache = 36500 && vreau sa vad toate randurile din istoric, indiferent de prospetime

*==========================================================
* 0. curatenie ramasa din rularea anterioara + watermark nou
*==========================================================
LOCAL lnWm
lnWm = IIF(TYPE('tcWatermark') = 'C' AND !EMPTY(tcWatermark), INT(VAL(tcWatermark)), 0)
IF lnWm > 0
	=goExecutor.oExecuta('DELETE FROM ISTORIC_CODURI_FISCALE WHERE ID > ' + TRANSFORM(lnWm))
	=goExecutor.oExecuta('COMMIT')
	DO WrLogB WITH 'curatenie facuta (ID > ' + TRANSFORM(lnWm) + ')'
ENDIF
PUBLIC gnIdMaxIstoric
gnIdMaxIstoric = 0
IF goExecutor.oExecuta('SELECT NVL(MAX(ID),0) AS IDMAX FROM ISTORIC_CODURI_FISCALE', 'crsWm')
	gnIdMaxIstoric = crsWm.IDMAX
	USE IN crsWm
ENDIF
DO WrLogB WITH 'watermark nou = ' + TRANSFORM(gnIdMaxIstoric)

*==========================================================
* 1. incarc cele 200 coduri reale + pregatesc obiectul GTT pentru varianta C
*==========================================================
LOCAL laReale[200], lnNReale, lcLinie, lnFH
lnNReale = 0
lnFH = FOPEN('D:\ROA\ROACONT\COMUN\utile\Teste\cache_anaf\codes_200_reale.txt')
IF lnFH >= 0
	DO WHILE !FEOF(lnFH)
		lcLinie = ALLTRIM(FGETS(lnFH))
		IF !EMPTY(lcLinie)
			lnNReale = lnNReale + 1
			laReale[lnNReale] = lcLinie
		ENDIF
	ENDDO
	=FCLOSE(lnFH)
ENDIF
DO WrLogB WITH 'coduri reale incarcate: ' + TRANSFORM(lnNReale)

DO WrLogB WITH ''
DO WrLogB WITH '=== pregatire GTT pentru varianta C ==='
LOCAL llGttOk
llGttOk = .T.
IF !goExecutor.oExecuta("SELECT 1 AS N FROM ALL_TABLES WHERE TABLE_NAME = 'BENCH_LOT_CF' AND OWNER = SYS_CONTEXT('USERENV','CURRENT_SCHEMA')", 'crsChk')
	llGttOk = .F.
ENDIF
LOCAL llExistaDeja
llExistaDeja = (TYPE('crsChk') = 'U' OR !USED('crsChk'))
IF USED('crsChk')
	llExistaDeja = (RECCOUNT('crsChk') > 0)
	USE IN crsChk
ENDIF
IF !llExistaDeja
	IF goExecutor.oExecuta('CREATE GLOBAL TEMPORARY TABLE bench_lot_cf (cod_fiscal_n NUMBER(13)) ON COMMIT PRESERVE ROWS')
		DO WrLogB WITH 'GTT bench_lot_cf creata'
	ELSE
		DO WrLogB WITH 'EROARE la crearea GTT: ' + gcUltimaEroareSQL
		llGttOk = .F.
	ENDIF
ELSE
	DO WrLogB WITH 'GTT bench_lot_cf exista deja (rulare anterioara neterminata) - refolosesc'
ENDIF

*==========================================================
* 2. rulez cele 3 variante pe loturile 1000 / 2000 / 5000
*==========================================================
LOCAL laLoturi[3], lnLot, lnCntLot
laLoturi[1] = 1000
laLoturi[2] = 2000
laLoturi[3] = 5000

FOR lnLot = 1 TO 3
	lnCntLot = laLoturi[lnLot]
	DO WrLogB WITH ''
	DO WrLogB WITH '######## LOT ' + TRANSFORM(lnCntLot) + ' coduri ########'

	LOCAL lcLista, lnNRandA, lnNRandB, lnNRandC
	lcLista = BenchListaCoduri(lnCntLot, @laReale, lnNReale)

	* --- Varianta A: transe de 1000 legate cu OR ---
	LOCAL laTA[3], lnI, lnT0, lcSqlA, lnRandA
	FOR lnI = 1 TO 3
		lnT0 = SECONDS()
		lnRandA = BenchRunVarA(lcLista)
		laTA[lnI] = SECONDS() - lnT0
	ENDFOR
	lnNRandA = lnRandA
	DO WrLogB WITH 'A (OR-transe)  timpi=' + BenchFmtArr(@laTA) + '  mediana=' + TRANSFORM(ROUND(BenchMediana(@laTA), 3)) + ' s  randuri=' + TRANSFORM(lnNRandA)

	* --- Varianta B: o interogare per transa de 1000, acumulate ---
	LOCAL laTB[3], lnRandB
	FOR lnI = 1 TO 3
		lnT0 = SECONDS()
		lnRandB = BenchRunVarB(lcLista)
		laTB[lnI] = SECONDS() - lnT0
	ENDFOR
	lnNRandB = lnRandB
	DO WrLogB WITH 'B (bucla transe) timpi=' + BenchFmtArr(@laTB) + '  mediana=' + TRANSFORM(ROUND(BenchMediana(@laTB), 3)) + ' s  randuri=' + TRANSFORM(lnNRandB)

	* --- Varianta C: GTT + IN (subquery), populare + interogare ---
	* NOTA: dezactivata din masuratoarea cronometrata - vezi test_debug_gtt3/4_log.txt:
	* pe conexiunea ODBC a aplicatiei (DSN CENTRAL, driver SQORA32.dll din instantclient_11_2_0_2),
	* CREATE GLOBAL TEMPORARY TABLE reuseste cu succes (SID neschimbat), dar orice interogare
	* ulterioara pe acelasi GTT, in ACEEASI sesiune, intoarce ORA-00942 "table or view does not
	* exist" - un tabel normal (CREATE TABLE, fara GLOBAL TEMPORARY), pe aceeasi conexiune, in
	* aceeasi succesiune de apeluri, functioneaza corect. Acelasi flux (create/insert/select),
	* rulat direct in sqlplus pe acelasi schema/instanta, functioneaza corect. Deci problema e
	* specifica GTT + acest driver ODBC, nu Oracle sau schema. Cifrele de populare (INSERT ALL)
	* raman valide ca measura a costului insertiei, dar timpul de interogare de mai jos nu e
	* comparabil (subquery pe GTT gol -> 0 randuri, cost artificial de mic).
	IF llGttOk
		LOCAL laTC[3], laTCins[3], lnRandC
		FOR lnI = 1 TO 3
			lnT0 = SECONDS()
			lnRandC = BenchRunVarC(lcLista, @laTCins, lnI)
			laTC[lnI] = SECONDS() - lnT0
		ENDFOR
		lnNRandC = lnRandC
		DO WrLogB WITH 'C (GTT+IN sub, NECONCLUDENT - vezi nota) timpi=' + BenchFmtArr(@laTC) + '  mediana=' + TRANSFORM(ROUND(BenchMediana(@laTC), 3)) + ' s  randuri=' + TRANSFORM(lnNRandC) + '  (din care populare GTT, mediana=' + TRANSFORM(ROUND(BenchMediana(@laTCins), 3)) + ' s)'
	ELSE
		DO WrLogB WITH 'C (GTT+IN sub) SARITA - GTT indisponibila'
	ENDIF

	* sanitate: A si B trebuie sa intoarca acelasi numar de randuri (C e neconcludenta, vezi nota)
	IF lnNRandA = lnNRandB
		DO WrLogB WITH '  OK: A si B au intors acelasi numar de randuri'
	ELSE
		DO WrLogB WITH '  ATENTIE: numar de randuri DIFERIT intre A si B!'
	ENDIF
ENDFOR

*==========================================================
* 3. varianta D: fezabilitate colectie PL/SQL peste ODBC din VFP
* NOTA: testul complet (test_debug_vard.prg) s-a incheiat cu terminarea abrupta a procesului
* vfp9.exe la incercarea de a lega un array VFP direct ca parametru colectie (?laCoduri) intr-un
* SQLExec - nu o eroare Oracle capturabila, ci un crash al gazdei. Vezi raportul pentru detalii;
* nu se re-ruleaza aici ca sa nu intrerupa restul masuratorilor.
*==========================================================

*==========================================================
* 4. curatenie
*==========================================================
DO WrLogB WITH ''
IF llGttOk
	=goExecutor.oExecuta('DROP TABLE bench_lot_cf')
	DO WrLogB WITH 'GTT bench_lot_cf stearsa'
ENDIF
IF TYPE('gnIdMaxIstoric') = 'N' AND gnIdMaxIstoric > 0
	=goExecutor.oExecuta('DELETE FROM ISTORIC_CODURI_FISCALE WHERE ID > ' + TRANSFORM(gnIdMaxIstoric))
	=goExecutor.oExecuta('COMMIT')
	DO WrLogB WITH 'curatenie finala: sterse randurile cu ID > ' + TRANSFORM(gnIdMaxIstoric)
ENDIF

DO WrLogB WITH 'GATA ' + TTOC(DATETIME())
QUIT


*==========================================================
* helpere
*==========================================================
PROCEDURE WrLogB
	LPARAMETERS tcText
	STRTOFILE(TRANSFORM(tcText) + CHR(13) + CHR(10), gcTestLog, .T.)
ENDPROC

PROCEDURE ErrBench
	LPARAMETERS tnError, tcMessage, tcProgram, tnLine
	STRTOFILE('EROARE ' + TRANSFORM(tnError) + ' [' + TRANSFORM(tcMessage) + '] in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLine) + CHR(13) + CHR(10), gcTestLog, .T.)
	RETURN
ENDPROC

FUNCTION BenchMediana
	LPARAMETERS taValori
	LOCAL laSort[3], lnI, lnJ, lnTmp
	FOR lnI = 1 TO 3
		laSort[lnI] = taValori[lnI]
	ENDFOR
	FOR lnI = 1 TO 2
		FOR lnJ = lnI + 1 TO 3
			IF laSort[lnJ] < laSort[lnI]
				lnTmp = laSort[lnI]
				laSort[lnI] = laSort[lnJ]
				laSort[lnJ] = lnTmp
			ENDIF
		ENDFOR
	ENDFOR
	RETURN laSort[2]
ENDFUNC

FUNCTION BenchFmtArr
	LPARAMETERS taValori
	LOCAL lcRez, lnI
	lcRez = '['
	FOR lnI = 1 TO 3
		lcRez = lcRez + TRANSFORM(ROUND(taValori[lnI], 3)) + IIF(lnI < 3, ', ', '')
	ENDFOR
	RETURN lcRez + ']'
ENDFUNC

*** construieste lista CSV de coduri normalizate pentru un lot de tnSize: primele MIN(tnSize,200)
*** din cele reale, restul sintetice (8000000001...) - distincte, deterministe intre variante
FUNCTION BenchListaCoduri
	LPARAMETERS tnSize, taReale, tnNReale
	LOCAL lcLista, lnI, lnNReal
	lcLista = ''
	lnNReal = MIN(tnSize, tnNReale)
	FOR lnI = 1 TO lnNReal
		lcLista = lcLista + IIF(EMPTY(lcLista), '', ',') + taReale[lnI]
	ENDFOR
	FOR lnI = lnNReal + 1 TO tnSize
		lcLista = lcLista + IIF(EMPTY(lcLista), '', ',') + TRANSFORM(8000000000 + lnI)
	ENDFOR
	RETURN lcLista
ENDFUNC

*** taie lcLista (CSV) in transe de maxim tnMax elemente, intoarce array de siruri CSV
FUNCTION BenchTransaza
	LPARAMETERS tcLista, tnMax, taTranse
	LOCAL lnTotal, lnTranse, lnI, lnDe, lnLa, lnJ
	lnTotal = GETWORDCOUNT(tcLista, ',')
	lnTranse = CEILING(lnTotal / tnMax)
	DIMENSION taTranse[MAX(lnTranse, 1)]
	FOR lnI = 1 TO lnTranse
		lnDe = (lnI - 1) * tnMax + 1
		lnLa = MIN(lnI * tnMax, lnTotal)
		LOCAL lcBucata
		lcBucata = ''
		FOR lnJ = lnDe TO lnLa
			lcBucata = lcBucata + IIF(EMPTY(lcBucata), '', ',') + GETWORDNUM(tcLista, lnJ, ',')
		ENDFOR
		taTranse[lnI] = lcBucata
	ENDFOR
	RETURN lnTranse
ENDFUNC

*** interogarea de baza (identica cu CitesteIstoricPentruCoduri, validare.prg:2731-2749),
*** cu <<lcCondCod>> ca placeholder pentru conditia pe cod_fiscal_n
FUNCTION BenchSqlBaza
	LPARAMETERS tcCondCod
	LOCAL lcSql
	TEXT TO lcSql TEXTMERGE NOSHOW
select cod_fiscal_n, firma, adresa, judet, regcom, dataorav_anaf,
       data_inceput_scptva, data_sfarsit_scptva, data_anul_imp_scptva,
       data_inceput_tvainc, data_sfarsit_tvainc, data_inactivare, data_reactivare,
       data_inceput_splittva, data_anulare_splittva,
       case when ?pdDataCache between data_inceput_scptva and nvl(data_sfarsit_scptva, to_date('29991231','yyyymmdd')) then 1 else 0 end as v_scptva,
       case when ?pdDataCache between data_inceput_tvainc and nvl(data_sfarsit_tvainc - 1, to_date('29991231','yyyymmdd')) then 1 else 0 end as v_tvainc,
       case when ?pdDataCache between data_inceput_splittva and nvl(data_anulare_splittva, to_date('29991231','yyyymmdd')) then 1 else 0 end as v_splittva,
       case when data_inactivare is not null and ?pdDataCache >= data_inactivare and (data_reactivare is null or ?pdDataCache < data_reactivare) then 1 else 0 end as v_inactiv
  from (select i.*, row_number() over (partition by cod_fiscal_n order by id desc) as rn
          from istoric_coduri_fiscale i
         where <<tcCondCod>>
           and dataorav_anaf is not null
           and sysdate - dataorav_anaf <= ?pnZileCache)
 where rn = 1
   and (data_inceput_scptva is null or ?pdDataCache >= data_inceput_scptva)
   and (data_inceput_tvainc is null or ?pdDataCache >= data_inceput_tvainc)
   and (data_inactivare is null or ?pdDataCache >= data_inactivare)
	ENDTEXT
	RETURN lcSql
ENDFUNC

*** A: transe de 1000 legate cu OR in ACEEASI interogare
FUNCTION BenchRunVarA
	LPARAMETERS tcLista
	LOCAL laTranse[1], lnTranse, lnI, lcCond, lcSql, lnRand
	lnTranse = BenchTransaza(tcLista, 1000, @laTranse)
	lcCond = ''
	FOR lnI = 1 TO lnTranse
		lcCond = lcCond + IIF(EMPTY(lcCond), '', ' or ') + 'cod_fiscal_n in (' + laTranse[lnI] + ')'
	ENDFOR
	IF lnTranse > 1
		lcCond = '(' + lcCond + ')'
	ENDIF
	lcSql = BenchSqlBaza(lcCond)
	IF USED('crsBenchA')
		USE IN crsBenchA
	ENDIF
	lnRand = 0
	IF goExecutor.oExecuta(lcSql, 'crsBenchA')
		lnRand = RECCOUNT('crsBenchA')
		USE IN crsBenchA
	ENDIF
	RETURN lnRand
ENDFUNC

*** B: o interogare per transa de 1000, acumulate in acelasi cursor rezultat
FUNCTION BenchRunVarB
	LPARAMETERS tcLista
	LOCAL laTranse[1], lnTranse, lnI, lcCond, lcSql, lnRand
	lnTranse = BenchTransaza(tcLista, 1000, @laTranse)
	IF USED('crsBenchBAcc')
		USE IN crsBenchBAcc
	ENDIF
	lnRand = 0
	FOR lnI = 1 TO lnTranse
		lcCond = 'cod_fiscal_n in (' + laTranse[lnI] + ')'
		lcSql = BenchSqlBaza(lcCond)
		IF USED('crsBenchBTmp')
			USE IN crsBenchBTmp
		ENDIF
		IF goExecutor.oExecuta(lcSql, 'crsBenchBTmp')
			IF RECCOUNT('crsBenchBTmp') > 0
				IF !USED('crsBenchBAcc')
					SELECT * FROM crsBenchBTmp WHERE .F. INTO CURSOR crsBenchBAcc READWRITE
				ENDIF
				SELECT crsBenchBTmp
				SCAN
					SCATTER MEMO NAME loRandB
					INSERT INTO crsBenchBAcc FROM NAME loRandB
					SELECT crsBenchBTmp
				ENDSCAN
			ENDIF
			USE IN crsBenchBTmp
		ENDIF
	ENDFOR
	IF USED('crsBenchBAcc')
		lnRand = RECCOUNT('crsBenchBAcc')
		USE IN crsBenchBAcc
	ENDIF
	RETURN lnRand
ENDFUNC

*** C: GTT populata cu INSERT ALL (transe de 300), interogare cu IN (select ... from GTT)
FUNCTION BenchRunVarC
	LPARAMETERS tcLista, taTimpInsert, tnRep
	LOCAL lnT0, lnRand
	=goExecutor.oExecuta('DELETE FROM bench_lot_cf')
	lnT0 = SECONDS()
	DO BenchPopuleazaGTT WITH tcLista
	taTimpInsert[tnRep] = SECONDS() - lnT0
	LOCAL lcCond, lcSql
	lcCond = 'cod_fiscal_n in (select cod_fiscal_n from bench_lot_cf)'
	lcSql = BenchSqlBaza(lcCond)
	IF USED('crsBenchC')
		USE IN crsBenchC
	ENDIF
	lnRand = 0
	IF goExecutor.oExecuta(lcSql, 'crsBenchC')
		lnRand = RECCOUNT('crsBenchC')
		USE IN crsBenchC
	ENDIF
	RETURN lnRand
ENDFUNC

*** populeaza bench_lot_cf cu codurile din tcLista, cate 300 pe instructiune (insert all ... select 1 from dual)
PROCEDURE BenchPopuleazaGTT
	LPARAMETERS tcLista
	LOCAL laTranse[1], lnTranse, lnI, lnJ, lnDe, lnLa, lnTotal, lcIns
	lnTotal = GETWORDCOUNT(tcLista, ',')
	lnTranse = CEILING(lnTotal / 300)
	FOR lnI = 1 TO lnTranse
		lnDe = (lnI - 1) * 300 + 1
		lnLa = MIN(lnI * 300, lnTotal)
		lcIns = 'insert all'
		FOR lnJ = lnDe TO lnLa
			lcIns = lcIns + ' into bench_lot_cf (cod_fiscal_n) values (' + GETWORDNUM(tcLista, lnJ, ',') + ')'
		ENDFOR
		lcIns = lcIns + ' select 1 from dual'
		=goExecutor.oExecuta(lcIns)
	ENDFOR
ENDPROC

DEFINE CLASS oexec_fara_dialog AS oexecutor

	FUNCTION oExecuta
		LPARAMETERS tcSql, tcCursor, tlProgress, tcTitluProgress, tnHandle, tlShowError, tlQuitOnError, tlReconnect
		LOCAL lcSql, lcCursor, lnHandle, lnRez
		lcSql = IIF(EMPTY(tcSql), This.cSql, tcSql)
		lcCursor = IIF(EMPTY(tcCursor), This.cCursor, tcCursor)
		lnHandle = IIF(EMPTY(tnHandle), This.nHandle, tnHandle)
		lnRez = This.oExecute(lcSql, lcCursor, tlProgress, tcTitluProgress, lnHandle)
		IF lnRez < 1
			gnErorriSQL = gnErorriSQL + 1
			gcUltimaEroareSQL = TRANSFORM(This.cEroare)
		ENDIF
		RETURN (lnRez >= 1)
	ENDFUNC

ENDDEFINE
