1 /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 /*
24 ** example file of UDF (user definable functions) that are dynamicly loaded
25 ** into the standard mysqld core.
26 **
27 ** The functions name, type and shared library is saved in the new system
28 ** table 'func'.  To be able to create new functions one must have write
29 ** privilege for the database 'mysql'.	If one starts MySQL with
30 ** --skip-grant-tables, then UDF initialization will also be skipped.
31 **
32 ** Syntax for the new commands are:
33 ** create function <function_name> returns {string|real|integer}
34 **		  soname <name_of_shared_library>
35 ** drop function <function_name>
36 **
37 ** Each defined function may have a xxxx_init function and a xxxx_deinit
38 ** function.  The init function should alloc memory for the function
39 ** and tell the main function about the max length of the result
40 ** (for string functions), number of decimals (for double functions) and
41 ** if the result may be a null value.
42 **
43 ** If a function sets the 'error' argument to 1 the function will not be
44 ** called anymore and mysqld will return NULL for all calls to this copy
45 ** of the function.
46 **
47 ** All strings arguments to functions are given as string pointer + length
48 ** to allow handling of binary data.
49 ** Remember that all functions must be thread safe. This means that one is not
50 ** allowed to alloc any global or static variables that changes!
51 ** If one needs memory one should alloc this in the init function and free
52 ** this on the __deinit function.
53 **
54 ** Note that the init and __deinit functions are only called once per
55 ** SQL statement while the value function may be called many times
56 **
57 ** Function 'metaphon' returns a metaphon string of the string argument.
58 ** This is something like a soundex string, but it's more tuned for English.
59 **
60 ** Function 'myfunc_double' returns summary of codes of all letters
61 ** of arguments divided by summary length of all its arguments.
62 **
63 ** Function 'myfunc_int' returns summary length of all its arguments.
64 **
65 ** Function 'sequence' returns an sequence starting from a certain number.
66 **
67 ** Function 'myfunc_argument_name' returns name of argument.
68 **
69 ** On the end is a couple of functions that converts hostnames to ip and
70 ** vice versa.
71 **
72 ** A dynamicly loadable file should be compiled shared.
73 ** (something like: gcc -shared -o my_func.so myfunc.cc).
74 ** You can easily get all switches right by doing:
75 ** cd sql ; make udf_example.o
76 ** Take the compile line that make writes, remove the '-c' near the end of
77 ** the line and add -shared -o udf_example.so to the end of the compile line.
78 ** The resulting library (udf_example.so) should be copied to some dir
79 ** searched by ld. (/usr/lib ?)
80 ** If you are using gcc, then you should be able to create the udf_example.so
81 ** by simply doing 'make udf_example.so'.
82 **
83 ** After the library is made one must notify mysqld about the new
84 ** functions with the commands:
85 **
86 ** CREATE FUNCTION metaphon RETURNS STRING SONAME "udf_example.so";
87 ** CREATE FUNCTION myfunc_double RETURNS REAL SONAME "udf_example.so";
88 ** CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME "udf_example.so";
89 ** CREATE FUNCTION sequence RETURNS INTEGER SONAME "udf_example.so";
90 ** CREATE FUNCTION lookup RETURNS STRING SONAME "udf_example.so";
91 ** CREATE FUNCTION reverse_lookup RETURNS STRING SONAME "udf_example.so";
92 ** CREATE AGGREGATE FUNCTION avgcost RETURNS REAL SONAME "udf_example.so";
93 ** CREATE FUNCTION myfunc_argument_name RETURNS STRING SONAME "udf_example.so";
94 **
95 ** After this the functions will work exactly like native MySQL functions.
96 ** Functions should be created only once.
97 **
98 ** The functions can be deleted by:
99 **
100 ** DROP FUNCTION metaphon;
101 ** DROP FUNCTION myfunc_double;
102 ** DROP FUNCTION myfunc_int;
103 ** DROP FUNCTION lookup;
104 ** DROP FUNCTION reverse_lookup;
105 ** DROP FUNCTION avgcost;
106 ** DROP FUNCTION myfunc_argument_name;
107 **
108 ** The CREATE FUNCTION and DROP FUNCTION update the func@mysql table. All
109 ** Active function will be reloaded on every restart of server
110 ** (if --skip-grant-tables is not given)
111 **
112 ** If you ge problems with undefined symbols when loading the shared
113 ** library, you should verify that mysqld is compiled with the -rdynamic
114 ** option.
115 **
116 ** If you can't get AGGREGATES to work, check that you have the column
117 ** 'type' in the mysql.func table.  If not, run 'mysql_upgrade'.
118 **
119 */
120 
121 #include <my_global.h>
122 #include <my_sys.h>
123 
124 #include <new>
125 #include <vector>
126 #include <algorithm>
127 
128 #if defined(MYSQL_SERVER)
129 #include <m_string.h>		/* To get my_stpcpy() */
130 #else
131 /* when compiled as standalone */
132 #include <string.h>
133 #define my_stpcpy(a,b) stpcpy(a,b)
134 #endif
135 
136 #include <mysql.h>
137 #include <ctype.h>
138 
139 #ifdef _WIN32
140 /* inet_aton needs winsock library */
141 #pragma comment(lib, "ws2_32")
142 #endif
143 
144 #ifdef HAVE_DLOPEN
145 
146 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
147 static native_mutex_t LOCK_hostname;
148 #endif
149 
150 /* These must be right or mysqld will not find the symbol! */
151 
152 C_MODE_START;
153 my_bool metaphon_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
154 void metaphon_deinit(UDF_INIT *initid);
155 char *metaphon(UDF_INIT *initid, UDF_ARGS *args, char *result,
156 	       unsigned long *length, char *is_null, char *error);
157 my_bool myfunc_double_init(UDF_INIT *, UDF_ARGS *args, char *message);
158 double myfunc_double(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
159 		     char *error);
160 my_bool myfunc_int_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
161 longlong myfunc_int(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
162 		    char *error);
163 my_bool sequence_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
164  void sequence_deinit(UDF_INIT *initid);
165 longlong sequence(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
166 		   char *error);
167 my_bool avgcost_init( UDF_INIT* initid, UDF_ARGS* args, char* message );
168 void avgcost_deinit( UDF_INIT* initid );
169 void avgcost_reset( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
170 void avgcost_clear( UDF_INIT* initid, char* is_null, char *error );
171 void avgcost_add( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
172 double avgcost( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
173 my_bool is_const_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
174 char *is_const(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long
175                *length, char *is_null, char *error);
176 C_MODE_END;
177 
178 /*************************************************************************
179 ** Example of init function
180 ** Arguments:
181 ** initid	Points to a structure that the init function should fill.
182 **		This argument is given to all other functions.
183 **	my_bool maybe_null	1 if function can return NULL
184 **				Default value is 1 if any of the arguments
185 **				is declared maybe_null.
186 **	unsigned int decimals	Number of decimals.
187 **				Default value is max decimals in any of the
188 **				arguments.
189 **	unsigned int max_length  Length of string result.
190 **				The default value for integer functions is 21
191 **				The default value for real functions is 13+
192 **				default number of decimals.
193 **				The default value for string functions is
194 **				the longest string argument.
195 **	char *ptr;		A pointer that the function can use.
196 **
197 ** args		Points to a structure which contains:
198 **	unsigned int arg_count		Number of arguments
199 **	enum Item_result *arg_type	Types for each argument.
200 **					Types are STRING_RESULT, REAL_RESULT
201 **					and INT_RESULT.
202 **	char **args			Pointer to constant arguments.
203 **					Contains 0 for not constant argument.
204 **	unsigned long *lengths;		max string length for each argument
205 **	char *maybe_null		Information of which arguments
206 **					may be NULL
207 **
208 ** message	Error message that should be passed to the user on fail.
209 **		The message buffer is MYSQL_ERRMSG_SIZE big, but one should
210 **		try to keep the error message less than 80 bytes long!
211 **
212 ** This function should return 1 if something goes wrong. In this case
213 ** message should contain something usefull!
214 **************************************************************************/
215 
216 #define MAXMETAPH 8
217 
metaphon_init(UDF_INIT * initid,UDF_ARGS * args,char * message)218 my_bool metaphon_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
219 {
220   if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
221   {
222     strcpy(message,"Wrong arguments to metaphon;  Use the source");
223     return 1;
224   }
225   initid->max_length=MAXMETAPH;
226   return 0;
227 }
228 
229 /****************************************************************************
230 ** Deinit function. This should free all resources allocated by
231 ** this function.
232 ** Arguments:
233 ** initid	Return value from xxxx_init
234 ****************************************************************************/
235 
236 
metaphon_deinit(UDF_INIT * initid MY_ATTRIBUTE ((unused)))237 void metaphon_deinit(UDF_INIT *initid MY_ATTRIBUTE((unused)))
238 {
239 }
240 
241 /***************************************************************************
242 ** UDF string function.
243 ** Arguments:
244 ** initid	Structure filled by xxx_init
245 ** args		The same structure as to xxx_init. This structure
246 **		contains values for all parameters.
247 **		Note that the functions MUST check and convert all
248 **		to the type it wants!  Null values are represented by
249 **		a NULL pointer
250 ** result	Possible buffer to save result. At least 255 byte long.
251 ** length	Pointer to length of the above buffer.	In this the function
252 **		should save the result length
253 ** is_null	If the result is null, one should store 1 here.
254 ** error	If something goes fatally wrong one should store 1 here.
255 **
256 ** This function should return a pointer to the result string.
257 ** Normally this is 'result' but may also be an alloced string.
258 ***************************************************************************/
259 
260 /* Character coding array */
261 static char codes[26] =  {
262     1,16,4,16,9,2,4,16,9,2,0,2,2,2,1,4,0,2,4,4,1,0,0,0,8,0
263  /* A  B C  D E F G  H I J K L M N O P Q R S T U V W X Y Z*/
264     };
265 
266 /*--- Macros to access character coding array -------------*/
267 
268 #define ISVOWEL(x)  (codes[(x) - 'A'] & 1)	/* AEIOU */
269 
270     /* Following letters are not changed */
271 #define NOCHANGE(x) (codes[(x) - 'A'] & 2)	/* FJLMNR */
272 
273     /* These form diphthongs when preceding H */
274 #define AFFECTH(x) (codes[(x) - 'A'] & 4)	/* CGPST */
275 
276     /* These make C and G soft */
277 #define MAKESOFT(x) (codes[(x) - 'A'] & 8)	/* EIY */
278 
279     /* These prevent GH from becoming F */
280 #define NOGHTOF(x)  (codes[(x) - 'A'] & 16)	/* BDH */
281 
282 
metaphon(UDF_INIT * initid MY_ATTRIBUTE ((unused)),UDF_ARGS * args,char * result,unsigned long * length,char * is_null,char * error MY_ATTRIBUTE ((unused)))283 char *metaphon(UDF_INIT *initid MY_ATTRIBUTE((unused)),
284                UDF_ARGS *args, char *result, unsigned long *length,
285                char *is_null, char *error MY_ATTRIBUTE((unused)))
286 {
287   const char *word=args->args[0];
288   const char *w_end;
289   char *org_result;
290   char *n, *n_start, *n_end; /* pointers to string */
291   char *metaph_end;	     /* pointers to end of metaph */
292   char ntrans[32];	     /* word with uppercase letters */
293   int  KSflag;		     /* state flag for X to KS */
294 
295   if (!word)					/* Null argument */
296   {
297     /* The length is expected to be zero when the argument is NULL. */
298     assert(args->lengths[0] == 0);
299     *is_null=1;
300     return 0;
301   }
302 
303   w_end=word+args->lengths[0];
304   org_result=result;
305 
306   /*--------------------------------------------------------
307    *  Copy word to internal buffer, dropping non-alphabetic
308    *  characters and converting to uppercase.
309    *-------------------------------------------------------*/
310 
311   for (n = ntrans + 1, n_end = ntrans + sizeof(ntrans)-2;
312 	word != w_end && n < n_end; word++ )
313     if ( isalpha ( *word ))
314       *n++ = toupper ( *word );
315 
316   if ( n == ntrans + 1 )	/* return empty string if 0 bytes */
317   {
318     *length=0;
319     return result;
320   }
321   n_end = n;			/* set n_end to end of string */
322   ntrans[0] = 'Z';		/* ntrans[0] should be a neutral char */
323   n[0]=n[1]=0;			/* pad with nulls */
324   n = ntrans + 1;		/* assign pointer to start */
325 
326   /*------------------------------------------------------------
327    *  check for all prefixes:
328    *		PN KN GN AE WR WH and X at start.
329    *----------------------------------------------------------*/
330 
331   switch ( *n ) {
332   case 'P':
333   case 'K':
334   case 'G':
335     if ( n[1] == 'N')
336       *n++ = 0;
337     break;
338   case 'A':
339     if ( n[1] == 'E')
340       *n++ = 0;
341     break;
342   case 'W':
343     if ( n[1] == 'R' )
344       *n++ = 0;
345     else
346       if ( *(n + 1) == 'H')
347       {
348 	n[1] = *n;
349 	*n++ = 0;
350       }
351     break;
352   case 'X':
353     *n = 'S';
354     break;
355   }
356 
357   /*------------------------------------------------------------
358    *  Now, loop step through string, stopping at end of string
359    *  or when the computed metaph is MAXMETAPH characters long
360    *----------------------------------------------------------*/
361 
362   KSflag = 0; /* state flag for KS translation */
363 
364   for (metaph_end = result + MAXMETAPH, n_start = n;
365 	n < n_end && result < metaph_end; n++ )
366   {
367 
368     if ( KSflag )
369     {
370       KSflag = 0;
371       *result++ = *n;
372     }
373     else
374     {
375       /* drop duplicates except for CC */
376       if ( *( n - 1 ) == *n && *n != 'C' )
377 	continue;
378 
379       /* check for F J L M N R or first letter vowel */
380       if ( NOCHANGE ( *n ) ||
381 	   ( n == n_start && ISVOWEL ( *n )))
382 	*result++ = *n;
383       else
384 	switch ( *n ) {
385 	case 'B':	 /* check for -MB */
386 	  if ( n < n_end || *( n - 1 ) != 'M' )
387 	    *result++ = *n;
388 	  break;
389 
390 	case 'C': /* C = X ("sh" sound) in CH and CIA */
391 	  /*   = S in CE CI and CY	      */
392 	  /*	 dropped in SCI SCE SCY       */
393 	  /* else K			      */
394 	  if ( *( n - 1 ) != 'S' ||
395 	       !MAKESOFT ( n[1]))
396 	  {
397 	    if ( n[1] == 'I' && n[2] == 'A' )
398 	      *result++ = 'X';
399 	    else
400 	      if ( MAKESOFT ( n[1]))
401 		*result++ = 'S';
402 	      else
403 		if ( n[1] == 'H' )
404 		  *result++ = (( n == n_start &&
405 				 !ISVOWEL ( n[2])) ||
406 			       *( n - 1 ) == 'S' ) ?
407 		    'K' : 'X';
408 		else
409 		  *result++ = 'K';
410 	  }
411 	  break;
412 
413 	case 'D':  /* J before DGE, DGI, DGY, else T */
414 	  *result++ =
415 	    ( n[1] == 'G' &&
416 	      MAKESOFT ( n[2])) ?
417 	    'J' : 'T';
418 	  break;
419 
420 	case 'G':   /* complicated, see table in text */
421 	  if (( n[1] != 'H' || ISVOWEL ( n[2]))
422 	      && (
423 		  n[1] != 'N' ||
424 		  (
425 		   (n + 1) < n_end  &&
426 		   (
427 		    n[2] != 'E' ||
428 		    *( n + 3 ) != 'D'
429 		    )
430 		   )
431 		  )
432 	      && (
433 		  *( n - 1 ) != 'D' ||
434 		  !MAKESOFT ( n[1])
435 		  )
436 	      )
437 	    *result++ =
438 	      ( MAKESOFT ( *( n  + 1 )) &&
439 		n[2] != 'G' ) ?
440 	      'J' : 'K';
441 	  else
442 	    if ( n[1] == 'H'   &&
443 		!NOGHTOF( *( n - 3 )) &&
444 		*( n - 4 ) != 'H')
445 	      *result++ = 'F';
446 	  break;
447 
448 	case 'H':   /* H if before a vowel and not after */
449 	  /* C, G, P, S, T */
450 
451 	  if ( !AFFECTH ( *( n - 1 )) &&
452 	       ( !ISVOWEL ( *( n - 1 )) ||
453 		 ISVOWEL ( n[1])))
454 	    *result++ = 'H';
455 	  break;
456 
457 	case 'K':    /* K = K, except dropped after C */
458 	  if ( *( n - 1 ) != 'C')
459 	    *result++ = 'K';
460 	  break;
461 
462 	case 'P':    /* PH = F, else P = P */
463 	  *result++ = *( n +  1 ) == 'H'
464 	    ? 'F' : 'P';
465 	  break;
466 	case 'Q':   /* Q = K (U after Q is already gone */
467 	  *result++ = 'K';
468 	  break;
469 
470 	case 'S':   /* SH, SIO, SIA = X ("sh" sound) */
471 	  *result++ = ( n[1] == 'H' ||
472 			( *(n  + 1) == 'I' &&
473 			  ( n[2] == 'O' ||
474 			    n[2] == 'A')))  ?
475 	    'X' : 'S';
476 	  break;
477 
478 	case 'T':  /* TIO, TIA = X ("sh" sound) */
479 	  /* TH = 0, ("th" sound ) */
480 	  if ( *( n  + 1 ) == 'I' && ( n[2] == 'O'
481 				      || n[2] == 'A') )
482 	    *result++ = 'X';
483 	  else
484 	    if ( n[1] == 'H' )
485 	      *result++ = '0';
486 	    else
487 	      if ( *( n + 1) != 'C' || n[2] != 'H')
488 		*result++ = 'T';
489 	  break;
490 
491 	case 'V':     /* V = F */
492 	  *result++ = 'F';
493 	  break;
494 
495 	case 'W':     /* only exist if a vowel follows */
496 	case 'Y':
497 	  if ( ISVOWEL ( n[1]))
498 	    *result++ = *n;
499 	  break;
500 
501 	case 'X':     /* X = KS, except at start */
502 	  if ( n == n_start )
503 	    *result++ = 'S';
504 	  else
505 	  {
506 	    *result++ = 'K'; /* insert K, then S */
507 	    KSflag = 1; /* this flag will cause S to be
508 			   inserted on next pass thru loop */
509 	  }
510 	  break;
511 
512 	case 'Z':
513 	  *result++ = 'S';
514 	  break;
515 	}
516     }
517   }
518   *length= (unsigned long) (result - org_result);
519   return org_result;
520 }
521 
522 
523 /***************************************************************************
524 ** UDF double function.
525 ** Arguments:
526 ** initid	Structure filled by xxx_init
527 ** args		The same structure as to xxx_init. This structure
528 **		contains values for all parameters.
529 **		Note that the functions MUST check and convert all
530 **		to the type it wants!  Null values are represented by
531 **		a NULL pointer
532 ** is_null	If the result is null, one should store 1 here.
533 ** error	If something goes fatally wrong one should store 1 here.
534 **
535 ** This function should return the result.
536 ***************************************************************************/
537 
myfunc_double_init(UDF_INIT * initid,UDF_ARGS * args,char * message)538 my_bool myfunc_double_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
539 {
540   uint i;
541 
542   if (!args->arg_count)
543   {
544     strcpy(message,"myfunc_double must have at least one argument");
545     return 1;
546   }
547   /*
548   ** As this function wants to have everything as strings, force all arguments
549   ** to strings.
550   */
551   for (i=0 ; i < args->arg_count; i++)
552     args->arg_type[i]=STRING_RESULT;
553   initid->maybe_null=1;		/* The result may be null */
554   initid->decimals=2;		/* We want 2 decimals in the result */
555   initid->max_length=6;		/* 3 digits + . + 2 decimals */
556   return 0;
557 }
558 
559 
myfunc_double(UDF_INIT * initid MY_ATTRIBUTE ((unused)),UDF_ARGS * args,char * is_null,char * error MY_ATTRIBUTE ((unused)))560 double myfunc_double(UDF_INIT *initid MY_ATTRIBUTE((unused)), UDF_ARGS *args,
561                      char *is_null, char *error MY_ATTRIBUTE((unused)))
562 {
563   unsigned long val = 0;
564   unsigned long v = 0;
565   uint i, j;
566 
567   for (i = 0; i < args->arg_count; i++)
568   {
569     if (args->args[i] == NULL)
570       continue;
571     val += args->lengths[i];
572     for (j=args->lengths[i] ; j-- > 0 ;)
573       v += args->args[i][j];
574   }
575   if (val)
576     return (double) v/ (double) val;
577   *is_null=1;
578   return 0.0;
579 }
580 
581 
582 /***************************************************************************
583 ** UDF long long function.
584 ** Arguments:
585 ** initid	Return value from xxxx_init
586 ** args		The same structure as to xxx_init. This structure
587 **		contains values for all parameters.
588 **		Note that the functions MUST check and convert all
589 **		to the type it wants!  Null values are represented by
590 **		a NULL pointer
591 ** is_null	If the result is null, one should store 1 here.
592 ** error	If something goes fatally wrong one should store 1 here.
593 **
594 ** This function should return the result as a long long
595 ***************************************************************************/
596 
597 /* This function returns the sum of all arguments */
598 
myfunc_int(UDF_INIT * initid MY_ATTRIBUTE ((unused)),UDF_ARGS * args,char * is_null MY_ATTRIBUTE ((unused)),char * error MY_ATTRIBUTE ((unused)))599 longlong myfunc_int(UDF_INIT *initid MY_ATTRIBUTE((unused)), UDF_ARGS *args,
600                     char *is_null MY_ATTRIBUTE((unused)),
601                     char *error MY_ATTRIBUTE((unused)))
602 {
603   longlong val = 0;
604   uint i;
605 
606   for (i = 0; i < args->arg_count; i++)
607   {
608     if (args->args[i] == NULL)
609       continue;
610     switch (args->arg_type[i]) {
611     case STRING_RESULT:			/* Add string lengths */
612       val += args->lengths[i];
613       break;
614     case INT_RESULT:			/* Add numbers */
615       val += *((longlong*) args->args[i]);
616       break;
617     case REAL_RESULT:			/* Add numers as longlong */
618       val += (longlong) *((double*) args->args[i]);
619       break;
620     default:
621       break;
622     }
623   }
624   return val;
625 }
626 
627 /*
628   At least one of _init/_deinit is needed unless the server is started
629   with --allow_suspicious_udfs.
630 */
myfunc_int_init(UDF_INIT * initid MY_ATTRIBUTE ((unused)),UDF_ARGS * args MY_ATTRIBUTE ((unused)),char * message MY_ATTRIBUTE ((unused)))631 my_bool myfunc_int_init(UDF_INIT *initid MY_ATTRIBUTE((unused)),
632                         UDF_ARGS *args MY_ATTRIBUTE((unused)),
633                         char *message MY_ATTRIBUTE((unused)))
634 {
635   return 0;
636 }
637 
638 /*
639   Simple example of how to get a sequences starting from the first argument
640   or 1 if no arguments have been given
641 */
642 
sequence_init(UDF_INIT * initid,UDF_ARGS * args,char * message)643 my_bool sequence_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
644 {
645   if (args->arg_count > 1)
646   {
647     my_stpcpy(message,"This function takes none or 1 argument");
648     return 1;
649   }
650   if (args->arg_count)
651     args->arg_type[0]= INT_RESULT;		/* Force argument to int */
652 
653   if (!(initid->ptr=(char*) malloc(sizeof(longlong))))
654   {
655     my_stpcpy(message,"Couldn't allocate memory");
656     return 1;
657   }
658   memset(initid->ptr, 0, sizeof(longlong));
659   /*
660     sequence() is a non-deterministic function : it has different value
661     even if called with the same arguments.
662   */
663   initid->const_item=0;
664   return 0;
665 }
666 
sequence_deinit(UDF_INIT * initid)667 void sequence_deinit(UDF_INIT *initid)
668 {
669   if (initid->ptr)
670     free(initid->ptr);
671 }
672 
sequence(UDF_INIT * initid MY_ATTRIBUTE ((unused)),UDF_ARGS * args,char * is_null MY_ATTRIBUTE ((unused)),char * error MY_ATTRIBUTE ((unused)))673 longlong sequence(UDF_INIT *initid MY_ATTRIBUTE((unused)), UDF_ARGS *args,
674                   char *is_null MY_ATTRIBUTE((unused)),
675                   char *error MY_ATTRIBUTE((unused)))
676 {
677   ulonglong val=0;
678   if (args->arg_count)
679     val= *((longlong*) args->args[0]);
680   return ++*((longlong*) initid->ptr) + val;
681 }
682 
683 
684 /****************************************************************************
685 ** Some functions that handles IP and hostname conversions
686 ** The orignal function was from Zeev Suraski.
687 **
688 ** CREATE FUNCTION lookup RETURNS STRING SONAME "udf_example.so";
689 ** CREATE FUNCTION reverse_lookup RETURNS STRING SONAME "udf_example.so";
690 **
691 ****************************************************************************/
692 
693 #ifndef _WIN32
694 #include <netdb.h>
695 #endif
696 #ifdef HAVE_ARPA_INET_H
697 #include <arpa/inet.h>
698 #endif
699 #ifdef HAVE_NETINET_IN_H
700 #include <netinet/in.h>
701 #endif
702 #ifdef HAVE_SYS_SOCKET_H
703 #include <sys/socket.h>
704 #endif
705 
706 C_MODE_START;
707 my_bool lookup_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
708 void lookup_deinit(UDF_INIT *initid);
709 char *lookup(UDF_INIT *initid, UDF_ARGS *args, char *result,
710 	     unsigned long *length, char *null_value, char *error);
711 my_bool reverse_lookup_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
712 void reverse_lookup_deinit(UDF_INIT *initid);
713 char *reverse_lookup(UDF_INIT *initid, UDF_ARGS *args, char *result,
714 		     unsigned long *length, char *null_value, char *error);
715 C_MODE_END;
716 
717 
718 /****************************************************************************
719 ** lookup IP for an hostname.
720 **
721 ** This code assumes that gethostbyname_r exists and inet_ntoa() is thread
722 ** safe (As it is in Solaris)
723 ****************************************************************************/
724 
725 
lookup_init(UDF_INIT * initid,UDF_ARGS * args,char * message)726 my_bool lookup_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
727 {
728   if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
729   {
730     my_stpcpy(message,"Wrong arguments to lookup;  Use the source");
731     return 1;
732   }
733   initid->max_length=11;
734   initid->maybe_null=1;
735 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
736   (void) native_mutex_init(&LOCK_hostname,MY_MUTEX_INIT_SLOW);
737 #endif
738   return 0;
739 }
740 
lookup_deinit(UDF_INIT * initid MY_ATTRIBUTE ((unused)))741 void lookup_deinit(UDF_INIT *initid MY_ATTRIBUTE((unused)))
742 {
743 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
744   (void) native_mutex_destroy(&LOCK_hostname);
745 #endif
746 }
747 
lookup(UDF_INIT * initid MY_ATTRIBUTE ((unused)),UDF_ARGS * args,char * result,unsigned long * res_length,char * null_value,char * error MY_ATTRIBUTE ((unused)))748 char *lookup(UDF_INIT *initid MY_ATTRIBUTE((unused)), UDF_ARGS *args,
749              char *result, unsigned long *res_length, char *null_value,
750              char *error MY_ATTRIBUTE((unused)))
751 {
752   uint length;
753   char name_buff[256];
754   struct hostent *hostent;
755 #if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST)
756   int tmp_errno;
757   char hostname_buff[2048];
758   struct hostent tmp_hostent;
759 #endif
760   struct in_addr in;
761 
762   if (!args->args[0] || !(length=args->lengths[0]))
763   {
764     *null_value=1;
765     return 0;
766   }
767   if (length >= sizeof(name_buff))
768     length=sizeof(name_buff)-1;
769   memcpy(name_buff,args->args[0],length);
770   name_buff[length]=0;
771 #if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST)
772   if (!(hostent=gethostbyname_r(name_buff,&tmp_hostent,hostname_buff,
773 				sizeof(hostname_buff), &tmp_errno)))
774   {
775     *null_value=1;
776     return 0;
777   }
778 #else
779   native_mutex_lock(&LOCK_hostname);
780   if (!(hostent= gethostbyname((char*) name_buff)))
781   {
782     native_mutex_unlock(&LOCK_hostname);
783     *null_value= 1;
784     return 0;
785   }
786   native_mutex_unlock(&LOCK_hostname);
787 #endif
788   memcpy(&in, *hostent->h_addr_list, sizeof(in.s_addr));
789   *res_length= (ulong) (my_stpcpy(result, inet_ntoa(in)) - result);
790   return result;
791 }
792 
793 
794 /****************************************************************************
795 ** return hostname for an IP number.
796 ** The functions can take as arguments a string "xxx.xxx.xxx.xxx" or
797 ** four numbers.
798 ****************************************************************************/
799 
reverse_lookup_init(UDF_INIT * initid,UDF_ARGS * args,char * message)800 my_bool reverse_lookup_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
801 {
802   if (args->arg_count == 1)
803     args->arg_type[0]= STRING_RESULT;
804   else if (args->arg_count == 4)
805     args->arg_type[0]=args->arg_type[1]=args->arg_type[2]=args->arg_type[3]=
806       INT_RESULT;
807   else
808   {
809     my_stpcpy(message,
810 	   "Wrong number of arguments to reverse_lookup;  Use the source");
811     return 1;
812   }
813   initid->max_length=32;
814   initid->maybe_null=1;
815 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
816   (void) native_mutex_init(&LOCK_hostname,MY_MUTEX_INIT_SLOW);
817 #endif
818   return 0;
819 }
820 
reverse_lookup_deinit(UDF_INIT * initid MY_ATTRIBUTE ((unused)))821 void reverse_lookup_deinit(UDF_INIT *initid MY_ATTRIBUTE((unused)))
822 {
823 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
824   (void) native_mutex_destroy(&LOCK_hostname);
825 #endif
826 }
827 
reverse_lookup(UDF_INIT * initid MY_ATTRIBUTE ((unused)),UDF_ARGS * args,char * result,unsigned long * res_length,char * null_value,char * error MY_ATTRIBUTE ((unused)))828 char *reverse_lookup(UDF_INIT *initid MY_ATTRIBUTE((unused)), UDF_ARGS *args,
829                      char *result, unsigned long *res_length,
830                      char *null_value, char *error MY_ATTRIBUTE((unused)))
831 {
832 #if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST)
833   char name_buff[256];
834   struct hostent tmp_hostent;
835   int tmp_errno;
836 #endif
837   struct hostent *hp;
838   unsigned long taddr;
839   uint length;
840 
841   if (args->arg_count == 4)
842   {
843     if (!args->args[0] || !args->args[1] ||!args->args[2] ||!args->args[3])
844     {
845       *null_value=1;
846       return 0;
847     }
848     sprintf(result,"%d.%d.%d.%d",
849 	    (int) *((longlong*) args->args[0]),
850 	    (int) *((longlong*) args->args[1]),
851 	    (int) *((longlong*) args->args[2]),
852 	    (int) *((longlong*) args->args[3]));
853   }
854   else
855   {					/* string argument */
856     if (!args->args[0])			/* Return NULL for NULL values */
857     {
858       *null_value=1;
859       return 0;
860     }
861     length=args->lengths[0];
862     if (length >= (uint) *res_length-1)
863       length=(uint) *res_length;
864     memcpy(result,args->args[0],length);
865     result[length]=0;
866   }
867 
868   taddr = inet_addr(result);
869   if (taddr == (unsigned long) -1L)
870   {
871     *null_value=1;
872     return 0;
873   }
874 #if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST)
875   if (!(hp=gethostbyaddr_r((char*) &taddr,sizeof(taddr), AF_INET,
876 			   &tmp_hostent, name_buff,sizeof(name_buff),
877 			   &tmp_errno)))
878   {
879     *null_value=1;
880     return 0;
881   }
882 #else
883   native_mutex_lock(&LOCK_hostname);
884   if (!(hp= gethostbyaddr((char*) &taddr, sizeof(taddr), AF_INET)))
885   {
886     native_mutex_unlock(&LOCK_hostname);
887     *null_value= 1;
888     return 0;
889   }
890   native_mutex_unlock(&LOCK_hostname);
891 #endif
892   *res_length=(ulong) (my_stpcpy(result,hp->h_name) - result);
893   return result;
894 }
895 
896 /*
897 ** Syntax for the new aggregate commands are:
898 ** create aggregate function <function_name> returns {string|real|integer}
899 **		  soname <name_of_shared_library>
900 **
901 ** Syntax for avgcost: avgcost( t.quantity, t.price )
902 **	with t.quantity=integer, t.price=double
903 ** (this example is provided by Andreas F. Bobak <bobak@relog.ch>)
904 */
905 
906 
907 struct avgcost_data
908 {
909   ulonglong	count;
910   longlong	totalquantity;
911   double	totalprice;
912 };
913 
914 
915 /*
916 ** Average Cost Aggregate Function.
917 */
918 my_bool
avgcost_init(UDF_INIT * initid,UDF_ARGS * args,char * message)919 avgcost_init( UDF_INIT* initid, UDF_ARGS* args, char* message )
920 {
921   struct avgcost_data*	data;
922 
923   if (args->arg_count != 2)
924   {
925     strcpy(
926 	   message,
927 	   "wrong number of arguments: AVGCOST() requires two arguments"
928 	   );
929     return 1;
930   }
931 
932   if ((args->arg_type[0] != INT_RESULT) || (args->arg_type[1] != REAL_RESULT) )
933   {
934     strcpy(
935 	   message,
936 	   "wrong argument type: AVGCOST() requires an INT and a REAL"
937 	   );
938     return 1;
939   }
940 
941   /*
942   **	force arguments to double.
943   */
944   /*args->arg_type[0]	= REAL_RESULT;
945     args->arg_type[1]	= REAL_RESULT;*/
946 
947   initid->maybe_null	= 0;		/* The result may be null */
948   initid->decimals	= 4;		/* We want 4 decimals in the result */
949   initid->max_length	= 20;		/* 6 digits + . + 10 decimals */
950 
951   if (!(data = new (std::nothrow) avgcost_data))
952   {
953     my_stpcpy(message,"Couldn't allocate memory");
954     return 1;
955   }
956   data->totalquantity	= 0;
957   data->totalprice	= 0.0;
958 
959   initid->ptr = (char*)data;
960 
961   return 0;
962 }
963 
964 void
avgcost_deinit(UDF_INIT * initid)965 avgcost_deinit( UDF_INIT* initid )
966 {
967   void *void_ptr= initid->ptr;
968   avgcost_data *data= static_cast<avgcost_data*>(void_ptr);
969   delete data;
970 }
971 
972 
973 /* This is only for MySQL 4.0 compability */
974 void
avgcost_reset(UDF_INIT * initid,UDF_ARGS * args,char * is_null,char * message)975 avgcost_reset(UDF_INIT* initid, UDF_ARGS* args, char* is_null, char* message)
976 {
977   avgcost_clear(initid, is_null, message);
978   avgcost_add(initid, args, is_null, message);
979 }
980 
981 /* This is needed to get things to work in MySQL 4.1.1 and above */
982 
983 void
avgcost_clear(UDF_INIT * initid,char * is_null MY_ATTRIBUTE ((unused)),char * message MY_ATTRIBUTE ((unused)))984 avgcost_clear(UDF_INIT* initid, char* is_null MY_ATTRIBUTE((unused)),
985               char* message MY_ATTRIBUTE((unused)))
986 {
987   struct avgcost_data* data = (struct avgcost_data*)initid->ptr;
988   data->totalprice=	0.0;
989   data->totalquantity=	0;
990   data->count=		0;
991 }
992 
993 
994 void
avgcost_add(UDF_INIT * initid,UDF_ARGS * args,char * is_null MY_ATTRIBUTE ((unused)),char * message MY_ATTRIBUTE ((unused)))995 avgcost_add(UDF_INIT* initid, UDF_ARGS* args,
996             char* is_null MY_ATTRIBUTE((unused)),
997             char* message MY_ATTRIBUTE((unused)))
998 {
999   if (args->args[0] && args->args[1])
1000   {
1001     struct avgcost_data* data	= (struct avgcost_data*)initid->ptr;
1002     longlong quantity		= *((longlong*)args->args[0]);
1003     longlong newquantity	= data->totalquantity + quantity;
1004     double price		= *((double*)args->args[1]);
1005 
1006     data->count++;
1007 
1008     if (   ((data->totalquantity >= 0) && (quantity < 0))
1009 	   || ((data->totalquantity <  0) && (quantity > 0)) )
1010     {
1011       /*
1012       **	passing from + to - or from - to +
1013       */
1014       if (   ((quantity < 0) && (newquantity < 0))
1015 	     || ((quantity > 0) && (newquantity > 0)) )
1016       {
1017 	data->totalprice	= price * (double)newquantity;
1018       }
1019       /*
1020       **	sub q if totalq > 0
1021       **	add q if totalq < 0
1022       */
1023       else
1024       {
1025 	price		  = data->totalprice / (double)data->totalquantity;
1026 	data->totalprice  = price * (double)newquantity;
1027       }
1028       data->totalquantity = newquantity;
1029     }
1030     else
1031     {
1032       data->totalquantity	+= quantity;
1033       data->totalprice		+= price * (double)quantity;
1034     }
1035 
1036     if (data->totalquantity == 0)
1037       data->totalprice = 0.0;
1038   }
1039 }
1040 
1041 
1042 double
avgcost(UDF_INIT * initid,UDF_ARGS * args MY_ATTRIBUTE ((unused)),char * is_null,char * error MY_ATTRIBUTE ((unused)))1043 avgcost( UDF_INIT* initid, UDF_ARGS* args MY_ATTRIBUTE((unused)),
1044          char* is_null, char* error MY_ATTRIBUTE((unused)))
1045 {
1046   struct avgcost_data* data = (struct avgcost_data*)initid->ptr;
1047   if (!data->count || !data->totalquantity)
1048   {
1049     *is_null = 1;
1050     return 0.0;
1051   }
1052 
1053   *is_null = 0;
1054   return data->totalprice/(double)data->totalquantity;
1055 }
1056 
1057 my_bool myfunc_argument_name_init(UDF_INIT *initid, UDF_ARGS *args,
1058 				  char *message);
1059 char *myfunc_argument_name(UDF_INIT *initid, UDF_ARGS *args, char *result,
1060 			   unsigned long *length, char *null_value,
1061 			   char *error);
1062 
myfunc_argument_name_init(UDF_INIT * initid,UDF_ARGS * args,char * message)1063 my_bool myfunc_argument_name_init(UDF_INIT *initid, UDF_ARGS *args,
1064 				  char *message)
1065 {
1066   if (args->arg_count != 1)
1067   {
1068     my_stpcpy(message,"myfunc_argument_name_init accepts only one argument");
1069     return 1;
1070   }
1071   initid->max_length= args->attribute_lengths[0];
1072   initid->maybe_null= 1;
1073   initid->const_item= 1;
1074   return 0;
1075 }
1076 
myfunc_argument_name(UDF_INIT * initid MY_ATTRIBUTE ((unused)),UDF_ARGS * args,char * result,unsigned long * length,char * null_value,char * error MY_ATTRIBUTE ((unused)))1077 char *myfunc_argument_name(UDF_INIT *initid MY_ATTRIBUTE((unused)),
1078                            UDF_ARGS *args, char *result,
1079                            unsigned long *length, char *null_value,
1080                            char *error MY_ATTRIBUTE((unused)))
1081 {
1082   if (!args->attributes[0])
1083   {
1084     *null_value= 1;
1085     return 0;
1086   }
1087   (*length)--; /* space for ending \0 (for debugging purposes) */
1088   if (*length > args->attribute_lengths[0])
1089     *length= args->attribute_lengths[0];
1090   memcpy(result, args->attributes[0], *length);
1091   result[*length]= 0;
1092   return result;
1093 }
1094 
1095 
1096 
is_const_init(UDF_INIT * initid,UDF_ARGS * args,char * message)1097 my_bool is_const_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
1098 {
1099   if (args->arg_count != 1)
1100   {
1101     my_stpcpy(message, "IS_CONST accepts only one argument");
1102     return 1;
1103   }
1104   initid->ptr= (char*)((args->args[0] != NULL) ? 1UL : 0);
1105   return 0;
1106 }
1107 
is_const(UDF_INIT * initid,UDF_ARGS * args MY_ATTRIBUTE ((unused)),char * result,unsigned long * length,char * is_null,char * error MY_ATTRIBUTE ((unused)))1108 char * is_const(UDF_INIT *initid, UDF_ARGS *args MY_ATTRIBUTE((unused)),
1109                 char *result, unsigned long *length,
1110                 char *is_null, char *error MY_ATTRIBUTE((unused)))
1111 {
1112   if (initid->ptr != 0) {
1113     sprintf(result, "const");
1114   } else {
1115     sprintf(result, "not const");
1116   }
1117   *is_null= 0;
1118   *length= (uint) strlen(result);
1119   return result;
1120 }
1121 
1122 
1123 
1124 extern "C"
check_const_len_init(UDF_INIT * initid,UDF_ARGS * args,char * message)1125 my_bool check_const_len_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
1126 {
1127   if (args->arg_count != 1)
1128   {
1129     my_stpcpy(message, "CHECK_CONST_LEN accepts only one argument");
1130     return 1;
1131   }
1132   if (args->args[0] == 0)
1133   {
1134     initid->ptr= (char*)"Not constant";
1135   }
1136   else if(strlen(args->args[0]) == args->lengths[0])
1137   {
1138     initid->ptr= (char*)"Correct length";
1139   }
1140   else
1141   {
1142     initid->ptr= (char*)"Wrong length";
1143   }
1144   initid->max_length = 100;
1145   return 0;
1146 }
1147 
1148 extern "C"
check_const_len(UDF_INIT * initid,UDF_ARGS * args MY_ATTRIBUTE ((unused)),char * result,unsigned long * length,char * is_null,char * error MY_ATTRIBUTE ((unused)))1149 char * check_const_len(UDF_INIT *initid, UDF_ARGS *args MY_ATTRIBUTE((unused)),
1150                 char *result, unsigned long *length,
1151                 char *is_null, char *error MY_ATTRIBUTE((unused)))
1152 {
1153   my_stpcpy(result, initid->ptr);
1154   *length= (uint) strlen(result);
1155   *is_null= 0;
1156   return result;
1157 }
1158 
1159 
1160 C_MODE_START;
1161 my_bool  my_median_init  (UDF_INIT *initid, UDF_ARGS *args, char *message);
1162 void     my_median_deinit(UDF_INIT* initid);
1163 void     my_median_add   (UDF_INIT* initid, UDF_ARGS* args,
1164                           char* is_null, char *error);
1165 void     my_median_clear (UDF_INIT* initid, UDF_ARGS* args,
1166                           char* is_null, char *error);
1167 longlong my_median       (UDF_INIT* initid, UDF_ARGS* args,
1168                           char* is_null, char *error);
1169 C_MODE_END;
1170 
1171 struct My_median_data
1172 {
1173   std::vector<longlong> vec;
1174 };
1175 
1176 
my_median_init(UDF_INIT * initid,UDF_ARGS * args,char * message)1177 my_bool  my_median_init  (UDF_INIT *initid, UDF_ARGS *args, char *message)
1178 {
1179   My_median_data *data= new (std::nothrow) My_median_data;
1180   if (!data)
1181   {
1182     my_stpcpy(message,"Could not allocate memory");
1183     return true;
1184   }
1185   initid->ptr= static_cast<char*>(static_cast<void*>(data));
1186   return false;
1187 }
1188 
my_median_deinit(UDF_INIT * initid)1189 void my_median_deinit(UDF_INIT* initid)
1190 {
1191   My_median_data *data=
1192     static_cast<My_median_data*>(static_cast<void*>(initid->ptr));
1193   delete data;
1194 }
1195 
my_median_add(UDF_INIT * initid,UDF_ARGS * args,char * is_null MY_ATTRIBUTE ((unused)),char * message MY_ATTRIBUTE ((unused)))1196 void my_median_add(UDF_INIT* initid, UDF_ARGS* args,
1197                    char* is_null MY_ATTRIBUTE((unused)),
1198                    char* message MY_ATTRIBUTE((unused)))
1199 {
1200   My_median_data *data=
1201     static_cast<My_median_data*>(static_cast<void*>(initid->ptr));
1202   if (args->args[0])
1203   {
1204     void *arg0= args->args[0];
1205     longlong number= *(static_cast<longlong*>(arg0));
1206     data->vec.push_back(number);
1207   }
1208 }
1209 
my_median_clear(UDF_INIT * initid,UDF_ARGS * args,char * is_null MY_ATTRIBUTE ((unused)),char * message MY_ATTRIBUTE ((unused)))1210 void my_median_clear(UDF_INIT* initid, UDF_ARGS* args,
1211                      char* is_null MY_ATTRIBUTE((unused)),
1212                      char* message MY_ATTRIBUTE((unused)))
1213 {
1214   My_median_data *data=
1215     static_cast<My_median_data*>(static_cast<void*>(initid->ptr));
1216   data->vec.clear();
1217 }
1218 
my_median(UDF_INIT * initid,UDF_ARGS * args,char * is_null,char * message MY_ATTRIBUTE ((unused)))1219 longlong my_median(UDF_INIT* initid, UDF_ARGS* args,
1220                    char* is_null,
1221                    char* message MY_ATTRIBUTE((unused)))
1222 {
1223   My_median_data *data=
1224     static_cast<My_median_data*>(static_cast<void*>(initid->ptr));
1225   if (data->vec.size() == 0)
1226   {
1227     *is_null= 1;
1228     return 0;
1229   }
1230   const size_t ix= data->vec.size() / 2;
1231   std::nth_element(data->vec.begin(), data->vec.begin() + ix, data->vec.end());
1232   return data->vec[ix];
1233 }
1234 
1235 #endif /* HAVE_DLOPEN */
1236