Hosting guebs

Capítulo 27. Extender MySQL

Tabla de contenidos

27.1. El interior de MySQL
27.1.1. Los subprocesos (threads) MySQL
27.1.2. El paquete de pruebas MySQL Test
27.2. Añadir nuevas funciones a MySQL
27.2.1. Características de la interfaz para funciones definidas por el usuario
27.2.2. Sintaxis de CREATE FUNCTION/DROP FUNCTION
27.2.3. Añadir una nueva función definida por el usuario
27.2.4. Añadir una nueva función nativa
27.3. Añadir nuevos procedimientos a MySQL
27.3.1. Procedimiento Analyse
27.3.2. Escribir un procedimiento

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 obvios 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 tests deberían tener éxito. Si no fuera así, debería intentar encontrar el porqué y reportar el problema si es un bug de MySQL. Consulte Sección 27.1.2.3, “Reportar bugs de la suite de test de MySQL”.

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 caracteres 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. Reportar bugs de la suite de test de MySQL

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 caracteres, 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 TypeC/C++ Type
    STRINGchar *
    INTEGERlong long
    REALdouble
  • 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 caracteres, 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 caracteres.

    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 caracteres en *error! error apunta a una variable de un byte, no a un búfer de cadenas de caracteres.

*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 caracteres 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 caracteres más una longitud, para permitir tratar datos binarios o datos arbitráriamente largos. Los contenidos de la cadena de caracteres están disponibles como args->args[i] y la longitud de la cadena es args->lengths[i]. No debe asumir que las cadenas de caracteres 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 caracteres 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 caracteres 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 caracteres 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 caracteres 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 caracteres necesita retornar una cadena de caracteres 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 caracteres metaphon del argumento de cadena de caracteres. Esto es algo como una cadena de caracteres soundex, pero más ajustado al inglés.

  • myfunc_double() retorna la suma de los valores ASCII de los caracteres 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 caracteres 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_ARGN) (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 caracteres.

  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 caracteres:

    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 caracteres 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 caracteres 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 caracteres que tiene el resultado o (char*) 0 si el resultado es NULL.

  • Todas las funciones de cadenas de caracteres 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.