# Database

> Valores que un sistema de la fábrica guarda en una tabla de base de datos (SQL Server, PostgreSQL, MySQL y MariaDB, SQLite, o cualquier base de datos por ODBC) leídos como tags, y escritos de vuelta donde un tag lo indica: las tarjetas Base de datos e Inicio de sesión, un tag que lee una celda por su tabla, su columna y su fila o ejecuta una consulta propia, las dos maneras en que un tag escribe, la exploración que ofrece un tag por columna, cuatro ejemplos completos y lo que el controlador no hace.

El controlador Database lee los valores que un sistema de la fábrica guarda en una tabla de base de
datos: la orden de producción que mantiene el MES, la receta o la consigna que guarda el ERP, el
resultado que registró otro sistema de ensayo. Cada tag lee un valor de una tabla, y sus valores llegan
donde llegan los de cualquier otro tag: a Logic, a los paneles de control, a los ensayos de un banco de
pruebas de [Process](process). Donde un tag lo indica, el controlador también escribe de vuelta: en la
celda que lee el tag, o mediante un comando propio del tag.

SQL Server, PostgreSQL, MySQL y MariaDB, y SQLite se alcanzan sin instalar nada en el ordenador.
Cualquier otra base de datos (Oracle, Access, Firebird y otras) se alcanza a través de su propio
controlador ODBC, que tiene que estar instalado en el ordenador en su versión de 64 bits.

Leer es el uso principal, y su camino es corto: un dispositivo indica dónde está su base de datos y
cómo inicia sesión la estación, la exploración ofrece un tag por columna de sus tablas, y todo tag nace
de solo lectura. Lo que tiene que ver con la escritura solo aparece en un tag cuando su acceso deja
escribir a la estación.

Un dispositivo de este controlador es una base de datos: dos bases de datos del mismo servidor son dos
dispositivos. Los dispositivos cuelgan del controlador y pueden archivarse en carpetas,
**Controlador → Carpeta → Dispositivo → Tag**, como en cualquier controlador sin línea serie ni broker.

## La página del controlador {#the-drivers-page}

La línea bajo la cabecera dice "Cada dispositivo lee una base de datos. Añada un dispositivo, indique dónde está su base de datos y cómo inicia sesión la estación, y después explórelo para añadir sus columnas como tags."
Para Configurar el conector, la página contiene la tarjeta Alcance de la exploración propia del
controlador, con el dispositivo cuya base de datos lee la exploración (vea [Descubrimiento](#discovery)).
Debajo está la tabla **Dispositivos** de cualquier controlador (vea
[La tabla de dispositivos](connector-drivers#the-device-table)), que crece con **Añadir dispositivo**,
a la derecha de su título; vacía, dice "Todavía no hay dispositivos." La barra del controlador lleva
**Descubrir**, en gris hasta que la tarjeta Alcance de la exploración está completa. El controlador
aparece en **Archivos y bases de datos**, junto al controlador [File](driver-file).

## Dispositivo {#device}

Un dispositivo de este controlador no tiene tarjeta Conexión: en su lugar está la tarjeta
**Base de datos**, seguida de la tarjeta **Inicio de sesión** en toda base de datos salvo SQLite, que no
tiene inicio de sesión. El **Intervalo de sondeo (ms)** de la tarjeta Dispositivo, 1000 por defecto,
indica cada cuánto leen los tags la base de datos, y cada tag puede sustituirlo como en cualquier
controlador.

Un dispositivo nuevo es uno de SQL Server sin servidor: dice "Servidor aún sin indicar", su página dice "La estación no llega a este dispositivo mientras no indique dónde está su base de datos, así que todavía no se lee nada de él y no hay ningún error que mostrar. Indíquelo en la tarjeta Base de datos de abajo (el servidor y el nombre de la base de datos, el archivo o el origen de datos) y la estación se conecta a ella."
y no se lee nada hasta que lo indica. La Base URI que muestra la cabecera es la dirección de ejecución,
que nunca lleva el usuario ni la contraseña:
`database://sqlserver?server=mes-sql&instance=SQLEXPRESS&database=MES` para una instancia de SQL
Server, `database://sqlite?file=C%3A%5CData%5Crecipes.db` para un archivo SQLite,
`database://odbc?source=ERP` para un origen de datos ODBC.

### La tarjeta Base de datos {#the-database-card}

| Campo | Se muestra | Significado | Predeterminado |
| --- | --- | --- | --- |
| **Tipo de base de datos** | siempre | **SQL Server**, **PostgreSQL**, **MySQL / MariaDB**, **SQLite** u **Otra (ODBC)**, que dice "Usa el controlador ODBC de 64 bits instalado en este ordenador para esa base de datos (Oracle, Access, Firebird y otras)." | **SQL Server** |
| **Servidor** | SQL Server, PostgreSQL, MySQL / MariaDB | En SQL Server: "Un nombre de host o una dirección IP. SERVIDOR\INSTANCIA y SERVIDOR,PUERTO se separan en sus cajas." En los otros dos: "Un nombre de host o una dirección IP. Un puerto escrito tras dos puntos va a la caja Puerto." | vacío |
| **Puerto** | los mismos | Vacío para el puerto propio de la base de datos, que la caja vacía muestra: 1433, 5432 o 3306. Con una instancia de SQL Server y sin puerto, la caja no muestra nada y el SQL Server Browser del servidor indica el puerto. | vacío |
| **Nombre de la instancia** | SQL Server | "Vacío para la instancia predeterminada. Una instancia con nombre y sin puerto se encuentra a través de SQL Server Browser." Hasta 16 letras, dígitos, `_` y `$`, empezando por una letra o `_`. | vacío |
| **Nombre de la base de datos** | SQL Server, PostgreSQL, MySQL / MariaDB | La base de datos del servidor que contiene las tablas, de hasta 128 caracteres. | vacío |
| **Archivo de base de datos** | SQLite | "La ruta completa del archivo SQLite, al que se accede con los permisos de Windows de la cuenta con la que se ejecuta la estación. Se abre solo para lectura salvo que un tag escriba." La caja muestra `C:\Data\recipes.db` como sugerencia. El archivo nunca se crea. | vacío |
| **Origen de datos o texto de conexión** | Otra (ODBC) | "El nombre de un origen de datos en Orígenes de datos ODBC de Windows (64 bits), o un texto de conexión como Driver={...};Dbq=C:\Data\plant.accdb. La contraseña va en la caja de la contraseña, nunca en el texto." | vacío |

Un campo que el tipo elegido no lee no se muestra, y conserva su valor para cuando lo lea. Elegir otro
tipo vacía un puerto que quedó en el puerto propio de la base de datos anterior, para que la nueva se
alcance en el suyo. La caja Servidor separa lo que las herramientas de bases de datos escriben junto:

| Escrito en Servidor | Queda |
| --- | --- |
| `mes-sql\SQLEXPRESS` (SQL Server) | Servidor `mes-sql`, Nombre de la instancia `SQLEXPRESS` |
| `mes-sql,1433` (SQL Server) | Servidor `mes-sql`, Puerto 1433 |
| `10.0.0.5:5433` | Servidor `10.0.0.5`, Puerto 5433 |

Mientras la tarjeta no indica dónde está la base de datos, el dispositivo se guarda tal como está y
espera, con el punto gris, sin leer nada y sin mostrar ningún error:

| Tipo de base de datos | Espera | El dispositivo dice |
| --- | --- | --- |
| SQL Server, PostgreSQL, MySQL / MariaDB | El servidor, después el nombre de la base de datos | "Servidor aún sin indicar", después "Nombre de la base de datos aún sin indicar" |
| SQLite | El archivo | "Archivo de base de datos aún sin indicar" |
| Otra (ODBC) | El origen de datos o el texto de conexión | "Origen de datos aún sin indicar" |

Un ajuste que la tarjeta no admite se indica bajo las tarjetas, y el dispositivo no se guarda hasta
que se corrige:

| Usted indica | La tarjeta dice |
| --- | --- |
| Un servidor con esquema, ruta o espacio, como `http://mes-sql` | "El servidor es solo un nombre de host o una dirección IP: sin esquema, sin ruta y sin espacios. Una instancia de SQL Server y un puerto escritos con él van a sus propias cajas." |
| Un nombre de instancia con un espacio o un signo que no sea `_` ni `$` | "El nombre de la instancia tiene hasta 16 letras, dígitos, guiones bajos y signos de dólar." |
| Un nombre de base de datos de más de 128 caracteres | "El nombre de la base de datos tiene como mucho 128 caracteres, sin caracteres de control." |
| Un archivo que no es una ruta completa, como `recipes.db` | "El archivo de base de datos es una ruta completa: una unidad, una carpeta y el archivo (C:\Data\recetas.db), o un recurso compartido de red (\\server\data\recetas.db)." |
| Una carpeta, o una ruta con `*`, `?`, `<`, `>` o comillas | "Indique la ruta del propio archivo de base de datos, no la de una carpeta, sin * ? \| < > ni comillas." |
| Un nombre de origen de datos con un corchete, una llave, un paréntesis, una coma, un punto y coma o uno de `? * ! @ \` | La tarjeta pide el nombre que tiene el origen en Orígenes de datos ODBC, o un texto de conexión. |
| Un texto de conexión que no son pares `key=value`, como una llave que queda abierta | "Un texto de conexión son pares key=value separados por punto y coma; un valor que contiene un punto y coma va entre llaves." |
| Un texto de conexión con `PWD=` o `Password=` | "Saque la contraseña del texto de conexión e indíquela en la caja de la contraseña: el texto se muestra y se escribe en el diario técnico, y esa caja la guarda protegida." |

El guardado sigue las mismas reglas, y el registro de acciones lo dice cuando rechaza una:
"El dispositivo 'MES' tiene un ajuste de base de datos que su controlador no puede usar. Ábralo y corrija el campo que marca su panel."

### La tarjeta Inicio de sesión {#the-sign-in-card}

Toda base de datos salvo SQLite lleva la tarjeta **Inicio de sesión**, con el usuario con el que inicia
sesión la estación.

| Campo | Se muestra | Significado | Predeterminado |
| --- | --- | --- | --- |
| **Iniciar sesión con la cuenta de Windows de la estación** | SQL Server | Activado: "Se inicia sesión en SQL Server con la cuenta de Windows con la que se ejecuta la estación; no se guarda usuario ni contraseña." Desactivado: "Se inicia sesión en SQL Server con un usuario de la base de datos y su contraseña." | desactivado |
| **Nombre de usuario** | un servidor en el que se inicia sesión con un usuario, y ODBC, donde es opcional | El usuario de la base de datos. | vacío |
| **Contraseña** | los mismos | La contraseña del usuario, guardada protegida como toda credencial de un controlador: nunca se vuelve a mostrar, nunca se escribe en la dirección ni en el diario técnico, y nunca se devuelve a un agente. | vacío |
| **Cifrado** | SQL Server | **Cifrada, certificado comprobado**: "La conexión va cifrada y el certificado del servidor se comprueba con los certificados en los que confía este ordenador." **Cifrada, se acepta cualquier certificado**: "La conexión va cifrada y se acepta cualquier certificado, como necesita un servidor de fábrica con su propio certificado. No se comprueba la identidad del servidor." **Sin cifrar**: "Los valores y los comandos viajan sin cifrar. Solo para un servidor antiguo que no ofrece cifrado." | **Cifrada, certificado comprobado** |

PostgreSQL y MySQL no tienen el campo Cifrado: su conexión va cifrada siempre que el servidor lo ofrece.
Un SQL Server con un certificado propio, como suele tenerlo un servidor de fábrica, rechaza la conexión
con las palabras de la base de datos sobre una cadena de certificados emitida por una entidad que no es
de confianza: **Cifrada, se acepta cualquier certificado** es la opción que pide ese servidor.

Bajo sus campos, la tarjeta dice qué usuario pedir:
"Pida al administrador de la base de datos un usuario con solo los permisos que la estación necesita: lectura, y escritura solo en las tablas que cambian sus tags."
La estación lee y escribe los datos de otro sistema con todo lo que su usuario puede hacer, así que ese
usuario es el límite real de lo que la estación puede cambiar. Toda escritura queda en el diario
técnico, como en cualquier controlador.

Un servidor en el que se inicia sesión con un usuario, sin usuario indicado todavía, dice en su lugar
"Indique el usuario con el que la estación inicia sesión. Hasta entonces la estación no conecta este dispositivo."
No es un rechazo: el dispositivo se guarda, y su error dice que todavía no se ha indicado ningún usuario
de la base de datos, hasta que el usuario esté ahí.

### En línea, en espera y fuera de línea {#online-waiting-and-offline}

| Situación | Lo que hacen el dispositivo y sus tags |
| --- | --- |
| La tarjeta Base de datos no indica dónde está la base de datos | El dispositivo espera, punto gris, sin leer nada y sin error (vea arriba). |
| Un servidor en el que se inicia sesión con un usuario, sin ninguno indicado | La estación no conecta el dispositivo; su error dice que todavía no se ha indicado ningún usuario de la base de datos. |
| El servidor no responde, rechaza el inicio de sesión, o el archivo SQLite no existe | El dispositivo queda fuera de línea con la causa que da la propia base de datos, y la estación lo conecta en cuanto puede. Una conexión se espera como mucho 10 s. |
| La conexión se pierde (el servidor se reinició, la red se cortó) | El dispositivo queda fuera de línea y se vuelve a conectar. |
| Una instrucción que la base de datos rechaza, o que sigue en marcha pasados 10 s | Lectura fallida de ese tag, con las palabras de la propia base de datos; el dispositivo sigue leyendo sus otros tags. |
| Un tag lee la fila más reciente de una tabla que todavía no tiene filas | Ese tag espera, sin error, y el estado de salud de su dispositivo lo deja fuera hasta que se escribe la primera fila: los otros tags del dispositivo dicen si está en línea. Una tabla que se vacía después de que el tag leyó de ella no es esa espera: el tag conserva su última lectura, que cuenta como fallida cuando es más antigua que su intervalo de sondeo más 10 s, como en cualquier controlador. |

Cada dispositivo tiene una conexión propia, abierta cuando se conecta y nunca compartida ni guardada en
un pool, así que una conexión que un servidor cortó nunca se vuelve a usar.

### Preparar el servidor {#prepare-the-server}

Las tres bases de datos de red vienen cerradas a otro ordenador en instalaciones habituales.
Cada uno de estos ajustes se hace en el servidor, por quien lo administra; la estación no cambia
ninguno.

| Base de datos | Qué deja fuera a la estación | Dónde se cambia |
| --- | --- | --- |
| SQL Server | TCP/IP desactivado (SQL Server Express y Developer vienen así) | SQL Server Configuration Manager > SQL Server Network Configuration > Protocols for la instancia: habilite TCP/IP y reinicie SQL Server (la instancia) en SQL Server Services |
| SQL Server | Una instancia con nombre escucha en un puerto que elige ella misma | TCP/IP > IP Addresses > IPAll: vacíe TCP Dynamic Ports y escriba el puerto en TCP Port, luego reinicie; o deje vacía la caja Puerto del dispositivo e indique el nombre de la instancia |
| SQL Server | Una instancia con nombre sin puerto se localiza con el SQL Server Browser, que está detenido o bloqueado (error 26) | SQL Server Services: SQL Server Browser iniciado, Start Mode Automatic; UDP 1434 permitido en el firewall del servidor |
| PostgreSQL | listen_addresses es localhost, su valor por defecto | postgresql.conf: listen_addresses = '*', luego reinicie PostgreSQL |
| PostgreSQL | Ninguna línea de pg_hba.conf deja entrar la dirección de esta estación (SQLSTATE 28000) | pg_hba.conf: una línea host para la base de datos, el usuario y la dirección de esta estación, con scram-sha-256 (md5 para una contraseña guardada con MD5), luego SELECT pg_reload_conf(); |
| MySQL, MariaDB | bind-address es 127.0.0.1, como lo definen muchos paquetes, o skip-networking está activado | La sección [mysqld] del archivo de configuración: bind-address = 0.0.0.0, skip-networking borrado, luego reinicie |
| MySQL, MariaDB | Ninguna cuenta para la dirección de esta estación (error 1130) | CREATE USER para el usuario en la dirección de esta estación, y GRANT de lo que necesitan los tags |

Todas necesitan además el puerto de la base de datos permitido en el firewall del ordenador del
servidor. Cuando el servidor responde de una de estas formas, la página del dispositivo dice qué
ajuste es, con estos pasos y los valores de este dispositivo (el puerto, la instancia, la
dirección de esta estación tal como la vio el servidor, la línea de pg_hba.conf y la cuenta de
MySQL, cada uno con un botón para copiar), en lugar del error en inglés de la base de datos. El
aviso desaparece solo en cuanto el dispositivo se conecta, o cuando el servidor responde de otra
forma. El registro y el agente leen los mismos pasos.

## Direccionamiento de tags {#tag-addressing}

### Cómo lee un tag {#how-a-tag-reads}

Un tag de este controlador no tiene caja de dirección propia: su tarjeta **Fuente** pregunta cómo lee.

| **Lee** | Lo que muestra el tag | Sus campos |
| --- | --- | --- |
| **Tabla y columna** | Una celda: la columna del valor de una fila de una tabla. | **Tabla**, **Columna del valor**, **Fila que leer** y los campos de la fila |
| **Consulta propia** | La primera columna de la primera fila que devuelve un SELECT propio. | **Texto de la consulta** |

Un tag nuevo lee **Tabla y columna**, por su clave, como Double. Los tipos que se ofrecen son Boolean,
Int32, Int64, Float, Double, String y DateTime. Al leer una celda, un tag de número entero rechaza un
valor con decimales en vez de redondearlo (la escritura es al revés, véase [Escritura](#writing)), y un
texto solo se lee como número cuando lo es. Todo tag nace de
**Solo lectura**; lo que añade la escritura está en [Escritura](#writing).

### Tabla y columna {#table-and-column}

| Campo | Se muestra | Significado | Predeterminado |
| --- | --- | --- | --- |
| **Tabla** | Tabla y columna | "Un nombre, o un esquema y un nombre (dbo.Orders). Un nombre con espacios o mayúsculas va entre comillas o corchetes, como [Order Lines]." | vacío |
| **Columna del valor** | Tabla y columna | La columna cuya celda es el valor del tag. | vacío |
| **Fila que leer** | Tabla y columna | **Por su clave**: "La fila cuya columna clave contiene el valor clave. Los tags de la misma tabla y fila se leen juntos en una sola consulta." **La más reciente**: "La fila con el valor más alto de la columna de fecha: una fecha y hora, o un número que solo crece. Los tags de la misma tabla y fila se leen juntos en una sola consulta." | **Por su clave** |
| **Columna clave** | Por su clave | La columna que elige la fila, como `OrderNo`. | vacío |
| **Valor clave** | Por su clave | Lo que contiene la columna clave en la fila que leer, como `4711`, escrito como texto: la base de datos lo lee como lee un valor de esa columna. | vacío |
| **Columna de fecha** | La más reciente | La columna cuyo valor más alto es la fila más reciente, como `updated_at`. | vacío |

Un nombre se lee como lo lee la base de datos:

| Escrito | Leído como |
| --- | --- |
| `Orders` | Un nombre simple, leído como la base de datos lee cualquier nombre simple: PostgreSQL en minúsculas, Oracle en mayúsculas. |
| `"Order Lines"`, `[Order Lines]` | El nombre exacto, sean cuales sean las comillas que admite la propia base de datos. |
| `dbo.Orders` | Un esquema y una tabla: un punto fuera de las comillas los separa. |

El controlador pone cada parte de un nombre entre las comillas que admite su base de datos, así que un
nombre nunca se pega en una instrucción como texto. Un tag cuyos campos no pueden funcionar no se
guarda, y su página dice lo que le falta:

| Usted deja o indica | La página del tag dice |
| --- | --- |
| Ninguna tabla | "Indique la tabla en la que está el valor." |
| Una tabla de tres partes, o unas comillas sin cerrar | "La tabla es un nombre, o un esquema y un nombre (dbo.Orders); ponga un nombre con espacios entre comillas o corchetes. Cada parte tiene como mucho 128 caracteres." |
| Ninguna columna del valor | "Indique la columna del valor." |
| Una columna del valor de dos partes | "La columna del valor es un solo nombre; ponga un nombre con espacios entre comillas o corchetes." |
| Por su clave, sin columna clave | "Indique la columna clave que elige la fila." |
| Por su clave, sin valor clave | "Indique el valor clave: lo que la columna clave contiene en la fila que leer." |
| La más reciente, sin columna de fecha | "Indique la columna de fecha cuyo valor más alto es la fila más reciente." |

La línea de solo lectura **Dirección de origen (bus)** muestra lo que recibe el controlador:
`row?table=dbo.ProductionOrders&column=Quantity&key=OrderNo&equals=4711` para una fila por su clave,
`newest?table=recipe_setpoints&column=oven_temperature&by=updated_at` para la fila más reciente.

### Consulta propia {#own-query}

| Campo | Significado | Predeterminado |
| --- | --- | --- |
| **Texto de la consulta** | "Un SELECT que solo lee; el valor es la primera columna de la primera fila. @now es la fecha y la hora de la estación." También se admite una consulta que empieza por WITH y solo lee. | vacío |

La consulta se lee como la lee la base de datos: lo que está dentro de un texto entre comillas, de un
nombre entre comillas o de un comentario no es una palabra de la instrucción. Una consulta que hace algo
más que leer se rechaza donde se escribe:

| Usted indica | La página del tag dice |
| --- | --- |
| Nada | "Indique la consulta: un SELECT cuya primera columna de la primera fila es el valor." |
| Una instrucción que no es un SELECT, como `UPDATE` o `DELETE` | "La consulta empieza por SELECT o WITH. Para escribir, deje que el acceso del tag escriba y use un comando de escritura." |
| Un SELECT que cambia la base de datos: `SELECT ... INTO`, `FOR UPDATE`, o uno que contiene `INSERT`, `UPDATE`, `EXEC` | "La consulta cambiaría la base de datos (INSERT, UPDATE, SELECT INTO, FOR UPDATE, EXEC y similares). Una consulta solo lee." |
| Dos instrucciones | "La consulta contiene más de una instrucción. Deje un solo SELECT." |
| Unas comillas, un corchete o un comentario sin cerrar | "Unas comillas, un corchete o un comentario de la consulta no están cerrados." |
| Un valor que no sea `@now`, como `@order` | "Una consulta solo admite @now, la fecha y la hora de la estación. Escriba cualquier otro valor en el texto." |

La dirección de bus de un tag de consulta es `query?sql=` seguido de la consulta, codificada con
porcentajes.

## Lectura {#reading}

| Un tag que lee | Cómo se lee |
| --- | --- |
| Por su clave | Su columna, en la fila cuya columna clave es igual al valor clave; el valor clave se entrega a la base de datos como parámetro. |
| La fila más reciente | Su columna, en la fila cuya columna de fecha tiene el valor más alto de esa columna (`WHERE ... = (SELECT MAX(...) FROM ...)`), una forma que toda base de datos y ODBC leen igual; cuando dos filas empatan, se lee la primera. |
| Varios tags de la misma tabla y fila | Una consulta por pasada para todas sus columnas, para no preguntar a la base de datos de la fábrica una vez por tag. Si la base de datos rechaza esa consulta (una columna que la tabla no tiene), la columna del propio tag se lee sola y esa fila se lee columna por columna desde entonces: una columna equivocada solo le cuesta a su propio tag. |
| Su consulta propia | La consulta tal como está escrita, con `@now` entregado como parámetro. |

| La base de datos responde | El tag |
| --- | --- |
| Un valor de un tipo que el tag lee | Lo lee. |
| Una celda vacía (NULL) | Falla su lectura. |
| Ninguna fila con el valor clave, o más de una | Falla su lectura: un tag lee una fila. |
| La fila más reciente de una tabla que todavía no tiene filas | Espera, sin error. |
| Una consulta que no devuelve ninguna fila | Falla su lectura. |
| Un valor que el tipo del tag no lee (un texto en un tag Int32, decimales en un tag de número entero) | Falla su lectura, con el motivo. |

Una lectura fallida conserva la última lectura del tag, que cuenta como fallida cuando es más antigua
que su intervalo de sondeo más 10 s, como en cualquier controlador. A la base de datos se le pregunta en
cada lectura: un cambio que hace otro programa se ve en la lectura siguiente, nunca en el instante en
que se hace.

## Escritura {#writing}

Un tag solo escribe cuando su **Acceso** es **Lectura y escritura** o **Solo escritura**. Su tarjeta
Fuente añade entonces lo que hace una escritura:

| Campo | Se muestra | Significado | Predeterminado |
| --- | --- | --- | --- |
| **Una escritura** | el tag escribe | **Cambia la celda que lee**: "Cambia la celda que lee este tag, en una sola fila. Un valor más nuevo que aún espera sustituye a uno más antiguo." **Ejecuta su comando de escritura**: "Ejecuta el comando de escritura de abajo. Cada escritura cuenta: ninguna que aún espera se descarta por otra más nueva." Un tag que lee su consulta propia no tiene celda que cambiar, y solo se le ofrece **Ejecuta su comando de escritura**. | **Cambia la celda que lee** |
| **Comando de escritura** | Ejecuta su comando de escritura | "Un UPDATE con su WHERE, un INSERT o la llamada a un procedimiento almacenado. @value es el valor escrito y @now la fecha y la hora de la estación; el valor siempre es un parámetro, nunca se pega en el texto." La caja vacía muestra `INSERT INTO results (result, recorded_at) VALUES (@value, @now)` como sugerencia. | vacío |

Un tag de **Solo escritura** que ejecuta su comando de escritura no lee nada, así que su tarjeta Fuente
contiene solo la escritura.

| Una escritura | Cómo se hace |
| --- | --- |
| Cambia la celda que lee | Un UPDATE que pone la columna del valor en `@value` en la fila del tag, dentro de una transacción que se deshace salvo que haya cambiado exactamente una fila: un valor clave que no tiene ninguna fila, o que tienen dos, no escribe nada y lo dice. Un valor más nuevo que aún espera sustituye a uno más antiguo, como en cualquier consigna. |
| Ejecuta su comando de escritura | El comando se ejecuta una vez por escritura, con el valor como `@value` y la fecha y la hora de la estación como `@now`. Cada escritura cuenta: dos INSERT son dos filas, y una escritura que aún espera nunca se descarta por otra más nueva. Un procedimiento almacenado se llama con `EXEC`, `CALL` o el `{call ...}` de ODBC. |
| Un valor con decimales, en un tag de número entero | Redondeado, no rechazado: un valor con decimales escrito en un tag de número entero (Int32 o Int64) sin Escala ni Calibración se redondea al número entero más cercano antes de llegar a la base de datos, y las mitades van lejos de cero (2,5 escribe 3, -2,5 escribe -3). Rechazar los decimales es la forma de leer una celda, no de escribir un valor. |
| Respuesta | Hecha, o rechazada con las palabras de la propia base de datos: un permiso que le falta al usuario, una restricción que la fila incumple, un valor que la columna no admite. |

Un comando que el controlador no puede ejecutar se rechaza en la página del tag:

| Usted indica | La página del tag dice |
| --- | --- |
| Nada | "Indique el comando de escritura: un UPDATE, un INSERT o la llamada a un procedimiento almacenado que contenga @value." |
| Un comando que empieza por otra palabra, como `DELETE` | "El comando de escritura empieza por UPDATE, INSERT, EXEC o CALL." |
| Dos instrucciones | "El comando de escritura contiene más de una instrucción. Deje una, o llame a un procedimiento almacenado." |
| Unas comillas, un corchete o un comentario sin cerrar | "Unas comillas, un corchete o un comentario del comando de escritura no están cerrados." |
| Sin `@value` | "Ponga @value donde va el valor escrito: siempre es un parámetro, nunca se pega en el texto." |
| Un valor que no sea `@value` ni `@now`, como `@line` | "Un comando de escritura solo admite @value, el valor escrito, y @now, la fecha y la hora de la estación." |
| Un UPDATE sin WHERE | "Un UPDATE sin WHERE cambia todas las filas de la tabla. Añada el WHERE que elige la fila." |

Los nombres con `@@`, las funciones de SQL Server, y los nombres de argumento de un `EXEC`
(`@speed = @value`) no son valores y se dejan como están escritos. El guardado sigue las mismas reglas,
y el registro de acciones lo dice cuando rechaza un tag:
"El tag 'Order quantity' no indica dónde está su valor en la base de datos, o tiene una consulta o un comando de escritura que su controlador rechaza. Ábralo y corrija el campo que marca el panel del tag."

La dirección de bus añade la escritura: `row?table=recipes&column=setpoint&key=code&equals=R-100&write=cell`
para una celda, `none?write=command&command=` seguido del comando para un tag de **Solo escritura** que
ejecuta su comando.

## Descubrimiento {#discovery}

### La tarjeta Alcance de la exploración {#the-scan-scope-card}

Para Configurar el conector, la página del controlador lleva su tarjeta **Alcance de la exploración**,
bajo la línea
"Inicia sesión como lo hace el dispositivo y ofrece un tag por cada columna de sus tablas, en la fila más reciente o en la fila de la clave más pequeña."

| Campo | Significado | Predeterminado |
| --- | --- | --- |
| **Dispositivo que explorar** | Un dispositivo del controlador, por su nombre. Una base de datos solo se explora a través de un dispositivo, que guarda su inicio de sesión; una edición del dispositivo que aún espera guardarse se guarda primero. | el primer dispositivo por nombre |
| **Tablas** | "Vacío para todas las tablas, o un nombre o un patrón en el que * vale por cualquier carácter, como Receta*." | vacío |

Un controlador sin dispositivos contiene solo
"Una base de datos se explora a través de un dispositivo, que guarda su inicio de sesión. Añada un dispositivo de este controlador e indique dónde está su base de datos."
Lo que todavía le falta al alcance se dice en rojo bajo la tarjeta, y Descubrir sigue en gris hasta que
está completo:

| El alcance | La tarjeta dice |
| --- | --- |
| Un dispositivo que no indica dónde está su base de datos, o que tiene un ajuste que su controlador no puede usar | "El dispositivo 'Recipes' aún no indica dónde está su base de datos, o tiene un ajuste que su controlador no puede usar. Complete primero su tarjeta Base de datos." |
| Un patrón con esquema, comillas, corchete o punto y coma, como `dbo.Recipe*` | "'dbo.Recipe*' no es un nombre ni un patrón de tabla. Escriba un nombre, o un patrón en el que * vale por cualquier carácter, sin esquema ni comillas." |
| Un dispositivo que ya no existe | "El dispositivo elegido para explorar ya no existe. Elija otro dispositivo que explorar." |

Cuando el dispositivo que explorar tiene una edición que no se puede guardar, la exploración no empieza:
"El descubrimiento no se inició porque no se pudo guardar la edición del dispositivo que se va a explorar."

### Lo que ofrece la exploración {#what-the-scan-offers}

La exploración inicia sesión como lo hace el dispositivo, lee la lista que la propia base de datos
tiene de sus tablas y vistas (sin las del sistema) y de sus columnas, y dice qué tabla lee, como
"Leyendo las columnas de recipes…". Ofrece un candidato, el propio dispositivo, marcado como
**Ya configurado**: su tarjeta lista, marcadas, las columnas que ningún tag del dispositivo lee todavía,
y **Añadir tags**, antes de **Abrir**, añade las marcadas al dispositivo, de solo lectura.

| La tabla | Sus tags candidatos |
| --- | --- |
| Tiene una columna de fecha y hora | Uno por columna, leyendo la fila más reciente según la primera columna de ese tipo. |
| No tiene | Uno por columna salvo su clave, leyendo la fila del valor más pequeño de su clave primaria (o de su primera columna, en una tabla sin ella): cambie el **Valor clave** del tag para leer otra fila. |
| No tiene, y no tiene filas | Se deja fuera. |
| Una columna que ningún tipo de tag lee (datos binarios, una geometría) | Se deja fuera, y se cuenta en la línea que cierra la exploración. |

Los tags llevan el nombre de su tabla y su columna (`recipes.oven_temperature`), el tipo de la columna,
y todos son de **Solo lectura**. Una exploración lee como mucho 100 tablas y 500 valores; una cortada
ahí dice
"La exploración de Recipes se detuvo antes de tiempo. Una exploración lee como mucho 100 tablas y 500 valores: indique un nombre de tabla o un patrón para ver los demás."
El resto del comportamiento de la exploración está en [Descubrimiento](connector-discovery).

La propia página de un dispositivo hace lo mismo sin la página de su controlador: **Descubrir tags**, en la cabecera de su tarjeta de tags, inicia sesión como el dispositivo, explora su base de datos tal como está guardado y ofrece solo las columnas para las que todavía no tiene tag (vea [Descubrir los tags de un dispositivo](connector-discovery#discovering-a-devices-tags)).

## Ejemplo: una orden de producción de SQL Server {#example-a-production-order-from-sql-server}

El MES guarda sus órdenes de producción en SQL Server, en la tabla `dbo.ProductionOrders` de la base de
datos `MES`, una fila por número de orden:

1. En la página del controlador, **Añadir dispositivo**; llámelo `MES`. En la tarjeta **Base de datos**
   mantenga **SQL Server**, indique **Servidor** `mes-sql` (o `mes-sql\SQLEXPRESS` para una instancia
   con nombre), deje **Puerto** vacío e indique **Nombre de la base de datos** `MES`.
2. En la tarjeta **Inicio de sesión** indique **Nombre de usuario** `station_reader` y su
   **Contraseña**, o active **Iniciar sesión con la cuenta de Windows de la estación**. Para un servidor
   de fábrica con certificado propio, elija **Cifrada, se acepta cualquier certificado**.
3. **Añadir tag** `Order quantity`, Int32: **Tabla** `dbo.ProductionOrders`, **Columna del valor**
   `Quantity`, **Fila que leer** **Por su clave**, **Columna clave** `OrderNo`, **Valor clave** `4711`.
   Su dirección de bus es `row?table=dbo.ProductionOrders&column=Quantity&key=OrderNo&equals=4711`.
4. **Añadir tag** `Product`, String, en la misma tabla y fila, **Columna del valor** `ProductCode`:
   los dos tags se leen en una sola consulta.

Con las herramientas:

```
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
```

## Ejemplo: una consigna de receta de PostgreSQL {#example-a-recipe-setpoint-from-postgresql}

El sistema de recetas añade una fila a `recipe_setpoints` cada vez que cambia una consigna, fechada en
su columna `updated_at`:

1. **Añadir dispositivo**; llámelo `Recipes`, **Tipo de base de datos** **PostgreSQL**, **Servidor**
   `recipes-db`, **Puerto** vacío (el 5432 de PostgreSQL), **Nombre de la base de datos** `recipes`, y
   después **Nombre de usuario** y **Contraseña** en la tarjeta **Inicio de sesión**.
2. **Añadir tag** `Oven setpoint`, Double: **Tabla** `recipe_setpoints`, **Columna del valor**
   `oven_temperature`, **Fila que leer** **La más reciente**, **Columna de fecha** `updated_at`.

Cada fila nueva se lee en la siguiente lectura del tag. PostgreSQL lee un nombre simple en minúsculas,
así que una columna creada con mayúsculas, como `OvenTemp`, se escribe entre comillas: `"OvenTemp"`.
Con las herramientas:

```
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
```

## Ejemplo: una línea de resultado escrita como INSERT {#example-a-result-line-written-as-an-insert}

El sistema de calidad quiere una fila por pieza medida en su tabla `test_results`:

1. En el dispositivo de esa base de datos, **Añadir tag** `Result`, Double, **Acceso**
   **Solo escritura**.
2. **Una escritura** **Ejecuta su comando de escritura**, y en **Comando de escritura** escriba
   `INSERT INTO test_results (result, recorded_at) VALUES (@value, @now)`, que añade la fila.

Cada valor que Logic escribe en `Result` se convierte en una fila propia, con la fecha y la hora de la
estación al lado; las escrituras que llegan seguidas se conservan todas, en orden, ninguna descartada
por otra más nueva. Un tag de **Lectura y escritura** que lee una celda cambiaría en su lugar su única
fila en cada escritura. Con las herramientas:

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

## Ejemplo: Access y Oracle por ODBC {#example-access-and-oracle-through-odbc}

| Base de datos | En este ordenador | En el dispositivo |
| --- | --- | --- |
| Microsoft Access | El Access Database Engine de Microsoft, de 64 bits | **Tipo de base de datos** **Otra (ODBC)**, **Origen de datos o texto de conexión** `Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=C:\Data\plant.accdb`, sin usuario |
| Oracle | El controlador ODBC de 64 bits de Oracle, y un origen de datos `ERP` creado para él en Orígenes de datos ODBC (64 bits) | **Tipo de base de datos** **Otra (ODBC)**, **Origen de datos o texto de conexión** `ERP`, **Nombre de usuario** y **Contraseña** en la tarjeta **Inicio de sesión** |

Cuando el origen de datos o el controlador que nombra un dispositivo existe solo para programas de 32 bits
(un Access de un Office de 32 bits, un origen de datos creado en Orígenes de datos ODBC (32 bits)), o no
existe en este ordenador, el panel del dispositivo dice cuál de los dos casos es, con el nombre que dio el
dispositivo, y da la instrucción completa mientras el dispositivo está activado y no se conecta. El
error del dispositivo dice lo mismo en inglés, y también `driver_capabilities`, en el `prerequisite`
del controlador Database (`perDevice` true):

| Parte | Qué dice |
| --- | --- |
| Qué falta | Un controlador ODBC de 64 bits para ese origen de datos o ese controlador: Ganter Lab es un programa de 64 bits y solo ve los controladores ODBC de 64 bits y los orígenes de datos creados con ellos, y no puede incluir el controlador de otro fabricante |
| Dónde obtenerlo | Del fabricante de la base de datos; para Access, la página de Microsoft "Download and install Microsoft 365 Access Runtime", cuyo runtime trae el Access Database Engine |
| Cuál elegir | La versión de 64 bits (para Access, el botón de descarga de 64 bits de su idioma), nunca la de 32 bits |
| Cómo instalarlo | Como administrador; después, para un dispositivo que nombra un origen de datos, crearlo en Orígenes de datos ODBC (64 bits) con el mismo nombre y la misma base de datos |
| Tropiezo conocido | Para Access en un equipo con Office: la página de Microsoft dice que el Access Runtime instalado corresponde al Office ya instalado y que no es compatible con un Office instalado con Windows Installer, y la página de Microsoft sobre cómo elegir entre la versión de 64 bits y la de 32 bits de Office dice que una versión se desinstala antes de instalar la otra |
| Después | En su siguiente intento de conexión, el dispositivo se conecta con la configuración que tiene, sin reiniciar Ganter Lab |

El aviso termina con los controladores ODBC de 64 bits que tiene este ordenador, con el nombre que lleva
`Driver={...}`. Un origen de datos o un controlador que existe en 64 bits y que aun así falla muestra el
error propio del administrador de controladores.

Por ODBC, un nombre simple se lee como lo lee la base de datos (en mayúsculas en Oracle) y un nombre
entre comillas tal cual; las fechas y horas se envían al segundo entero, lo más fino que admite todo
controlador ODBC. Con las herramientas:

```
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>
```

## Herramientas del agente {#agent-tools}

`driver_capabilities` describe este controlador en `database`: el camino sencillo, cada base de datos
con sus campos y su puerto propio, las opciones de cifrado, las dos maneras en que lee un tag y las dos
en que escribe, la regla de que un valor solo entra en una instrucción como `@value` o `@now`, los
tipos, los tiempos de espera, el consejo sobre el usuario de la base de datos, el descubrimiento y
cuatro ejemplos completos. `device_upsert driver=Database` admite `databaseKind` (`SqlServer`, el
predeterminado, `PostgreSql`, `MySql`, `Sqlite` u `Odbc`), y para un servidor `host` (un
`server\INSTANCE` o `server,port` de SQL Server se separa), `port`, `databaseName`, y en SQL Server
`databaseInstance`, `databaseWindowsSignIn` y `databaseEncryption` (`Verified`,
`TrustServerCertificate` u `Off`); `databaseFile` para SQLite, `databaseOdbc` para ODBC; `username` y
`password`, que nunca se devuelve. Un dispositivo que devuelve lleva `database`, con la dirección y si
el dispositivo aún espera su usuario. `tag_upsert` admite la columna del valor como `address`,
`databaseSource` (`Fields` o `Query`), `databaseTable`, `databaseRow` (`Key` o `Newest`),
`databaseKeyColumn`, `databaseKeyValue`, `databaseDateColumn`, `databaseQuery`, y cuando el
`accessType` escribe, `databaseWriteMode` (`Cell` o `Command`) y `databaseCommand`; un tag nuevo es de
solo lectura y Double salvo que `opcUaType` diga otra cosa. `device_discover driver=Database` admite
`databaseDevice`, el nombre de un dispositivo configurado del controlador, y opcionalmente
`databaseTables`, un nombre de tabla o un patrón con `*`; los tags que le faltan al dispositivo se
añaden con `tag_upsert`. `connector_browse` devuelve el `databaseKind` de cada dispositivo. Las demás
herramientas están en [Herramientas del agente](agent-tools).

## Límites que conviene conocer {#limits-worth-knowing}

- El controlador lee y escribe los valores de sus tags, un valor por tag. No registra los ensayos de la
  estación en una base de datos: una fila por ensayo con su número de serie, su veredicto y sus medidas
  no es algo que escriba este controlador.
- El producto no incluye ningún controlador ODBC. Oracle, Access, Firebird y cualquier base de datos que
  no sea una de las cuatro que se alcanzan directamente necesitan su propio controlador ODBC de 64 bits
  instalado en el ordenador; uno de 32 bits no se ve.
- El dispositivo no guarda ninguna contraseña de Windows: el inicio de sesión de Windows en SQL Server,
  y un archivo SQLite en un recurso compartido de red, usan la cuenta con la que se ejecuta la estación,
  así que la base de datos o el recurso compartido tienen que dejar entrar a esa cuenta.
- Una consulta solo lee. Lo que cambia la base de datos es una escritura, y una escritura solo ocurre en
  un tag cuyo acceso escribe.
- Un valor nunca entra en el texto de una instrucción: el valor escrito es siempre el parámetro
  `@value`, la fecha y la hora de la estación `@now`, y un valor clave también es un parámetro. Una
  instrucción que nombra cualquier otro valor se rechaza.
- Una escritura de celda cambia exactamente una fila; un comando UPDATE necesita su WHERE.
- A la base de datos se le pregunta en cada lectura: un cambio que hace otro programa se ve en la
  lectura siguiente, y a una base de datos con muchos tags de una misma fila se le pregunta una vez por
  pasada por todos ellos.
- Una columna binaria no se lee.
