Git i CI per a processos Oracle

Traçabilitat, revisió i automatització

Raúl Salinas Monteagudo

2026-09-16

Objectiu

Què volem aconseguir?

  • Una font de veritat per als scripts
  • Historial dels canvis
  • Revisió entre companys abans d’executar
  • Validacions automàtiques
  • Menys coneixement tàcit: el que només està en un cap

Com anirà el matí

Hora Bloc Pràctica
10:00–10:15 Per què: problemes operatius i el patró habitual
10:15–11:00 Git essencial: el cicle mínim, veure què ha canviat, recuperar una versió Exercici 1
11:00–11:45 Branques, conflictes i revisió entre companys Exercicis 2, 3 i 3b
11:45–11:55 Pausa
11:55–12:15 Què versionem d’Oracle, què no hi va mai, quants repositoris
12:15–12:45 Entorn de proves efímer, dades sintètiques i traçabilitat Demostració
12:45–12:55 Pausa
12:55–13:35 Integració contínua: què comprova i els tres nivells Exercici 5
13:35–13:50 Per on començar: el primer pilot
13:50–14:00 Tancament i preguntes

Pareu-me quan vulgueu: els dubtes en veu alta són part del curs.

On està tot

Manual, diapositives, exercicis i xuleta de comandes, en el zip del curs.

L’enunciat de cada pràctica està en Exercicis, amb les comandes per a copiar.

Què no us demane

  • No cal que munteu cap entorn ni instal·leu cap base de dades
  • L’Oracle de proves el veureu en pantalla: el munte jo
  • Els exercicis són de Git i de revisió: repositori, branca, pull request

Si ja useu Git

En esta sala hi ha qui l’usa cada dia i qui no l’ha obert mai. Els primers blocs són per als segons.

Encara que Git us sone, probablement no heu fet mai:

  • integració contínua: que cada canvi es valide a soles, sense que ho demane ningú
  • fer eixes mateixes comprovacions en la teua màquina, abans del commit
  • validar SQL contra un Oracle que es crea i es destruïx en cada execució
  • versionar el que no se sol versionar: DDL, dades mestres, rols, grants, jobs
  • saber què no recupera Git quan el canvi ja s’ha executat sobre dades reals

Problema

On és la versió correcta?

  • Directori personal
  • Servidor de producció
  • Correu electrònic
  • Historial del terminal
  • script_final_bueno_2.sql

Un patró habitual

En carpetes que han crescut durant anys:

informe.sql
informe_v2.sql
informe_final.sql
informe_final_bueno.sql
  • el nom fa de control de versions, perquè no n’hi ha cap altre
  • i, abans que tot això: cada u té la seua còpia, i ja no es poden comparar

Un sol fitxer

git log --oneline -- informe.sql   # totes les versions: data, autor i motiu
git show HEAD~3:informe.sql        # com estava fa tres canvis
git diff HEAD~3 -- informe.sql     # què ha canviat des d'aleshores

Quatre fitxers passen a ser un, i no es perd cap versió.

De procediments manuals a canvis traçables

Tiquet
  ↓
Branca
  ↓
Canvi
  ↓
Pull request
  ↓
CI
  ↓
Revisió
  ↓
Versió aplicada

Per ací passen les ferramentes: scripts, esquemes, configuració. Una tasca —un canvi fet a mà— no hi passa: usa una ferramenta, i queda al registre.

La integració contínua (CI) vol dir que, cada vegada que algú puja un canvi, s’executa a soles un codi que el comprova. Eixe codi és la pipeline, i viu versionada en el repositori com qualsevol altra cosa.

Git essencial

Aconseguir el repositori

git clone https://bitbucket.org/uveg/curs-git-ci-oracle.git
cd curs-git-ci-oracle

Un clon no és una descàrrega: et portes l’historial sencer, i pots treballar sense xarxa.

git config --global user.name "Nom Cognom"
git config --global user.email "usuari@uv.es"

Per a començar un repositori de zero, git init dins de la carpeta.

Què tinc ara mateix?

$ git status

Changes to be committed:          ← triat per al pròxim commit
        modified:   consulta_tablespaces.sql

Changes not staged for commit:    ← tocat, però encara no triat
        modified:   validacio_cites.sql

És la comanda que més usareu, i anomena ella mateixa les dues zones.

Les tres zones

directori de treball  ──git add──>  zona de preparació  ──git commit──>  historial
      (el que edites)                  (el que entrarà)                  (permanent)
  • git add no guarda res: tria què entrarà en el pròxim commit
  • per això es poden fer dos commits separats de dos canvis del mateix fitxer
  • git status diu sempre en quina zona està cada cosa

El cicle mínim

git pull
git status
git diff
git add fitxer.sql
git commit -m "Afig validació de cites duplicades"
git push

Què aporta un commit?

Un canvi d’una línia en la comprovació de tablespaces:

-LLINDAR_OCUPACIO=85
+LLINDAR_OCUPACIO=90
El commit ho contesta
Què ha canviat LLINDAR_OCUPACIO, de 85 a 90
Qui ho ha fet l’autor del commit
Quan la data i l’hora exactes
Per quin motiu «Puja el llindar d’avís de tablespaces (INC-0912)»
Com tornar arrere git revert a183fe2

El commit, per dins

$ git show -s --format=raw a183fe2
commit a183fe2c9b4d7e1a6f3c8b2d5e9a0f4c7b1d6e38
tree 9c1b7a4f2e5d8c0a1b3e6f7d2c4a8b9e0f1d3c5a
parent 4f2a1c8e7b3d9a0f6c5e2d4b8a1f7c3e9d0b6a2f
author Raúl Salinas <raulsa8@uv.es> 1788249600 +0200
committer Raúl Salinas <raulsa8@uv.es> 1788249600 +0200

    Puja el llindar d'avís de tablespaces de 85 a 90 (INC-0912)
commit el hash: no és un número de sèrie, ix del contingut
tree la fotografia completa del projecte, no el canvi
parent el commit anterior: ací es forma la cadena
author / committer qui el va escriure i qui el va aplicar: difereixen si el commit s’ha reaplicat (rebase, cherry-pick)
el missatge l’única part que no es pot deduir mirant el codi

Què ha canviat un company?

git diff                    # el que he tocat jo
git diff main...validacio-cites   # tot el que aporta una branca
git show a183fe2            # un canvi concret, amb el seu motiu

La vista d’una pull request és això mateix, en el navegador i amb un lloc per a comentar cada línia.

Funcionava fa una setmana

git log --oneline -- validacio_cites.sql
git show a183fe2
git blame create_user.sql

Git no sols permet tornar arrere: permet reconstruir com hem arribat ací.

Mirar, restaurar, revertir

Vull…
Mirar com estava git show HEAD~3:informe.sql
Tornar el fitxer enrere git restore --source=HEAD~3 informe.sql
Desfer un canvi ja integrat git revert a183fe2

En una branca compartida, sempre git revert.

El repositori remot

git remote -v        # a on apunte
git fetch            # baixa el que hi ha, sense tocar-me res
git pull             # fetch + integrar-ho en la meua branca
git push             # pujar el que he consolidat
$ git remote -v
origin  https://bitbucket.org/uveg/curs-git-ci-oracle.git (fetch)
origin  https://bitbucket.org/uveg/curs-git-ci-oracle.git (push)

El teu clon i el servidor són dos repositoris complets. fetch només mira; pull a més integra.

I el git add? git commit -a se salta la preparació. Però amb quatre passes ja no és un cartell.

Les tres que es confonen

Què toca Quan
git restore fitxer el teu directori m’he equivocat i encara no he fet commit
git reset --soft HEAD~1 l’historial local el commit era prematur; els canvis es queden
git reset --hard HEAD~1 historial i fitxers no vull res del que he fet ⚠️
git revert a183fe2 afig un commit nou ja està integrat i ho vol desfer tothom

reset reescriu; revert afig. En allò que ja han baixat altres, sempre revert.

M’han interromput

git stash            # guarda el que tinc a mitges i deixa el directori net
git switch main      # atenc la urgència
git stash pop        # torne a on estava

Per a quan et criden a mitjan canvi i has de mirar una altra cosa ara.

Revertir un commit recupera el codi, no les dades.

Un UPDATE o un DDL ja executat no es desfà amb Git.

D’ací ix una decisió: els processos amb dades reals acaben amb una persona que decidix.

Reescriure l’historial

commit --amend, rebase -i i filter-repo no modifiquen commits: en creen d’altres i abandonen els vells.

Branca Reescriure
main i les compartides Mai. Per a desfer, git revert
La teua, encara sense fusionar Endavant, i sovint convé
  • publicar-ho exigix git push --force: «descarta el que hi ha al servidor»
  • les branques principals es protegixen, i el servidor el rebutja
  • si has de forçar: --force-with-lease, que falla si algú havia pujat res

Què forma realment el projecte

git ls-files              # l'inventari: el que algú va decidir que hi forma part
git ls-files '*.sql'      # només el SQL
git grep DBMS_SCHEDULER   # buscar només dins d'eixe inventari

ls mostra el que hi ha. git ls-files mostra el que s’ha decidit tindre.

El .gitignore és l’altra cara: deixa fora generats, logs i temporals.

El missatge diu per què

Canvia timeout a 60
Evita timeout durant la càrrega de matrícula (INC-1234)
  • el què ja el diu el diff
  • el per què no està en cap altre lloc
  • un commit, una cosa: barrejar-ho tot encarix llegir-lo, revisar-lo i desfer-lo
  • en present: «Afig…», que en valencià val alhora d’imperatiu. Mai infinitiu ni passat
  • Conventional Commits (feat:, fix:) calcula versions d’un producte que es publica: ací no compra res

Dos canvis en un mateix fitxer

Has afegit una funcionalitat i has arreglat el fallo que t’ho impedia. Són dos commits.

git add -p consulta_tablespaces.sql

Git va mostrant els canvis tros a tros i pregunta per cada u:

Stage this hunk [y,n,q,a,d,s,e,?]?

y sí · n no · s parteix el tros en trossos més menuts

Practiquem: exercici 1

Entendre el cicle modificar → revisar → commit.

export UVLOGIN=raulsa8              # ← el vostre
git config --global core.editor nano

L’editor es posa amb git config: és de Git, no del terminal, i no cal repetir-ho.

L’enunciat dona per fet un terminal tipus Unix —Git Bash, WSL, MobaXterm—. Si no, hi ha l’alternativa escrita.

Enunciat: el document Exercicis del material del curs

Branques i revisió

L’historial és una cadena

graph RL
  classDef commit fill:#002e52,stroke:#002e52,color:#fff;
  classDef punter fill:#e4ebe9,stroke:#002e52,color:#002e52,stroke-width:2px;
  c3(( )) --> c2(( )) --> c1(( ))
  main["main"] -.-> c3
  class c3 commit;
  class c2 commit;
  class c1 commit;
  class main punter;

Cada cercle és un commit, i apunta al que tenia darrere: és la línia parent.

main no és una carpeta ni una còpia: és un punter a l’últim commit.

Una branca és un punter nou

git switch -c validacio-cites

graph RL
  classDef commit fill:#002e52,stroke:#002e52,color:#fff;
  classDef punter fill:#e4ebe9,stroke:#002e52,color:#002e52,stroke-width:2px;
  c3(( )) --> c2(( )) --> c1(( ))
  main["main"] -.-> c3
  vc["validacio-cites"] -.-> c3
  inf["informes"] -.-> c2
  class c3 commit;
  class c2 commit;
  class c1 commit;
  class main punter;
  class vc punter;
  class inf punter;

Tres branques, tres punters. validacio-cites i main assenyalen el mateix commit; informes es va quedar arrere.

No s’ha copiat cap fitxer: només hi ha un nom nou.

Els teus commits van a la teua branca

graph RL
  classDef commit fill:#002e52,stroke:#002e52,color:#fff;
  classDef punter fill:#e4ebe9,stroke:#002e52,color:#002e52,stroke-width:2px;
  c4(( )) --> c3(( )) --> c2(( )) --> c1(( ))
  main["main"] -.-> c3
  vc["validacio-cites"] -.-> c4
  inf["informes"] -.-> c2
  class c4 commit;
  class c3 commit;
  class c2 commit;
  class c1 commit;
  class main punter;
  class vc punter;
  class inf punter;

El mateix dibuix, amb un commit més. S’ha mogut un sol punter: el teu.

main i informes estan exactament on estaven. Ningú més veu encara els teus canvis.

Mentrestant, main avança

%%{init: {'theme':'base','themeVariables':{'git0':'#002e52','git1':'#0082c5','gitBranchLabel0':'#ffffff','gitBranchLabel1':'#ffffff','commitLabelColor':'#002e52','commitLabelBackground':'#e4ebe9','commitLabelFontSize':'15px'},'gitGraph':{'showCommitLabel':false,'showBranches':true}}}%%
gitGraph
   commit id: "a183fe2"
   commit id: "d4e5f6a"
   commit id: "3e88c17"
   branch validacio-cites
   checkout validacio-cites
   commit id: "9f01aa3"
   commit id: "7c22bd1"
   checkout main
   commit id: "b7d3e90"

Les dues històries han divergit. És el cas normal quan treballa més d’una persona, no una avaria.

Fusionar

%%{init: {'theme':'base','themeVariables':{'git0':'#002e52','git1':'#0082c5','gitBranchLabel0':'#ffffff','gitBranchLabel1':'#ffffff','commitLabelColor':'#002e52','commitLabelBackground':'#e4ebe9','commitLabelFontSize':'15px'},'gitGraph':{'showCommitLabel':false,'showBranches':true}}}%%
gitGraph
   commit id: "a183fe2"
   commit id: "d4e5f6a"
   commit id: "3e88c17"
   branch validacio-cites
   checkout validacio-cites
   commit id: "9f01aa3"
   commit id: "7c22bd1"
   checkout main
   commit id: "b7d3e90"
   merge validacio-cites

M és un commit amb dos pares. Per això no es perd res de cap de les dues bandes.

Moure’s entre branques

git branch                    # quines tinc
git branch -a                 # també les del servidor
git switch validacio-cites    # canviar-me
git switch -c nova            # crear-la i canviar-me
git switch -                  # tornar a l'anterior

git checkout fa això mateix i moltes coses més: switch i restore el partiren en dos, precisament perquè confonia.

Dues maneres d’integrar

El mateix punt de partida: la teua branca i main han divergit.

%%{init: {'theme':'base','themeVariables':{'git0':'#002e52','git1':'#0082c5','gitBranchLabel0':'#ffffff','gitBranchLabel1':'#ffffff','commitLabelColor':'#002e52','commitLabelBackground':'#e4ebe9','commitLabelFontSize':'15px'},'gitGraph':{'showCommitLabel':false,'showBranches':true}}}%%
gitGraph
   commit id: "o"
   commit id: "o"
   branch branca
   checkout branca
   commit id: "A"
   commit id: "B"
   checkout main
   commit id: "X"
   commit id: "Y"

Per a incorporar X i Y al teu treball hi ha dos camins, i no fan el mateix.

Opció A: git merge main

%%{init: {'theme':'base','themeVariables':{'git0':'#002e52','git1':'#0082c5','gitBranchLabel0':'#ffffff','gitBranchLabel1':'#ffffff','commitLabelColor':'#002e52','commitLabelBackground':'#e4ebe9','commitLabelFontSize':'15px'},'gitGraph':{'showCommitLabel':false,'showBranches':true}}}%%
gitGraph
   commit id: "o"
   commit id: "o"
   branch branca
   checkout branca
   commit id: "A"
   commit id: "B"
   checkout main
   commit id: "X"
   commit id: "Y"
   checkout branca
   merge main

  • no reescriu res: A i B es queden com van passar
  • Mdos pares, i per això l’historial mostra que hi va haver dues línies
  • els conflictes es resolen una vegada, en el moment de fusionar

Opció B: git rebase main

Després de git rebase main, la teua branca ix d’on acaba main:

%%{init: {'theme':'base','themeVariables':{'git0':'#002e52','git1':'#0082c5','gitBranchLabel0':'#ffffff','gitBranchLabel1':'#ffffff','commitLabelColor':'#002e52','commitLabelBackground':'#e4ebe9','commitLabelFontSize':'15px'},'gitGraph':{'showCommitLabel':false,'showBranches':true}}}%%
gitGraph
   commit id: "o"
   commit id: "o"
   commit id: "X"
   commit id: "Y"
   branch branca
   checkout branca
   commit id: "A'"
   commit id: "B'"

  • els teus commits es tornen a aplicar damunt de main
  • A' i B' són commits nous: mateix contingut, hash distint
  • l’historial queda lineal, com si hagueres començat hui

Quina trie?

merge rebase
L’historial fidel: es veu que hi va haver dues línies lineal i fàcil de llegir
Els teus commits queden intactes es reescriuen, amb hash nou
Conflictes es resolen una vegada poden repetir-se commit a commit
Es veu quan es va integrar no, es perd
En una branca compartida segur mai

La regla: rebase només en la teua branca, i només mentre no l’haja baixada ningú.

La recomanació per a nosaltres

  • per a integrar: merge. És segur i no cal pensar-hi
  • per a netejar la teua branca abans de la pull request: rebase -i
  • per a posar-te al dia sense omplir l’historial de fusions: git pull --rebase

I això no cal dibuixar-ho

git log --graph --oneline --all
*   ffff11 Merge branch 'validacio-cites'
|\
| * 7c22bd Afig validació de cites duplicades
* | 3e88c1 Reescriu la condició de duplicats (INC-1234)
|/
* a183fe2 Documenta consulta de tablespaces

Pull request

  • Context del canvi
  • Diferències línia a línia
  • Comentaris
  • Aprovació
  • Evidència de revisió

No és control personal

És:

  • transmissió de coneixement
  • detecció d’errors
  • criteris compartits
  • continuïtat del servei

I, sobretot: la manera de proposar un canvi en el codi d’un altre sense tocar-li res.

Dos toquen la mateixa línia

Carpeta compartida: l’últim que guarda sobreescriu l’altre. En silenci.

Git: s’atura i obliga a decidir.

<<<<<<< HEAD
WHERE data_inici <= SYSDATE
=======
WHERE data_inici <= TO_DATE('&&data_referencia', 'YYYY-MM-DD')
>>>>>>> validacio-cites

Practiquem: exercicis 2, 3 i 3b

  • 2 — una branca de treball, sense tocar main
  • 3 — obrir una pull request i revisar la d’un company
  • 3b — provocar un conflicte, resoldre’l i recuperar una versió

Enunciat: el document Exercicis del material del curs

Pausa

Tornem a les 11:55

SQL i Oracle

Què podem versionar?

Base de dades

  • SQL operatiu, informes i extraccions
  • DDL, migracions i PL/SQL
  • rols, grants i perfils
  • jobs de DBMS_SCHEDULER
  • dades mestres: centres, titulacions, calendaris
  • validacions de negoci i dades de prova

Què podem versionar?

Sistema

  • wrappers, crons i systemd
  • tnsnames.ora i configuració de connexions
  • inventari de màquines i serveis
  • Dockerfile i definicions d’entorn
  • pipelines de CI

Procés

  • README.md de cada procés
  • calendari de matrícula, actes, beques
  • llistes de comprovació
  • decisions tècniques i el seu motiu

Què no hi va mai

  • contrasenyes, wallets, claus, tokens
  • volcats de producció i dades personals
  • fitxers generats
  • binaris grans
  • logs

Dels secrets sí que hi va la referència: quin fa falta i qui el gestiona. Mai el valor.

Si un secret ja ha entrat

Esborrar la línia en el commit següent no el lleva de l’historial.

  1. rotar o revocar la credencial, immediatament
  2. traure-la del codi i posar-la on toca
  3. netejar l’historial, després i si cal

Es versiona la font, no el producte.

Quants repositoris?

La validació sol ser més senzilla quan els components que canvien junts es poden provar de manera coordinada.

Si dues coses que depenen l’una de l’altra estan separades:

  • cap commit representa “les dues juntes”
  • un canvi són dues revisions i dues pipelines

Els dos errors no costen igual:

  • massa junts: ho pagues una vegada, el dia que ho dividisques
  • massa separats: ho pagues cada vegada que un canvi toque els dos costats

En cas de dubte, menys repositoris dels que sembla que fan falta.

El que canvia junt, viu junt.

Criteris d’organització

Sol convindre organitzar per procés, producte o domini funcional. Menys pràctic:

  • per persona → pot dificultar la continuïtat
  • per script → desenes de pipelines i cap canvi transversal
  • per any → l’any és una etiqueta, no una unitat

La mida no sol ser el criteri

Per a decidir com repartir pesen més:

  • els permisos i la confidencialitat
  • els responsables i els cicles de vida
  • les dependències entre components
  • la freqüència dels canvis

Un repositori pot ser gran sense que això siga un problema per si mateix.

Exemple: alta d’un usuari

provisioning/users/
├── request.yaml
├── create_user.sql
├── validate_user.sql
└── README.md

create_user.sql

WHENEVER SQLERROR EXIT SQL.SQLCODE
WHENEVER OSERROR EXIT FAILURE

-- Exemple docent. No executar en producció sense adaptar-lo.
-- Ús esperat:
-- @create_user.sql APP_ORDENAMAT PROFILE_APPLICATION USERS

DEFINE username = '&1'
DEFINE profile_name = '&2'
DEFINE default_ts = '&3'

CREATE USER &&username
    IDENTIFIED EXTERNALLY
    DEFAULT TABLESPACE &&default_ts
    TEMPORARY TABLESPACE TEMP
    PROFILE &&profile_name;

GRANT CREATE SESSION TO &&username;

Important

Les contrasenyes, wallets, claus i tokens no van al repositori.

Sí que pot anar:

  • àlies
  • serveis
  • entorns
  • paràmetres no secrets
  • plantilles
  • validacions

Entorns reproduïbles

“En la meua màquina funciona”

  • versió d’Oracle i del client
  • joc de caràcters i variables NLS_
  • zona horària
  • paquets del sistema
  • rutes lligades a un ordinador: X:\…, \\servidor\…
  • passos manuals que ningú va escriure

L’entorn també és codi

# Base de dades de proves efímera: la mateixa que arranca la CI.
#   docker compose up -d     # crea l'esquema i carrega el joc de dades
#   docker compose down -v   # no deixa rastre
#
# La contrasenya és local i d'usar i tirar: esta base no conté dades reals.
services:
  oracle:
    image: gvenzl/oracle-free:23-slim-faststart
    environment:
      ORACLE_PASSWORD: local_throwaway
      APP_USER: matricula
      APP_USER_PASSWORD: local_throwaway
      # Fixat perquè el resultat no depenga de la màquina que ho execute.
      TZ: Europe/Madrid
      ORACLE_CHARACTERSET: AL32UTF8
      NLS_DATE_FORMAT: "YYYY-MM-DD HH24:MI:SS"
    ports:
      - "1521:1521"
    volumes:
      - ./01_schema_matricula.sql:/container-entrypoint-initdb.d/01_schema.sql:ro
      - ./02_seed_matricula.sql:/container-entrypoint-initdb.d/02_seed.sql:ro
    healthcheck:
      test: ["CMD", "healthcheck.sh"]
      interval: 10s
      retries: 30

I la màquina que ja tens

El compose val per al que naix nou. Per a un servidor que fa anys que corre:

sudo apt install etckeeper && sudo etckeeper init
sudo etckeeper commit "Estat inicial"

/etc ja és un repositori. A partir d’ahí ningú ha de recordar-se de res:

sudo apt install logrotate
cd /etc && sudo git log --oneline -3
b3293df committing changes in /etc after apt run
e1edf17 Estat inicial
5c38ef2 Initial commit

No ix d’eixa màquina: dins de /etc hi ha shadow i claus privades.

Quan està acabada una tasca?

Quan passa la integració contínua.

  • no quan funciona en el meu portàtil
  • no quan “a mi em va bé”
  • qualsevol company pot reproduir el resultat
  • la mateixa imatge en local i en la CI

Dades de prova

  1. esquema buit amb el DDL de producció
  2. càrrega sintètica menuda
  3. casos límit inclosos expressament
  4. destruir-ho tot en acabar

Mai una còpia de dades reals d’estudiants. I tampoc una còpia completa: per a provar una regla no fan falta totes les dades.

Càrrega sintètica

WHENEVER SQLERROR EXIT SQL.SQLCODE

-- Els scripts d'inici del contenidor s'executen com a SYSDBA contra el CDB,
-- i l'usuari de l'aplicació viu dins del PDB: cal dir a quin contenidor va.
ALTER SESSION SET CONTAINER = FREEPDB1;

-- Càrrega de prova sintètica: noms inventats, volum menut i casos límit.
-- Mai s'han de copiar dades reals d'estudiants a un entorn de proves.
-- La base de dades és efímera, per això el joc de dades no esborra res.

INSERT INTO matricula.estudiant (id, nom, centre) VALUES (1, 'Aitana Ferrer Bonet', 'FIS');
INSERT INTO matricula.estudiant (id, nom, centre) VALUES (2, 'Joan Marí Escrivà', 'FIS');
INSERT INTO matricula.estudiant (id, nom, centre) VALUES (3, 'Neus Bellver Tormo', 'ETSE');

-- Cas normal: cada estudiant, una franja distinta.
-- TO_DATE explícit i no la cadena a seques: així la càrrega no depén del
-- NLS_DATE_FORMAT de qui l'execute i dona el mateix resultat.
INSERT INTO matricula.centre_franja VALUES
  (1, 'FIS', 'G1100', TO_DATE('2026-09-01 09:00', 'YYYY-MM-DD HH24:MI'));
INSERT INTO matricula.centre_franja VALUES
  (2, 'FIS', 'G1100', TO_DATE('2026-09-01 09:30', 'YYYY-MM-DD HH24:MI'));
INSERT INTO matricula.centre_franja VALUES
  (3, 'ETSE', 'G1400', TO_DATE('2026-09-01 09:00', 'YYYY-MM-DD HH24:MI'));

COMMIT;

Pensar les condicions de prova

De què depén realment este procés?

  • quines taules necessita
  • quines dades mínimes fan que el cas tinga sentit
  • quins casos límit hem de cobrir
  • què hauria de passar si les dades són incorrectes

El registre l’escriu la màquina

A mà, el registre es fa després i el fa una persona cansada:

  • es copia la comanda al tiquet
  • s’apega un tros d’eixida
  • s’anota l’hora aproximada

Automatitzat, el registre és un subproducte: comanda exacta, commit, hora real, eixida completa, resultat.

commit=a183fe2

A les 08:00 es detecta que la càrrega de la nit ha deixat dades incorrectes. El log de les 03:00 diu commit=a183fe2.

git show a183fe2
git log --oneline a183fe2..HEAD

Quin codi va córrer, qui el va escriure, per què, i què s’ha tocat després.

“Això funcionava i ara no”

I ningú ha tocat res.

  • SYSDATE dins de la prova
  • còpia de producció que canvia cada dia
  • NLS_, zona horària
  • ordre de files sense ORDER BY
  • estat d’execucions anteriors

Fixar la data

-- Caduca sola
WHERE data_inici <= SYSDATE

-- Reproduïble
WHERE data_inici <= TO_DATE('&&data_referencia', 'YYYY-MM-DD')

Amb la data com a paràmetre, el canvi d’any s’assaja en juliol.

Assajar la matrícula

Amb base efímera i dades sintètiques, el procés es pot executar tantes vegades com faça falta:

  • cada canvi es valida contra el mateix joc de proves
  • una regressió es detecta el dia que s’introdueix
  • no cal reservar cap entorn compartit
  • el dia de la matrícula és una execució més

Pausa

Tornem a les 12:55

Integració contínua

Què pot comprovar una pipeline?

  • secrets accidentals
  • DELETE sense WHERE
  • GRANT DBA
  • scripts sense WHENEVER SQLERROR
  • errors de Bash
  • estructura del repositori
  • documentació mínima
  • validacions sobre Oracle de proves

Una validació de negoci

-- Ha de retornar zero files si no hi ha cites duplicades.

SELECT centre,
       titulacion,
       franja,
       COUNT(*) AS repetitions
FROM matricula.centre_franja
GROUP BY centre, titulacion, franja
HAVING COUNT(*) > 1;

La pipeline més menuda

# Comprova que tot el Python del repositori compila: no és
# cap prova, però atrapa l'error d'edició que arriba a
# producció a les 3 de la matinada.
# Es pot posar el primer dia, sense proves i sense entorn.
image: python:3-slim

pipelines:
  default:
    - step:
        name: El Python compila
        max-time: 5
        script:
          # compileall torna un codi distint de zero si un
          # fitxer té un error de sintaxi, i això ja posa la
          # pipeline en roig. No fa falta res més.
          - python -m compileall -q .

Cap prova, cap entorn. Un error de sintaxi ja no arriba a producció.

Exemple de pipeline

commit
  ↓
detecció de secrets
  ↓
validació d'estructura
  ↓
anàlisi estàtica
  ↓
execució en proves
  ↓
informe

Abans de pujar

pip install pre-commit
pre-commit install        # una vegada per repositori

A partir d’ahí, cada git commit passa les regles en la teua màquina.

  • segons, en compte d’esperar la pipeline
  • no és un control: --no-verify se’l salta
  • la CI és la que talla; el hook és la comoditat

Tres nivells

Què vol dir Qui ho decideix
Integració contínua cada canvi es valida ningú, passa sempre
Entrega contínua queda llest per a desplegar una persona
Desplegament continu es publica sol ningú, passa sempre

Per a publicar sol fan falta

  1. una validació de fiar
  2. un desplegament reproduïble
  3. tornar arrere barat

Si la pipeline es posa verda amb coses trencades, publicar sol només serveix per a difondre errors més ràpid.

Si tornar arrere no és fàcil, ràpid i segur, la gent té por d’anar cap avant.

On sí i on no

Documentació, informes, entorns de proves: desplegament continu.

Processos amb dades reals: entrega contínua i botó humà.

L’objectiu no és desplegar sol, és que quan es decidisca desplegar no faça falta cap passa manual.

Estes diapositives

push a main
     ↓
Bitbucket Pipelines
     ↓
[1] comprovacions estàtiques               ← segons; si falla, s'acaba ací
     ↓
[2] construir en un contenidor Debian net  ← si falla, s'acaba ací
     ↓
[3] empaquetar el web en un zip           ← artefacte descarregable
     ↓
web publicat

Ningú “puja les transparències”.

Un node Oracle des de zero

# Assaig de construcció d'un node Oracle des de zero. No prova que la
# instal·lació siga correcta: prova que el procediment escrit està complet.
pipelines:
  custom:                                  # s'engega a mà, no per commit
    node-oracle-des-de-zero:
      - step:
          runs-on: [self.hosted, linux]    # cal un runner que puga crear VM
          script:
            - IP=$(provisio/vm.sh create "assaig-$BITBUCKET_BUILD_NUMBER")
            - ssh oracle@$IP 'sudo provisio/00-preparar-so.sh'
            # La mateixa passa, una segona vegada: si no és idempotent, el
            # procediment no es pot repetir i encara no val com a document.
            - ssh oracle@$IP 'sudo provisio/00-preparar-so.sh'
            - ssh oracle@$IP 'provisio/10-comprovacions-previes.sh'  # cluvfy
            - ssh oracle@$IP 'provisio/20-instalar.sh'   # runInstaller -silent
            - ssh oracle@$IP 'provisio/30-fum.sh'        # la base respon?
          after-script:                    # s'executa també si ha fallat
            - provisio/vm.sh destroy "assaig-$BITBUCKET_BUILD_NUMBER"

De les diapositives que es publiquen soles a un node sencer: el mecanisme és el mateix.

Practiquem: exercici 5

Provocar una fallada de la pipeline i corregir-la.

Idees: una cadena que parega una contrasenya, un DELETE sense WHERE, un marcador de conflicte oblidat.

Enunciat: el document Exercicis del material del curs

Exercici complet

Flux de pràctica

  1. Crear branca
  2. Modificar SQL
  3. Actualitzar documentació
  4. Fer commit
  5. Publicar branca
  6. Obrir pull request
  7. Veure fallada de CI
  8. Corregir
  9. Revisar
  10. Resoldre un conflicte, si n’hi ha
  11. Fusionar

Per on començar

Un repositori nou d’operació Oracle

No migrar un procés que ja funciona, sinó crear un repositori nou per al que es faça d’ara endavant:

Tot script operatiu nou naix versionat, revisable i amb comprovacions.

  • comprovacions de tablespaces, sessions, objectes invàlids
  • usuaris, rols, perfils i grants
  • jobs de DBMS_SCHEDULER, paràmetres, wrappers
  • plantilles de configuració i validacions

Cada operació repetible, amb

  1. un fitxer identificable
  2. un motiu o tiquet
  3. una revisió
  4. una comprovació prèvia
  5. una comprovació posterior
  6. una manera de tornar arrere documentada

Dos repositoris, no un calaix

  • Operació Oracle compartida: administració, comprovació, configuració, manteniment
  • Cada projecte o procés: DDL, PL/SQL, migracions, dades mestres, validacions

El primer pilot

Crear un repositori nou de scripts i configuració Oracle i fer que el pròxim script operatiu nasca versionat.

  • comprovació de tablespaces
  • validació d’objectes invàlids
  • alta d’un usuari de proves
  • definició d’un job sintètic
  • canvi de configuració en un entorn descartable

Adopció gradual

  1. els scripts nous naixen en Git
  2. els canvis delicats es revisen abans d’executar
  3. els processos repetits incorporen comprovacions
  4. els objectes antics s’exporten quan cal tocar-los
  5. de l’execució manual a l’entrega controlada, poc a poc

Tancament

Què sabem fer ara

  • versionar scripts i llegir l’historial d’un canvi
  • veure exactament què ha tocat un company, i per què
  • treballar en branques i revisar-se amb pull requests
  • resoldre un conflicte senzill i recuperar una versió anterior
  • muntar un entorn de proves efímer i validar-hi SQL automàticament
  • entendre què comprova una pipeline i per què es posa roja o verda
  • fer que un script operatiu nou naixca versionat

Per si ho pregunten: git reflog

git reflog
6083d59 HEAD@{0}: merge lateral
36faf91 HEAD@{1}: checkout: moving from lateral to main
d4fa12d HEAD@{2}: commit: C

No és l’historial: és on ha estat HEAD en els teus últims moviments.

Si creus que has perdut un commit —un reset --hard, una branca esborrada—, ací està.

Per si ho pregunten: etiquetes

git tag -a v2026.09 -m "Versió desplegada en la matrícula de setembre"
git push --tags
git show v2026.09

Una etiqueta és un nom fix per a un commit. A diferència d’una branca, no es mou.

Útil per a marcar què es va executar en una campanya: git diff v2026.09..v2027.01.

Per si ho pregunten: cherry-pick

git cherry-pick a183fe2

Agafa un sol commit d’una altra branca i l’aplica ací.

  • útil per a portar un arreglament urgent a una branca de manteniment
  • crea un commit nou, amb hash distint: el mateix canvi existix dues vegades
  • si acabes fent-ne molts, el que volies era un merge

Per si ho pregunten: per què no rebase sempre?

«Si deixa l’historial net, per què no tancar també la pull request amb rebase

Es pot. La regla «mai en branca compartida» no valia ací: la branca ja està acabada i s’esborra.

commit de fusió squash rebase
Historial bifurcat lineal lineal
Commits nous en main 1 1 un per commit de la branca
Els ha provat la CI només l’últim
Revertir-ho tot revert -m 1 un revert un per commit
Es veu què anava junt només en la pull request

Es tria una vegada i es configura en Bitbucket. No és una decisió del dia a dia.

Per si ho pregunten: i des de Bitbucket?

«Tot això no es configura des de la web, sense tocar la consola?»

Una part sí, i convé saber-ho:

  • editor de bitbucket-pipelines.yml en el navegador, amb validació i commit directe
  • variables i secrets: Repository settings → Repository variables
  • entorns i aprovacions: Deployments
  • executors propis: Repository settings → Runners

El que no hi ha és un constructor de caixes i fletxes: la pipeline sempre és el fitxer YAML del repositori.

Per a seguir

  • Git: git-scm.com/doc i el llibre Pro Git (lliure)
  • Bitbucket Pipelines: la documentació del propi Bitbucket
  • Detecció de secrets: gitleaks
  • Linters de SQL: sqlfluff, sqlcheck
  • Proves de PL/SQL: utPLSQL

codi font · 4d16c0d