Capítulo 14. Estendendo o MySQL

Índice

14.1. MySQL Internals
14.1.1. Threads MySQL
14.1.2. Pacotes de Teste do MySQL
14.2. Adicionando Novas Funções ao MySQL
14.2.1. Sintaxe CREATE FUNCTION/DROP FUNCTION
14.2.2. Adicionando Novas Funções Definidas Por Usuário
14.2.3. Adicionando uma Nova Função Nativa
14.3. Adicionado Novos Procedimentos ao MySQL
14.3.1. Análise de Procedimento
14.3.2. Escrevendo um Procedimento

14.1. MySQL Internals

Este capítulo descreve várias coisas que você precisa saber ao trabalhar no código do MySQL. Se você planeja contribuir com o desenvolvimento do MySQL, quiser ter acesso ao código entre versões, ou apenas deseja acompanhar o desenvolvimento, siga as instruções em Secção 2.3.4, “Instalando pela árvore de fontes do desenvolvimento”. Se você está interessada nos MySQL internals, você também deve se inscrever na nossa lista de emails internals. Esta lista é relativamente de baixo tráfico. Para detalhes de como se inscrever, por favor veja Secção 1.7.1.1, “As Listas de Discussão do MySQL”. Todos os desenvolvedores na MySQL AB estão na lista internals e nós ajudamos outras pessoal que estão trabalhando no código MySQL. Esteja a vontade de utilizar esta tanto para perguntas sobre o código qunato para enviar patches com os auqis você gostaria de contribui no projeto MySQL!

14.1.1. Threads MySQL

O servidor MySQL cria as seguintes threads:

  • A thread da conexão TCP/IP trata todas as requisições de conexão e cria uma nova thread dedicada para tratar a autenticação e consulta SQL processada por cada conexão.

  • No Windows NT existe um thread que trata named pipe que fazem o mesmo trabalho que as threads da conexão TCP/IP em pedidos de conexão de named pipe.

  • A thread de sinal trata todos os sinais. Esta thread também trata normalmente de alarmes e chamadas process_alarm() para forçar um tempo limite em conexões que têm estado parados por um tempo grande.

  • Se o mysqld é compilado com -DUSE_ALARM_THREAD, uma thread dedicada que trata dos alarmes é criada. Ela só é utilizadas em alguns sistemas onde há problemas com sigwait() ou se deseja utilizar o código thr_alarm() em aplicações sem uma thread dedicada para tratar sianis.

  • Se é utilizada a opção --flush_time=#, uma thread dedicada é criada para descarregar todas as tabelas em um dado intervalo.

  • Cada conexão tem a sua própria thread.

  • Cada tabela diferente na qual é utilizada INSERT DELAYED tem sua própria thread.

  • Se você quiser utilizar --master-host, uma thread de replicação slave será iniciada para ler e aplicar atualizações do master.

mysqladmin processlist mostra apenas a thread da conexão, do INSERT DELAYED, e da replicação.

14.1.2. Pacotes de Teste do MySQL

Até pouco tempo, o nosso principal pacote de teste com cobertura total era baseado em dados proprietários de clientes e por esta razão não era disponível publicamente. A única parte disponível publicamente de nosso processo de teste consistia de um teste crash-me, um benchamrk Perl DBI/DBD encontrado no diretório sql-bench e testes variadaos localizadaos no diretório tests. A falta de um um pacote de teste padronizado disponível publicamente tem criado dificuldade para nosso usuários e para nossos desenvolvedores de fazer teste de regressão no código do MySQL. Para resolver este problema, nós criamos um novo sistema de teste que é incluído nas distribuições fonte e binária do Unix a partir da versão 3.23.29. Os testes podem ser executados no Unix ou no Windows usando um ambiente Cygwin. Eles não podem ser executados em um ambiente Windows nativo.

O conjunto de testes de atual não testa tudo no MySQL, mas deve pegar os bugs mais óbvios no código de processamento SQL, detalhes de SO/biblioteca, e é bem compleo em teste de replicações. Nosso objetivo eventual é ter os testes cobrindo 100% do código. Contibuições para o nosso pacote de teste são benvindas. Você pode desejar contribuir com testes que examinam a funcionalidade critica ao seu sistema, o que irá assegurar que todas as futuras versões do MySQL irão funcionar bem com suas aplicações.

14.1.2.1. Executando o Pacote de Testes do MySQL

O sistema de teste consiste de um interpretador de linguagem de teste (mysqltest), um script shell para executar todos os testes (mysql-test-run), os casos de teste atual escritos em uma linguagem de teste especial e seus resultados esperados. Para executar o pacote de teste em seu sistema depois de uma construção, digite make test ou mysql-test/mysql-test-run da raiz do fonte. Se você tiver uma distribuição binária instalada, digite cd para a raíz de instalação. (ex. /usr/local/mysql), e faça scripts/mysql-test-run. Todos os testes devem dar certo. Se não, você deve tentar encontrar o porque e relatar o problema se este é um bug n MySQL. See Secção 14.1.2.3, “Relatando Bugs no Pacote de Teste do MySQL”.

Se você tiver uma cópia de mysqld executando ná máquina onde você deseja executar o teste, você não tem de pará-lo, desde que não esteja usando as portas 9306 e 9307. Se uma destas portas forem tomadas, você deve editar mysql-test-run e alterar os valores da porta do master e/ou slave para uma disponível.

Você pode executar um cado de teste individual com mysql-test/mysql-test-run test_name.

Se um teste falhar, você de testar executando mysql-test-run com a opção --force para verificar se nenhum outro teste falhou.

14.1.2.2. Extendendo o Pacote de Teste do MySQL

Você pode utilizar a linguagem mysqltest para escrever o seu próprio caso de teste. Infelizmente nós ainda não escrevemos a documentação completa para ela. Você pode, no entanto, olhar os nosso casos de teste atuais e usá-los como um exemplo. O seguintes pontos devem ajudá-lo a começar:

  • Os teste estão localizados em mysql-test/t/*.test

  • Um caso de teste consiste de instruções terminadas em ; e é similar a entrada do cliente de linha de comando mysql. Uma instrução por padrão é uma consulta a ser enviada ao servidor MySQL, a menos que ele seja reconhecido como um comando insterno (ex. sleep).

  • Todas as consultas que produzem resultados¯ex., SELECT, SHOW, EXPLAIN, etc., devem ser precedidas com @/path/to/result/file. O arquivo deve conter os resultados esperados. Um modo fácil de gerar o arquivo resultante é executar mysqltest -r < t/test-case-name.test do diretório mysql-test, e então editar o arquivo resultante gerado e, se necessário, ajustá-los a saída esperada. Neste caso, tenha cuidado de não adicionar ou deletar quaisquer caracteres invisíveis - tenha certeza de apenas alterar o texto e/ou adicionar linhas deletadas. Se você tiver que inserir uma linha, esteja certo que os campos são separados com tabulação e que há uma tabulação no final. Você pode querer utilizar od -c para ter certeza que seu editor de texto não bagunçõu nada durante a edição. Nós, é claro, esperamos que você nunca tenha que editar a saída de mysqltest -r já que você só deverá fazê-lo quando encontra um bug.

  • Para estar consistente com a nossa configuração, você deve colocar seus arquivos de resultados no diretório mysql-test/r e o nomeie como test_name.result. Se o teste produzir mais de um resultado, você deve usar test_name.a.result, test_name.b.result, etc.

  • Se uma instrução retornar um erro, você eve espacificar na linha anterior a instrução com --error error-number. O número do erro pode ser uma lista de números de erros possíveis separados com ','.

  • Se você estiver escrevendo em teste de replicação, você deve coloca source include/master-slave.inc; na primeira linha do arquivo. Para trocar entre master e slave, utilize connection master; e connection slave;. se você precisar fazer alguma coisa em uma conexão alternativa, você pode fazer connection master1; para o master e connection slave1; para o slave.

  • Se você precisar fazer alguma coisa em um loop, você pode usar algo assim:

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

  • Para 'dormir' entre consultas, use o comando sleep. Ele suporta frações de um segundo, assim você pode fazer sleep 1.3;, por exemplo, para dormir 1.3 segundos.

  • Para executar o slave com opções adicionais para o seu caso de teste, coloque-os na formato de linha de comando mysql-test/t/test_name-slave.opt. Para o master, coloque-os em mysql-test/t/test_name-master.opt.

  • Se você tiver uma questão sobre o pacote de testes, ou tiver um caso de teste para contribuir, envie um e-mail para lista de email ``internals'' do MySQL. See Secção 1.7.1.1, “As Listas de Discussão do MySQL”. Como a lista não aceita anexos, você deve utilizar o ftp para enviar os arquivos relevantes: ftp://support.mysql.com/pub/mysql/Incoming/

14.1.2.3. Relatando Bugs no Pacote de Teste do MySQL

Se a sua versão não passar no pacote de teste você deve fazer o seguinte:

  • Não envie um relatório de bug antes de ter feito tudo possível para encontrar o que esta errado! Quando o fizer, por favor, utilize o script mysqlbug assim podemoster informações sobre o seu sistema e a versão do MySQL. See Secção 1.7.1.3, “Como relatar erros ou problemas”.

  • Esteja certo de inluir a saída de mysql-test-run, assim como o conteúdoi de todos os arquivos .reject no diretório mysql-test/r.

  • Se um pacote de teste falhar, verifique se o teste também falha quando executado sozinho:

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

    Se falhar, você deve configurar o MySQL com --with-debug e executar mysql-test-run com a opção --debug. Se into também falhar envie o arquivo de rastreamento var/tmp/master.trace para ftp://support.mysql.com/pub/mysql/secret assim nós podemos examiná-los. Por favor, se lembre de também incluir uma descrição completa do seu sistema, a versão do binário do mysqld e como você o compilou.

  • Tente também executar mysql-test-run com a opção --force para ver se há qualquer outro teste que tenha falhado.

  • Se você próprio compilou o MySQL, verifique nosso manual sobre como compilar o MySQL na sua platforma ou, de preferência, use um dos binários que nós compilamos para você no http://www.mysql.com/downloads/. Todos os seus binários padrões devem passar no pacote de teste!

  • Se você obter um erro, como Result length mismatch ou Result content mismatch, significa que a saída do teste não é igual a saída esperada. Este pode ser um bug no MySQL ou que o seu versão do mysqld produz resultados um pouco diferentes sobre certas circuntâncias.

    Resultado de testes que falharam são colocados em um arquivo com o mesmo nome base que o arquivo de resultado com a extensão .reject. Se o seu caso de teste está falhando, você deve fazer um diff nos dois arquivos. Se você não puder ver como els são diferentes, examine ambos com od -c e també verifique os seus tamanhos.

  • Se um teste falhar totalmente, você deve verificar os arquivos de log no diretório mysql-test/var/log para avisos sobre o que deu errado.

  • Se você tiver compilado o MySQL com depuração você pode tentar depurá-lo executando mysql-test-run com a opções --gdb e/ou --debug. See Secção E.1.2, “Criando Arquivos Trace (Rastreamento)”.

    Se você não tiver compilado o MySQL com depuração você deve, provavelmente, fazê-lo. Apenas especifique a opção --with-debug no configure! See Secção 2.3, “Instalando uma distribuição com fontes do MySQL”.

14.2. Adicionando Novas Funções ao MySQL

Existem dois modos de se adicionar novas funções ao MySQL:

  • Você pode adicionar novas funções através da interface de funções definidas por usuários - user-definable function (UDF). Funções definidas por usuários são adicionadas e removidas dinamicamente usando as instruções CREATE FUNCTION e DROP FUNCTION. See Secção 14.2.1, “Sintaxe CREATE FUNCTION/DROP FUNCTION.

  • Você pode adicionar as funções como uma função nativa do MySQL. Funções nativas são compiladas no servidor mysqld e ficam disponíveis em uma base permanente.

Cada método tem suas vantagens e desvantagens:

  • Se você escreve uma função definida pelo usuário, você deve instalar o arquivo objeto no seu servidor. Se você compilou a sua função dentro do servidor você não precisará fazer isto.

  • Você pode adicionar UDFs para um distribuição binária MySQL. Funções nativas exigem que você modifique a sua distribuição fonte.

  • Se você atualizar a sua ditribuição MySQL, você pode continuar a usar a sua UDF previamente instalada. Para funções nativas, você deve repetir as suas modificações a cada vez que você atualizar.

Seja qual for o método que você utilizou para adicionar novas funções, eles podem ser usados como funções nativas tais como ABS() ou SOUNDEX().

14.2.1. Sintaxe CREATE FUNCTION/DROP FUNCTION

CREATE [AGGREGATE] FUNCTION nome_função RETURNS {STRING|REAL|INTEGER}
       SONAME nome_bibliot_compartilhada

DROP FUNCTION function_name

Uma função definida pelo usuário (user-definable function - UDF) é um modo de extender o MySQL com uma nova função que funciona como funções nativas do MySQL tais como ABS() e CONCAT().

AGGREGATE é uma nova opção do MySQL Versão 3.23. Uma função AGGREGATE funciona exatamente como uma função GROUP nativa do MySQL como SUM ou COUNT().

CREATE FUNCTION salva o nome e o tipo da função e o nome da biblioteca compartilhada na tabela do sistema mysql.func. Você deve ter privilégios INSERT e DELETE no banco de dados mysql para criar e deletar funções.

Todas as funções ativas são recarregadas a cada vez que o servidor é reiniciado, a menos que você reinicie o mysqld com a opção --skip-grant-tables. Neste caso, a inicialização de UDF é ignorada e as UDFs estão indisponíveis. (Uma função ativa é aquela que foi carregada com CREATE FUNCTION e não foi removida com DROP FUNCTION.)

Para instruções sobre como escrever funções denidas por usuários, veja Secção 14.2, “Adicionando Novas Funções ao MySQL”. Para o mecanisnmo UDF funcionar, as funções dever ser escritas em C ou C++, seu sistema operacional deve suporta carregamento dinâmico e você deve compilar o mysqld dinamicamente (e não estaticamente).

Note que para fazer AGGREGATE funcioanr, você deve ter uma tabela mysql.func que contém a coluna type. Se você não tem esta tabela, você deve executar o script mysql_fix_privilege_tables para criá-la.

14.2.2. Adicionando Novas Funções Definidas Por Usuário

Para o mecanismo UDF funcionar, as funções devem estar em C ou C++ e o seu sistema operacional deve suporta carregamento dinâmico. A distribuição fonte do MySQL inclui um arquivo sql/udf_example.cc que definem 5 novas funções. Consulte este arquivo para ver como a convenção de chamadas UDF funciona.

Para o mysqld estar apto a usar funções UDF, você deve configurar o MySQL com --with-mysqld-ldflags=-rdynamic. A razão é que para muitas plataformas (incluindo Linux) você pode carregar uma biblioteca (com dlopen()) de um programa ligado estaticamente, que você teria se estivesse usando --with-mysqld-ldflags=-all-static. Se você quiser usar uma UDF que precisa acessar símbolos do mysqld (como o exemplo metaphone em sql/udf_example.cc que usa default_charset_info), você deve ligar o programa com -rdynamic (veja man dlopen).

Se você estiver usando uma versão precompilada do servidor, use o MySQL-Max, que suporta carregamento dinâmico.

Para cada função que você deseja usar nas instruções SQL, você deve definir funções C (ou C++) correspondente. Na discussão abaixo, o nome ``xxx'' é usado um nome de função exemplo. Para distinguir entre o uso de SQL e C/C++, XXX() (maiúscula) indica a chamada da função SQL e xxx() (minúscula) indica da chamada da função C/C++.

Aa funções C/C++ que você escreve para implemmentar a interface para XXX() são:

  • xxx() (exigido)

    A função principal. É onde o resultado da função é computado. A correspondência entre o tipo SQL e o tipo retornado da sua função C/C++ é mostrada aqui:

    Tipo SQLTipo C/C++
    STRINGchar *
    INTEGERlong long
    REALdouble
  • xxx_init() (opcional)

    A função de inicialização para xxx(). Ela pode ser usada para:

    • Verifica o número de argumentos para XXX().

    • Verifica se os argumentos são de um tipo exigido ou, alternativamente, diga ao MySQL para converter os argumentos para o tipo desejado quando a função principal é chamada.

    • Aloca a memória exigida pela função principal.

    • Especifica o tamanho máximo do resultado.

    • Especifica (para funções REAL) o número máximo de decimais.

    • Especifica se o resultado pode ser NULL.

  • xxx_deinit() (opicional)

    A função de finalização para xxx(). Ela deve liberar qualquer memória alocada pela função de inicialização.

Quando uma instrução SQL invoka XXX(), o MySQL chama a função de inicialização xxx_init() para realizar qualquer configuração necessária, tais como verificação de argumentos e alocação de memória. Se xxx_init() retorna um erro, a instrução SQL é abortada com uma mensagem e as funções principais e de finalização não são chamadas. Senão, a função principal xxx() é chamada uma vez para cada linha. Depois de todas as linhas tiverem sido processadas, a função de finalização xxx_deinit() é chamada, podendo assim realizar qualquer 'limpeza'.

Para funções agregadas (como SUM()), você também deve fornecer as seguintes funções:

  • xxx_reset() (exigida)

    Zera a soma e insere um argumento como o valor inicial para um novo grupo.

  • xxx_add() (exigida)

    Adiciona o argumento a soma antiga.

Quando se usa UDF's agregadas o MySQL funciona da seguinte maneira:

  1. Chama xxx_init() para deixar funções agregadas alocarem a memória necessária para armazenar os resultados.

  2. Ordena a tabela de acordo com a expressão GROUP BY.

  3. Para a primeira linha em um novo grupo, chama a função xxx_reset().

  4. Para cada nova linha que pertence ao mesmo grupo, chame a função xxx_add().

  5. Quando o grupo muda ou depois da última linha ter sido processada, chame xxx() para obter o resultado para o conjunto.

  6. Repita 3-5 até que todas as linhas tenham sido processada.

  7. Chame xxx_deinit() para deixar a UDF liberar a memória alocada.

Todas as funções devem ser seguras com thread (não apenas a função principal, mas também as funções de inicialização e finalização). Isto significa que você não tem permissão para alocar qualquer variável global ou estática que alterou! Se você precisa de memória, você deve alocá-la em xxx_init() e liberá-la em xxx_deinit().

14.2.2.1. Sequência de Chamadas UDF para Funções Simples

A função principal deve ser declarada como mostrado aqui. Note que o tipo retornado e os parâmetros diferem, dependendo se você irá declarar a função SQL XXX() para retornar STRING, INTEGER, ou REAL na instrução CREATE FUNCTION:

Para funções STRING:

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

Para funções INTEGER:

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

Para funções REAL:

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

As funções de inicialização e finalização são declaradas desta forma:

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

void xxx_deinit(UDF_INIT *initid);

O parâmetro initid é passado para todas as três funções. Ela aponta para uma estrutura UDF_INIT que é usada para passar informações entre as funções. Os membros da estrutura UDF_INIT são listados abaixo. A função de inicialização deve estar em todos os menbros que desejam ser alterados. (Para utilizar o padrão para um membro, deixe-o inalterado.):

  • my_bool maybe_null

    xxx_init() deve definir maybe_null com 1 se xxx() pode retornar NULL. O valor padrão é 1 se qualquer um dos argumentos são declarados como maybe_null.

  • unsigned int decimals

    Número de decimais. O valor padrão é o número máximo de deciamis no argumento passado na função principal. (Por exemplo, se a função é passada function is passed 1.34, 1.345 e 1.3, o padrão seria 3, pois 1.345 tem 3 decimais.

  • unsigned int max_length

    O tamanho máximo de um resultado string. O valor padrão difere dependendo do tipo de resultado da função. Para funções strings, o padrão é o temanho do maior argumento. Para funções do tipo inteiro, o padrão é 21 digitos. Para funções do tipo real, o padrão é 13 mais o número de decimais indicados por initid->decimals. (Para funções numéricas, o tamanho inclui qualquer caracter de sinal ou ponto decimal.)

    Se você quiser retornar um blon, você pode definí-lo com 65K ou 16M; esta memória não é alocada, mas usada para decidir qual tipo de coluna utilizar se houver necessidade dese armazenar dados temporários.

  • char *ptr

    Um ponteiro que a função pode usar para o seus propósitos. Por exemplo, funções pode usar initid->ptr para comunicar memórias alocadas entre funções. Na xxx_init(), aloca a memória e a atribui a este ponteiro:

    initid->ptr = allocated_memory;
    

    Em xxx() e xxx_deinit(), se refira a initid->ptr para usar ou liberar a memória.

14.2.2.2. Sequência de Chamadas UDF para Funções Agregadas

Aqui segue uma descrição das diferentes funções que você precisa definir quando você quer criar uma função UDF agregada.

Note que a seguinte função NÃO é necessária ou usada pelo MySQL 4.1.1. Você ainda pode manter a definição de sua função se você quiser o seu código funcinonando com o MySQL 4.0 e MySQL 4.1.1

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

Esta função é chamada quando o MySQL encontra a primiera linha em um novo grupo. Na função você deve zerar quaisquer variáveis sumárias internas e então definir o argumento dados como o primeiro argumento no grupo.

Em muitos casos isto é implementado internamente zerando todas as variáveis (por exemplo, chamando xxx_clear() e então chamando xxx_add().

A seguinte função só é exigida pelo MySQL 4.1.1 e acima:

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

Esta função é chamada quando o MySQL precisa de zerar o resumo dos resultados. Ele será chamado no começo de cada grupo novo mas também pode ser chamado para zerar os valores para uma consulta que não tiver registros coincidentes. is_null será definido para apontar para CHAR(0) antes de chamar xxx_clear().

Você pode usar o ponteiro error para armazenar um byte se alguma coisa der errado.

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

Esta função é chamada por todas as linhas que pertencem ao mesmo grupo, exceto na primeira linha. Nesta você deve adicionar o valor em UDF_ARGS a sua variavel sumária interna.

A função xxx() deve ser declarada da mesma forma que você define uam função UDF simples. See Secção 14.2.2.1, “Sequência de Chamadas UDF para Funções Simples”.

A função é chamada quando todas as linhas no grupo tem sido processada. Normamente você nunca deve acessar a variável args aqui mas retornar o seu valor baseado em sua variável sumária interna.

Todos os argumentos processados em xxx_reset() e xxx_add() devem ser feito de forma idêntica as UDF's normais. See Secção 14.2.2.3, “Processando Argumentos”.

O tratamento do valor de retorno em xxx() deve ser feito de forma idêntica a uma UDF normal. See Secção 14.2.2.4, “Valor de Retorno e Tartamento de Erros”.

O argumento ponteiro para is_null e error é o mesmo para todas as chamadas xxx_reset(), xxx_clear(), xxx_add() e xxx(). Você pode utilizar isto para lembrar que você obteve um erro ou se a função xxx() deve retornar NULL. Note que você não deve armazenar uma string em *error! Ela é um parâmetro de apenas 1 byte!

is_null é zerado para cada grupo (antes de chamar xxx_clear()). error nunca é zerado.

Se isnull ou error são definidos depois de xxx() então o MySQL retornará NULL como o rsultado para a função do grupo.

14.2.2.3. Processando Argumentos

O parâmetro args aponta para uma estrutura UDF_ARGS que tem os mambros listados abaixo:

  • unsigned int arg_count

    O número de argumentos. Verifique o valor na função de inicialização se você quiser que ssua função seja chamada com um número específico de argumentos. For exemplo:

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

  • enum Item_result *arg_type

    Os tipos para cada argumento. Os valores de tipos possíveis são STRING_RESULT, INT_RESULT, e REAL_RESULT.

    Para ter certeza que os argumentos são de um tipo dado e retornar um erro se não forem, verifique o vetor arg_type na função de inicialização. Por exemplo:

    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 uma alternativa para exigir que os argumentos de sua função sejam de um tipo específico, você pode usar a função de inicialização para definir o elemento arg_type com o tipo que você quiser. Isto faz com que o MySQL converta argumentos para aqueles tipo a cada chamada de xxx(). Por exemplo, para fazer conversão dos dois primeiros argumentos para string e integer, faça isto com xxx_init():

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

  • char **args

    args->args informa a função de inicialização sobre a natureza geral dos argumentos chamados com sua função. Para um argumento constante i, args->args[i] aponta para o valor do argumento. (Veja abaixo sobre instruções de como acessar o valor de forma apropriada). Para um argumento não constante, args->args[i] é 0. Um argumento constante é uma expressão é uma expressão que utiliza apenas constante, tais como 3 ou 4*7-2 ou SIN(3.14). Um argumento não constante é uma expressão que refere a valores que podem alterar a cada linha, tais como nomes de coluna ou funções que são chamadas com argumentos não contantes.

    Para cada chamada da função principal, args->args contém os argumentos atuais que são passados pela linhas sendo processadas atualmente.

    As funções podem se referir a um argumento i como a seguir:

    • Um argumento do tipo STRING_RESULT é dado como um apontador string mais um tamanho, para permitir o tratamento de dados binários de tamanho arbitrário. Os conteúdo da string estão disponíveis como args->args[i] e o tamanho da string é args->lengths[i]. Você não deve assumir aue as strings são terminadas em null.

    • Para um argumnto do tipo INT_RESULT, você deve converter args->args[i] para um valor long long:

      long long int_val;
      int_val = *((long long*) args->args[i]);
      
    • Para um argumento do tipo REAL_RESULT, você deve converter args->args[i] para um valor double:

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

  • unsigned long *lengths

    Para a função de inicialização, o vetor lengths indica o tamanho máximo da string para cada argumento. Você não deve alterá-los. Para cada chamada da função principal, lengths contém o tamanho atual de quaisquer argumentos string que são passados para a linha sendo processada atualmente. Para argumentos do tipo INT_RESULT ou REAL_RESULT, lengths ainda contém o tamanho máximo do argumento (como para a função de inicialização).

14.2.2.4. Valor de Retorno e Tartamento de Erros

A função de inicialização deve retornar 0 se nenhum erro ocorrer e 1 em outro caso. Se ocorrer um erro, xxx_init() deve armazenar uma mensagem de erro terminada em null no parâmetro message. A mensagem será retornada ao cliente. O buffer de mensagens tem MYSQL_ERRMSG_SIZE caracteres, mas você deve tentar manter a mensagem com menos que 80 caracteres assim ela cabe na tela de terminal padrão.

O valor de retorno de uma função principal xxx() é o valor da função, para funções long long e double. Uma função string deve retornar um ponteiro ao resultado e armazenar o tamanho da string no argumento length.

Definá-os ao conteúdo e tamanho do valor de retorno. Por exemplo:

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

O buffer result que é passado para o cálculo da função é de 255 bytes. Se o seu resultado couber nele, você não terá que se preocupar com alocação de memória para os resultados.

Se a sua função string precisar retornar uma string maior que 255 bytes, você deve alocar o espaço para ela com malloc() em sua função xxx_init() ou sua função xxx() e liberá-la em sua função xxx_deinit(). Você pode armazenar a memória alocada na posição ptr na estrutura UDF_INIT para ser reutilizado por chamadas xxx() futuras. See Secção 14.2.2.1, “Sequência de Chamadas UDF para Funções Simples”.

Para indicar um valor de retorno de NULL na função principal, defina is_null com 1:

*is_null = 1;

Para indicar um erro retornado na função principal, atribua 1 ao parâmetro error:

*error = 1;

Se xxx() definir *error com 1 para qualquer linha, o valor da função é NULL para a linha atual e qualquer linha subsequente processada pela instrução na qual XXX() foi chamado. (xxx() nem mesmo será chamado para linhas subsequentes.) Nota: na versão do MySQL anterior a 3.22.10, você deve configurar *error e *is_null:

*error = 1;
*is_null = 1;

14.2.2.5. Compilando e Instalando Funções Definidas Por Usuário

Arquivos implementando UDFs devem ser compilados e instalados na máquina onde o servidor está sendo executado. Este processo é descrito abaixo pelo arquivo UDF exemplo udf_example.cc que é incluído na distribuição fonte do MySQL. Este arquivo contém as seguintes funções:

  • metaphon() retorna uma string metafonica do argumento string. Ela é algo como uma string soundex, mas é mais voltada para o inglês.

  • myfunc_double() retorna a soma de valores ASCII de caracteres e seus argumentos, dividido pela soma de tamanho de seus argumentos.

  • myfunc_int() retorna a soma do tamanho de seus argumentos.

  • sequence([const int]) retorna uma sequência iniciando a partir de um número dado ou 1 se nenhum número for fornecido.

  • lookup() retorna o IP de um nome de máquina.

  • reverse_lookup() retorna o nome de mauina para um número IP. A função pode ser chamada com uma string "xxx.xxx.xxx.xxx" ou quatro números.

A arquivo carregável dinamicamente deve ser compilado como um arquivo objeto compartilhável usando um comando como este:

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

Você pode encontrar facilmente as opções de compilador corretas para seu sistema executando este comando no diretório sql da sua árvore de fonte MySQL:

shell> make udf_example.o

Você deve executar comando de compilador similar àquele que o make mostra, exceto que você deve remover a opção -c próxima ao fim da linha e adicionar -o udf_example.so. (Em alguns sistemas você pode precisar deixar o comando -c.)

Uma vez que você tenha compilado um objeto compartilhado contendo UDFs, você deve instalá-lo e avisar o MySQL sobre ele. Compilar um objeto compartilhado de udf_example.cc produz um arquivo com nome parecido com udf_example.so (o nome exato pode variar de plataforma para plataforma). Copie este arquivo para algum diretório procurado com o ligador dinâmico ld, tal como /usr/lib ou adicione o diretório no qual você colocou o objeto compartilhado ao arquivo de configuração do ligador (e.g. /etc/ld.so.conf).

Em muitos sistemas você pode as variáveis de ambiente LD_LIBRARY ou LD_LIBRARY_PATH para apontar para o diretório onde se encontra os seus arquivos de funções UDF. A página dlopen do manual diz a você quais variáveis você deve utilizar em seu sistema. Você deve configurar isto nos scripts de inicialização mysql.server ou mysqld_safe e reiniciar o mysqld.

Depois da biblioteca ser instalada, notifique mysqld sobre as novas funções com estes 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";

Funções podem ser deletadas utilizando-se 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;

As instruções CREATE FUNCTION e DROP FUNCTION atualizam a tabela de sistema func no banco de dados mysql. O nome da função, tipo e biblioteca compartilhada são salvas na tabela. Você deve ter os privilégios INSERT e DELETE para o banco de dados mysql para criar e deletar funções.

Você não deve usar CREATE FUNCTION para adicionar uma função que já tenha sido criada. Se você precisar reinstalar uma função, você deve removê-la com DROP FUNCTION e então reinstalá-la com CREATE FUNCTION. Você precisaria fazer isto, por exemplo, se você recompilar uma nova versão da sua função, assim o mysqld obtem a nova versão. Por outro lado, o servidor continuará a utilizar a versão antiga.

Funções ativas são recarregadas a cada vez que o servidor inicia, a menos que você inicie mysqld com a opção --skip-grant-tables. Neste caso, a a inicialização de UDF é ignorada e as UDFs ficam indisponíveis. Uma função ativa é aquela que carregada com CREATE FUNCTION e não removida com DROP FUNCTION.)

14.2.3. Adicionando uma Nova Função Nativa

O procedimento para adicionar uma nova função nativa é descrito aqui. Note que você não pode adicionar funções nativas a distribuição binária porque o procedimento envolve modificação no código fonte do MySQL. Você deve compilar o MySQL de uma distribuição fonte. Note também que se você migrar para outra versão do MySQL (por exemplo, quando uma nova versão é liberada), você precisará repetir o procedimento com a nova versão.

Para adicionar uma função MySQL nativa, siga estes passos:

  1. Adicionr uma linha a lex.h que defina o nome da função no vetor sql_functions[].

  2. Na função protótipo é simples (utilize apenas zero, um, dois ou três argumentos), você deve especificar SYM(FUNC_ARG#) em lex.h (onde # é o número de argumentos) como o segundo argumento no vetor sql_functions[] e adicionar uma função que cria um objeto de função em item_create.cc. De uma olhada em "ABS" e create_funcs_abs() para um exemplo disto.

    Se o protótipo da função for complicado (por exemplo, tiver um número variável de argumentos), você deve adicionar duas linhas a sql_yacc.yy. Uma indica o símbolo pre-processador que o yacc deve difinir (isto deve ser adicionado no começo do arquivo). Então defina os parâmetros da função e adicione um ``item'' com estes parâmetros a regra simple_expr do analizador. Por exemplo, verifique todas as acorrências de ATAN em sql_yacc.yy para ver como ele é feito.

  3. Em item_func.h, declare uma classe herdada de Item_num_func ou Item_str_func, dependendo se sua função retorna um número ou uma string.

  4. Em item_func.cc, adicione uma das seguintes declarações, dependendo se você está definindo uma função numérica ou string:

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

    Se você herdar seu objeto de qualquer um dos itens padrões (como Item_num_func), você provavelmente só deverá definir uma das funções acima e deixar os objetos pais cuidar das outras funções. Por exemplo, a classe Item_str_func define uma função val() que executa atof() no valor retornado por ::str().

  5. Você também deve, provavelmente, definir a seguinte função objeto:

    void Item_func_newname::fix_length_and_dec()
    

    Esta função deve pelo menos calcular max_length baseado nos argumentos dados. max_length é o número máximo de caracteres que a função pode retornar. Esta função também deve definir maybe_null = 0 se a função principal não puder retornar um valor NULL. A função pode verificar se algum dos argumentos da função pode retornar NULL verificando a variável de argumentos maybe_null. Você pode dar uma olhada em Item_func_mod::fix_length_and_dec para um exemplo típico de como fazer isto.

Todas as funções devem ser seguras com thread (em outras palavras, não utilize qualquer variável global ou estática nas funções sem protege-las com mutexes).

Se você retornar NULL, de ::val(), ::val_int() ou ::str() você deve definir null_value com 1 e retornar 0.

Para funções objetos ::str(), existem algumas considerações adicionais das quais você deve estar ciente:

  • O arguemto String *str fornece um buffer string que pode ser utilizado para guardar o resultado. (Para mais informações sobre o tipo String, dê uma olhada no arquivo sql_string.h.)

  • A função ::str() deve retornar a string que guarda o resultado ou (char*) 0 se o resultado é NULL.

  • Todas as funções string atuais tentam evitar a alocação de memória a menos que seja absolutamente necessário!

14.3. Adicionado Novos Procedimentos ao MySQL

No MySQL, você pode definir um procedimento em C++ que pode acessar e modificar os dados em uma consulta antes que ela seja enviada ao cliente. A modificação pode ser feita linha a linha ou a nivel GROUP BY.

Nós criamos um procedimento exemplo no MySQL Versão 3.23 para mostrar o que pode ser feito.

Adicionalmente recomendamos que você de uma olhada em mylua. Com isto você pode utilizar a linguagem LUA para carregar um procedimento em tempo de execução no mysqld.

14.3.1. Análise de Procedimento

analyse([max elements,[max memory]])

Este procedimento é definido em sql/sql_analyse.cc. Ele examina o resultado de sua consulta e retorna uma análise do resultado:

  • max elements (padrão 256) é o número máximo de valores distintos que analyse notificará por coluna. Isto é utilizado por analyse para verificar se o tipo ótimo da coluna deve ser do tipo ENUM.

  • max memory (padrão 8192) é a memória máxima que analyse deve alocar por coluna enquanto tenta encontrar todos os valores distintos.

SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max elements,[max memory]])

14.3.2. Escrevendo um Procedimento

No momento, a única documentação sobre isto é o código fonte.

Você pode encontrar todas as informações sobre procedimentos examinando os seguintes arquivos:

  • sql/sql_analyse.cc

  • sql/procedure.h

  • sql/procedure.cc

  • sql/sql_select.cc


This is a translation of the MySQL Reference Manual that can be found at dev.mysql.com. The original Reference Manual is in English, and this translation is not necessarily as up to date as the English version.