Limitando la cantidad de usuarios conectados

10 comentarios

A veces, puede interesarnos limitar la cantidad de usuarios que se conectan a una Base de Datos usando nuestra aplicación. Por ejemplo, si le vendimos 5 licencias a un cliente queremos que solamente puedan conectarse desde 5 computadoras.

Fíjate que no le podemos limitar las conexiones a la Base de Datos a nuestro cliente, porque los datos son suyos, no nuestros. O sea que si él quiere conectarse usando ISQL.EXE, EMS SQL Manager, IBExpert, Flame Robin, o cualquier otro programa puede hacerlo, no se lo podemos impedir a no ser que él haya firmado un contrato donde está claramente establecido que tiene prohibido hacerlo.

En otras palabras, no le podemos impedir que se conecte, 10, 20, ó 590 veces concurrentemente desde afuera de nuestra aplicación.

Pero lo que sí podemos hacer es limitar la cantidad de usuarios que se conectan usando nuestra aplicación. Por ejemplo, podemos impedirle que se conecten más de 5 usuarios usando nuestro sistema de Contabilidad.

Para conseguir nuestro objetivo usaremos excepciones y triggers de la Base de Datos.

Ejemplo:

Tenemos una aplicación cuyo ejecutable se llama MIAPLICACION.EXE y queremos limitar a un máximo de 5 conexiones simultáneas.

CREATE EXCEPTION E_ACCESO_NO_PERMITIDO 'La cantidad de licencias ya alcanzó el límite';

Método 1

CREATE TRIGGER DB_TRIGGER_CONNECT_1
   ACTIVE ON CONNECT
   POSITION 0
AS
   DECLARE VARIABLE lnCantidadConexiones SMALLINT;
BEGIN

   lnCantidadConexiones = (SELECT
                              COUNT(*)
                           FROM
                              MON$ATTACHMENTS
                           WHERE
                              MON$REMOTE_PROCESS LIKE '%MIAPLICACION.EXE%');

   IF (lnCantidadConexiones > 5) THEN
      EXCEPTION E_ACCESO_NO_PERMITIDO;

END;

Esto funcionará perfectamente … siempre y cuando alguien no le cambie el nombre al archivo .EXE porque la verificación se hace tomando en cuenta el nombre del archivo .EXE

Método 2

En este caso creamos una tabla llamada CONEXIONES y cada vez que alguien se conecta a la Base de Datos usando nuestro programa le agregamos una fila a esa tabla  y cuando se desconecta le borramos esa fila. La tabla CONEXIONES tiene esta estructura:

CONEXION1

Captura 1. Si haces clic en la imagen la verás más grande

donde como puedes ver tres columnas tienen valores por defecto, eso significa que si no se especifican en el INSERT los valores de los campos CON_FECHOR,  CON_USUARI y CON_CONEXI el Firebird automáticamente colocará en esas columnas la fecha y hora actuales, el nombre del usuario, y la conexión, respectivamente. Desde luego que puedes agregarle más columnas a esta tabla si lo deseas.

En nuestra Base de Datos creamos un trigger de Base de Datos, como éste:

CREATE TRIGGER DB_TRIGGER_CONNECT_2
   ACTIVE ON CONNECT
   POSITION 1
AS
   DECLARE VARIABLE lnCantidadConexiones SMALLINT;
BEGIN

   lnCantidadConexiones = (SELECT COUNT(*) FROM CONEXIONES);

   IF (lnCantidadConexiones > 4) THEN
      EXCEPTION E_ACCESO_NO_PERMITIDO;

END

donde lo que hacemos es verificar que la cantidad de conexiones no sobrepase el límite que habíamos determinado. ¿Pero por qué ponemos 4 y no 5? Porque la inserción en la tabla CONEXIONES se hace después de haberse conectado a la Base de Datos y por lo tanto en la variable lnCantidadConexiones no se está contando la conexión actual, por ese motivo hay que colocar el número anterior al del límite. Si la cantidad de conexiones supera a 5 entonces se lanzará una excepción y se desconectará automáticamente de la Base de Datos.

Y cuando alguien ejecuta nuestro programa lo primero que hacemos es insertarle una fila a la tabla CONEXIONES, de esta manera:

INSERT INTO CONEXIONES
   (CON_IPXXXX)
   SELECT
      MON$REMOTE_ADDRESS
   FROM
      MON$ATTACHMENTS
   WHERE
      MON$ATTACHMENT_ID = CURRENT_CONNECTION

Y cuando alguien sale de nuestra aplicación eliminamos una fila de la tabla CONEXIONES, así:

DELETE FROM
   CONEXIONES
WHERE
   CON_CONEXI = CURRENT_CONNECTION
END

Entonces, lo que sucede es lo siguiente:

  • Usando nuestra aplicación se conectan a la Base de Datos
  • Verificamos si se sobrepasó el límite de conexiones permitidas. Si así fue, se desconecta de la Base de Datos
  • Desde nuestra aplicación le insertamos una fila a la tabla CONEXIONES
  • Cuando salen de nuestra aplicación borramos una fila de la tabla CONEXIONES

De esta manera conseguimos el objetivo que nos habíamos propuesto: que jamás usen nuestra aplicación más de 5 computadoras a la vez.

Sin embargo este método tiene un pequeño problema: si ocurre un corte de la energía eléctrica varias computadoras podrían haberse desconectado de la Base de Datos pero en la tabla CONEXIONES continuarán sus datos, como si estuvieran conectadas.

En el peor de los casos podríamos tener 5 computadoras desconectadas pero la tabla CONEXIONES nos dirá que las 5 están conectadas.

Para solucionar el problema podemos usar el método 3.

Método 3

En nuestro programa, antes de insertar la fila en la tabla CONEXIONES, borramos todas las filas de la tabla CONEXIONES cuyos datos no se encuentren en la tabla MON$ATTACHMENT.

De esa manera en la tabla CONEXIONES siempre tendremos conexiones actuales y nunca tendremos conexiones “fantasmas”.

¿Por qué?

Porque en la tabla MON$ATTACHMENT siempre tenemos los datos de todas las conexiones actuales. Si en la tabla CONEXIONES tenemos una conexión que no existe en la tabla MON$ATTACHMENT entonces está mal y debemos borrarla.

DELETE FROM
   CONEXIONES
WHERE
   CON_CONEXI NOT IN (SELECT MON$ATTACHMENT_ID FROM MON$ATTACHMENTS)

Entonces, lo que sucedería es lo siguiente:

  • Usando nuestra aplicación se conectan a la Base de Datos
  • Verificamos si se sobrepasó el límite de conexiones permitidas. Si así fue, se desconecta de la Base de Datos
  • Desde nuestra aplicación borramos todas las filas de la tabla CONEXIONES que no se encuentren también en la tabla MON$ATTACHMENTS
  • Desde nuestra aplicación le insertamos una fila a la tabla CONEXIONES
  • Cuando salen de nuestra aplicación borramos una fila de la tabla CONEXIONES

Conclusión:

Si limitamos la cantidad de conexiones a la Base de Datos entonces tenemos una protección contra la piratería pues aunque consigan copiar nuestra aplicación en otra computadora no podrán realizar la conexión y de nada les servirá. Además, tenemos la posibilidad de instalar nuestra aplicación en muchas computadoras para que la revisen y la evalúen pero nos aseguramos que no la usen más usuarios de los que determinemos nosotros.

Artículos relacionados:

Los triggers de la Base de Datos

El índice del blog Firebird21

Usando la restrición CHECK

12 comentarios

Para evitar que ingrese basura en nuestra tabla (se le llama basura a un dato que está pero que no debería estar) el Firebird nos provee de varias armas: dominios, triggers y checks.

Un check es una restricción, una limitación que deben cumplir los datos para que sean considerados válidos y puedan ser grabados.

Por ejemplo, si los precios no pueden ser negativos podríamos tener un check que evite guardar precios negativos. Si las notas de los alumnos deben estar entre 0 y 100 podemos tener un check que evite ingresar notas fuera de ese rango. Si las fechas no pueden ser anteriores al día 1 de enero de 2013 podemos tener un check que evite ingresar fechas anteriores.

Todos esos ejemplos involucran a una sola tabla cada vez, pero el Firebird también nos permite tener checks que involucren a varias tablas. Los usaríamos por ejemplo para evitar que la cobranza se realice antes de la venta, o que el examen se realice antes de la inscripción del alumno, o que el cheque sea emitido antes de la apertura de la cuenta corriente en el Banco.

Después de haber creado una tabla le podemos agregar todos los checks que necesitemos. La sintaxis es la siguiente:

ALTER TABLE MiTabla ADD CONSTRAINT NombreCheck CHECK (MiCondición);

En EMS SQL Manager para escribir un check debes hacer click sobre la pestaña respectiva, como se muestra en esta imagen:

CHECK1

(haciendo click en la imagen la verás más grande)

Ejemplo 1. Evitar precios de costo negativos

ALTER TABLE
   PRODUCTOS
ADD CONSTRAINT
   CHK_PRODUCTOS1
CHECK (PRD_PRECTO >= 0);

La tabla se llama PRODUCTOS y en la columna PRD_PRECTO se guardan los precios de costo de los productos

Ejemplo 2. Evitar que las notas de los alumnos estén fuera de rango

ALTER TABLE
   EXAMENES
ADD CONSTRAINT
   CHK_EXAMENES1
CHECK(EXA_NOTAXX >= 0 AND EXA_NOTAXX <= 100);

La tabla se llama EXAMENES y en la columna EXA_NOTAXX se guardan las notas de los alumnos

Ejemplo 3. Evitar que la fecha de la venta sea anterior al 1 de enero de 2013

ALTER TABLE
   VENTASCAB
ADD CONSTRAINT
   CHK_VENTASCAB1
CHECK (VTC_FECHAX >= '01-01-2013');

La tabla se llama VENTASCAB (cabecera de las ventas) y en la columna VTC_FECHAX se guardan las fechas de las ventas

Ejemplo 4. Evitar la grabación si el valor de una columna no existe en otra tabla

ALTER TABLE
   BANCOS
ADD CONSTRAINT
   CHK_BANCOS1
CHECK (BAN_CODSUC IN (SELECT SUC_CODIGO FROM SUCURSALES));

La tabla se llama BANCOS y en la columna BAN_CODSUC se guarda el código de la Sucursal. Se busca ese código en la tabla de SUCURSALES. Si no existe, entonces esta fila no podrá ser grabada. Esta no es la única forma de resolver este problema, pero se muestra aquí para que puedas ver como usarla cuando necesites que una fila se grabe solamente si existe un valor (o más de un valor) en otra tabla.

Conclusión:

La restricción CHECK es extremadamente útil para evitar que alguien ingrese basura en las tablas de nuestra Base de Datos. Si la usamos bien entonces no estaremos dependiendo de que los programas (escritos en Visual FoxPro, Visual Basic, C, C++, Delphi, Java, PHP, etc.) se acuerden de evitar el ingreso de la basura. Simplemente si un dato no cumple con la restricción del check no será grabado. Punto.

En los ejemplos de arriba se mostró como evitar algunas situaciones comunes pero desde luego que hay muchísimas otras posibilidades. Como el Firebird nos permite tener un SELECT (o más de un SELECT) dentro de un CHECK entonces la cantidad de condiciones que podemos escribir es impresionante, y deberíamos usar esta facilidad para que nuestra Base de Datos sea confiable.