Files
prep-step/docs/db-cad-index.md
T
davideandClaude Sonnet 5 3cfc040fb0 Aggiungi documentazione tecnica su cad_index.db
Schema, logica di scansione/upsert incrementale di Indexer.ScanAsync,
come l'app individua il DB all'avvio (incluso il caveat sulla cartella
di build in dev) e le query piu' usate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 14:37:08 +02:00

9.0 KiB

Il database cad_index.db

Documentazione tecnica del DB SQLite che indicizza i file CAD: schema, come viene popolato/aggiornato (Indexer.ScanAsync), come l'app lo trova all'avvio e i problemi più comuni quando i risultati sembrano "mancanti".

Schema

Tabella files (schema SQL in Indexer.Schema, PrepStep/Indexer.cs): un record per ogni file trovato sotto la radice del disco (default X:\, Form1.RadiceDiscoDefault).

Colonna Contenuto
id chiave primaria autoincrementale
percorso_completo percorso assoluto del file — chiave univoca, usata per upsert
percorso_relativo percorso relativo alla cartella di primo livello (cartella_radice)
nome_file solo il nome del file
cartella_radice prima cartella sotto la radice del disco (es. __B.end, Commerciali, ...)
famiglia primo livello di sottocartella dentro cartella_radice (null se il file è direttamente dentro cartella_radice)
codice estratto dal nome file col pattern Pro-E <codice>.<tipo>.<versione>
tipo_file es. prt, asm, o altro se il nome non rispetta il pattern
versione numero di versione Pro-E (intero), null se non rispetta il pattern
dimensione_byte dimensione del file
data_modifica LastWriteTimeUtc del file, formato ISO 8601
scansionato_il timestamp UTC dell'ultima scansione in cui il file è stato visto

Indici su codice, tipo_file, cartella_radice (le colonne usate nelle query di ricerca componenti e nel filtro per cartella).

Vista files_ultima_versione: stessa struttura di files, filtrata alla sola riga con versione più alta per ogni (cartella_radice, codice, tipo_file), tramite ROW_NUMBER() OVER (PARTITION BY ... ORDER BY versione DESC). Tutte le ricerche di componenti (Form1. TrovaPercorsiCad, Form1.CaricaCodiciNoti) interrogano questa vista, non files direttamente, così un componente con più versioni sulla share risolve sempre all'ultima. Deliberatamente una ROW_NUMBER() invece di un self-join con GROUP BY: su ~200k righe un self-join degenera in tempi quasi quadratici, ROW_NUMBER() resta lineare.

Nota: cartella_radice fa parte della partition, quindi se lo stesso codice+tipo esiste in due cartelle radice diverse (duplicati sulla share), la vista restituisce una riga per ciascuna — non una sola in assoluto. TrovaPercorsiCad gestisce questo caso deduplicando per tipo_file lato C# (vedi sotto).

Come viene popolato: Indexer.ScanAsync

Chiamato da "Aggiorna indice" (fase 1) e riutilizza lo stesso dbPath già caricato.

  1. Crea lo schema se non esiste (CREATE TABLE/VIEW IF NOT EXISTS) — quindi funziona sia su un DB vuoto (prima esecuzione) sia su uno esistente (aggiornamento incrementale).
  2. Cancella subito tutte le righe con cartella_radice LIKE 'temp\_%': le cartelle temp_paris, temp_davide, temp_studi esterni, ecc. sono lavori personali/temporanei che possono contenere copie datate con lo stesso codice numerico di file "ufficiali" altrove sulla share, causando falsi positivi nella risoluzione dei sotto-componenti (vedi docs/formato-asm.md). Vengono escluse a monte, non solo filtrate nelle singole query.
  3. Elenca le cartelle di primo livello sotto la radice con Directory.GetDirectories (non un elenco fisso in codice: una nuova cartella sulla share entra nell'indice al prossimo aggiornamento senza bisogno di modificare PrepStep), escludendo quelle che iniziano per temp_.
  4. Per ogni cartella di primo livello, cammina l'albero con uno stack manuale (Walk, in Indexer.cs) invece di Directory.EnumerateFiles(..., SearchOption.AllDirectories): più veloce su una share di rete con centinaia di migliaia di file, e ogni sottocartella irraggiungibile (permessi, link rotti) viene saltata con un try/catch invece di far fallire l'intera scansione.
  5. Per ogni file trovato, estrae codice/tipo/versione dal nome (ParseFilename, vedi sotto) e fa un upsert riga per riga dentro una transazione per cartella: confronta dimensione_byte + data_modifica con quanto già salvato per lo stesso percorso_completo—
    • non esiste ancora → INSERT (contatore "nuovi")
    • esiste ma dimensione/data sono cambiate → UPDATE (contatore "aggiornati")
    • esiste e non è cambiato nulla → solo scansionato_il viene aggiornato (contatore "invariati"), per non riscrivere l'intera riga inutilmente
  6. Al termine di ogni cartella, cancella dal DB le righe di quella cartella_radice il cui scansionato_il non è quello di questa scansione (DELETE ... WHERE scansionato_il != @now): sono file rimossi dal disco dall'ultima scansione. Il conteggio finisce nel contatore "rimossi". Questo avviene per cartella, non a scansione intera completata: se annulli a metà, le cartelle già scansionate hanno comunque gli eliminati ripuliti.
  7. Il progresso (ScanProgress, con conteggi nuovi/aggiornati/invariati/rimossi, file fatti/previsti e tempo trascorso) viene riportato ogni 200 file tramite IProgress<T>, marshalato automaticamente sul thread UI, e mostrato in ProgressForm (che stima anche il tempo rimanente dal ritmo file/secondo rispetto al totale atteso, preso dal conteggio della scansione precedente salvato nel DB).

Estrazione codice/tipo/versione: ParseFilename

Regex: ^(?<codice>.+)\.(?<tipo>[A-Za-z0-9]+)\.(?<versione>\d+)$ applicata al nome file (non al percorso). Esempio: 9251208030c.asm.5 → codice="9251208030c", tipo="asm", versione=5. Il gruppo codice è greedy, quindi cattura tutto quello che precede l'ultimo .tipo.versione — se un componente ha una lettera di revisione attaccata nel nome del file (es. 9248908002a.asm.3), quella lettera fa parte del codice, non è un gruppo separato.

Se il nome file non rispetta il pattern (niente .tipo.numero finale), l'intero nome finisce in codice e tipo_file diventa "altro" (usato per capire quanti file "strani" ci sono sulla share, non tipicamente cercato dalla UI).

Come l'app trova il DB all'avvio

Form1.InizializzaDatabase prova, in ordine, i seguenti candidati e usa il primo che esiste su disco:

  1. l'ultimo DB caricato con successo, salvato in %AppData%/PrepStep/ultimo_db.txt (Form1.LeggiUltimoDbUsato/SalvaUltimoDbUsato)
  2. cad_index.db accanto all'eseguibile (AppContext.BaseDirectory)
  3. cad_index.db nella working directory corrente

Se nessuno dei tre esiste, la fase 1 resta bloccata su "Database non trovato" finché l'utente non preme "Apri DB..." (Form1.ScegliDatabase, file picker libero) o "Aggiorna indice" (Form1.AggiornaIndice, che crea cad_index.db da zero accanto all'eseguibile se non c'è ancora un _dbPath).

Attenzione in sviluppo (dotnet run / IDE): AppContext.BaseDirectory è la cartella di build (PrepStep/bin/Release/net10.0-windows o Debug/...), non la radice del repo. Se in quella cartella finisce per errore una copia di cad_index.db (es. creata lì da una vecchia sessione), l'app la userà invece di quella nella radice del repo, e le due copie divergeranno a ogni "Aggiorna indice" fatto nell'uno o nell'altro ambiente. Se i risultati di una ricerca sembrano incoerenti con quello che ti aspetti di vedere sul DB "giusto", controlla con "Apri DB..." quale file è effettivamente caricato (mostrato in fase 1 accanto al conteggio dei file indicizzati) prima di sospettare un bug nella ricerca.

Query più usate

  • Verifica completezza / crea raw (Form1.TrovaPercorsiCad): per ogni codice del CSV, SELECT tipo_file, percorso_completo FROM files_ultima_versione WHERE LOWER(codice) = LOWER(@codice) AND LOWER(tipo_file) IN ('prt','asm') ORDER BY tipo_file, data_modifica DESC. Il match sul codice è esatto (case-insensitive, non un LIKE): se il codice del CSV ha una lettera di revisione in più o in meno rispetto al nome del file su disco, non c'è match e la cella "Percorso CAD" resta vuota — non è un errore della query, è che il DB non ha nessuna riga con quell'esatto codice. In presenza di duplicati su più cartella_radice, tiene solo il primo per tipo_file (il più recente, dato l'ORDER BY data_modifica DESC) e scarta gli altri.
  • Risoluzione codici noti per lo scanner assiemi (Form1.CaricaCodiciNoti, usata sia dalla GUI in fase 4 sia da scripts/BomViaDb.cs): carica in un'unica query tutti i codici prt/asm noti con i relativi percorsi, invece di una query per codice — usata per validare i token generici trovati nei binari .asm (vedi docs/formato-asm.md).

Debug ad-hoc

Per interrogare il DB direttamente da riga di comando senza aprire un tool SQLite esterno:

dotnet run scripts/Query.cs -- cad_index.db "SELECT codice, tipo_file, percorso_completo FROM files_ultima_versione WHERE codice LIKE '925%' LIMIT 20"

Utile per capire in fretta se un codice mancante nella UI non è nell'indice affatto (nessuna riga, nemmeno con un LIKE più permissivo) oppure c'è ma con un codice leggermente diverso da quello atteso (es. lettera di revisione, maiuscole/minuscole, spazi).