Capítulo 27. Extender MySQL
Anterior |  | Siguiente

## Capítulo 27. Extender MySQL

## 27.1. El interior de MySQL

Este capítulo describe muchas cosas que necesita saber cuando trabaje con el código de MySQL. Si planea contribuir al desarrollo de MySQL, quiere tener acceso al límite de código de versiones, o sólo quiere estar al día del desarrollo, siga las instrucciones en Sección 2.8.3, “Instalar desde el árbol de código fuente de desarrollo”. Si está interesado en conceptos internos de MySQL, debe suscribirse a la lista de correo `internals` . Esta lista tiene poco tráfico. Para más detalles sobre cómo suscribirse, consulte Sección 1.6.1.1, “Las listas de correo de MySQL”. Todos los desarrolladores en MySQL AB están en la lista `internals` y ayudamos a otra gente trabajando en el código de MySQL. Siéntase libre de usar esta lista para preguntar cosas sobre el código y para enviar parches con los que quiera contribuir al proyecto MySQL!

### 27.1.1. Los subprocesos (threads) MySQL

MySQL server crea los siguientes flujos:

- El flujo de conexión TCP/IP trata todas las peticiones de conexión y crea un nuevo flujo dedicado para tratar el proceso de autenticación y de consulta SQL para cada conexión.

- En Windows NT hay un flujo para tratar los named pipes que hace el mismo trabajo que el flujo de conexiones TCP/IP en peticiones de conexión por named pipe.

- El flujo de señales trata todas las señales. Este flujo también trata normalmente alarmas y llamadas `process_alarm()` para forzar tiempos máximos en las conexiones que llevan en espera demasiado tiempo.

- Si se compila mysqld con `-DUSE_ALARM_THREAD`,se crea un flujo dedicado que trata alarmas. Sólo se usa en algunos sistemas donde hay problemas con `sigwait()` o si quiere usar el código `thr_alarm()` en su aplicación sin un flujo dedicado a tratar señales.

- Si usa la opción `--flush_time=#` , se crea un flujo dedicado para volcar todas las tablas en un intervalo dado.

- Cada conexión tiene su propio flujo.

- Cada tabla diferente en que se usa `INSERT DELAYED` tiene su propio flujo.

- Si usa `--master-host`, se arranca un flujo de replicación esclavo para leer y aplicar actualizaciones del maestro.

mysqladmin processlist sólo muestra la conexión, `INSERT DELAYED`, y flujos de replicación.

### 27.1.2. El paquete de pruebas MySQL Test

El sistema de testeo incluído en distribuciones fuente Unix y distribuciones binarias hace que sea posible para los usuarios y desarrolladores realizar tests de regresión en código MySQL. Estos tests pueden ejecutarse en Unix o en Windows (usando el entorno Cygwin) si el servidor se ha compilado bajo Cygwin. No pueden ejecutarse en un entorno Windows nativo.

El conjunto actual de casos de uso no testea todo en MySQL, pero debería atrapar la mayoría de bugs óbvios en el código de proceso SQL, temas de bibliotecas del SO, y es bastante útil para testear replicación. Nuestra meta eventual es que el test cubra el 100% del código. Los contribuidores de nuestra suite de test son bienvenidos. Puede querer contribuir con tests que examinen la funcionalidad crítica del sistema, ya que esto asegura que todas las futuras versiones de MySQL funcionen bien con sus aplicaciones.

#### 27.1.2.1. Ejecutar el paquete de pruebas MySQL Test

El sistema de test consiste en un intérprete de idioma de test (mysqltest), un shell script para ejecutar todos los tests(mysql-test-run), los casos de usos de test actuales escritos en un lenguaje especial de test, y sus resultados esperados. Para ejecutar la test suite en su sistema tras construirlo escriba make test o `mysql-test/mysql-test-run` de la raíz del código fuente. Si ha instalado una distribución binaria, `cd` a la raíz de la instalación (ej. `/usr/local/mysql`), y ejecute `scripts/mysql-test-run`. Todos los test deberían tener éxito. Si no es así, debería intentar encontrar porqué y reportar el problema si es un bug de MySQL. Consulte Sección 27.1.2.3, “Reporting Bugs in the MySQL Test Suite”.

Desde MySQL 4.1 , si tiene una copia de mysqld ejecutándose en la máquina donde quiere ejecutar el test suite no tiene que pararlo, mientras no use los puertos `9306` y `9307`. Si uno de esos puertos está ocupado, debe editar mysql-test-run y cambiar los valores de los puertos maestro y/o esclavo a uno que esté disponible.

Antes de MySQL 4.1, mysql-test-run no intenta ejecutar su propio servidor por defecto pero intenta usar su servidor que esté en ejecución. Para cambiar este comportamiento y hacer que mysql-test-run arranque su propio servidor, ejecútelo con la opción `--local` .

Puede ejecutar un caso de test individual con `mysql-test/mysql-test-run test_name`.

Si falla un test, debe probar ejecutar mysql-test-run con la opción `--force` para chequear si algún otro test falla.

#### 27.1.2.2. Extender el paquete de pruebas MySQL Test

Puede usar el lenguaje mysqltest para crear sus propios casos de test. Desafortunadamente, todavía no hay una documentación completa para el mismo. Sin embargo, puede ojear nuestros casos de uso y usarlos como ejemplo. Los siguientes puntos pueden ayudarle a empezar:

- Los tests se encuentran en `mysql-test/t/*.test`

- Un caso de test consiste en comandos terminados por `;` y es similar a la entrada del cliente de línea de comandos mysql . Un comando por defecto es una consulta que se envía a MySQL server, a no ser que se reconozca como comando interno (p.e. sleep).

- Todas las consultas que producen resultados--por ejemplo, `SELECT`, `SHOW`, `EXPLAIN`, etc., debe precederse con `@/path/to/result/file`. El fichero debe contener los resultados esperados. Una forma fácil de generar el fichero de resultados es ejecutar mysqltest -r <t/test-case-name.test del directorio `mysql-test`, y editar los ficheros resultantes, si es necesario, para ajustarlos a la salida esperada. En ese caso, sea muy cuidadoso de no añadir o borrar carácteres invisibles -- asegúrese de cambiar sólo el texto y/o borrar líneas. Si tiene que insertar una línea, aségurese que los campos estén separados por un tabulador, y que hay un tabulador al final. Si quiere puede usar od -c para asegurarse que su editor de texto no ha desbaratado nada durante la edición. Esperamos que nunca tenga que editar la salida de mysqltest -r ya que sólo tiene que hacerlo al encontrar un bug.

- Para ser consistente con nuestra inicialización, debe poner sus ficheros resultantes en el directorio `mysql-test/r` y llamarlos `test_name.result`. Si el test produce más de un resultado, debe usar `test_name.a.result`, `test_name.b.result`, etc.

- Si un comando retorna un error, debe especificarlo con `--error error-number` en la línea anterior al comando. El número de error puede ser una lista de números de error posibles separados por '`,`'.

- Si está escribiendo un caso de test de replicación, debe poner en la primera línea del fichero de test, `source include/master-slave.inc;`. Para cambiar entre maestro y esclavo, use `connection master;` y `connection slave;`. Si necesita hacer algo en una conexión alternativa, puede hacer `connection master1;` para el maestro, y `connection slave1;` para el esclavo.

- Si necesita hacer algo en un bucle, puede usar algo como esto:

      let $1=1000;
      while ($1)
      {
       # do your queries here
       dec $1;
      }

- Para dormir entre consultas, use el comando sleep. Soporta fracciones de segundo, así que puede usar sleep 1.3;, por ejemplo, para dormir 1.3 segundos.

- Para ejecutar el esclavo con opciones adicionales para sus casos de test, póngalos en el formato de línea de comandos en `mysql-test/t/test_name-slave.opt`. Para el maestro, póngalas en `mysql-test/t/test_name-master.opt`.

- Si tiene una pregunta sobre la suite de test, o tiene un caso de test al que contribuir, envíe un email a la lista de correo MySQL `internals` . Consulte Sección 1.6.1.1, “Las listas de correo de MySQL”. Como esta lista no acepta adjuntos, debe subir por ftp todos los ficheros relevantes a: ftp://ftp.mysql.com/pub/mysql/upload/

#### 27.1.2.3. Reporting Bugs in the MySQL Test Suite

Si su versión de MySQL no pasa el suite de test debe hacer lo siguiente:

- No envíe un reporte de bug antes que haya encontrado todo lo posible sobre qué ha ido mal! Cuando lo haga, por favor use el script mysqlbug para que podamos obtener información sobre el sistema y la versión MySQL. Consulte Sección 1.6.1.3, “Cómo informar de bugs y problemas”.

- Asegúrese de incluir la salida de mysql-test-run, así como los contenidos de todos los ficheros `.reject` en el directorio `mysql-test/r` .

- Si falla un test en la suite, chequee si el test también falla al ejecutarlo en solitario:

      cd mysql-test
      mysql-test-run --local test-name

  Si esto falla, debe configurar MySQL con `--with-debug` y ejecutar mysql-test-run con la opción `--debug` . Si también falla envíe el fichero de traza `var/tmp/master.trace` a ftp://ftp.mysql.com/pub/mysql/upload/ para que podamos examinarlo. Por favor recuerde de incluir una descripción completa de su sistema, la versión del binario mysqld y cómo lo compiló.

- También intente ejecutar mysql-test-run con la opción `--force` para ver si hay algún otro test que falla.

- Si ha compilado MySQL usted mismo, chequee nuestro manual para ver cómo compilar MySQL en su plataforma, o preferiblemente, usar uno de los binarios que hemos compilado en http://dev.mysql.com/downloads/. Todos nuestros binarios estándard deben pasar la suite de tests!

- Si obtiene un error como `Result length mismatch` o `Result content mismatch` significa que la salida del test no coincide exactamente con la salida esperada. Este puede ser un bug en MySQL o que su versión de mysqld produce resultados ligeramente distintos bajo algunas circunstancias.

  Los resultados de tests fallidos se ponen en un fichero con el mismo nombre base que el fichero de resultados con la extensión `.reject` . Si su caso de test falla, debe hacer un diff entre los dos ficheros. Si no puede ver en qué se distinguen, exemine ambos con `od -c` y compruebe los tamaños.

- Si un test falla completamente, debe chequear los ficheros de logs en el directorio `mysql-test/var/log` para ayudas sobre qué ha fallado.

- Si ha compilado MySQL con depuración puede intentar depurarlo ejecutando mysql-test-run con las opciones `--gdb` y/o `--debug` . Consulte Sección D.1.2, “Crear ficheros de traza”.

  Si no ha compilado MySQL para depuración debería hacerlo. Especifique la opción `--with-debug` en configure. Consulte Sección 2.8, “Instalación de MySQL usando una distribución de código fuente”.

## 27.2. Añadir nuevas funciones a MySQL

Hay dos formas de añadir nuevas funciones a MySQL:

- Puede añadir funciones con la interfaz de funciones definidas de usuario (UDF) (N.del T. Acrónimo para User Defined Functions). Las funciones definidas por el usuario se ocmpilan como ficheros objeto y se añaden y borran del servidor dinámicamente usando los comandos `CREATE FUNCTION` y `DROP FUNCTION` . Consulte Sección 27.2.2, “Sintaxis de `CREATE FUNCTION/DROP FUNCTION`”.

- Puede añadir funciones como funciones nativas MySQL. Se compilan en el servidor mysqld y están disponibles permanentemente.

Cada método tiene ventajas y desventajas:

- Si escribe funciones definidas por el usuario, debe instalar ficheros objeto además del servidor mismo. Si compilar su función en el servidor, no necesita hacerlo.

- Puede añadir UDFs a distribuciones binarias MySQL . Las funciones nativas requieren modificar una distribución fuente.

- Si actualiza su distribución MySQL , puede continuar usando las UDFs previamente instaladas, a no ser que actualice a una versión en la que la interfaz UDF cambie. (Un cambio incompatible ocurrió en MySQL 4.1.1 para funciones agregadas. Una función llamada `xxx_clear()` debe definirse en lugar de `xxx_reset()`.) Para funciones nativas, debe repetir sus modificaciones cada vez que actualice.

Use el método que use para añadir nuevas fucnciones, pueden invocarse en comandos SQL como funciones nativas tales como `ABS()` o `SOUNDEX()`.

Otra forma de añadir funciones es creando funciones almacenadas. Se escriben con comandos SQL en lugar de compilando código objeto. La sintaxis para escribr funciones almacenadas se describe en `Stored Procedures`.

La siguiente sección describe características de la interfaz UDF, proporciona intrucciones para escribir UDFs, y discute sobre precauciones de seguridad que toma MySQSL para prevenir un mal uso de UDF.

Para código fuente de ejemplo que ilustra cómo escribir UDFs, mire el fichero `sql/udf_example.cc` que se proporciona en las distribuciones fuentes de MySQL.

### 27.2.1. Características de la interfaz para funciones definidas por el usuario

La interfaz de MySQL para funciones definidas por el usuario proporciona las siguientes funcionalidades y capacidades:

- Las funciones pueden retornar cadenas de carácteres, enteros o valores reales.

- Puede definir funciones simples que operen en con un único registro a la vez, o agregar funciones que operen con grupos de registros.

- Se proporciona información a las funciones que permite chequear el tipo y número de argumentos que se les pasa.

- Le puede decir a MySQL que coercione argumentos de un tipo dado antes de pasarlos a la función.

- Puede indicar que una funcion retorne `NULL` o que ha ocurrido un error.

### 27.2.2. Sintaxis de `CREATE FUNCTION/DROP FUNCTION`

    CREATE [AGGREGATE] FUNCTION function_name RETURNS {STRING|INTEGER|REAL}
           SONAME shared_library_name

    DROP FUNCTION function_name

Una funciones definidas por el usuario (UDF) es un modo de extender MySQL con una nueva función que funciona como una función nativa de MySQL tal como `ABS()` o `CONCAT()`.

`function_name` es el nombre que debe usarse en comandos SQL para invocar la función. La cláusula `RETURNS` indica el tipo del valor de retorno de la función. `shared_library_name` es el nombre base de la fichero del objeto compartido que contiene el código que implementa la función. El fichero debe localizarse en un directorio en el que busque el lincador dinámico del sistema.

Para crear una función , debe tener el privilegio `INSERT` para la base de datos `mysql` . Para borrar una función, debe tener el privilegio `DELETE` para la base de datos `mysql` . Esto es así porque `CREATE FUNCTION` añade un registro a la tabla de sistema `mysql.func` que registra los nombres de función, tipo, y nombre de la biblioteca compartida, y `DROP FUNCTION` borra el registro de la función de dicha tabla. Si no tiene esta tabla, debe ejecutar el script mysql_fix_privilege_tables para crearla. Consulte Sección 2.10.2, “Aumentar la versión de las tablas de privilegios”.

Una función activa es una que se ha cargado con `CREATE FUNCTION` y no se ha eliminado con `DROP FUNCTION`. Todas las funciones activas se recargan cada vez que el servidor arranca, a no ser que arranque mysqld con la opción `--skip-grant-tables` . En este caso, la inicialización de UDF no se hace y no están disponibles.

Para instrucciones sobre escribir funciones definidas por el usuario consulte Sección 27.2.3, “Añadir una nueva función definida por el usuario”. Para que funcione el mecanismo de UDF, las funciones deben escribirse en C o C++, su sistema operativo debe soportar carga dinámica y debe haber compilado mysqld dinámicamente (no estáticamente).

`AGGREGATE` es una nueva opción para MySQL 3.23. Una función `AGGREGATE` funciona exactamente como una función agregada (resumen) de MySQL tal como `SUM` o `COUNT()`. Para que funcione `AGGREGATE` , su tabla `mysql.func` debe contener una columna `type` . Si su tabla `mysql.func` no tiene esta columna, debe ejecutar el script mysql_fix_privilege_tables para crearla.

### 27.2.3. Añadir una nueva función definida por el usuario

Para que funciones el mecanismo UDF, las funciones deben escribirse en C o C++ y su sistema operativo debe soportar carga dinámica. La distribución fuente de MySQL incluye un fichero `sql/udf_example.cc` que define 5 nuevas funciones. Consulte este fichero para ver cómo funcionan las convenciones de llamadas de UDF.

Para poder usar UDFs, necesita lincar mysqld dinámicamente. No configure MySQL usando `--with-mysqld-ldflags=-all-static`. Si quiere usar una UDF que necesite acceder a símbolos desde mysqld (por ejemplo la función `metaphone` en `sql/udf_example.cc` que usa `default_charset_info`), debe lincar el programa con `-rdynamic` (consulte `man dlopen`). Si planea usar UDFs, la rula es configurar MySQL con `--with-mysqld-ldflags=-rdynamic` a no ser que tenga una muy buena razón para no hacerlo.

Si usa una distribución precompilada de MySQL, use MySQL-Max, que contiene un servidor lincado dinámicamente que soporta carga dinámica.

Para cada función que quiera usar en comandos SQL, debe definir las funciones correspondientes en C (o C++). En la siguiente discusión, el nombre “xxx” se usa como nombre de función de ejemplo. Para distinguir entre el uso de SQL y C/C++ , `XXX()` (mayúsculas) indicata una llamada de función SQL, `xxx()` (minúsculas) indica una llamada de función C/C++ .

Las funciones C/C++ que escribe para implementar la interfaz para `XXX()` son:

- `xxx()` (requerido)

  La función principal. Es donde el resultado de la función se computa. La correspondencia entre los tipos de datos de la función SQL y el tipo de retorno de la función C/C++ se muestra aquí:

  SQL Type | C/C++ Type
  `STRING` | `char *`
  `INTEGER` | `long long`
  `REAL` | `double`

- `xxx_init()` (opcional)

  La función de inicialización para `xxx()`. Puede usarse para:

  - Chequea el número de argumentos para `XXX()`.

  - Chequea que los argumentos son de un tipo requerido o, alternativamente, le dice a MySQL que coercione argumentos a los tipos que quiera cuando se llama a la función principal.

  - Reserva cualquier memoria requerida por la función principal.

  - Especifica la longitud máxima del resultado.

  - Especifica (para funciones `REAL` ) el máximo número de decimales.

  - Especifica si el resultado puede ser `NULL`.

- `xxx_deinit()` (opcional)

  La función de deinicialización para `xxx()`. Debe liberar cualquier memoria reservada por la función de inicialización.

Cuando un comando SQL invoca `XXX()`, MySQL llama a la función de inicialización `xxx_init()` para que realice cualquier inicialización necesaria, tales como chequeo de argumentos o reserva de memoria. Si `xxx_init()` retorna un error, el comando SQL se aborta con un mensaje de error y no se llama ni a la función principal ni a la de deinicialización. En caso contrario, se llama a la función principal `xxx()` una vez para cada registro. Tras procesar todos los registros, se llama a la función de deinicialización `xxx_deinit()` para que pueda realizar cualquier limpieza requerida.

Para funciones agregadas que funcionan como `SUM()`, debe proporcionar las siguientes funciones:

- `xxx_reset()` (necesaria antes de 4.1.1)

  Resetea el valor agregado actual e inserta el argumento como valor agregado inicial para un nuevo grupo.

- `xxx_clear()` (requerido a partir de 4.1.1)

  Resetea el valor agregado actual pero no inserta el argumento como valor agregado inicial para un nuevo grupo.

- `xxx_add()` (requerido)

  Añade el argumento al valor agregado actual.

MySQL trata UDFs agregados como sigue:

1. Llama a `xxx_init()` para permitir a la función agregada reservar la memoria necesaria para ordenar resultados.

2. Ordena la table según la expresión `GROUP BY`.

3. Llama a `xxx_clear()` para el primer registro en cada grupo.

4. Llama a `xxx_add()` para cada nuevo registro que permita al mismo grupo.

5. Llama a `xxx()` para obtener el resultado del agregado cuando el grupo cambia o cuando el último registro se ha procesado.

6. Repite 3-5 hasta que se procesan todos los registros

7. Llama a `xxx_deinit()` para permitir al UDF liberar la memoria reservada.

Todas las funciones deben ser flujos seguros. Esto incluye no sólo la función principal,también las funciones de inicialización o deinicialización, y las funciones adicionales requeridas por las funciones agregadas. Una consecuencia de esta restricción es que no se le permite reservar ninguna variable global o estática que cambien! Si necesita memoria, debe reservarla en `xxx_init()` y liberarla en `xxx_deinit()`.

#### 27.2.3.1. Secuencias de llamada UDF para funciones simples

Esta sección describe las distintas funciones que necesita definir cuando crea un UDF simple. Sección 27.2.3, “Añadir una nueva función definida por el usuario” describe el orden en que MySQL llama a estas funciones.

La función principal `xxx()` debe declararse como se muestra en esta sección. Tenga en cuenta que el tipo de retorno y los parámetros difieren, dependiendo de si declara la función SQL `XXX()` para retornar `STRING`, `INTEGER`, o `REAL` en el comando `CREATE FUNCTION` :

Para funciones `STRING` :

    char *xxx(UDF_INIT *initid, UDF_ARGS *args,
              char *result, unsigned long *length,
              char *is_null, char *error);

Para funciones `INTEGER` :

    long long xxx(UDF_INIT *initid, UDF_ARGS *args,
                  char *is_null, char *error);

Para funciones `REAL` :

    double xxx(UDF_INIT *initid, UDF_ARGS *args,
                  char *is_null, char *error);

Las funciones de inicialización y deinicialización se declaran así:

    my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message);

    void xxx_deinit(UDF_INIT *initid);

El parámetro `initid` se pasa a las tres funciones. Apunta a la estructura `UDF_INIT` que se usa para comunicar información entre funciones. Los mienbros de la estructura `UDF_INIT` se muestran a continuación. La función de inicialización debe rellenar cualquier miembro que quiera cambiar. (Para usar el valor por defecto de un miembro no lo cambie.)

- `my_bool maybe_null`

  `xxx_init()` debe asignar a `maybe_null` `1` si `xxx()` puede retornar `NULL`. El valor por defecto es `1` si alguno de los argumentos se declaran `maybe_null`.

- `unsigned int decimals`

  El número de decimales. El valor por defecto es el número máximo de decimales en los argumentos pasados a la función principal. (Por ejemplo, si a la función se pasa `1.34`, `1.345`, y `1.3`, el valor por defecto es 3, ya que `1.345` tiene 3 decimales.

- `unsigned int max_length`

  Longitud máxima del resultado. El valor por defecto `max_length` difiere en función del tipo de resultado de la función. Para funciones de cadenas de carácteres, el valor por defecto es el argumento más largo. Para funciones enteras, el valor por defecto es de 21 dígitos. Para funciones reales, el valor por defecto es 13 mas el número de decimales indicados por `initid->decimals`. (Para funciones numéricas, la longitud incluye cualquier signo o carácter de punto decimal.)

  Si quiere retornar un valor blob, puede asignar a `max_length` de 65KB a 16MB. Esta memoria no se reserva, pero el valor se usa para decidir qué tipo de columna usar si hay una necesidad de almacenar los datos temporalmente.

- `char *ptr`

  Puntero que la función puede usar para su propio propósito. Por ejemplo, las funciones pueden usar `initid->ptr` para comunicar memoria reservada entre ellos. `xxx_init()` debe reservar la memoria y asignarla al puntero:

      initid->ptr = allocated_memory;

  En `xxx()` y `xxx_deinit()`, refiérase a `initid->ptr` para usar o liberar la memoria.

#### 27.2.3.2. Secuencias de llamada UDF para funciones agregadas

Esta sección describe las distintas funciones que necesita definir cuando crea un UDF agregado. Sección 27.2.3, “Añadir una nueva función definida por el usuario” describe el orden en que MySQL llama a estas funciones.

- `xxx_reset()`

  Esta función se llama cuando MySQL encuentra el primer registro en un nuevo grupo. Debe resetear cualquier variable resumen interna y usar el argumento dado `UDF_ARGS` como primer valor en su resumen interno del grupo. Declare `xxx_reset()` como se muestra:

      char *xxx_reset(UDF_INIT *initid, UDF_ARGS *args,
                      char *is_null, char *error);

  `xxx_reset()` se necesita sólo antes de MySQL 4.1.1. NO se necesita usar desde MySQL 4.1.1, cuando la interfaz UDF cambió para usar `xxx_clear()` en su lugar. Sin embargo, puede definir `xxx_reset()` y `xxx_clear()` si quiere que su UDF funcione antes y después del cambio de interfaz. (Si no incluye ambas funciones, la función `xxx_reset()` en muchos casos puede implementarse internamente llamando `xxx_clear()` para resetear todas las variables, y luego llamar `xxx_add()` para añadir el argumento `UDF_ARGS` como primer valor del grupo.)

- `xxx_clear()`

  Esta función se llama cuando MySQL necesita resetear los resultados resumen. Se llama al principio para cada nuevo grupo pero sólo puede llamarse para resetear los valores para una consulta donde no hubieran registros que coincidan con la búsqueda. Declare `xxx_clear()` como sigue:

      char *xxx_clear(UDF_INIT *initid, char *is_null, char *error);

  `is_null` se asigna para que apunte a `CHAR(0)` antes de llamar a `xxx_clear()`.

  Si algo falla, puede almacenar un valor en la variable a la que apunta el argumento `error`. `error` apunta a una variable de un byte, no a un búfer de cadenas de carácteres.

  `xxx_clear()` se requiere sólo a partir de MySQL 4.1.1. Antes de MySQL 4.1.1, use `xxx_reset()` en su lugar.

- `xxx_add()`

  Esta función se llama para todos los registros que pertenezcan al mismo grupo, excepto para el primer registor. Debe ussarlo para añadir el valor en el argumento `UDF_ARGS` a su variable de resumen interna.

      char *xxx_add(UDF_INIT *initid, UDF_ARGS *args,
                    char *is_null, char *error);

La función `xxx()` para un UDF agregado debe declararse de la misma forma que UDF no agregados. Consulte Sección 27.2.3.1, “Secuencias de llamada UDF para funciones simples”.

Para un UDF agregado, MySQL llama a la función `xxx()` una vez que todos los registros en el grupo han sido procesados. Normalmente no debe acceder el argumento `UDF_ARGS` aquí sino devolver un valor basado en sus variables de resumen internas.

El tratamiento de valores retornados en `xxx()` debe hacerse del mismo modo que para UDF no agregados. Consulte Sección 27.2.3.4, “Valores de retorno y tratamiento de errores”.

Las funciones `xxx_reset()` y `xxx_add()` tratan sus argumentos `UDF_ARGS` del mismo modo que las funciones para UDFs no agregados. Consulte Sección 27.2.3.3, “Proceso de argumentos”.

Los argumentos punteros de `is_null` y `error` son los mismos para todas las llamadas a `xxx_reset()`, `xxx_clear()`, `xxx_add()` y `xxx()`. Puede usar esto para recordar que obtuvo un error o si la función `xxx()` debería retornar `NULL`. No debe almacenar una cadena de carácteres en `*error`! `error` apunta a una variable de un byte, no a un búfer de cadenas de carácteres.

`*is_null` se resetea para cada grupo (antes de llamar `xxx_clear()`). `*error` nunca se resetea.

Si `*is_null` o `*error` se asignan cuando `xxx()` retorna, MySQL retorna `NULL` como resultado para la función de grupo.

#### 27.2.3.3. Proceso de argumentos

El parámetro `args` apunta a una estructura `UDF_ARGS` que tiene los miembros listados a continuación:

- `unsigned int arg_count`

  Número de argumentos. Chequee este valor en la función de inicialización si necesita que su función sea llamada con un número particular de argumentos. Por ejemplo:

      if (args->arg_count != 2)
      {
          strcpy(message,"XXX() requires two arguments");
          return 1;
      }

- `enum Item_result *arg_type`

  Puntero a una matriz conteniendo los tipos para cada argumento. Los tipos posibles son `STRING_RESULT`, `INT_RESULT`, y `REAL_RESULT`.

  Par asegurar que los argumentos sean de un tipo dado y retorne un error si no lo son, chequee la matriz `arg_type` en la función de inicialización. Por ejemplo:

      if (args->arg_type[0] != STRING_RESULT ||
          args->arg_type[1] != INT_RESULT)
      {
          strcpy(message,"XXX() requires a string and an integer");
          return 1;
      }

  Como alternativa a requerir que los argumentos de la función sean de un tipo particular, puede usar la función de inicialización para asignar los elementos `arg_type` con los tipos que quiera. Esto hace que MySQL fuerce a los argumentos a los tipos para cada llamada de `xxx()`. Por ejemplo, para especificar que los primeros dos argumentos se fuercen a una cadena de carácteres de enteros, respectivamente, haga lo siguiente en `xxx_init()`:

      args->arg_type[0] = STRING_RESULT;
      args->arg_type[1] = INT_RESULT;

- `char **args`

  `args->args` comunica información a la función de inicialización acerca de la naturaleza general de los argumentos pasados a la función. Para un argumento constante `i`, `args->args[i]` apunta al valor del argumento. (Consulte a continuación instrucciones sobre cómo acceder al valor apropiadamente.) Para argumentos no constantes, `args->args[i]` es `0`. Un argumento constante es una expresión que usa sólo constantes, como `3` o `4*7-2` o `SIN(3.14)`. Un argumento no constante es una expresión que se refiere a valores que pueden cambiar de registro a registro, tales como nombres de columna o funciones que se llaman con argumentos no constantes.

  Para cada invocación de la función principal, `args->args` contiene los argumentos que se pasan para el registro que está procesando.

  Las funciones pueden referirse a un argumento `i` como se muestra:

  - Un argumento de tipo `STRING_RESULT` se da como puntero a una cadena de carácteres más una longitud, para permitir tratar datos binarios o datos arbitráriamente largos. Los contenidos de la cadena de carácteres están disponibles como `args->args[i]` y la longitud de la cadena es `args->lengths[i]`. No debe asumir que las cadenas de carácteres están terminadas por null.

  - Para un argumento de tipo `INT_RESULT`, debe convertir `args->args[i]` a un valor `long long` :

        long long int_val;
        int_val = *((long long*) args->args[i]);

  - Para un argumento de tipo `REAL_RESULT`, debe convertir `args->args[i]` a un valor `double` :

        double    real_val;
        real_val = *((double*) args->args[i]);

- `unsigned long *lengths`

  Para la función de inicialización, la matriz `lengths` indica la longitud máxima de cadena de carácteres para cada argumento. No debe cambiar este valor. Para cada invocación de la función principal `lengths` contiene las longitudes reales de cualquier argumento de cadenas de carácteres que se pasa al registro en proceso. Para argumentos de tipos `INT_RESULT` o `REAL_RESULT`, `lengths` contiene la longitud máxima del argumento (como para la función de inicialización).

#### 27.2.3.4. Valores de retorno y tratamiento de errores

La función de inicialización debe retornar `0` si no hay errores y `1` en cualquier otro caso. Si ocurre un error, `xxx_init()` debe almacenar un mensaje de error terminado en null en el parámetro `message` . El mensaje se retorna al cliente. El búffer de mensajes es de longitud `MYSQL_ERRMSG_SIZE`, pero debe tratar que el mensaje ea inferior a 80 carácteres para que coincida con la anchura de una pantalla de terminal estándar.

El valor retornado por una función principal `xxx()` es el valor de la función, para funciones `long long` y `double` . Una función de cadenas de carácteres debe retornar un puntero al resultado y asignar `*result` y `*length` con los contenidos y longitud del valor de retorno. Por ejemplo:

    memcpy(result, "result string", 13);
    *length = 13;

El búffer `result` que se pasa a la función `xxx()` tiene longitud de 255 bytes. Si su resultado coincide con esto, no tiene que preocuparse acerca de reservar memoria para los resultados.

Si su función de cadenas de carácteres necesita retornar una cadena de carácteres mayor a 255 bytes, debe reservar el espacio para ello con `malloc()` en su función `xxx_init()` o su función `xxx()` y liberarla en su función `xxx_deinit()` . Puede almacenar la memoria reservada en la entrada `ptr` en la estructura `UDF_INIT` para reusar para llamadas futuras `xxx()` . Consulte Sección 27.2.3.1, “Secuencias de llamada UDF para funciones simples”.

Para indicar un valor de retorno de `NULL` en la función principal, asigne a `*is_null` `1`:

    *is_null = 1;

Para indicar un retorno de error en la función principal, inicialice `*error` con `1`:

    *error = 1;

Si `xxx()` asigna a `*error` `1` para cualquier registro, el valor de la función es `NULL` para el registro actual y para cualquier subsecuente registro procesado por el comando en que se invoca `XXX()` . (`xxx()` no se llama ni por registros subsecuentes.) Nota: Antes de MySQL 3.22.10, debe asignar tanto `*error` como `*is_null`:

    *error = 1;
    *is_null = 1;

#### 27.2.3.5. Compilar e instalar funciones definidas por el usuario

Los ficheros implementando UDFs deben compilarse e instalarse en el equipo donde corre el servidor. Este proceso se describe a continuación para el fichero UDF de ejemplo `sql/udf_example.cc` que se incluye en la distribución fuente de MySQL.

Las instrucciones siguientes son para Unix. Las instructiones para Windows se dan posteriormente en esta sección.

El fichero `udf_example.cc` contiene las siguientes funciones:

- `metaphon()` retorna una cadena de carácteres metaphon del argumento de cadena de carácteres. Esto es algo como una cadena de carácteres soundex, pero más ajustado al inglés.

- `myfunc_double()` retorna la suma de los valores ASCII de los carácteres en los argumentos, divididos por la suma de la longitud de los argumentos.

- `myfunc_int()` retorna la suma de la longitud de los argumentos.

- `sequence([const int])` retorna una secuencia empezando por el número dado o 1 si no se da ningún número.

- `lookup()` retorna la IP para un nombre de equipo.

- `reverse_lookup()` retorna el nombre de equipo para una IP. La función puede llamarse con un único argumento de cadena de carácteres de la forma `'xxx.xxx.xxx.xxx'` o con cuatro números.

Un fichero cargable dinámicamente debe compilarse como fichero objeto compartible, usando un comando como:

    shell> gcc -shared -o udf_example.so udf_example.cc

Si usa gcc, debe ser capaz de crear `udf_example.so` con un comando más simple:

    shell> make udf_example.so

Puede determinar fácilmente las opciones de compilación adecuadas para su sistema ejecutando este comando en el directorio `sql` del árbol fuente de MySQL:

    shell> make udf_example.o

Debe ejecutar un comando de compilación similar al que muestra make , excepto que debe quitar la opción `-c` cerca del final de la línea y añadir `-o udf_example.so` al final de la línea. (En algunos sistemas, puede necesitar dejar `-c` en el comando.)

Tras compilar un objeto compartido conteniendo UDFs, debe instalarlo y comunicarlo a MySQL. Compilar un objeto compartido de `udf_example.cc` produce un fichero llamado algo como `udf_example.so` (el nombre exacto puede variar de plataforma a plataforma). Copie este fichero en un directorio como `/usr/lib` en el que busque el lincador dinámico del sistema (en tiempo de ejecución), o añada el directorio en el que está el objeto compartido en el fichero de configuración del lincador (por ejemplo, `/etc/ld.so.conf`).

El nombre del lincador depende del sistema (por ejemplo, ld-elf.so.1 en FreeBSD, ld.so en Linux, o dyld en Mac OS X). Consulte su documentación del sistema para información acerca del nombre del lincador y cómo configurarlo.

En muchos sistemas, puede cambiar las variables de entorno `LD_LIBRARY` o `LD_LIBRARY_PATH` para que apunten al directorio donde tiene los ficheros de su UDF. La página de manual `dlopen` explica qué variables debe usar en su sistema. Debe inicializarla en los scripts de arranque mysql.server o mysqld_safe y reiniciar mysqld.

En algunos sistemas, el programa ldconfig que configura el lincador dinámico no reconoce un objeto compartido a no ser que su nombre comience con `lib`. En ese caso debe renombar el fichero como `udf_example.so` a `libudf_example.so`.

En Windows, puede compilar funciones definidas por el usuario usando el siguiente procedimiento:

1. Necesita obtener el repositorio fuente BitKeeper para MySQL 4.0 o superior. Consulte Sección 2.8.3, “Instalar desde el árbol de código fuente de desarrollo”.

2. En el repositorio fuente, busque el directorio `VC++Files/examples/udf_example` . Hay ficheros llamados `udf_example.def`, `udf_example.dsp`, y `udf_example.dsw` .

3. En el repositorio fuente, busque en el directorio `sql` . Copie `udf_example.cc` de este directorio al directorio `VC++Files/examples/udf_example` y renombre el fichero a `udf_example.cpp`.

4. Abra el fichero `udf_example.dsw` con Visual Studio VC++ y úselo para compilar los UDFs como un proyecto normal.

Cuando el objeto compartido se ha instalado, notifique a mysqld con las nuevas funciones con estos comandos:

    mysql> CREATE FUNCTION metaphon RETURNS STRING SONAME 'udf_example.so';
    mysql> CREATE FUNCTION myfunc_double RETURNS REAL SONAME 'udf_example.so';
    mysql> CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME 'udf_example.so';
    mysql> CREATE FUNCTION lookup RETURNS STRING SONAME 'udf_example.so';
    mysql> CREATE FUNCTION reverse_lookup
        ->        RETURNS STRING SONAME 'udf_example.so';
    mysql> CREATE AGGREGATE FUNCTION avgcost
        ->        RETURNS REAL SONAME 'udf_example.so';

Las funciones pueden borrarse con `DROP FUNCTION`:

    mysql> DROP FUNCTION metaphon;
    mysql> DROP FUNCTION myfunc_double;
    mysql> DROP FUNCTION myfunc_int;
    mysql> DROP FUNCTION lookup;
    mysql> DROP FUNCTION reverse_lookup;
    mysql> DROP FUNCTION avgcost;

Los comandos `CREATE FUNCTION` y `DROP FUNCTION` actualizan la tabla de sistema `func` en la base de datos `mysql` . El nombre de función, tipo y nombre de biblioteca compartida se salvan en la tabla. Debe tener los privilegios `INSERT` y `DELETE` para la base de datos `mysql` para crear y borrar funciones.

No debe usar `CREATE FUNCTION` para añadir una función que se ha creado préviamente. Si necesita reinstalar una función, debe borrarla con `DROP FUNCTION` y reinstalarla con `CREATE FUNCTION`. Puede necesitar hacer esto, por ejemplo si recompila una nueva versión de su función, de forma que mysqld tenga la nueva versión. De otro modo, el servidor continua usando la versión antigua.

Una función activa es una que se ha cargado con `CREATE FUNCTION` y no se ha borrado con `DROP FUNCTION`. Todas las funciones activas se recargan cada vez que el servidor arranca, a no ser que arranque mysqld con la opción `--skip-grant-tables` . En ese caso, la inicialización de UDFs no se hace y no están disponibles.

#### 27.2.3.6. Precauciones de seguridad en funciones definidas por usuarios

MySQL toma las siguientes medidas para evitar uso inadecuado de funciones definidas por el usuario.

Debe tener el privilegio `INSERT` para poder usar `CREATE FUNCTION` y el privilegio `DELETE` para poder usar `DROP FUNCTION`. Esto es necesario ya que estos comandos añaden y borran registros de la tabla `mysql.func` .

UDFs debe tener como mínimo un símbolo definido además del símbolo `xxx` que corresponde a la función principal `xxx()` . Estos símbolos auxiliares se corresponden con las funciones `xxx_init()`, `xxx_deinit()`, `xxx_reset()`, `xxx_clear()`, y `xxx_add()` . Desde MySQL 4.0.24, 4.1.10a, y 5.0.3, mysqld soporta una opción `--allow-suspicious-udfs` que controla si UDFs que tienen un solo símbolo `xxx` pueden cargarse. Por defecto, la opción está desactivada, para evitar intentos de cargar funciones de ficheros de objetos compartidos otros aparte de los que contienen UDFs legítimos. Si tiene UDFs antiguos que contienen sólo el símbolo `xxx` y no pueden recompilarse para incluir un símbolo auxiliar, puede ser necesario especificar la opción `--allow-suspicious-udfs` . De otro modo, debe evitar activar esta capacidad.

Los ficheros objeto UDF no pueden guardarse en un directorio arbitrario. Deben estar localizado en un directorio de sistema en el que busque el lincador dinámico. Para forzar esta restricción y evitar intentos de especificar rutas fuera de los directorios buscados por el lincador dinámico, MySQL chequea el fichero de objeto compartido especificado en comandos `CREATE FUNCTION` para delimitadores de rutas. Desde MySQL 4.0.24, 4.1.10a, y 5.0.3, MySQL también chequea los delimitadores de rutas en nombres de ficheros almacenados en la tabla `mysql.func` cuando carga funciones. Esto evita intentos de especificar rutas ilegítimas manipuladas en la tabla `mysql.func` . Para información acerca de UDFs y lincador en tiempo de ejecución, consulte Sección 27.2.3.5, “Compilar e instalar funciones definidas por el usuario”.

### 27.2.4. Añadir una nueva función nativa

El procedimiento para añadir una nueva función nativa se describe aquí. Tenga en cuenta que no puede añadir funciones nativas a una distribución binaria ya que el procedimiento implica modificar código fuente MySQL. Debe compilar MySQL de una distribución fuente. También tenga en cuenta que si migra a otra versión de MySQL (por ejemplo, cuando una nueva versión aparece), necesita repetir el procedimiento con la nueva versión.

Para añadir una nueva función MySQL nativa, siga estos pasos:

1. Añada una línea en `lex.h` que defina el nombre de función en la matriz `sql_functions[]`.

2. Si el prototipo de función es simple (sólo tiene cero, uno, dos o tres argumentos), debe especificar en `lex.h` `SYM(FUNC_ARG`N`)` (donde `N` es el número de argumentos) como el segundo argumento en la matriz `sql_functions[]` y añadir una nueva función que cree un objeto función en `item_create.cc`. Consulte `"ABS"` y `create_funcs_abs()` para un ejemplo.

   Si la función prototipo es complicada (por ejemplo, tiene un número variable de argumentos), debe añadir dos líneas en `sql_yacc.yy`. Una indica el símbolo de preprocesador que yacc debe definir (debe añadirse al principio del fichero). Luego defina los parámetros de función y añada un “item” con estos parámetros a la regla de parseo `simple_expr` . Para un ejemplo, consulte todas las ocurrencias de `ATAN` en `sql_yacc.yy` para ver cómo se hace.

3. En `item_func.h`, declare una clase heredando de `Item_num_func` o `Item_str_func`, en función de si su función retorna un número o una cadena de carácteres.

4. En `item_func.cc`, añada una de las siguientes declaraciones, dependiendo de si está definiendo una función numérica o de cadena de carácteres:

       double   Item_func_newname::val()
       longlong Item_func_newname::val_int()
       String  *Item_func_newname::Str(String *str)

   Si hereda su objeto de cualquiera de los objetos estándar (como `Item_num_func`), probablemente sólo tiene que definier una de estas funciones y dejar que el objeto padre se ocupe de las otras funciones. Por ejemplo, la clase `Item_str_func` define una función `val()` que ejecuta `atof()` en el valor retornado por `::str()`.

5. Debería probablemente definir la siguiente función objeto:

       void Item_func_newname::fix_length_and_dec()

   Esta función debe calcular al menos `max_length` basándose en los argumentos dados. `max_length` es el máximo número de carácteres que la función puede retornar. Esta función debería también asignar `maybe_null = 0` si la función principal no puede retornar un valor `NULL` . La función puede chequear si algunos de los argumentos de la función puede retornar `NULL` chequeando la variable `maybe_null` de los argumentos. Puede consultar `Item_func_mod::fix_length_and_dec` para un ejemplo típico de cómo hacer esto.

Todas las funciones deben ser flujos seguros. En otras palabras, no usar ninguna variable global o estática en las funciones sin protegerlas con semáforos

Si quiere retornar `NULL`, desde `::val()`, `::val_int()` or `::str()` debe asignar a `null_value` 1 y retornar 0.

Para funciones objeto `::str()` , hay algunas consideraciones adicionales a tener en cuenta:

- El argumento `String *str` proporciona un búffer de cadenas de carácteres que puede usarse para guardar el resultado. (Para más información acerca del tipo `String` , consulte el fichero `sql_string.h` .)

- La función `::str()` debe retornar la cadena de carácteres que tiene el resultado o `(char*) 0` si el resultado es `NULL`.

- Todas las funciones de cadenas de carácteres tratan de evitar cualquier reserva de memoria a no ser que sea absolutamente necesario!

## 27.3. Añadir nuevos procedimientos a MySQL

En MySQL, puede definir un procedimiento en C++ que pueda acceder y modificar los datos en una consulta antes de enviarla al cliente. La modificación puede hacerse registro a registro o a nivel `GROUP BY` .

Hemos creado un procedimiento de ejemplo en MySQL 3.23 para mostrar cómo se hace.

Adicionalmente, recomendamos que consulte `mylua`. Con esto puede usar el lenguaje LUA para cargar procedimientos en tiempo de ejecución en mysqld.

### 27.3.1. Procedimiento Analyse

`analyse([`max_elements`,[`max_memory`]])`

Este procedimiento se define en `sql/sql_analyse.cc`. Esto examina el resultado de su consulta y retorna un análisis del resultado:

- `max_elements` (por defecto 256) es el máximo número de valores distintos que `analyse` puede tratar por columna. Esto lo usa `analyse` para chequear si el tipo óptimo de columna debe ser de tipo `ENUM`.

- `max_memory` (por defecto 8192) es la cantidad máxima de memoria que `analyse` puede reservar por columna mientras trata de encontrar todos los valores distintos.

    SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]])

### 27.3.2. Escribir un procedimiento

De momento, la única documentación para esto son las fuentes.

Puede encontrar toda la información acerca de procedimientos examinando los siguientes ficheros:

- `sql/sql_analyse.cc`

- `sql/procedure.h`

- `sql/procedure.cc`

- `sql/sql_select.cc`

Ésta es una traducción del manual de referencia de MySQL, que puede encontrarse en dev.mysql.com. El manual de referencia original de MySQL está escrito en inglés, y esta traducción no necesariamente está tan actualizada como la versión original. Para cualquier sugerencia sobre la traducción y para señalar errores de cualquier tipo, no dude en dirigirse a mysql-es@vespito.com.

Anterior |  | Siguiente
Capítulo 26. Manejo de errores en MySQL | Inicio | Apéndice A. Problemas y errores comunes