# Database

> I valori che un sistema della fabbrica tiene in una tabella di database (SQL Server, PostgreSQL, MySQL e MariaDB, SQLite, o qualsiasi database tramite ODBC) letti come tag, e riscritti dove un tag lo chiede: le schede Connessione al database e Accesso, un tag che legge una cella per tabella, colonna e riga oppure esegue una query propria, i due modi in cui un tag scrive, la scansione che propone un tag per colonna, quattro esempi completi e ciò che il driver non fa.

Il driver Database legge i valori che un sistema della fabbrica tiene in una tabella di database:
l'ordine di produzione che il MES conserva, la ricetta o il setpoint che l'ERP tiene, il risultato che
un altro sistema di prova ha registrato. Ogni tag legge un valore di una tabella, e i suoi valori
arrivano dove arrivano quelli di ogni altro tag: in Logic, sulle dashboard, nelle prove di un banco di
collaudo di [Process](process). Dove un tag lo chiede, il driver scrive anche all'indietro: nella cella
che il tag legge, oppure con un comando proprio del tag.

SQL Server, PostgreSQL, MySQL e MariaDB, e SQLite si raggiungono senza installare nulla sul computer.
Ogni altro database (Oracle, Access, Firebird e altri) si raggiunge tramite il suo driver ODBC, che deve
essere installato sul computer nella versione a 64 bit.

La lettura è l'uso principale, e il suo percorso è breve: un dispositivo dice dove si trova il suo
database e come la stazione vi accede, la scansione propone un tag per colonna delle sue tabelle, e
ogni tag nasce in sola lettura. Ciò che riguarda la scrittura compare su un tag solo dopo che il suo
accesso lascia scrivere la stazione.

Un dispositivo di questo driver è un database: due database dello stesso server sono due dispositivi.
I dispositivi stanno sotto il driver e possono essere archiviati in cartelle,
**Driver → Cartella → Dispositivo → Tag**, come sotto ogni driver senza linea seriale né broker.

## La pagina del driver {#the-drivers-page}

La riga sotto l'intestazione dice « Ogni dispositivo legge un database. Aggiungere un dispositivo, inserire dove si trova il suo database e come la stazione vi accede, poi scansionarlo per aggiungerne le colonne come tag. »
Per Configurare il connector, la pagina porta la scheda Ambito della scansione propria del driver, con
il dispositivo di cui la scansione legge il database (vedi [Rilevamento](#discovery)). Sotto si trova la
tabella **Dispositivi** di ogni driver (vedi [La tabella dei dispositivi](connector-drivers#the-device-table)),
che cresce con **Aggiungi dispositivo**, a destra del suo titolo; vuota, dice « Nessun dispositivo. » La
barra del driver porta **Rileva**, in grigio finché la scheda Ambito della scansione non è completa. Il
driver sta sotto **File e database**, accanto al driver [File](driver-file).

## Dispositivo {#device}

Un dispositivo di questo driver non ha la scheda Connessione: al suo posto c'è la scheda
**Connessione al database**, seguita dalla scheda **Accesso** su ogni database tranne SQLite, che non ha
accesso. L'**Intervallo di interrogazione (ms)** della scheda Dispositivo, 1000 per impostazione
predefinita, dice ogni quanto i tag leggono il database, e ogni tag può sostituirlo come in ogni driver.

Un dispositivo nuovo è un SQL Server senza server: dice « Server non ancora inserito », la sua pagina dice « La stazione non raggiunge questo dispositivo finché non dice dove si trova il suo database, quindi non ne viene ancora letto nulla e non c'è alcun errore da mostrare. Inserirlo nella scheda Connessione al database qui sotto (il server e il nome del database, il file o l'origine dati) e la stazione vi si connette. »
e non si legge nulla finché non lo dice. La Base URI che mostra l'intestazione è l'indirizzo di
esecuzione, che non contiene mai l'utente né la password:
`database://sqlserver?server=mes-sql&instance=SQLEXPRESS&database=MES` per un'istanza di SQL Server,
`database://sqlite?file=C%3A%5CData%5Crecipes.db` per un file SQLite,
`database://odbc?source=ERP` per un'origine dati ODBC.

### La scheda Connessione al database {#the-database-card}

| Campo | Mostrato | Significato | Predefinito |
| --- | --- | --- | --- |
| **Tipo di database** | sempre | **SQL Server**, **PostgreSQL**, **MySQL / MariaDB**, **SQLite** oppure **Altro (ODBC)**, che dice « Usa il driver ODBC a 64 bit installato su questo computer per quel database (Oracle, Access, Firebird e altri). » | **SQL Server** |
| **Server del database** | SQL Server, PostgreSQL, MySQL / MariaDB | Su SQL Server: « Un nome host o un indirizzo IP. SERVER\ISTANZA e SERVER,PORTA vengono separati nei rispettivi campi. » Sugli altri due: « Un nome host o un indirizzo IP. Una porta scritta dopo i due punti va nel campo Porta. » | vuoto |
| **Porta** | gli stessi | Vuoto per la porta propria del database, che il campo vuoto mostra: 1433, 5432 o 3306. Con un'istanza di SQL Server e senza porta, il campo non mostra nulla e il SQL Server Browser del server dice la porta. | vuoto |
| **Nome dell'istanza** | SQL Server | « Vuoto per l'istanza predefinita. Un'istanza denominata senza porta si trova tramite SQL Server Browser. » Fino a 16 lettere, cifre, `_` e `$`, iniziando con una lettera o `_`. | vuoto |
| **Nome del database** | SQL Server, PostgreSQL, MySQL / MariaDB | Il database del server che contiene le tabelle, fino a 128 caratteri. | vuoto |
| **File del database** | SQLite | « Il percorso completo del file SQLite, raggiunto con i permessi Windows dell'account con cui gira la stazione. Si apre in sola lettura a meno che un tag non scriva. » Il campo mostra `C:\Data\recipes.db` come suggerimento. Il file non viene mai creato. | vuoto |
| **Origine dati o testo di connessione** | Altro (ODBC) | « Il nome di un'origine dati in Origine dati ODBC di Windows (64 bit), oppure un testo di connessione come Driver={...};Dbq=C:\Data\plant.accdb. La password va nel campo della password, mai nel testo. » | vuoto |

Un campo che il tipo scelto non legge non viene mostrato, e conserva il suo valore per quando lo
leggerà. Scegliere un altro tipo svuota una porta rimasta sulla porta propria del database precedente,
così che il nuovo si raggiunga sulla sua. Il campo Server del database separa ciò che gli strumenti di
database scrivono insieme:

| Scritto in Server del database | Diventa |
| --- | --- |
| `mes-sql\SQLEXPRESS` (SQL Server) | Server `mes-sql`, Nome dell'istanza `SQLEXPRESS` |
| `mes-sql,1433` (SQL Server) | Server `mes-sql`, Porta 1433 |
| `10.0.0.5:5433` | Server `10.0.0.5`, Porta 5433 |

Finché la scheda non dice dove si trova il database, il dispositivo viene salvato così com'è e
attende, con il punto grigio, senza leggere nulla e senza mostrare errori:

| Tipo di database | Attende | Il dispositivo dice |
| --- | --- | --- |
| SQL Server, PostgreSQL, MySQL / MariaDB | Il server, poi il nome del database | « Server non ancora inserito », poi « Nome del database non ancora inserito » |
| SQLite | Il file | « File del database non ancora inserito » |
| Altro (ODBC) | L'origine dati o il testo di connessione | « Origine dati non ancora inserita » |

Un'impostazione che la scheda non accetta viene detta sotto le schede, e il dispositivo non viene
salvato finché non è corretta:

| Si inserisce | La scheda dice |
| --- | --- |
| Un server con schema, percorso o spazio, come `http://mes-sql` | « Il server è solo un nome host o un indirizzo IP: niente schema, niente percorso e niente spazi. Un'istanza di SQL Server e una porta scritte insieme vanno nei rispettivi campi. » |
| Un nome di istanza con uno spazio o un segno diverso da `_` e `$` | « Il nome dell'istanza ha fino a 16 lettere, cifre, trattini bassi e simboli del dollaro. » |
| Un nome di database di più di 128 caratteri | « Il nome del database ha al massimo 128 caratteri, senza caratteri di controllo. » |
| Un file che non è un percorso completo, come `recipes.db` | « Il file del database è un percorso completo: un'unità, una cartella e il file (C:\Data\ricette.db), oppure una condivisione di rete (\\server\data\ricette.db). » |
| Una cartella, o un percorso con `*`, `?`, `<`, `>` o virgolette | « Inserire il percorso del file del database stesso, non di una cartella, senza * ? \| < > né virgolette. » |
| Un nome di origine dati con una parentesi quadra, una graffa, una tonda, una virgola, un punto e virgola o uno tra `? * ! @ \` | La scheda chiede il nome che l'origine ha in Origine dati ODBC, oppure un testo di connessione. |
| Un testo di connessione che non è fatto di coppie `key=value`, come una graffa rimasta aperta | « Un testo di connessione è fatto di coppie key=value separate da punti e virgola; un valore che contiene un punto e virgola va tra parentesi graffe. » |
| Un testo di connessione con `PWD=` o `Password=` | « Togliere la password dal testo di connessione e inserirla nel campo della password: il testo viene mostrato e scritto nel diario tecnico, e quel campo la tiene protetta. » |

Il salvataggio segue le stesse regole, e il registro delle azioni lo dice quando ne rifiuta una:
« Il dispositivo 'MES' ha un'impostazione di database che il suo driver non può usare. Aprirlo e correggere il campo che il pannello segnala. »

### La scheda Accesso {#the-sign-in-card}

Ogni database tranne SQLite porta la scheda **Accesso**, con l'utente con cui la stazione accede.

| Campo | Mostrato | Significato | Predefinito |
| --- | --- | --- | --- |
| **Accedere con l'account Windows della stazione** | SQL Server | Attivo: « Si accede a SQL Server con l'account Windows con cui gira la stazione; non si salva né utente né password. » Disattivo: « Si accede a SQL Server con un utente del database e la sua password. » | disattivo |
| **Nome utente** | un server a cui si accede con un utente, e ODBC, dove è facoltativo | L'utente del database. | vuoto |
| **Password di accesso** | gli stessi | La password dell'utente, conservata protetta come ogni credenziale di un driver: non viene mai più mostrata, mai scritta nell'indirizzo né nel diario tecnico, e mai restituita a un agente. | vuoto |
| **Crittografia** | SQL Server | **Crittografata, certificato verificato**: « La connessione è crittografata e il certificato del server viene verificato con i certificati di cui questo computer si fida. » **Crittografata, qualsiasi certificato accettato**: « La connessione è crittografata e qualsiasi certificato viene accettato, come serve a un server di fabbrica con un proprio certificato. L'identità del server non viene verificata. » **Non crittografata**: « I valori e i comandi viaggiano in chiaro. Solo per un vecchio server che non offre crittografia. » | **Crittografata, certificato verificato** |

PostgreSQL e MySQL non hanno il campo Crittografia: la loro connessione è crittografata ogni volta che
il server lo offre. Un SQL Server con un certificato proprio, come spesso ha un server di fabbrica,
rifiuta la connessione con le parole del database su una catena di certificati emessa da un'autorità
non attendibile: **Crittografata, qualsiasi certificato accettato** è la scelta che quel server chiede.

Sotto i suoi campi, la scheda dice quale utente chiedere:
« Chiedere all'amministratore del database un utente con i soli permessi di cui la stazione ha bisogno: la lettura, e la scrittura solo nelle tabelle che i suoi tag cambiano. »
La stazione legge e scrive i dati di un altro sistema con tutto ciò che il suo utente può fare, quindi
quell'utente è il vero limite di ciò che la stazione può cambiare. Ogni scrittura viene annotata nel
diario tecnico, come in ogni driver.

Un server a cui si accede con un utente, senza utente ancora inserito, dice invece
« Inserire l'utente con cui la stazione accede. Fino ad allora la stazione non collega questo dispositivo. »
Non è un rifiuto: il dispositivo viene salvato, e il suo errore dice che nessun utente del database è
ancora inserito, finché l'utente non c'è.

### Online, in attesa e offline {#online-waiting-and-offline}

| Situazione | Cosa fanno il dispositivo e i suoi tag |
| --- | --- |
| La scheda Connessione al database non dice dove si trova il database | Il dispositivo attende, punto grigio, nulla letto e nessun errore (vedi sopra). |
| Un server a cui si accede con un utente, e nessuno inserito | La stazione non collega il dispositivo; il suo errore dice che nessun utente del database è ancora inserito. |
| Il server non risponde, rifiuta l'accesso, oppure il file SQLite non esiste | Il dispositivo è offline con la causa data dal database stesso, e la stazione lo collega appena può. Una connessione si attende al massimo 10 s. |
| La connessione si perde (il server si è riavviato, la rete si è interrotta) | Il dispositivo va offline e si collega di nuovo. |
| Un'istruzione che il database rifiuta, o che è ancora in corso dopo 10 s | Lettura fallita di quel tag, con le parole del database stesso; il dispositivo continua a leggere gli altri tag. |
| Un tag legge la riga più recente di una tabella che non ha ancora righe | Quel tag attende, senza errore, e lo stato di salute del suo dispositivo non lo conta finché non viene scritta la prima riga: gli altri tag del dispositivo dicono se è online. Una tabella svuotata dopo che il tag vi ha letto non è questa attesa: il tag conserva l'ultima lettura, che conta come fallita quando è più vecchia del suo intervallo di interrogazione più 10 s, come in ogni driver. |

Ogni dispositivo ha una connessione propria, aperta quando si collega e mai condivisa né tenuta in un
pool, quindi una connessione che un server ha chiuso non viene mai riusata.

### Preparare il server {#prepare-the-server}

I tre database di rete arrivano chiusi a un altro computer nelle installazioni comuni. Ognuna di
queste impostazioni si fa sul server, da chi lo amministra; la stazione non ne cambia nessuna.

| Database | Che cosa lascia fuori la stazione | Dove si cambia |
| --- | --- | --- |
| SQL Server | TCP/IP disattivato (SQL Server Express e Developer arrivano così) | SQL Server Configuration Manager > SQL Server Network Configuration > Protocols for l'istanza: abilitare TCP/IP, poi riavviare SQL Server (l'istanza) in SQL Server Services |
| SQL Server | Un'istanza denominata ascolta su una porta che sceglie da sé | TCP/IP > IP Addresses > IPAll: svuotare TCP Dynamic Ports e scrivere la porta in TCP Port, poi riavviare; oppure lasciare vuoto il campo Porta del dispositivo e indicare il nome dell'istanza |
| SQL Server | Un'istanza denominata senza porta si trova tramite il SQL Server Browser, fermo o bloccato (errore 26) | SQL Server Services: SQL Server Browser avviato, Start Mode Automatic; UDP 1434 consentita nel firewall del server |
| PostgreSQL | listen_addresses vale localhost, il suo valore predefinito | postgresql.conf: listen_addresses = '*', poi riavviare PostgreSQL |
| PostgreSQL | Nessuna riga di pg_hba.conf lascia entrare l'indirizzo di questa stazione (SQLSTATE 28000) | pg_hba.conf: una riga host per il database, l'utente e l'indirizzo di questa stazione, con scram-sha-256 (md5 per una password salvata con MD5), poi SELECT pg_reload_conf(); |
| MySQL, MariaDB | bind-address vale 127.0.0.1, come lo impostano molti pacchetti, o skip-networking è attivo | La sezione [mysqld] del file di configurazione: bind-address = 0.0.0.0, skip-networking cancellato, poi riavviare |
| MySQL, MariaDB | Nessun account per l'indirizzo di questa stazione (errore 1130) | CREATE USER per l'utente all'indirizzo di questa stazione, e GRANT di ciò che serve ai tag |

Ognuno richiede anche la porta del database consentita nel firewall del computer del server.
Quando il server risponde in uno di questi modi, la pagina del dispositivo dice di quale
impostazione si tratta, con questi passi e i valori di questo dispositivo (la porta, l'istanza,
l'indirizzo di questa stazione come lo ha visto il server, la riga di pg_hba.conf e l'account
MySQL, ciascuno con un pulsante per copiarlo), al posto dell'errore in inglese del database.
L'avviso scompare da solo non appena il dispositivo si connette, o quando il server risponde in
altro modo. Il registro e l'agente leggono gli stessi passi.

## Indirizzamento del tag {#tag-addressing}

### Come legge un tag {#how-a-tag-reads}

Un tag di questo driver non ha un campo indirizzo proprio: la sua scheda **Sorgente** chiede come legge.

| **Legge** | Cosa mostra il tag | I suoi campi |
| --- | --- | --- |
| **Tabella e colonna** | Una cella: la colonna del valore di una riga di una tabella. | **Tabella**, **Colonna del valore**, **Riga da leggere** e i campi della riga |
| **Query propria** | La prima colonna della prima riga che restituisce un SELECT proprio. | **Testo della query** |

Un tag nuovo legge **Tabella e colonna**, tramite la sua chiave, come Double. I tipi offerti sono
Boolean, Int32, Int64, Float, Double, String e DateTime. Nella lettura di una cella, un tag di numero
intero rifiuta un valore con decimali invece di arrotondarlo (la scrittura fa il contrario, vedi
[Scrittura](#writing)), e un testo si legge come numero solo quando lo è. Ogni tag nasce in
**Sola lettura**; ciò che la scrittura aggiunge si trova in [Scrittura](#writing).

### Tabella e colonna {#table-and-column}

| Campo | Mostrato | Significato | Predefinito |
| --- | --- | --- | --- |
| **Tabella** | Tabella e colonna | « Un nome, oppure uno schema e un nome (dbo.Orders). Un nome con spazi o maiuscole va tra virgolette o parentesi quadre, come [Order Lines]. » | vuoto |
| **Colonna del valore** | Tabella e colonna | La colonna la cui cella è il valore del tag. | vuoto |
| **Riga da leggere** | Tabella e colonna | **Tramite la sua chiave**: « La riga la cui colonna chiave contiene il valore chiave. I tag della stessa tabella e della stessa riga si leggono insieme in una sola query. » **La più recente**: « La riga con il valore più alto della colonna data: una data e ora, oppure un numero che cresce soltanto. I tag della stessa tabella e della stessa riga si leggono insieme in una sola query. » | **Tramite la sua chiave** |
| **Colonna chiave** | Tramite la sua chiave | La colonna che sceglie la riga, come `OrderNo`. | vuoto |
| **Valore chiave** | Tramite la sua chiave | Ciò che la colonna chiave contiene nella riga da leggere, come `4711`, scritto come testo: il database lo legge come legge un valore di quella colonna. | vuoto |
| **Colonna data** | La più recente | La colonna il cui valore più alto è la riga più recente, come `updated_at`. | vuoto |

Un nome si legge come lo legge il database:

| Scritto | Letto come |
| --- | --- |
| `Orders` | Un nome semplice, letto come il database legge ogni nome semplice: PostgreSQL in minuscolo, Oracle in maiuscolo. |
| `"Order Lines"`, `[Order Lines]` | Il nome esatto, qualunque siano le virgolette che il database stesso accetta. |
| `dbo.Orders` | Uno schema e una tabella: un punto fuori dalle virgolette li separa. |

Il driver mette ogni parte di un nome tra le virgolette che il suo database accetta, quindi un nome non
viene mai incollato in un'istruzione come testo. Un tag i cui campi non possono funzionare non viene
salvato, e la sua pagina dice cosa manca:

| Si lascia o si inserisce | La pagina del tag dice |
| --- | --- |
| Nessuna tabella | « Inserire la tabella in cui si trova il valore. » |
| Una tabella in tre parti, o una virgoletta non chiusa | « La tabella è un nome, oppure uno schema e un nome (dbo.Orders); mettere un nome con spazi tra virgolette o parentesi quadre. Ogni parte ha al massimo 128 caratteri. » |
| Nessuna colonna del valore | « Inserire la colonna del valore. » |
| Una colonna del valore in due parti | « La colonna del valore è un solo nome; mettere un nome con spazi tra virgolette o parentesi quadre. » |
| Tramite la sua chiave, senza colonna chiave | « Inserire la colonna chiave che sceglie la riga. » |
| Tramite la sua chiave, senza valore chiave | « Inserire il valore chiave: ciò che la colonna chiave contiene nella riga da leggere. » |
| La più recente, senza colonna data | « Inserire la colonna data il cui valore più alto è la riga più recente. » |

La riga in sola lettura **Indirizzo di origine (bus)** mostra ciò che il driver riceve:
`row?table=dbo.ProductionOrders&column=Quantity&key=OrderNo&equals=4711` per una riga tramite la sua
chiave, `newest?table=recipe_setpoints&column=oven_temperature&by=updated_at` per la riga più recente.

### Query propria {#own-query}

| Campo | Significato | Predefinito |
| --- | --- | --- |
| **Testo della query** | « Un SELECT che legge soltanto; il valore è la prima colonna della prima riga. @now è la data e l'ora della stazione. » Anche una query che inizia con WITH e legge soltanto viene accettata. | vuoto |

La query si legge come la legge il database: ciò che sta dentro un testo tra virgolette, un nome tra
virgolette o un commento non è una parola dell'istruzione. Una query che fa più che leggere viene
rifiutata dove viene scritta:

| Si inserisce | La pagina del tag dice |
| --- | --- |
| Nulla | « Inserire la query: un SELECT la cui prima colonna della prima riga è il valore. » |
| Un'istruzione che non è un SELECT, come `UPDATE` o `DELETE` | « La query inizia con SELECT o WITH. Per scrivere, lasciare che l'accesso del tag scriva e usare un comando di scrittura. » |
| Un SELECT che cambia il database: `SELECT ... INTO`, `FOR UPDATE`, oppure uno che contiene `INSERT`, `UPDATE`, `EXEC` | « La query cambierebbe il database (INSERT, UPDATE, SELECT INTO, FOR UPDATE, EXEC e simili). Una query legge soltanto. » |
| Due istruzioni | « La query contiene più di un'istruzione. Lasciare un solo SELECT. » |
| Una virgoletta, una parentesi o un commento non chiuso | « Una virgoletta, una parentesi o un commento della query non è chiuso. » |
| Un valore diverso da `@now`, come `@order` | « Una query accetta solo @now, la data e l'ora della stazione. Scrivere ogni altro valore nel testo. » |

L'indirizzo di bus di un tag di query è `query?sql=` seguito dalla query, codificata in percentuale.

## Lettura {#reading}

| Un tag che legge | Come viene letto |
| --- | --- |
| Tramite la sua chiave | La sua colonna, nella riga la cui colonna chiave è uguale al valore chiave; il valore chiave viene passato al database come parametro. |
| La riga più recente | La sua colonna, nella riga la cui colonna data ha il valore più alto di quella colonna (`WHERE ... = (SELECT MAX(...) FROM ...)`), una forma che ogni database e ODBC leggono allo stesso modo; quando due righe sono pari, si legge la prima. |
| Più tag della stessa tabella e della stessa riga | Una query per passata per tutte le loro colonne, per non interrogare il database della fabbrica una volta per tag. Se il database rifiuta quella query (una colonna che la tabella non ha), la colonna del tag stesso viene letta da sola e quella riga viene letta colonna per colonna da quel momento: una colonna sbagliata costa solo il suo tag. |
| La sua query propria | La query così come è scritta, con `@now` passato come parametro. |

| Il database risponde | Il tag |
| --- | --- |
| Un valore di un tipo che il tag legge | Lo legge. |
| Una cella vuota (NULL) | Fallisce la lettura. |
| Nessuna riga con il valore chiave, o più di una | Fallisce la lettura: un tag legge una riga. |
| La riga più recente di una tabella che non ha ancora righe | Attende, senza errore. |
| Una query che non restituisce nessuna riga | Fallisce la lettura. |
| Un valore che il tipo del tag non legge (un testo in un tag Int32, decimali in un tag di numero intero) | Fallisce la lettura, con il motivo. |

Una lettura fallita conserva l'ultima lettura del tag, che conta come fallita quando è più vecchia del
suo intervallo di interrogazione più 10 s, come in ogni driver. Il database viene interrogato a ogni
lettura: un cambiamento che fa un altro programma si vede alla lettura successiva, mai nell'istante in
cui viene fatto.

## Scrittura {#writing}

Un tag scrive solo quando il suo **Accesso** è **Lettura e scrittura** o **Sola scrittura**. La sua
scheda Sorgente aggiunge allora cosa fa una scrittura:

| Campo | Mostrato | Significato | Predefinito |
| --- | --- | --- | --- |
| **Una scrittura** | il tag scrive | **Cambia la cella che legge**: « Cambia la cella che questo tag legge, in una sola riga. Un valore più nuovo ancora in attesa sostituisce uno più vecchio. » **Esegue il suo comando di scrittura**: « Esegue il comando di scrittura qui sotto. Ogni scrittura conta: nessuna ancora in attesa viene scartata per una più nuova. » Un tag che legge la sua query propria non ha una cella da cambiare, e gli viene offerto solo **Esegue il suo comando di scrittura**. | **Cambia la cella che legge** |
| **Comando di scrittura** | Esegue il suo comando di scrittura | « Un UPDATE con il suo WHERE, un INSERT o la chiamata a una procedura memorizzata. @value è il valore scritto e @now la data e l'ora della stazione; il valore è sempre un parametro, mai incollato nel testo. » Il campo vuoto mostra `INSERT INTO results (result, recorded_at) VALUES (@value, @now)` come suggerimento. | vuoto |

Un tag in **Sola scrittura** che esegue il suo comando di scrittura non legge nulla, quindi la sua
scheda Sorgente contiene solo la scrittura.

| Una scrittura | Come avviene |
| --- | --- |
| Cambia la cella che legge | Un UPDATE che porta la colonna del valore a `@value` nella riga del tag, dentro una transazione annullata a meno che sia cambiata esattamente una riga: un valore chiave che nessuna riga contiene, o che due righe contengono, non scrive nulla e lo dice. Un valore più nuovo ancora in attesa sostituisce uno più vecchio, come per ogni setpoint. |
| Esegue il suo comando di scrittura | Il comando viene eseguito una volta per scrittura, con il valore come `@value` e la data e l'ora della stazione come `@now`. Ogni scrittura conta: due INSERT sono due righe, e una scrittura ancora in attesa non viene mai scartata per una più nuova. Una procedura memorizzata si chiama con `EXEC`, `CALL` o il `{call ...}` di ODBC. |
| Un valore con decimali, in un tag di numero intero | Arrotondato, non rifiutato: un valore con decimali scritto in un tag di numero intero (Int32 o Int64) senza Scalatura né Taratura viene arrotondato all'intero più vicino prima di raggiungere il database, con le metà lontano da zero (2,5 scrive 3, -2,5 scrive -3). Rifiutare i decimali è il modo di leggere una cella, non di scrivere un valore. |
| Risposta | Fatta, oppure rifiutata con le parole del database stesso: un permesso che manca all'utente, un vincolo che la riga viola, un valore che la colonna non accetta. |

Un comando che il driver non può eseguire viene rifiutato sulla pagina del tag:

| Si inserisce | La pagina del tag dice |
| --- | --- |
| Nulla | « Inserire il comando di scrittura: un UPDATE, un INSERT o la chiamata a una procedura memorizzata che contenga @value. » |
| Un comando che inizia con un'altra parola, come `DELETE` | « Il comando di scrittura inizia con UPDATE, INSERT, EXEC o CALL. » |
| Due istruzioni | « Il comando di scrittura contiene più di un'istruzione. Lasciarne una, oppure chiamare una procedura memorizzata. » |
| Una virgoletta, una parentesi o un commento non chiuso | « Una virgoletta, una parentesi o un commento del comando di scrittura non è chiuso. » |
| Nessun `@value` | « Mettere @value dove va il valore scritto: è sempre un parametro, mai incollato nel testo. » |
| Un valore diverso da `@value` e `@now`, come `@line` | « Un comando di scrittura accetta solo @value, il valore scritto, e @now, la data e l'ora della stazione. » |
| Un UPDATE senza WHERE | « Un UPDATE senza WHERE cambia tutte le righe della tabella. Aggiungere il WHERE che sceglie la riga. » |

I nomi con `@@`, le funzioni di SQL Server, e i nomi di argomento di un `EXEC` (`@speed = @value`) non
sono valori e restano come sono scritti. Il salvataggio segue le stesse regole, e il registro delle
azioni lo dice quando rifiuta un tag:
« Il tag 'Order quantity' non dice dove si trova il suo valore nel database, oppure ha una query o un comando di scrittura che il suo driver rifiuta. Aprirlo e correggere il campo che il pannello del tag segnala. »

L'indirizzo di bus aggiunge la scrittura: `row?table=recipes&column=setpoint&key=code&equals=R-100&write=cell`
per una cella, `none?write=command&command=` seguito dal comando per un tag in **Sola scrittura** che
esegue il suo comando.

## Rilevamento {#discovery}

### La scheda Ambito della scansione {#the-scan-scope-card}

Per Configurare il connector, la pagina del driver porta la sua scheda **Ambito della scansione**,
sotto la riga
« Accede come fa il dispositivo e propone un tag per ogni colonna delle sue tabelle, alla riga più recente o alla riga della chiave più piccola. »

| Campo | Significato | Predefinito |
| --- | --- | --- |
| **Dispositivo da scansionare** | Un dispositivo del driver, per nome. Un database si scansiona solo tramite un dispositivo, che ne conserva l'accesso; una modifica del dispositivo ancora in attesa di salvataggio viene salvata prima. | il primo dispositivo per nome |
| **Tabelle** | « Vuoto per tutte le tabelle, oppure un nome o un modello in cui * sta per qualsiasi carattere, come Ricetta*. » | vuoto |

Un driver senza dispositivi contiene solo
« Un database si scansiona tramite un dispositivo, che ne conserva l'accesso. Aggiungere un dispositivo di questo driver e dire dove si trova il suo database. »
Ciò che ancora manca all'ambito viene detto in rosso sotto la scheda, e Rileva resta in grigio finché
non è completo:

| L'ambito | La scheda dice |
| --- | --- |
| Un dispositivo che non dice dove si trova il suo database, o che ha un'impostazione che il suo driver non può usare | « Il dispositivo 'Recipes' non dice ancora dove si trova il suo database, oppure ha un'impostazione che il suo driver non può usare. Completare prima la sua scheda Connessione al database. » |
| Un modello con schema, virgolette, parentesi quadre o punto e virgola, come `dbo.Recipe*` | « 'dbo.Recipe*' non è un nome né un modello di tabella. Scrivere un nome, oppure un modello in cui * sta per qualsiasi carattere, senza schema né virgolette. » |
| Un dispositivo che non esiste più | « Il dispositivo scelto per la scansione non esiste più. Scegliere un altro dispositivo da scansionare. » |

Quando il dispositivo da scansionare ha una modifica che non si può salvare, la scansione non parte:
« Il rilevamento non è partito perché non è stato possibile salvare la modifica del dispositivo da scansionare. »

### Cosa offre la scansione {#what-the-scan-offers}

La scansione accede come fa il dispositivo, legge l'elenco che il database stesso tiene delle sue
tabelle e viste (escluse quelle di sistema) e delle loro colonne, e dice quale tabella legge, come
« Lettura delle colonne di recipes… ». Propone un candidato, il dispositivo stesso, segnato come
**Già configurato**: la sua scheda elenca, spuntate, le colonne che nessun tag del dispositivo legge
ancora, e **Aggiungi i tag**, prima di **Apri**, aggiunge quelle spuntate al dispositivo, in sola
lettura.

| La tabella | I suoi tag candidati |
| --- | --- |
| Ha una colonna di data e ora | Uno per colonna, che legge la riga più recente secondo la prima colonna di quel tipo. |
| Non ne ha | Uno per colonna tranne la sua chiave, che legge la riga del valore più piccolo della sua chiave primaria (o della sua prima colonna, in una tabella che non ne ha): cambiare il **Valore chiave** del tag per leggere un'altra riga. |
| Non ne ha, e non ha righe | Lasciata fuori. |
| Una colonna che nessun tipo di tag legge (dati binari, una geometria) | Lasciata fuori, e contata nella riga che chiude la scansione. |

I tag prendono il nome dalla loro tabella e colonna (`recipes.oven_temperature`), il tipo dalla
colonna, e ognuno è in **Sola lettura**. Una scansione legge al massimo 100 tabelle e 500 valori; una
scansione interrotta lì dice
« La scansione di Recipes si è fermata prima. Una scansione legge al massimo 100 tabelle e 500 valori: indicare un nome di tabella o un modello per vedere gli altri. »
Il resto del comportamento della scansione si trova in [Rilevamento](connector-discovery).

La pagina stessa di un dispositivo fa lo stesso senza la pagina del suo driver: **Rileva tag**, nell'intestazione della sua scheda dei tag, si autentica come il dispositivo, scansiona il suo database così com'è salvato e offre solo le colonne per cui non ha ancora un tag (vedere [Rilevare i tag di un dispositivo](connector-discovery#discovering-a-devices-tags)).

## Esempio: un ordine di produzione da SQL Server {#example-a-production-order-from-sql-server}

Il MES tiene i suoi ordini di produzione in SQL Server, nella tabella `dbo.ProductionOrders` del
database `MES`, una riga per numero d'ordine:

1. Sulla pagina del driver, **Aggiungi dispositivo**; chiamarlo `MES`. Sulla scheda
   **Connessione al database** tenere **SQL Server**, inserire **Server del database** `mes-sql` (o
   `mes-sql\SQLEXPRESS` per un'istanza denominata), lasciare **Porta** vuoto e inserire
   **Nome del database** `MES`.
2. Sulla scheda **Accesso** inserire **Nome utente** `station_reader` e la sua
   **Password di accesso**, oppure attivare **Accedere con l'account Windows della stazione**. Per
   un server di fabbrica con un proprio certificato, scegliere
   **Crittografata, qualsiasi certificato accettato**.
3. **Aggiungi tag** `Order quantity`, Int32: **Tabella** `dbo.ProductionOrders`, **Colonna del valore**
   `Quantity`, **Riga da leggere** **Tramite la sua chiave**, **Colonna chiave** `OrderNo`,
   **Valore chiave** `4711`. Il suo indirizzo di bus è
   `row?table=dbo.ProductionOrders&column=Quantity&key=OrderNo&equals=4711`.
4. **Aggiungi tag** `Product`, String, sulla stessa tabella e la stessa riga, **Colonna del valore**
   `ProductCode`: i due tag si leggono in una sola query.

Con gli strumenti:

```
device_upsert name=MES driver=Database databaseKind=SqlServer host="mes-sql\SQLEXPRESS" databaseName=MES username=station_reader password=<password> databaseEncryption=TrustServerCertificate
tag_upsert device=MES name="Order quantity" opcUaType=Int32 databaseTable=dbo.ProductionOrders address=Quantity databaseRow=Key databaseKeyColumn=OrderNo databaseKeyValue=4711
tag_upsert device=MES name=Product opcUaType=String databaseTable=dbo.ProductionOrders address=ProductCode databaseRow=Key databaseKeyColumn=OrderNo databaseKeyValue=4711
```

## Esempio: un setpoint di ricetta da PostgreSQL {#example-a-recipe-setpoint-from-postgresql}

Il sistema delle ricette aggiunge una riga a `recipe_setpoints` ogni volta che un setpoint cambia,
datata nella sua colonna `updated_at`:

1. **Aggiungi dispositivo**; chiamarlo `Recipes`, **Tipo di database** **PostgreSQL**,
   **Server del database** `recipes-db`, **Porta** vuoto (la 5432 di PostgreSQL), **Nome del database**
   `recipes`, poi **Nome utente** e **Password di accesso** sulla scheda **Accesso**.
2. **Aggiungi tag** `Oven setpoint`, Double: **Tabella** `recipe_setpoints`, **Colonna del valore**
   `oven_temperature`, **Riga da leggere** **La più recente**, **Colonna data** `updated_at`.

Ogni riga nuova si legge alla lettura successiva del tag. PostgreSQL legge un nome semplice in
minuscolo, quindi una colonna creata con maiuscole, come `OvenTemp`, si scrive tra virgolette:
`"OvenTemp"`. Con gli strumenti:

```
device_upsert name=Recipes driver=Database databaseKind=PostgreSql host=recipes-db databaseName=recipes username=station password=<password>
tag_upsert device=Recipes name="Oven setpoint" opcUaType=Double databaseTable=recipe_setpoints address=oven_temperature databaseRow=Newest databaseDateColumn=updated_at
```

## Esempio: una riga di risultato scritta con un INSERT {#example-a-result-line-written-as-an-insert}

Il sistema qualità vuole una riga per pezzo misurato nella sua tabella `test_results`:

1. Sul dispositivo di quel database, **Aggiungi tag** `Result`, Double, **Accesso** **Sola scrittura**.
2. **Una scrittura** **Esegue il suo comando di scrittura**, e in **Comando di scrittura** scrivere
   `INSERT INTO test_results (result, recorded_at) VALUES (@value, @now)`, che aggiunge la riga.

Ogni valore che Logic scrive in `Result` diventa una riga a sé, con accanto la data e l'ora della
stazione; le scritture che si susseguono rapidamente vengono tutte tenute, in ordine, nessuna scartata
per una più nuova. Un tag in **Lettura e scrittura** che legge una cella cambierebbe invece la sua
unica riga a ogni scrittura. Con gli strumenti:

```
tag_upsert device=Quality name=Result opcUaType=Double accessType=WriteOnly databaseWriteMode=Command databaseCommand="INSERT INTO test_results (result, recorded_at) VALUES (@value, @now)"
```

## Esempio: Access e Oracle tramite ODBC {#example-access-and-oracle-through-odbc}

| Database | Su questo computer | Sul dispositivo |
| --- | --- | --- |
| Microsoft Access | L'Access Database Engine di Microsoft, a 64 bit | **Tipo di database** **Altro (ODBC)**, **Origine dati o testo di connessione** `Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=C:\Data\plant.accdb`, senza utente |
| Oracle | Il driver ODBC a 64 bit di Oracle, e un'origine dati `ERP` creata per esso in Origine dati ODBC (64 bit) | **Tipo di database** **Altro (ODBC)**, **Origine dati o testo di connessione** `ERP`, **Nome utente** e **Password di accesso** sulla scheda **Accesso** |

Quando l'origine dati o il driver che un dispositivo indica esiste solo per i programmi a 32 bit (un
Access di un Office a 32 bit, un'origine dati creata in Origine dati ODBC (32 bit)), oppure non esiste
affatto su questo computer, il pannello del dispositivo dice quale dei due casi è, con il nome che il
dispositivo ha dato, e dà l'istruzione completa finché il dispositivo è attivo e non si connette.
L'errore del dispositivo dice lo stesso in inglese, e così `driver_capabilities`, nel `prerequisite`
del driver Database (`perDevice` true):

| Parte | Cosa dice |
| --- | --- |
| Cosa manca | Un driver ODBC a 64 bit per quell'origine dati o quel driver: Ganter Lab è un programma a 64 bit e vede solo i driver ODBC a 64 bit e le origini dati create con essi, e non può includere il driver di un altro produttore |
| Dove procurarlo | Dal produttore del database; per Access, la pagina di Microsoft "Download and install Microsoft 365 Access Runtime", il cui runtime porta l'Access Database Engine |
| Quale scegliere | La versione a 64 bit (per Access, il pulsante di download a 64 bit della propria lingua), mai quella a 32 bit |
| Come installarlo | Come amministratore; poi, per un dispositivo che indica un'origine dati, crearla in Origine dati ODBC (64 bit) con lo stesso nome e lo stesso database |
| Inciampo noto | Per Access su un computer che ha Office: la pagina di Microsoft dice che l'Access Runtime installato corrisponde all'Office già installato e non è compatibile con un Office installato con Windows Installer, e la pagina di Microsoft sulla scelta tra la versione a 64 bit e quella a 32 bit di Office dice che una versione va disinstallata prima di installare l'altra |
| Dopo | Al prossimo tentativo di connessione il dispositivo si connette con la configurazione che ha, senza riavviare Ganter Lab |

L'avviso termina con i driver ODBC a 64 bit di questo computer, con il nome che prende `Driver={...}`.
Un'origine dati o un driver che esiste a 64 bit e che fallisce comunque mostra l'errore proprio del
gestore dei driver.

Tramite ODBC un nome semplice si legge come lo legge il database (in maiuscolo su Oracle) e un nome tra
virgolette esattamente; date e ore vengono inviate al secondo intero, la precisione più fine che ogni
driver ODBC accetta. Con gli strumenti:

```
device_upsert name="Plant book" driver=Database databaseKind=Odbc databaseOdbc="Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=C:\Data\plant.accdb"
device_upsert name=ERP driver=Database databaseKind=Odbc databaseOdbc=ERP username=STATION password=<password>
```

## Strumenti dell'agente {#agent-tools}

`driver_capabilities` descrive questo driver sotto `database`: il percorso semplice, ogni database con
i suoi campi e la sua porta propria, le scelte di crittografia, i due modi in cui un tag legge e i due
in cui scrive, la regola per cui un valore entra in un'istruzione solo come `@value` o `@now`, i tipi,
i tempi di attesa, il consiglio sull'utente del database, il rilevamento e quattro esempi completi.
`device_upsert driver=Database` accetta `databaseKind` (`SqlServer`, il predefinito, `PostgreSql`,
`MySql`, `Sqlite` o `Odbc`), e per un server `host` (un `server\INSTANCE` o `server,port` di SQL Server
viene separato), `port`, `databaseName`, e su SQL Server `databaseInstance`, `databaseWindowsSignIn` e
`databaseEncryption` (`Verified`, `TrustServerCertificate` o `Off`); `databaseFile` per SQLite,
`databaseOdbc` per ODBC; `username` e `password`, che non viene mai restituita. Un dispositivo che
restituisce porta `database`, con l'indirizzo e se il dispositivo attende ancora il suo utente.
`tag_upsert` accetta la colonna del valore come `address`, `databaseSource` (`Fields` o `Query`),
`databaseTable`, `databaseRow` (`Key` o `Newest`), `databaseKeyColumn`, `databaseKeyValue`,
`databaseDateColumn`, `databaseQuery`, e quando l'`accessType` scrive, `databaseWriteMode` (`Cell` o
`Command`) e `databaseCommand`; un tag nuovo è in sola lettura e Double a meno che `opcUaType` dica
altro. `device_discover driver=Database` accetta `databaseDevice`, il nome di un dispositivo
configurato del driver, e facoltativamente `databaseTables`, un nome di tabella o un modello con `*`; i
tag che mancano al dispositivo si aggiungono con `tag_upsert`. `connector_browse` restituisce il
`databaseKind` di ogni dispositivo. Gli altri strumenti si trovano in
[Strumenti dell'agente](agent-tools).

## Limiti da conoscere {#limits-worth-knowing}

- Il driver legge e scrive i valori dei suoi tag, un valore per tag. Non registra le prove della
  stazione in un database: una riga per prova con il suo numero di serie, il suo verdetto e le sue
  misure non è qualcosa che questo driver scrive.
- Nessun driver ODBC viene fornito con il prodotto. Oracle, Access, Firebird e ogni database diverso
  dai quattro raggiunti direttamente hanno bisogno del loro driver ODBC a 64 bit installato sul
  computer; uno a 32 bit non viene visto.
- Il dispositivo non conserva nessuna password di Windows: l'accesso Windows a SQL Server, e un file
  SQLite su una condivisione di rete, usano l'account con cui gira la stazione, quindi il database o la
  condivisione devono lasciar entrare quell'account.
- Una query legge soltanto. Ciò che cambia il database è una scrittura, e una scrittura avviene solo su
  un tag il cui accesso scrive.
- Un valore non entra mai nel testo di un'istruzione: il valore scritto è sempre il parametro
  `@value`, la data e l'ora della stazione `@now`, e anche un valore chiave è un parametro.
  Un'istruzione che nomina qualsiasi altro valore viene rifiutata.
- Una scrittura di cella cambia esattamente una riga; un comando UPDATE ha bisogno del suo WHERE.
- Il database viene interrogato a ogni lettura: un cambiamento che fa un altro programma si vede alla
  lettura successiva, e un database con molti tag di una stessa riga viene interrogato una volta per
  passata per tutti.
- Una colonna binaria non viene letta.
