/* Copyright (C) 2002 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* ** Aggregate String Concat file of UDF (user definable functions) that are dynamically loaded ** into the standard mysqld core. ** ** The functions name, type and shared library is saved in the new system ** table 'func'. To be able to create new functions one must have write ** privilege for the database 'mysql'. If one starts MySQL with ** --skip-grant, then UDF initialization will also be skipped. ** ** Syntax for the new commands are: ** create function returns {string|real|integer} ** soname ** drop function ** ** Each defined function may have a xxxx_init function and a xxxx_deinit ** function. The init function should alloc memory for the function ** and tell the main function about the max length of the result ** (for string functions), number of decimals (for double functions) and ** if the result may be a null value. ** ** If a function sets the 'error' argument to 1 the function will not be ** called anymore and mysqld will return NULL for all calls to this copy ** of the function. ** ** All strings arguments to functions are given as string pointer + length ** to allow handling of binary data. ** Remember that all functions must be thread safe. This means that one is not ** allowed to alloc any global or static variables that changes! ** If one needs memory one should alloc this in the init function and free ** this on the __deinit function. ** ** Note that the init and __deinit functions are only called once per ** SQL statement while the value function may be called many times ** ** Function 'group_concat' returns the concatenation of strings in a SELECT query with "GROUP BY" clause (AGGREGATION). ** ** A dynamicly loadable file should be compiled shared. ** (something like: gcc -shared -o my_func.so myfunc.cc). ** You can easily get all switches right by doing: ** cd sql ; make udf_example.o ** Take the compile line that make writes, remove the '-c' near the end of ** the line and add -shared -o udf_example.so to the end of the compile line. ** The resulting library (udf_example.so) should be copied to some dir ** searched by ld. (/usr/lib ?) ** If you are using gcc, then you should be able to create the udf_example.so ** by simply doing 'make udf_example.so'. ** ** After the library is made one must notify mysqld about the new ** functions with the commands: ** ** CREATE AGGREGATE FUNCTION group_concat RETURNS STRING SONAME "MyGroupConcat.dll"; ** ** After this the functions will work exactly like native MySQL functions. ** Functions should be created only once. ** ** The functions can be deleted by: ** ** DROP FUNCTION group_concat; ** ** The CREATE FUNCTION and DROP FUNCTION update the func@mysql table. All ** Active function will be reloaded on every restart of server ** (if --skip-grant-tables is not given) ** ** If you ge problems with undefined symbols when loading the shared ** library, you should verify that mysqld is compiled with the -rdynamic ** option. ** ** If you can't get AGGREGATES to work, check that you have the column ** 'type' in the mysql.func table. If not, run 'mysql_fix_privilege_tables'. ** */ #ifdef STANDARD #include #include #ifdef __WIN__ typedef unsigned __int64 ulonglong; /* Microsofts 64 bit types */ typedef __int64 longlong; #else typedef unsigned long long ulonglong; typedef long long longlong; #endif /*__WIN__*/ #else #include #include #endif #include #include #include // To get strmov() #ifdef HAVE_DLOPEN /* ** String Concat Aggregate Function. */ /* These must be right or mysqld will not find the symbol! */ extern "C" { my_bool group_concat_init( UDF_INIT* initid, UDF_ARGS* args, char* message ); void group_concat_deinit( UDF_INIT* initid ); void group_concat_reset( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error ); void group_concat_add( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error ); char *group_concat(UDF_INIT * initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char * /*error*/ ); } /* ** Syntax for the new aggregate commands are: ** create aggregate function returns {string|real|integer} ** soname ** */ /* ** Data structure for String Concat Aggregate Function. */ struct group_concat_data { ulong count; ulong start_count; ulong end_count; ulong length; ulong previous_length; char *concat_string; }; /************************************************************************* ** init function for group_concat ** Arguments: ** initid Points to a structure that the init function should fill. ** This argument is given to all other functions. ** my_bool maybe_null 1 if function can return NULL ** Default value is 1 if any of the arguments ** is declared maybe_null. ** unsigned int decimals Number of decimals. ** Default value is max decimals in any of the ** arguments. ** unsigned int max_length Length of string result. ** The default value for integer functions is 21 ** The default value for real functions is 13+ ** default number of decimals. ** The default value for string functions is ** the longest string argument. ** char *ptr; A pointer that the function can use. ** ** args Points to a structure which contains: ** unsigned int arg_count Number of arguments ** enum Item_result *arg_type Types for each argument. ** Types are STRING_RESULT, REAL_RESULT ** and INT_RESULT. ** char **args Pointer to constant arguments. ** Contains 0 for not constant argument. ** unsigned long *lengths; max string length for each argument ** char *maybe_null Information of which arguments ** may be NULL ** ** message Error message that should be passed to the user on fail. ** The message buffer is MYSQL_ERRMSG_SIZE big, but one should ** try to keep the error message less than 80 bytes long! ** ** This function should return 1 if something goes wrong. In this case ** message should contain something usefull! **************************************************************************/ my_bool group_concat_init( UDF_INIT* initid, UDF_ARGS* args, char* message ) { struct group_concat_data* data = NULL; if (args->arg_count < 1 || args->arg_count > 5) { strcpy( message, "wrong number of arguments: group_concat() requires 1 to 5 arguments (field name, separator, start, end, max_len_for_sep)" ); return 1; } if ( args->arg_type[0] != STRING_RESULT ) { strcpy(message, "wrong argument type: group_concat() requires a STRING as parameter 1"); return 1; } if (args->arg_count >= 2) { if ( args->arg_type[1] != STRING_RESULT ) { strcpy(message, "wrong argument type: group_concat() requires a STRING as parameter 2"); return 1; } } if (args->arg_count >= 3) { if ( args->arg_type[2] != INT_RESULT ) { strcpy(message, "wrong argument type: group_concat() requires an INT as parameter 3"); return 1; } } if (args->arg_count >= 4) { if ( args->arg_type[3] != INT_RESULT ) { strcpy(message, "wrong argument type: group_concat() requires an INT as parameter 4"); return 1; } } if (args->arg_count >= 5) { if ( args->arg_type[3] != INT_RESULT ) { strcpy(message, "wrong argument type: group_concat() requires an INT as parameter 5"); return 1; } } data = new struct group_concat_data; data->count = 0; data->start_count = 0; data->end_count = 0; data->length = 0; data->previous_length = 0; data->concat_string = NULL; initid->maybe_null = 0; // The result may be null initid->max_length = 65535; // 65535 characters maximum (Maximum size of a MySQL "TEXT" field) initid->ptr = (char*)data; return 0; } /**************************************************************************** ** Deinit function. This should free all resources allocated by ** this function. ** Arguments: ** initid Return value from xxxx_init ****************************************************************************/ void group_concat_deinit( UDF_INIT* initid ) { struct group_concat_data* data = (struct group_concat_data*)initid->ptr; if (data != NULL) { if (data->concat_string != NULL) { free(data->concat_string); data->concat_string = NULL; } data->count = 0; data->start_count = 0; data->end_count = 0; // Unlimited data->length = 0; data->previous_length = 0; delete data; initid->ptr = NULL; } } void group_concat_reset( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char* message ) { ulong intStartCount = 1; ulong intEndCount = 0; // Unlimited if (args->arg_count >= 3) { intStartCount = *((ulong*)args->args[2]); } if (args->arg_count >= 4) { intEndCount = *((ulong*)args->args[3]); } struct group_concat_data* data = (struct group_concat_data*)initid->ptr; if (data->concat_string != NULL) { free(data->concat_string); data->concat_string = NULL; } data->count = 0; data->start_count = intStartCount-1; data->end_count = intEndCount; data->previous_length = 0; data->length = 0; *is_null = 0; group_concat_add( initid, args, is_null, message ); } void group_concat_add( UDF_INIT* initid, UDF_ARGS* args, char* /*is_null*/, char* /*message*/ ) { struct group_concat_data* data = (struct group_concat_data*)initid->ptr; // Check if current string is between allowed range (start_count, end_count) if (data->count >= data->start_count && (data->count < data->end_count || data->end_count == 0) ) { if (args->args[0]) { unsigned long intToConcat = args->lengths[0]; char *lpstrToConcat = args->args[0]; ulong intTotal = 0; char *lpstrTotal = NULL; ulong intSeparator = 0; char *lpstrSeparator = NULL; ulong intMaxLenForSep = 0; // No maximum if (args->arg_count >= 2) { intSeparator = args->lengths[1]; lpstrSeparator = args->args[1]; } if (args->arg_count >= 5) { intMaxLenForSep = *((ulong*)args->args[4]); } // Compute total string size after concatenation intTotal = intToConcat + data->length; if (data->concat_string != NULL && (data->previous_length < intMaxLenForSep || intMaxLenForSep == 0)) { // We will add a separator - so we need space to store it intTotal += intSeparator; } lpstrTotal = (char *)malloc(intTotal+1); // Copy previous string if (data->concat_string == NULL || data->length == 0) { lpstrTotal[0] = '\0'; } else { strncpy(lpstrTotal, data->concat_string, data->length); // Add a termination character in order to do a strncat (concatenation) lpstrTotal[data->length] = '\0'; } // Copy separator (if any, and if previous string is not empty/null) if (data->concat_string != NULL && (data->previous_length < intMaxLenForSep || intMaxLenForSep == 0)) { strncat(lpstrTotal, (lpstrSeparator == NULL ? "" : lpstrSeparator), intSeparator); } // Append new string strncat(lpstrTotal, lpstrToConcat,intToConcat); // Force end of string (we never know) lpstrTotal[intTotal] = '\0'; // Free previous string, if allocated if (data->concat_string != NULL) { free(data->concat_string); } // Store results in data structure data->concat_string = lpstrTotal; data->length = intTotal; data->previous_length = intToConcat; } } // Count number of concatenated strings data->count++; } /*************************************************************************** ** UDF string function. ** Arguments: ** initid Structure filled by xxx_init ** args The same structure as to xxx_init. This structure ** contains values for all parameters. ** Note that the functions MUST check and convert all ** to the type it wants! Null values are represented by ** a NULL pointer ** result Possible buffer to save result. At least 255 byte long. ** length Pointer to length of the above buffer. In this the function ** should save the result length ** is_null If the result is null, one should store 1 here. ** error If something goes fatally wrong one should store 1 here. ** ** This function should return a pointer to the result string. ** Normally this is 'result' but may also be an alloced string. ***************************************************************************/ char *group_concat(UDF_INIT * initid, UDF_ARGS * /*args*/, char * /*result*/, unsigned long *length, char *is_null, char * /*error*/ ) { struct group_concat_data* data = (struct group_concat_data*)initid->ptr; if (!data->count || !data->concat_string) { *is_null = 1; (*length) = 0; return NULL; } *is_null = 0; ulong intResult = data->length; initid->max_length = data->length; // Set length of the ouput string (*length) = intResult; return data->concat_string; } #endif