MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ft_parser.c
1 /* Copyright (c) 2000, 2011, 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 as published by
5  the Free Software Foundation; version 2 of the License.
6 
7  This program is distributed in the hope that it will be useful,
8  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  GNU General Public License for more details.
11 
12  You should have received a copy of the GNU General Public License
13  along with this program; if not, write to the Free Software
14  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
15 
16 /* Written by Sergei A. Golubchik, who has a shared copyright to this code */
17 
18 #include "ftdefs.h"
19 
20 typedef struct st_ft_docstat {
21  FT_WORD *list;
22  uint uniq;
23  double sum;
24 } FT_DOCSTAT;
25 
26 typedef struct st_my_ft_parser_param
27 {
28  TREE *wtree;
29  MEM_ROOT *mem_root;
31 
32 static int FT_WORD_cmp(CHARSET_INFO* cs, FT_WORD *w1, FT_WORD *w2)
33 {
34  return ha_compare_text(cs, (uchar*) w1->pos, w1->len,
35  (uchar*) w2->pos, w2->len, 0, 0);
36 }
37 
38 static int walk_and_copy(FT_WORD *word,uint32 count,FT_DOCSTAT *docstat)
39 {
40  word->weight=LWS_IN_USE;
41  docstat->sum+=word->weight;
42  memcpy((docstat->list)++, word, sizeof(FT_WORD));
43  return 0;
44 }
45 
46 /* transforms tree of words into the array, applying normalization */
47 
48 FT_WORD * ft_linearize(TREE *wtree, MEM_ROOT *mem_root)
49 {
50  FT_WORD *wlist,*p;
51  FT_DOCSTAT docstat;
52  DBUG_ENTER("ft_linearize");
53 
54  if ((wlist=(FT_WORD *) alloc_root(mem_root, sizeof(FT_WORD)*
55  (1+wtree->elements_in_tree))))
56  {
57  docstat.list=wlist;
58  docstat.uniq=wtree->elements_in_tree;
59  docstat.sum=0;
60  tree_walk(wtree,(tree_walk_action)&walk_and_copy,&docstat,left_root_right);
61  }
62  delete_tree(wtree);
63  if (!wlist)
64  DBUG_RETURN(NULL);
65 
66  docstat.list->pos=NULL;
67 
68  for (p=wlist;p->pos;p++)
69  {
70  p->weight=PRENORM_IN_USE;
71  }
72 
73  for (p=wlist;p->pos;p++)
74  {
75  p->weight/=NORM_IN_USE;
76  }
77 
78  DBUG_RETURN(wlist);
79 }
80 
81 my_bool ft_boolean_check_syntax_string(const uchar *str)
82 {
83  uint i, j;
84 
85  if (!str ||
86  (strlen((char*) str)+1 != sizeof(DEFAULT_FTB_SYNTAX)) ||
87  (str[0] != ' ' && str[1] != ' '))
88  return 1;
89  for (i=0; i<sizeof(DEFAULT_FTB_SYNTAX); i++)
90  {
91  /* limiting to 7-bit ascii only */
92  if ((unsigned char)(str[i]) > 127 || my_isalnum(default_charset_info, str[i]))
93  return 1;
94  for (j=0; j<i; j++)
95  if (str[i] == str[j] && (i != 11 || j != 10))
96  return 1;
97  }
98  return 0;
99 }
100 
101 /*
102  RETURN VALUE
103  0 - eof
104  1 - word found
105  2 - left bracket
106  3 - right bracket
107  4 - stopword found
108 */
109 uchar ft_get_word(const CHARSET_INFO *cs, uchar **start, uchar *end,
110  FT_WORD *word, MYSQL_FTPARSER_BOOLEAN_INFO *param)
111 {
112  uchar *doc=*start;
113  int ctype;
114  uint mwc, length;
115  int mbl;
116 
117  param->yesno=(FTB_YES==' ') ? 1 : (param->quot != 0);
118  param->weight_adjust= param->wasign= 0;
119  param->type= FT_TOKEN_EOF;
120 
121  while (doc<end)
122  {
123  for (; doc < end; doc+= (mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1)))
124  {
125  mbl= cs->cset->ctype(cs, &ctype, (uchar*)doc, (uchar*)end);
126  if (true_word_char(ctype, *doc))
127  break;
128  if (*doc == FTB_RQUOT && param->quot)
129  {
130  *start=doc+1;
131  param->type= FT_TOKEN_RIGHT_PAREN;
132  goto ret;
133  }
134  if (!param->quot)
135  {
136  if (*doc == FTB_LBR || *doc == FTB_RBR || *doc == FTB_LQUOT)
137  {
138  /* param->prev=' '; */
139  *start=doc+1;
140  if (*doc == FTB_LQUOT)
141  param->quot= (char*) 1;
142  param->type= (*doc == FTB_RBR ? FT_TOKEN_RIGHT_PAREN : FT_TOKEN_LEFT_PAREN);
143  goto ret;
144  }
145  if (param->prev == ' ')
146  {
147  if (*doc == FTB_YES ) { param->yesno=+1; continue; } else
148  if (*doc == FTB_EGAL) { param->yesno= 0; continue; } else
149  if (*doc == FTB_NO ) { param->yesno=-1; continue; } else
150  if (*doc == FTB_INC ) { param->weight_adjust++; continue; } else
151  if (*doc == FTB_DEC ) { param->weight_adjust--; continue; } else
152  if (*doc == FTB_NEG ) { param->wasign= !param->wasign; continue; }
153  }
154  }
155  param->prev=*doc;
156  param->yesno=(FTB_YES==' ') ? 1 : (param->quot != 0);
157  param->weight_adjust= param->wasign= 0;
158  }
159 
160  mwc=length=0;
161  for (word->pos= doc; doc < end; length++,
162  doc+= (mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1)))
163  {
164  mbl= cs->cset->ctype(cs, &ctype, (uchar*)doc, (uchar*)end);
165  if (true_word_char(ctype, *doc))
166  mwc=0;
167  else if (!misc_word_char(*doc) || mwc)
168  break;
169  else
170  mwc++;
171  }
172  param->prev='A'; /* be sure *prev is true_word_char */
173  word->len= (uint)(doc-word->pos) - mwc;
174  if ((param->trunc=(doc<end && *doc == FTB_TRUNC)))
175  doc++;
176 
177  if (((length >= ft_min_word_len && !is_stopword((char*) word->pos,
178  word->len))
179  || param->trunc) && length < ft_max_word_len)
180  {
181  *start=doc;
182  param->type= FT_TOKEN_WORD;
183  goto ret;
184  }
185  else if (length) /* make sure length > 0 (if start contains spaces only) */
186  {
187  *start= doc;
188  param->type= FT_TOKEN_STOPWORD;
189  goto ret;
190  }
191  }
192  if (param->quot)
193  {
194  *start= doc;
195  param->type= 3; /* FT_RBR */
196  goto ret;
197  }
198 ret:
199  return param->type;
200 }
201 
202 uchar ft_simple_get_word(const CHARSET_INFO *cs, uchar **start,
203  const uchar *end,
204  FT_WORD *word, my_bool skip_stopwords)
205 {
206  uchar *doc= *start;
207  uint mwc, length;
208  int mbl;
209  int ctype;
210  DBUG_ENTER("ft_simple_get_word");
211 
212  do
213  {
214  for (;; doc+= (mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1)))
215  {
216  if (doc >= end)
217  DBUG_RETURN(0);
218  mbl= cs->cset->ctype(cs, &ctype, (uchar*)doc, (uchar*)end);
219  if (true_word_char(ctype, *doc))
220  break;
221  }
222 
223  mwc= length= 0;
224  for (word->pos= doc; doc < end; length++,
225  doc+= (mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1)))
226  {
227  mbl= cs->cset->ctype(cs, &ctype, (uchar*)doc, (uchar*)end);
228  if (true_word_char(ctype, *doc))
229  mwc= 0;
230  else if (!misc_word_char(*doc) || mwc)
231  break;
232  else
233  mwc++;
234  }
235 
236  word->len= (uint)(doc-word->pos) - mwc;
237 
238  if (skip_stopwords == FALSE ||
239  (length >= ft_min_word_len && length < ft_max_word_len &&
240  !is_stopword((char*) word->pos, word->len)))
241  {
242  *start= doc;
243  DBUG_RETURN(1);
244  }
245  } while (doc < end);
246  DBUG_RETURN(0);
247 }
248 
249 void ft_parse_init(TREE *wtree, const CHARSET_INFO *cs)
250 {
251  DBUG_ENTER("ft_parse_init");
252  if (!is_tree_inited(wtree))
253  init_tree(wtree,0,0,sizeof(FT_WORD),(qsort_cmp2)&FT_WORD_cmp,0,NULL, cs);
254  DBUG_VOID_RETURN;
255 }
256 
257 
258 static int ft_add_word(MYSQL_FTPARSER_PARAM *param,
259  char *word, int word_len,
260  MYSQL_FTPARSER_BOOLEAN_INFO *boolean_info __attribute__((unused)))
261 {
262  TREE *wtree;
263  FT_WORD w;
264  MY_FT_PARSER_PARAM *ft_param=param->mysql_ftparam;
265  DBUG_ENTER("ft_add_word");
266  wtree= ft_param->wtree;
267  if (param->flags & MYSQL_FTFLAGS_NEED_COPY)
268  {
269  uchar *ptr;
270  DBUG_ASSERT(wtree->with_delete == 0);
271  ptr= (uchar *)alloc_root(ft_param->mem_root, word_len);
272  memcpy(ptr, word, word_len);
273  w.pos= ptr;
274  }
275  else
276  w.pos= (uchar*) word;
277  w.len= word_len;
278  if (!tree_insert(wtree, &w, 0, wtree->custom_arg))
279  {
280  delete_tree(wtree);
281  DBUG_RETURN(1);
282  }
283  DBUG_RETURN(0);
284 }
285 
286 
287 static int ft_parse_internal(MYSQL_FTPARSER_PARAM *param,
288  char *doc_arg, int doc_len)
289 {
290  uchar *doc= (uchar*) doc_arg;
291  uchar *end= doc + doc_len;
292  MY_FT_PARSER_PARAM *ft_param=param->mysql_ftparam;
293  TREE *wtree= ft_param->wtree;
294  FT_WORD w;
295  DBUG_ENTER("ft_parse_internal");
296 
297  while (ft_simple_get_word(wtree->custom_arg, &doc, end, &w, TRUE))
298  if (param->mysql_add_word(param, (char*) w.pos, w.len, 0))
299  DBUG_RETURN(1);
300  DBUG_RETURN(0);
301 }
302 
303 
304 int ft_parse(TREE *wtree, uchar *doc, int doclen,
305  struct st_mysql_ftparser *parser,
306  MYSQL_FTPARSER_PARAM *param, MEM_ROOT *mem_root)
307 {
308  MY_FT_PARSER_PARAM my_param;
309  DBUG_ENTER("ft_parse");
310  DBUG_ASSERT(parser);
311 
312  my_param.wtree= wtree;
313  my_param.mem_root= mem_root;
314 
315  param->mysql_parse= ft_parse_internal;
316  param->mysql_add_word= ft_add_word;
317  param->mysql_ftparam= &my_param;
318  param->cs= wtree->custom_arg;
319  param->doc= (char*) doc;
320  param->length= doclen;
321  param->mode= MYSQL_FTPARSER_SIMPLE_MODE;
322  DBUG_RETURN(parser->parse(param));
323 }
324 
325 
326 #define MAX_PARAM_NR 2
327 
328 MYSQL_FTPARSER_PARAM* ftparser_alloc_param(MI_INFO *info)
329 {
330  if (!info->ftparser_param)
331  {
332  /*
333 . info->ftparser_param can not be zero after the initialization,
334  because it always includes built-in fulltext parser. And built-in
335  parser can be called even if the table has no fulltext indexes and
336  no varchar/text fields.
337 
338  ftb_find_relevance... parser (ftb_find_relevance_parse,
339  ftb_find_relevance_add_word) calls ftb_check_phrase... parser
340  (ftb_check_phrase_internal, ftb_phrase_add_word). Thus MAX_PARAM_NR=2.
341  */
342  info->ftparser_param= (MYSQL_FTPARSER_PARAM *)
343  my_malloc(MAX_PARAM_NR * sizeof(MYSQL_FTPARSER_PARAM) *
344  info->s->ftkeys, MYF(MY_WME | MY_ZEROFILL));
345  init_alloc_root(&info->ft_memroot, FTPARSER_MEMROOT_ALLOC_SIZE, 0);
346  }
347  return info->ftparser_param;
348 }
349 
350 
351 MYSQL_FTPARSER_PARAM *ftparser_call_initializer(MI_INFO *info,
352  uint keynr, uint paramnr)
353 {
354  uint32 ftparser_nr;
355  struct st_mysql_ftparser *parser;
356 
357  if (!ftparser_alloc_param(info))
358  return 0;
359 
360  if (keynr == NO_SUCH_KEY)
361  {
362  ftparser_nr= 0;
363  parser= &ft_default_parser;
364  }
365  else
366  {
367  ftparser_nr= info->s->keyinfo[keynr].ftkey_nr;
368  parser= info->s->keyinfo[keynr].parser;
369  }
370  DBUG_ASSERT(paramnr < MAX_PARAM_NR);
371  ftparser_nr= ftparser_nr*MAX_PARAM_NR + paramnr;
372  if (! info->ftparser_param[ftparser_nr].mysql_add_word)
373  {
374  /* Note, that mysql_add_word is used here as a flag:
375  mysql_add_word == 0 - parser is not initialized
376  mysql_add_word != 0 - parser is initialized, or no
377  initialization needed. */
378  info->ftparser_param[ftparser_nr].mysql_add_word=
379  (int (*)(struct st_mysql_ftparser_param *, char *, int,
381  if (parser->init && parser->init(&info->ftparser_param[ftparser_nr]))
382  return 0;
383  }
384  return &info->ftparser_param[ftparser_nr];
385 }
386 
387 void ftparser_call_deinitializer(MI_INFO *info)
388 {
389  uint i, j, keys= info->s->state.header.keys;
390  free_root(&info->ft_memroot, MYF(0));
391  if (! info->ftparser_param)
392  return;
393  for (i= 0; i < keys; i++)
394  {
395  MI_KEYDEF *keyinfo= &info->s->keyinfo[i];
396  for (j=0; j < MAX_PARAM_NR; j++)
397  {
398  MYSQL_FTPARSER_PARAM *ftparser_param=
399  &info->ftparser_param[keyinfo->ftkey_nr * MAX_PARAM_NR + j];
400  if (keyinfo->flag & HA_FULLTEXT && ftparser_param->mysql_add_word)
401  {
402  if (keyinfo->parser->deinit)
403  keyinfo->parser->deinit(ftparser_param);
404  ftparser_param->mysql_add_word= 0;
405  }
406  else
407  break;
408  }
409  }
410 }
411