--- name: roa-vfp-null-guards description: Scrie si verifica garzile pe valorile NULL venite din Oracle in cod VFP (campuri de cursor, comparatii, Empty(), NVL, ISNULL). Foloseste-l OBLIGATORIU cand scrii sau revezi cod VFP care compara, valideaza sau concateneaza valori din Oracle; cand apare "valoare NULL din Oracle", "Empty() da rezultat gresit", "coloana poate fi null", "SQL statement parameter is required for non-prepared SQLEXEC() calls" sau rezultate gresite/erori inselatoare pe coloane care pot fi NULL. Se declanseaza si cand NU se rosteste cuvantul "NULL": daca doar compari, validezi sau construiesti text/SQL din campuri de cursor Oracle, regula se aplica. O singura regula, dar platita scump - fara garda rezultatul e gresit sau eroarea apare departe de cauza. --- # Garzi pe valorile NULL venite din Oracle Un camp de cursor intors de Oracle poate fi `.NULL.`. In VFP `EMPTY(.NULL.)` intoarce `.F.`, comparatia cu NULL nu e nici adevarata nici eroare, iar concatenarea cu `+` face tot sirul `.NULL.`. O garda gresita nu da eroare: da rezultat gresit, tacit. ## Cand se foloseste - Scrii sau revezi cod VFP care citeste, compara sau valideaza campuri de cursor Oracle. - Folosesti `Empty()`, `Nvl()`, `IsNull()` sau `Type()` pe o valoare de cursor. - Construiesti text, SQL, mesaje sau chei prin `+` din campuri de cursor. - Adaugi o interogare sau o coloana noua si nu esti sigur daca poate fi NULL. - Simptom: rezultat gresit fara eroare, sau `SQL statement parameter is required for non-prepared SQLEXEC() calls` in `OEXECUTOR.OEXECUTE`. ## Regula Tipul se evalueaza PRIMUL, ca sa opreasca acolo, iar `Nvl(...)` se aplica INAUNTRUL lui `Empty()` sau al comparatiei - niciodata pe langa. Un singur camp NULL anuleaza tot sirul construit prin `+`, deci fiecare camp il primeste. ```foxpro * GRESIT: Empty() singur nu prinde NULL; comparatia cu NULL e falsa; sirul devine .NULL. If Empty(tcCont) Return Endif tcSql = 'Where cont = ' + tcCont * CORECT: Type() opreste primul, Nvl(...) sta in interiorul lui Empty() / comparatiei If Type('tcCont') # 'C' Or Empty(Nvl(tcCont, '')) Return Endif If Type('tdData') # 'D' Or Empty(Nvl(tdData, {})) Return Endif If Nvl(tcStare, '') == 'A' Return Endif tcSql = 'Where cont = ' + Nvl(tcCont, '') ``` ## Capcane - `EMPTY(.NULL.)` este `.F.`, deci `Empty()` singur NU prinde NULL - nici pe text, nici pe data. - `IsNull(x) Or Empty(x)` e forma lunga si usor de uitat pe o ramura; `Empty(Nvl(x, ''))` e forma unica, greu de scris gresit. - `TYPE()` pe o variabila caracter cu NULL intoarce tot `'C'` - nu tine loc de `Nvl`, doar prinzi tipul neasteptat cu el. - Concatenarea prin `+` cu un camp NULL anuleaza tot sirul; eroarea apare departe, la `SQLEXEC`, nu la linia care l-a construit. - `NVL(col, '')` in interogarea Oracle mascheaza NULL-ul in SELECT, dar nu repara codul care primeste deja cursorul - garda ramane in VFP. - `Nvl(x, '')` pe un camp numeric/date da conversie gresita: valoarea implicita trebuie sa aiba acelasi tip (`{}`, `0`). ## Criterii de acceptare si dovada Fara dovada da/nu de mai jos, garda NU se raporteaza ca pusa. "Arata bine" nu e dovada. 1. Dovada ca valoarea chiar poate fi NULL (nu doar goala), pe datele de test: `SELECT SUM(CASE WHEN IS NULL THEN 1 ELSE 0 END) FROM ` -> rezultat > 0. 2. Test headless pereche: pe cursorul cu NULL, garda corectata da exact acelasi rezultat ca pe valoarea goala. Dovada = ambele cazuri trecute, cod de iesire 0. 3. Assert care cade fara fix: ruleaza testul si pe varianta fara `Nvl` - trebuie sa esueze; un test care trece in ambele variante nu dovedeste nimic. 4. Garduri ramase neacoperite in functia atinsa: `grep -n "Empty(" ` -> fiecare aparitie are `Nvl(` in interior; `grep -n "' + " ` -> fiecare camp de cursor are `Nvl(` inainte de `+`. ## Referinta (doar la nevoie) - `D:\ROA\ROAFACTURARE\COMUN\docs\conventie_null_vfp.md` - contextul complet si simptomul `SQLEXEC`, daca iti trebuie dupa ce ai aplicat regula.