Adaptive Framework  0.9.0
All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
afw_adaptor_impl_index.c
Go to the documentation of this file.
1 // See the 'COPYING' file in the project root for licensing information.
2 /*
3  * Helpers for afw_adaptor implementation index development
4  *
5  * Copyright (c) 2010-2023 Clemson University
6  *
7  */
8 
14 #include "afw_internal.h"
15 #include "afw_adaptor_impl_index.h"
16 
17 
18 /*
19  *
20  * An index definition looks like:
21  *
22  * "FullName" : {
23  * "value" : "string-concat(object.get('givenName'), object.get('surname'))"
24  * "objectType" : ['Person']
25  * "filter" : "string-equal(object.get('department'),'ENG')"
26  * "options" : [ "case-insensitive-string" ]
27  * }
28  *
29  * The "key" property is the named identifier for the property, which is
30  * used as a reference in a retrieve_objects() query.
31  *
32  * The "value" property is the computed value(s) for the index, which
33  * is described by an AFW expression.
34  *
35  * The "objectType" property optionally determines a list of applicable objectType(s)
36  * this index definition. An empty list implies any/all objectType's.
37  *
38  * The "filter" property optionally determines if a given object is applicable for
39  * this index definition. It is also represented by an AFW expression.
40  *
41  * The "options" property is a list of options for how the index needs to
42  * be used. Some options may possibly be:
43  *
44  * presence
45  * case-insensitive-string
46  * case-sensitive-string
47  * starts-with
48  * ends-with
49  * contains
50  * geospatial
51  * soundex
52  *
53  */
54 
55 
56 /*
57  * Convenience routine to check if an indexDefinition contains
58  * the necessary objectType specifiers to allow the provided
59  * object_type_id to pass. The objectType property allows
60  * for a list of values:
61  *
62  * [] - an empty (or no list) implies all objectType's
63  *
64  * [ 'Person', 'eduPerson' ] - specifies two applicable
65  * objectTypes.
66  *
67  */
68 afw_boolean_t afw_adaptor_impl_index_object_type_applicable(
69  const afw_object_t * indexDefinition,
70  const afw_utf8_t * object_type_id,
71  afw_xctx_t * xctx)
72 {
73  const afw_utf8_t * nextObjectType;
74  const afw_list_t * objectTypes;
75  const afw_iterator_t * object_type_iterator;
76 
78  indexDefinition, &afw_s_objectType, xctx);
79 
80  /* no objectTypes means all/any are applicable */
81  if (objectTypes == NULL)
82  return true;
83 
84  object_type_iterator = NULL;
85  nextObjectType = afw_list_of_string_get_next(
86  objectTypes, &object_type_iterator, xctx);
87  if (nextObjectType == NULL)
88  return true;
89 
90  /* loop until we find a matching objectType */
91  while (nextObjectType) {
92  if (afw_utf8_equal(nextObjectType, object_type_id))
93  return true;
94 
95  nextObjectType = afw_list_of_string_get_next(
96  objectTypes, &object_type_iterator, xctx);
97  }
98 
99  return false;
100 }
101 
102 /*
103  * This routine uses the configured filter on the indexDefinition to determine
104  * if the object in question passes the filter test. Any variables required for
105  * the filter expression should already be set on the xctx for evaluation. A
106  * filter is an AFW Expression that must evaluate to a single boolean value. If
107  * the filter is omitted, then the default is 'true'.
108  */
110  const afw_object_t * object,
111  const afw_object_t * indexDefinition,
112  afw_xctx_t * xctx)
113 {
114  const afw_utf8_t * filter;
115  const afw_value_t * filterValue;
116  const afw_value_t * eval;
117 
118  /* if we have a filter, evaluate it to decide if we should apply this indexDefinition */
120  indexDefinition, &afw_s_filter, xctx);
121  if (filter) {
122  filterValue = afw_compile_to_value(filter, NULL,
123  afw_compile_type_expression, NULL, NULL, object->p, xctx);
124  } else {
125  /* No filter means it always passes */
126  return true;
127  }
128 
129  eval = afw_value_evaluate(filterValue, object->p, xctx);
130  if (afw_value_is_boolean(eval)) {
131  if (afw_value_as_boolean(eval, xctx)) {
132  /* the filter expression evaluated to true */
133  return true;
134  }
135  } else {
136  /* the filter expression did not evaluate to a boolean expression */
138  }
139 
140  return false;
141 }
142 
143 /*
144  * A handy routine to check of an indexDefinition is case-insensitive.
145  * This will run through the "options" property, looking for the
146  * 'case-insensitive' string.
147  *
148  */
149 afw_boolean_t afw_adaptor_impl_index_option_case_insensitive(
150  const afw_object_t * indexDefinition,
151  afw_xctx_t * xctx)
152 {
153  const afw_list_t * options;
154  const afw_value_t * option;
155  const afw_iterator_t * option_iterator;
156 
158  indexDefinition, &afw_s_options, xctx);
159 
160  if (options) {
161  option_iterator = NULL;
162  option = afw_list_get_next_value(options, &option_iterator,
163  xctx->p, xctx);
164  while (option) {
165  if (afw_value_is_string(option)) {
166  const afw_utf8_t *option_str =
167  afw_value_as_utf8(option, xctx->p, xctx);
168 
169  if (afw_utf8_equal(option_str, &afw_s_case_insensitive_string))
170  return true;
171  }
172 
173  option = afw_list_get_next_value(
174  options, &option_iterator, xctx->p, xctx);
175  }
176  }
177 
178  /* default to false, as case-sensitive is the default */
179  return false;
180 }
181 
182 /*
183  * A handy routine to check of an indexDefinition is unique.
184  * This will run through the "options" property, looking for the
185  * 'unique' string.
186  *
187  */
188 afw_boolean_t afw_adaptor_impl_index_option_unique(
189  const afw_object_t * indexDefinition,
190  afw_xctx_t * xctx)
191 {
192  const afw_list_t * options;
193  const afw_value_t * option;
194  const afw_iterator_t * option_iterator;
195 
197  indexDefinition, &afw_s_options, xctx);
198 
199  if (options) {
200  option_iterator = NULL;
201  option = afw_list_get_next_value(options, &option_iterator,
202  xctx->p, xctx);
203  while (option) {
204  if (afw_value_is_string(option)) {
205  const afw_utf8_t *option_str = afw_value_as_utf8(option,
206  xctx->p, xctx);
207 
208  if (afw_utf8_equal(option_str, &afw_s_unique))
209  return true;
210  }
211 
212  option = afw_list_get_next_value(
213  options, &option_iterator, xctx->p, xctx);
214  }
215  }
216 
217  /* default to false, as case-sensitive is the default */
218  return false;
219 }
220 
221 /*
222  * When we need to add or remove an index value, this routine
223  * will determine the appropriate way to do so, depending on
224  * the indexDefinition options.
225  */
226 void afw_adaptor_impl_index_apply(
227  const afw_adaptor_impl_index_t * instance,
228  const afw_object_t * indexDefinition,
229  const afw_utf8_t * object_type_id,
230  const afw_utf8_t * object_id,
231  const afw_object_t * object,
232  const afw_utf8_t * key,
233  const afw_value_t * value,
234  const int operation,
235  afw_xctx_t * xctx)
236 {
237  const afw_list_t * options;
238  const afw_value_t * option;
239  const afw_iterator_t * option_iterator;
240  afw_boolean_t case_sensitive = true;
241  afw_boolean_t unique = false;
242  const afw_utf8_t * value_string;
243 
245  indexDefinition, &afw_s_options, xctx);
246 
247  if (options) {
248  option_iterator = NULL;
249  option = afw_list_get_next_value(options, &option_iterator,
250  xctx->p, xctx);
251  while (option) {
252  if (afw_value_is_string(option)) {
253  const afw_utf8_t *option_str = afw_value_as_utf8(option,
254  xctx->p, xctx);
255 
256  if (afw_utf8_equal(option_str, &afw_s_case_insensitive_string))
257  case_sensitive = false;
258  else if (afw_utf8_equal(option_str, &afw_s_unique))
259  unique = true;
260  }
261 
262  option = afw_list_get_next_value(
263  options, &option_iterator, xctx->p, xctx);
264  }
265  }
266 
267  /* Generate the utf8 value of the index; this will be used as the
268  index key in the underlying database.
269  */
270  value_string = afw_value_as_utf8(value, object->p, xctx);
271  if (!case_sensitive) {
272  /* For case-insensitive indexes, lower-case it so we will always
273  do case-insensitive comparisons */
274  value_string = afw_utf8_to_lower(value_string, object->p, xctx);
275  }
276 
277  /* figure out which operation we are doing */
278  if (operation == afw_adaptor_impl_index_mode_add) {
279  afw_adaptor_impl_index_add(instance, object_type_id,
280  object_id, key, value_string, unique, object->p, xctx);
281  }
282 
283  else if (operation == afw_adaptor_impl_index_mode_delete) {
284  afw_adaptor_impl_index_delete(instance, object_type_id,
285  object_id, key, value_string, object->p, xctx);
286  }
287 }
288 
289 /*
290  * This routine takes an object and a possible indexDefinition
291  * and tries to add/remove the index, if it's applicable. It will
292  * return true if it was successful in and false otherwise.
293  *
294  * This routine was broken out, because it's used by both the
295  * adaptive function index routines, and by adaptor session
296  * routines.
297  */
298 afw_boolean_t afw_adaptor_impl_index_try(
299  const afw_adaptor_impl_index_t * instance,
300  const afw_utf8_t * key,
301  const afw_object_t * object,
302  const afw_utf8_t * object_type_id,
303  const afw_utf8_t * object_id,
304  const afw_object_t * indexDefinition,
305  afw_adaptor_impl_index_mode_t operation,
306  afw_xctx_t * xctx)
307 {
308  const afw_value_t * const *index_values;
309  const afw_value_t * index_value;
310  afw_boolean_t indexed = false;
311  const afw_utf8_t * value_expression;
312  const afw_value_t * value;
313  const afw_value_t * eval;
314  int top;
315 
316  /* first we make sure the objectType is applicable */
317  if (!afw_adaptor_impl_index_object_type_applicable(
318  indexDefinition, object_type_id, xctx)) {
319  /* the object_type_id does not apply for this definition */
320  return false;
321  }
322 
324  object, indexDefinition, xctx)) {
325  /* the filter expression did not pass */
326  return false;
327  }
328 
329  /* generate an index value based on the provided expression */
330  value_expression = afw_object_old_get_property_as_string(
331  indexDefinition, &afw_s_value, xctx);
332  if (value_expression)
333  {
334  value = afw_compile_to_value(value_expression, NULL,
335  afw_compile_type_expression, NULL, NULL, object->p, xctx);
336 
337  /* create a new stack frame so that our temporary variables are
338  cleared the next time we go to evaluate an expression */
339  top = afw_xctx_begin_stack_frame(xctx);
340 
341  /* Add variables for the filter and value expressions to use */
342  afw_xctx_set_local_variable(&afw_s_objectId,
343  afw_value_create_string(object_id, xctx->p, xctx), xctx);
344  afw_xctx_set_local_variable(&afw_s_objectType,
345  afw_value_create_string(object_type_id, xctx->p, xctx), xctx);
346  afw_xctx_set_local_variable(&afw_s_object,
347  afw_value_create_object(object, xctx->p, xctx), xctx);
348 
349  /*
350  Evaluate the 'value' definition for the index. This may generate
351  one or more afw_value_t types. For each one, generate/remove an
352  index.
353  */
354  eval = NULL;
355  AFW_TRY {
356  eval = afw_value_evaluate(value, object->p, xctx);
357  }
358  AFW_FINALLY {
359  /* always release the stack frame */
360  afw_xctx_end_stack_frame(top, xctx);
361  }
362  AFW_ENDTRY;
363  }
364 
365  else
366  {
367  /* if we do not have a "value" expression, then assume we are after the property
368  by the same name as the index key */
369  eval = afw_object_get_property(object, key, xctx);
370  }
371 
372  /* if eval is NULL, then we didn't generate a value and shouldn't index */
373  if (afw_value_is_null(eval))
374  {
375  /* nothing to do */
376  }
377 
378  /* Check the type of afw_value_t we got back. */
379  else if (afw_value_is_object(eval))
380  {
381  /* we can't use an object as an index key */
382  AFW_THROW_ERROR_Z(general,
383  "Error: value expression generated an object and cannot be used as an index.",
384  xctx);
385  }
386 
387  /* if we have multiple values, then index each one */
388  else if (afw_value_is_list(eval))
389  {
390  int i;
391  index_values = afw_value_as_array_of_values(
392  eval, object->p, xctx);
393  for (i = 0; index_values[i]; i++) {
394  index_value = index_values[i];
395 
396  afw_adaptor_impl_index_apply(instance, indexDefinition,
397  object_type_id, object_id, object, key, index_value, operation, xctx);
398  }
399  indexed = true;
400  }
401 
402  /* a single value can be converted to a single utf8 string */
403  else if (afw_value_is_defined_and_evaluated(eval))
404  {
405  /* scalar value */
406  afw_adaptor_impl_index_apply(instance, indexDefinition,
407  object_type_id, object_id, object, key, eval, operation, xctx);
408  indexed = true;
409  }
410 
411  else {
412  AFW_THROW_ERROR_Z(general,
413  "Error: value expression generated an unknown and unhandled index value.",
414  xctx);
415  }
416 
417  return indexed;
418 }
419 
420 void afw_adaptor_impl_index_open_definition(
421  const afw_adaptor_impl_index_t * indexer,
422  const afw_utf8_t * key,
423  const afw_object_t * indexDefinition,
424  const afw_pool_t * pool,
425  afw_xctx_t * xctx)
426 {
427  const afw_list_t *objectType;
428  const afw_list_t *options;
429  const afw_value_t *option;
430  const afw_iterator_t *option_iterator;
431  const afw_iterator_t *object_type_iterator;
432  const afw_utf8_t *object_type_id;
433  afw_boolean_t unique = false;
434  afw_boolean_t reverse = false;
435  afw_boolean_t integer = false;
436 
438  indexDefinition, &afw_s_options, xctx);
439  if (options) {
440  option_iterator = NULL;
441  option = afw_list_get_next_value(options, &option_iterator,
442  xctx->p, xctx);
443  while (option) {
444  if (afw_value_is_string(option)) {
445  const afw_utf8_t *option_str = afw_value_as_utf8(option,
446  xctx->p, xctx);
447 
448  if (afw_utf8_equal(option_str, &afw_s_sort_reverse))
449  reverse = true;
450  else if (afw_utf8_equal(option_str, &afw_s_integer))
451  integer = true;
452  else if (afw_utf8_equal(option_str, &afw_s_unique))
453  unique = true;
454  }
455 
456  option = afw_list_get_next_value(
457  options, &option_iterator, xctx->p, xctx);
458  }
459  }
460 
462  indexDefinition, &afw_s_objectType, xctx);
463 
464  if (objectType) {
465  object_type_iterator = NULL;
466  object_type_id = afw_list_of_string_get_next(
467  objectType, &object_type_iterator, xctx);
468  while (object_type_id) {
469  afw_adaptor_impl_index_open(indexer, object_type_id,
470  key, integer, unique, reverse, pool, xctx);
471 
472  object_type_id = afw_list_of_string_get_next(
473  objectType, &object_type_iterator, xctx);
474  }
475  } else {
476  afw_adaptor_impl_index_open(indexer, NULL, key,
477  integer, unique, reverse, pool, xctx);
478  }
479 }
480 
481 AFW_DEFINE(void) afw_adaptor_impl_index_open_definitions(
482  const afw_adaptor_impl_index_t * indexer,
483  const afw_object_t * indexDefinitions,
484  const afw_pool_t * pool,
485  afw_xctx_t * xctx)
486 {
487  const afw_object_t * indexDefinition;
488  const afw_iterator_t * index_iterator;
489  const afw_utf8_t * key;
490 
491  index_iterator = NULL;
493  indexDefinitions, &index_iterator, &key, xctx);
494 
495  while (indexDefinition) {
496  afw_adaptor_impl_index_open_definition(indexer, key,
497  indexDefinition, pool, xctx);
498 
500  indexDefinitions, &index_iterator, &key, xctx);
501  }
502 }
503 
504 /*
505  * Callback routine for maintaining an index. This is invoked
506  * from the "create" and "delete" routines when we need to apply
507  * an index retroactively against active records in the database.
508  */
509 afw_boolean_t afw_adaptor_impl_index_cb(
510  const afw_object_t * object,
511  void * context,
512  afw_xctx_t * xctx)
513 {
516  const afw_utf8_t * object_id;
517  const afw_utf8_t * object_type_id;
518  const afw_utf8_t * key;
519 
520  if (object == NULL) {
521  /* no more objects, so we are finished */
522  return false;
523  }
524 
525  /* increment our number of processed for metrics */
526  ctx->num_processed++;
527 
528  /* fetch the objectId from the object, so we can use as the index value */
529  object_id = afw_object_meta_get_object_id(object, xctx);
530  if (!object_id)
531  AFW_THROW_ERROR_Z(general,
532  "Error: unable to determine object_id for object.", xctx);
533 
534  object_type_id = afw_object_meta_get_object_type_id(object, xctx);
535  if (!object_type_id) {
536  AFW_THROW_ERROR_Z(general,
537  "Error: unable to determine object_type_id for object.", xctx);
538  }
539 
540  /* Add the object as a variable for the filter and value expressions to use */
541  afw_xctx_set_local_variable(&afw_s_object,
542  afw_value_create_object(object, xctx->p, xctx), xctx);
543 
544  /* The index "key" is the name that will match the query */
545  key = ctx->key;
546 
547  /* Try the index operation, which may fail and return false */
548  if (afw_adaptor_impl_index_try(ctx->instance, key, object,
549  object_type_id, object_id, ctx->indexDefinition, ctx->mode, xctx)) {
550  ctx->num_indexed++;
551  }
552 
553  /* release object to free memory */
554  afw_object_release(object, xctx);
555 
556  /* Return indicating not to short circuit */
557  return false;
558 }
559 
560 /*
561  * void afw_adaptor_impl_index_list()
562  *
563  * This routine will use adaptor interfaces to go through
564  * and list indexes for a particular adaptorId.
565  *
566  */
567 AFW_DEFINE(const afw_object_t *) afw_adaptor_impl_index_list(
568  const afw_utf8_t * adaptorId,
569  const afw_utf8_t * object_type_id,
570  const afw_pool_t * pool,
571  afw_xctx_t * xctx)
572 {
573  const afw_adaptor_impl_index_t * instance;
574  const afw_adaptor_session_t * session;
575  const afw_iterator_t * index_iterator;
576  const afw_object_t * indexDefinition;
577  const afw_object_t * result;
578  const afw_utf8_t * key;
579 
580  session = afw_adaptor_session_get_cached(adaptorId, false, xctx);
581 
582  instance = afw_adaptor_session_get_index_interface(session, xctx);
583  if (instance == NULL) {
584  AFW_THROW_ERROR_Z(general,
585  "Error: Cannot find index interface for adaptorId.", xctx);
586  }
587 
588  result = instance->indexDefinitions;
589 
590  if (object_type_id) {
591  result = afw_object_create_managed(pool, xctx);
592 
593  index_iterator = NULL;
595  instance->indexDefinitions, &index_iterator, &key, xctx);
596  while (indexDefinition) {
597  if (afw_adaptor_impl_index_object_type_applicable(
598  indexDefinition, object_type_id, xctx)) {
600  key, indexDefinition, xctx);
601  }
602 
604  instance->indexDefinitions, &index_iterator, &key, xctx);
605  }
606  }
607 
608  return result;
609 }
610 
611 AFW_DEFINE(const afw_object_t *) afw_adaptor_impl_index_remove(
612  const afw_utf8_t * adaptorId,
613  const afw_utf8_t * key,
614  const afw_pool_t * pool,
615  afw_xctx_t * xctx)
616 {
617  const afw_adaptor_session_t * session;
618  const afw_adaptor_impl_index_t * indexer;
620  const afw_object_t * indexDefinition;
621  const afw_list_t * objectTypes;
622  const afw_object_t * result;
623  const afw_iterator_t * object_type_iterator;
624  const afw_utf8_t * object_type_id;
625  afw_rc_t rc;
626 
627  session = afw_adaptor_session_get_cached(adaptorId, true, xctx);
628 
629  indexer = afw_adaptor_session_get_index_interface(session, xctx);
630  if (indexer == NULL) {
631  AFW_THROW_ERROR_Z(general,
632  "Error: unable to get index interface.", xctx);
633  }
634 
635  /* create our result object to be returned */
636  result = afw_object_create_managed(pool, xctx);
637 
638  if (indexer->indexDefinitions == NULL) {
639  AFW_THROW_ERROR_Z(general,
640  "Error: there are no index definitions to remove.", xctx);
641  }
642 
643  indexDefinition = afw_object_old_get_property_as_object(
644  indexer->indexDefinitions, key, xctx);
645  if (indexDefinition == NULL) {
646  AFW_THROW_ERROR_Z(general,
647  "Error there is no index definition by this key.", xctx);
648  }
649 
650  /* first, we remove the index from the configuration,
651  so it's no longer in use */
652  afw_object_remove_property(indexer->indexDefinitions, key, xctx);
653 
655  indexer, indexer->indexDefinitions, xctx);
656 
657  /* get all applicable objectTypes */
659  indexDefinition, &afw_s_objectType, xctx);
660  if (objectTypes) {
661  object_type_iterator = NULL;
662 
663  object_type_id = afw_list_of_string_get_next(
664  objectTypes, &object_type_iterator, xctx);
665  do
666  {
667  /* try to drop it first, if possible */
668  rc = afw_adaptor_impl_index_drop(indexer,
669  object_type_id, key, pool, xctx);
670  if (rc) {
671  ctx.instance = indexer;
672  ctx.key = key;
673  ctx.indexDefinition = indexDefinition;
674  ctx.num_indexed = 0;
675  ctx.num_processed = 0;
676  ctx.mode = afw_adaptor_impl_index_mode_delete;
677 
679  afw_adaptor_session_retrieve_objects(session, NULL, NULL,
680  NULL, &ctx, afw_adaptor_impl_index_cb, NULL, pool, xctx);
682  }
683 
684  if (object_type_id)
685  object_type_id = afw_list_of_string_get_next(
686  objectTypes, &object_type_iterator, xctx);
687 
688  } while (object_type_id);
689  }
690 
691  return result;
692 }
693 
694 /*
695  * void afw_adaptor_impl_index_create()
696  *
697  * This routine will create an index definition.
698  *
699  */
700 AFW_DEFINE(const afw_object_t *) afw_adaptor_impl_index_create(
701  const afw_utf8_t * adaptorId,
702  const afw_utf8_t * key,
703  const afw_utf8_t * value,
704  const afw_list_t * objectType,
705  const afw_utf8_t * filter,
706  const afw_list_t * options,
707  afw_boolean_t retroactive,
708  afw_boolean_t test,
709  const afw_pool_t * pool,
710  afw_xctx_t * xctx)
711 {
712  const afw_adaptor_session_t * session;
713  const afw_adaptor_impl_index_t * indexer;
715  const afw_object_t * indexDefinitions;
716  const afw_object_t * indexDefinition;
717  const afw_object_t * result;
718  const afw_adaptor_transaction_t * transaction;
719 
720  session = afw_adaptor_session_get_cached(adaptorId, false, xctx);
721 
722  indexer = afw_adaptor_session_get_index_interface(session, xctx);
723  if (indexer == NULL) {
724  AFW_THROW_ERROR_Z(general,
725  "Error: unable to get index interface.", xctx);
726  }
727 
728  /* create our result object to be returned */
729  result = afw_object_create_managed(pool, xctx);
730 
731  indexDefinitions = indexer->indexDefinitions;
732 
733  /* if we don't have any index definitions, create a new object */
734  if (indexDefinitions == NULL) {
735  indexDefinitions = afw_object_create_managed(pool, xctx);
736  }
737 
738  indexDefinition = afw_object_old_get_property_as_object(
739  indexDefinitions, key, xctx);
740  if (indexDefinition) {
742  afw_object_set_property_as_string_from_utf8_z(result, &afw_s_message,
743  "An index definition by this key already exists.", xctx);
744  return result;
745  }
746 
747  /* create a new indexDefinition */
748  indexDefinition = afw_object_create_managed(pool, xctx);
749 
750  if (value)
751  afw_object_set_property_as_string(indexDefinition,
752  &afw_s_value, value, xctx);
753 
754  if (objectType)
755  afw_object_set_property_as_list(indexDefinition,
756  &afw_s_objectType, objectType, xctx);
757 
758  if (filter)
759  afw_object_set_property_as_string(indexDefinition,
760  &afw_s_filter, filter, xctx);
761 
762  if (options)
763  afw_object_set_property_as_list(indexDefinition,
764  &afw_s_options, options, xctx);
765 
766  ctx.instance = indexer;
767  ctx.key = key;
768  ctx.indexDefinition = indexDefinition;
769  ctx.num_indexed = 0;
770  ctx.num_processed = 0;
771  ctx.mode = afw_adaptor_impl_index_mode_add;
772  ctx.test = test;
773 
774  /* open the index definition ahead of time */
775  afw_adaptor_impl_index_open_definition(indexer, key,
776  indexDefinition, pool, xctx);
777 
778  transaction = afw_adaptor_session_begin_transaction(session, xctx);
779 
780  /* the callback routine does all of our work for us */
781  if (retroactive) {
782  /* if we need to build indexes retroactively, go ahead and
783  scan for objects. */
785  afw_adaptor_session_retrieve_objects(session, NULL, NULL,
786  NULL, &ctx, afw_adaptor_impl_index_cb, NULL, pool, xctx);
787  }
788 
789  /* now, tell the adaptor to add the new indexDefinition for configuration */
791  indexDefinitions, key, indexDefinition, xctx);
792 
794  indexer, indexDefinitions, xctx);
795 
796  afw_adaptor_transaction_commit(transaction, xctx);
797  afw_adaptor_transaction_release(transaction, xctx);
798 
799  /* return metrics */
801  result, &afw_s_num_indexed, ctx.num_indexed, xctx);
803  result, &afw_s_num_processed, ctx.num_processed, xctx);
804 
805  return result;
806 }
807 
808 
809 /*
810  * afw_boolean_t afw_adaptor_impl_index_is_property_indexed()
811  *
812  * Looks through the list of internal indexes and returns true/false
813  * if the property is indexed or not.
814  *
815  */
816 afw_boolean_t afw_adaptor_impl_index_is_property_indexed(
817  const afw_adaptor_impl_index_t * instance,
818  const afw_utf8_t * object_type_id,
819  const afw_utf8_t * property_name,
820  afw_xctx_t * xctx)
821 {
822  const afw_object_t * indexDefinition;
823 
824  if (instance->indexDefinitions) {
825  indexDefinition = afw_object_old_get_property_as_object(
826  instance->indexDefinitions, property_name, xctx);
827  if (indexDefinition) {
828  if (afw_adaptor_impl_index_object_type_applicable(
829  indexDefinition, object_type_id, xctx)) {
830  return true;
831  }
832  }
833  }
834 
835  return false;
836 }
837 
838 /*
839  * const afw_object_t * afw_adaptor_impl_index_get_index_definition()
840  *
841  * Looks through the list of internal indexes and returns the
842  * matching indexDefinition, if one is found.
843  *
844  */
845 const afw_object_t * afw_adaptor_impl_index_get_index_definition(
846  const afw_adaptor_impl_index_t * instance,
847  const afw_utf8_t * object_type_id,
848  const afw_utf8_t * property_name,
849  afw_xctx_t * xctx)
850 {
851  const afw_object_t * indexDefinition = NULL;
852 
853  if (instance->indexDefinitions) {
854  indexDefinition = afw_object_old_get_property_as_object(
855  instance->indexDefinitions, property_name, xctx);
856  if (indexDefinition) {
857  if (afw_adaptor_impl_index_object_type_applicable(
858  indexDefinition, object_type_id, xctx)) {
859  return indexDefinition;
860  } else {
861  indexDefinition = NULL;
862  }
863  }
864  }
865 
866  return indexDefinition;
867 }
868 
869 /*
870  * void afw_adaptor_impl_index_object()
871  *
872  * When an object is created, this routine will iterate through
873  * each index and apply them, if appropriate.
874  *
875  */
876 AFW_DEFINE(void) afw_adaptor_impl_index_object(
877  const afw_adaptor_impl_index_t * instance,
878  const afw_utf8_t * object_type_id,
879  const afw_object_t * object,
880  const afw_utf8_t * object_id,
881  afw_xctx_t * xctx)
882 {
883  const afw_utf8_t * index_name;
884  const afw_object_t * indexDefinition;
885  const afw_iterator_t * index_iterator;
886 
887  if (instance->indexDefinitions) {
888  /* iterate through each indexDefinition to see if it applies */
889  index_iterator = NULL;
891  instance->indexDefinitions, &index_iterator, &index_name, xctx);
892  while (indexDefinition) {
893  afw_adaptor_impl_index_try(instance, index_name, object,
894  object_type_id, object_id, indexDefinition,
895  afw_adaptor_impl_index_mode_add, xctx);
896 
898  instance->indexDefinitions, &index_iterator, &index_name, xctx);
899 
900  }
901  }
902 }
903 
904 /*
905  * void afw_adaptor_impl_index_unindex_object()
906  *
907  * When an object is removed from the data store,
908  * this routine will remove any associated indexes.
909  *
910  */
911 AFW_DEFINE(void) afw_adaptor_impl_index_unindex_object(
912  const afw_adaptor_impl_index_t * instance,
913  const afw_utf8_t * object_type_id,
914  const afw_object_t * object,
915  const afw_utf8_t * object_id,
916  afw_xctx_t * xctx)
917 {
918  const afw_utf8_t * index_name;
919  const afw_object_t * indexDefinition;
920  const afw_iterator_t * index_iterator;
921 
922  if (instance->indexDefinitions) {
923  /* iterate through each indexDefinition to see if it applies */
924  index_iterator = NULL;
926  instance->indexDefinitions, &index_iterator, &index_name, xctx);
927  while (indexDefinition) {
928  afw_adaptor_impl_index_try(instance, index_name, object,
929  object_type_id, object_id, indexDefinition,
930  afw_adaptor_impl_index_mode_delete, xctx);
931 
933  instance->indexDefinitions, &index_iterator, &index_name, xctx);
934 
935  }
936  }
937 }
938 
939 
940 /*
941  * void afw_adaptor_impl_index_reindex_object()
942  *
943  * When an object is modified in the data store, this
944  * routine removes any old indexes that existed, and
945  * replaces them with new indexes.
946  *
947  */
948 AFW_DEFINE(void) afw_adaptor_impl_index_reindex_object(
949  const afw_adaptor_impl_index_t * instance,
950  const afw_utf8_t * object_type_id,
951  const afw_object_t * old_object,
952  const afw_object_t * new_object,
953  const afw_utf8_t * object_id,
954  afw_xctx_t * xctx)
955 {
956  const afw_utf8_t * index_name;
957  const afw_iterator_t * index_iterator;
958  const afw_object_t * indexDefinition;
959 
960  if (instance->indexDefinitions) {
961  index_iterator = NULL;
963  instance->indexDefinitions, &index_iterator, &index_name, xctx);
964  while (indexDefinition) {
965  /* remove indexes from the old object */
966  afw_adaptor_impl_index_try(instance, index_name, old_object,
967  object_type_id, object_id, indexDefinition,
968  afw_adaptor_impl_index_mode_delete, xctx);
969 
970  /* now add back the new ones */
971  afw_adaptor_impl_index_try(instance, index_name, new_object,
972  object_type_id, object_id, indexDefinition,
973  afw_adaptor_impl_index_mode_add, xctx);
974 
976  instance->indexDefinitions, &index_iterator, &index_name, xctx);
977  }
978  }
979 }
980 
981 /* useful macro for determining if node, x, is a non-leaf node */
982 #define AFW_QUERY_CRITERIA_CONTINUE(x) \
983  (x != AFW_QUERY_CRITERIA_FALSE && x != AFW_QUERY_CRITERIA_TRUE)
984 
985 /*
986  * afw_boolean_t afw_adaptor_impl_index_sargable_entry()
987  *
988  * This recursive function takes a filter entry and evaluates
989  * the full decision tree to determine if it's sargable. The
990  * sargability looks opposite to the semantic of the expression
991  * being evaluated on purpose.
992  *
993  * For example, when evaluating the sargability of an exclusive
994  * relationship (AND), we only need one clause to be sargable
995  * to gain a performance increase, using an index.
996  *
997  * However, when evaluating an inclusive relationship (OR), we need
998  * every clause to be sargable in order for indexes to be useful.
999  *
1000  * FIXME: This routine will only report a clause is sargable if the
1001  * operation is eq, lt, lte, gt, or gte.
1002  *
1003  */
1004 AFW_DEFINE(afw_boolean_t) afw_adaptor_impl_index_sargable_entry(
1005  const afw_adaptor_impl_index_t * instance,
1006  const afw_utf8_t * object_type_id,
1007  const afw_query_criteria_filter_entry_t * entry,
1008  afw_xctx_t * xctx)
1009 {
1010  afw_boolean_t sargable;
1011  afw_boolean_t on_true, on_false;
1012 
1013  /* For now, we will only evaluate certain operations for sargability */
1014  if (! (
1015  entry->op_id == afw_query_criteria_filter_op_id_eq ||
1016  entry->op_id == afw_query_criteria_filter_op_id_lt ||
1017  entry->op_id == afw_query_criteria_filter_op_id_le ||
1018  entry->op_id == afw_query_criteria_filter_op_id_gt ||
1019  entry->op_id == afw_query_criteria_filter_op_id_ge
1020  )
1021  )
1022  return false;
1023 
1024  /* Determine if this property is indexed */
1025  sargable = afw_adaptor_impl_index_is_property_indexed(instance,
1026  object_type_id, entry->property_name, xctx);
1027 
1028  /* A bottom leaf of our query decision tree */
1029  if (entry->on_true == AFW_QUERY_CRITERIA_TRUE &&
1030  entry->on_false == AFW_QUERY_CRITERIA_FALSE) {
1031  return sargable;
1032  }
1033 
1034  /* (entry AND on_true) OR (on_false) */
1035  else if (AFW_QUERY_CRITERIA_CONTINUE(entry->on_true) &&
1036  AFW_QUERY_CRITERIA_CONTINUE(entry->on_false)) {
1037  on_true = afw_adaptor_impl_index_sargable_entry(
1038  instance, object_type_id, entry->on_true, xctx);
1039 
1040  on_false = afw_adaptor_impl_index_sargable_entry(
1041  instance, object_type_id, entry->on_false, xctx);
1042 
1043  return ((sargable || on_true) && (on_false));
1044  }
1045 
1046  /* (entry AND on_true) */
1047  else if (AFW_QUERY_CRITERIA_CONTINUE(entry->on_true)) {
1048  on_true = afw_adaptor_impl_index_sargable_entry(
1049  instance, object_type_id, entry->on_true, xctx);
1050 
1051  return (sargable || on_true);
1052  }
1053 
1054  /* (entry OR on_false) */
1055  else if (AFW_QUERY_CRITERIA_CONTINUE(entry->on_false)) {
1056  on_false = afw_adaptor_impl_index_sargable_entry(
1057  instance, object_type_id, entry->on_false, xctx);
1058 
1059  return (sargable && on_false);
1060  }
1061 
1062  /* bug?? */
1063  else {
1064  AFW_THROW_ERROR_Z(general,
1065  "Error: unexpected condition while parsing filter expression.", xctx);
1066  }
1067 
1068  return false;
1069 }
1070 
1071 /*
1072  * afw_boolean_t afw_adaptor_impl_index_sargable()
1073  *
1074  * "Sargable", or Search ARGument ABLE
1075  *
1076  * Returns true if the query_criteria contains properties
1077  * that can leverage index(es) to locate.
1078  *
1079  */
1080 AFW_DEFINE(afw_boolean_t) afw_adaptor_impl_index_sargable(
1081  const afw_adaptor_impl_index_t * instance,
1082  const afw_utf8_t * object_type_id,
1083  const afw_query_criteria_t * criteria,
1084  afw_xctx_t * xctx)
1085 {
1086  const afw_query_criteria_filter_entry_t * entry;
1087 
1088  /* if we have no criteria, then there's no index to help */
1089  entry = (criteria) ? criteria->filter : NULL;
1090  if (entry == NULL)
1091  return false;
1092 
1093  return afw_adaptor_impl_index_sargable_entry(
1094  instance, object_type_id, entry, xctx);
1095 }
1096 
1097 /*
1098  * afw_adaptor_impl_index_cursor_list_cardinality()
1099  *
1100  * Takes a list of cursor(s) and computes the cardinality
1101  * for the entire set. For individual cursors, this is
1102  * exact. For a conjunction of cursors, this computes the
1103  * worst-case scenario (a total sum).
1104  */
1105 afw_boolean_t afw_adaptor_impl_index_cursor_list_cardinality(
1106  const afw_adaptor_impl_index_t * instance,
1107  apr_array_header_t * cursor_list,
1108  size_t * cardinality,
1109  afw_xctx_t * xctx)
1110 {
1111  const afw_adaptor_impl_index_cursor_t *cursor;
1112  afw_boolean_t rc;
1113  size_t c;
1114  int i;
1115 
1116  /* no cursors in the list indicates they were not sargable */
1117  if (apr_is_empty_array(cursor_list))
1118  return false;
1119 
1120  *cardinality = 0;
1121  for (i = 0; i < cursor_list->nelts; i++) {
1122  cursor = ((const afw_adaptor_impl_index_cursor_t **)
1123  cursor_list->elts)[i];
1124 
1126  cursor, &c, xctx);
1127  if (!rc) return rc;
1128 
1129  /* Each conjunction may, at worst-case, add to
1130  the total overall cardinality */
1131  *cardinality += c;
1132  }
1133 
1134  return true;
1135 }
1136 
1137 /*
1138  * afw_adaptor_impl_index_cursor_list_join()
1139  *
1140  * During a disjunction decision, we would ideally like to
1141  * choose the cursor_list with lower cardinality. This
1142  * routine computes those and returns the better choice.
1143  *
1144  * If cardinality cannot be computed, we simply choose one.
1145  */
1146 apr_array_header_t * afw_adaptor_impl_index_cursor_list_join(
1147  const afw_adaptor_impl_index_t * instance,
1148  apr_array_header_t * this_list,
1149  apr_array_header_t * that_list,
1150  afw_xctx_t * xctx)
1151 {
1152  size_t this_cardinality, that_cardinality;
1154  afw_boolean_t rc;
1155  int i;
1156 
1157  /* unless we can do a real inner-join, we must indicate that
1158  the "joined" result list is not the result of an inner-join, but
1159  a left or right outer join */
1160  for (i = 0; i < this_list->nelts; i++) {
1161  cursor = ((afw_adaptor_impl_index_cursor_t**)this_list->elts)[i];
1162  cursor->inner_join = false;
1163  }
1164 
1165  for (i = 0; i < that_list->nelts; i++) {
1166  cursor = ((afw_adaptor_impl_index_cursor_t**)that_list->elts)[i];
1167  cursor->inner_join = false;
1168  }
1169 
1170  rc = afw_adaptor_impl_index_cursor_list_cardinality(
1171  instance, this_list, &this_cardinality, xctx);
1172  if (!rc) {
1173  /* Unable to compute the cardinality, so we must simply choose one */
1174  return that_list;
1175  }
1176 
1177  rc = afw_adaptor_impl_index_cursor_list_cardinality(
1178  instance, that_list, &that_cardinality, xctx);
1179  if (!rc) {
1180  /* Unable to compute the cardinality, so we must simply choose one */
1181  return this_list;
1182  }
1183 
1184  return (this_cardinality <= that_cardinality) ? this_list : that_list;
1185 }
1186 
1187 /*
1188  * afw_adaptor_impl_index_cursor_list_merge()
1189  *
1190  * During a conjunction, we merge two lists together.
1191  * We like to have them sorted from highest
1192  * cardinality to lowest to make the process of
1193  * removing duplicates easier.
1194  *
1195  */
1196 apr_array_header_t * afw_adaptor_impl_index_cursor_list_merge(
1197  const afw_adaptor_impl_index_t * instance,
1198  apr_array_header_t * this_list,
1199  apr_array_header_t * that_list,
1200  afw_xctx_t * xctx)
1201 {
1202  apr_array_header_t *merged_list;
1203  apr_array_header_t *temp;
1204  const afw_adaptor_impl_index_cursor_t *this_cursor;
1205  const afw_adaptor_impl_index_cursor_t *that_cursor;
1206  size_t this_cardinality, that_cardinality;
1207  afw_boolean_t rc;
1208  int merged_size;
1209  int i, j;
1210 
1211  merged_size = this_list->nelts + that_list->nelts;
1212  merged_list = NULL;
1213  temp = that_list;
1214 
1215  /* walk through each item in this_list and merge it into a new one */
1216  for (i = 0; i < this_list->nelts; i++) {
1217  merged_list = apr_array_make(afw_pool_get_apr_pool(xctx->p),
1218  merged_size, sizeof(const afw_adaptor_impl_index_cursor_t *));
1219 
1220  this_cursor = ((const afw_adaptor_impl_index_cursor_t **)
1221  this_list->elts)[i];
1222 
1224  this_cursor, &this_cardinality, xctx);
1225  if (!rc) return NULL;
1226 
1227  for (j = 0; j < temp->nelts; j++) {
1228  that_cursor = ((const afw_adaptor_impl_index_cursor_t **)
1229  temp->elts)[j];
1230 
1232  that_cursor, &that_cardinality, xctx);
1233 
1234  if (this_cardinality > that_cardinality) {
1236  apr_array_push(merged_list) = this_cursor;
1237  /* now allow the rest of that_list to be merged */
1238  this_cardinality = 0;
1239  }
1240 
1242  apr_array_push(merged_list) = that_cursor;
1243  }
1244 
1245  temp = apr_array_copy(afw_pool_get_apr_pool(xctx->p), merged_list);
1246  }
1247 
1248  return merged_list;
1249 }
1250 
1251 /*
1252  * void afw_adaptor_impl_index_cursor_list()
1253  *
1254  * Recursive routine for building out an array of cursors,
1255  * The end result is the indexed result set, in conjunctive
1256  * form. To evaluate it, the union of the cursor lists
1257  * must be evaluated.
1258  *
1259  * The query_criteria must be sargable before calling this
1260  * routine.
1261  *
1262  * Note: The cursor arrays may contain "outer joins", which
1263  * need to be evaluated to exclude false results. This
1264  * is because we do not do actual conjunctive joins
1265  * at the time we make this decision. Moreover, an
1266  * individual clause may not have indexes we can hit.
1267  *
1268  * In order to do inner joins, we may rely on the adaptor to
1269  * efficiently perform this step. For now, these joins
1270  * are effectively performed by calculating cardinality.
1271  *
1272  * This will return an array of cursors, representing the
1273  * resulting conjunction.
1274  *
1275  */
1277  const afw_adaptor_impl_index_t * instance,
1278  const afw_utf8_t * object_type_id,
1279  const afw_query_criteria_filter_entry_t * entry,
1280  afw_xctx_t * xctx)
1281 {
1282  afw_adaptor_impl_index_cursor_t *cursor = NULL;
1283  apr_array_header_t *cursor_list, *next_list;
1284  const afw_object_t *indexDefinition;
1285  const afw_utf8_t *value_string;
1286  afw_boolean_t unique;
1287 
1288  /* allocate our cursor_list that will contain a conjunction
1289  of cursors, representing this particular decision branch. */
1290  cursor_list = apr_array_make(afw_pool_get_apr_pool(xctx->p), 8,
1291  sizeof(const afw_adaptor_impl_index_cursor_t*));
1292 
1293  if (entry == NULL) {
1294  /* No entry means empty cursor list */
1295  return cursor_list;
1296  }
1297 
1298  /* use the internal utf-8 string representation */
1300  value_string = afw_value_as_utf8(entry->value, xctx->p, xctx);
1301 
1302  /* Determine if this property is indexed */
1303  indexDefinition = afw_adaptor_impl_index_get_index_definition(
1304  instance, object_type_id, entry->property_name, xctx);
1305  if (indexDefinition)
1306  {
1307  /* if the indexDefinition is case-insensitive, then we need
1308  to lowercase our query value for comparison */
1309  if (afw_adaptor_impl_index_option_case_insensitive(
1310  indexDefinition, xctx)) {
1311  value_string = afw_utf8_to_lower(value_string, xctx->p, xctx);
1312  }
1313  unique = afw_adaptor_impl_index_option_unique(
1314  indexDefinition, xctx);
1315 
1316  /* get this cursor and add it to our current cursor list */
1319  cursor = afw_adaptor_impl_index_open_cursor(instance, object_type_id,
1320  entry->property_name, entry->op_id, value_string, unique, xctx->p, xctx);
1321  }
1322 
1323  if (cursor) {
1324  cursor->inner_join = true;
1325 
1326  /* remember the afw_query_criteria_filter_entry for later */
1327  cursor->filter_entry = entry;
1328 
1330  apr_array_push(cursor_list) = cursor;
1331  }
1332 
1333  /* A bottom leaf of our query decision tree */
1334  if (entry->on_true == AFW_QUERY_CRITERIA_TRUE &&
1335  entry->on_false == AFW_QUERY_CRITERIA_FALSE)
1336  {
1337  /* Here there is nothing to do. We simply return the
1338  current cursor for this entry */
1339  }
1340 
1341  /* (entry AND on_true) OR (on_false) */
1342  else if (AFW_QUERY_CRITERIA_CONTINUE(entry->on_true) &&
1343  AFW_QUERY_CRITERIA_CONTINUE(entry->on_false))
1344  {
1345  /*
1346  The disjunction of the a cursor on the current criteria
1347  entry and the on_true entry results in a subset of either
1348  one.
1349 
1350  This results in a superset, which will shrink later on when
1351  we perform evaluations. If the cursor implementation supports
1352  complete inner joins, then we won't need to evaluate objects
1353  later on.
1354  */
1355 
1357  instance, object_type_id, entry->on_true, xctx);
1358 
1359  /* compute the optimal disjunction choice */
1360  cursor_list = afw_adaptor_impl_index_cursor_list_join(
1361  instance, cursor_list, next_list, xctx);
1362 
1363  /*
1364  However, the conjunction clause with on_false requires that
1365  we evaluate it's cursor(s) and add it to our list as well.
1366  */
1368  instance, object_type_id, entry->on_false, xctx);
1369 
1370  cursor_list = afw_adaptor_impl_index_cursor_list_merge(instance,
1371  cursor_list, next_list, xctx);
1372  }
1373 
1374  /* (entry AND on_true) */
1375  else if (AFW_QUERY_CRITERIA_CONTINUE(entry->on_true))
1376  {
1377  /* disjunction of cursors, again, choose the best one */
1378  next_list = afw_adaptor_impl_index_cursor_list(instance,
1379  object_type_id, entry->on_true, xctx);
1380 
1381  cursor_list = afw_adaptor_impl_index_cursor_list_join(
1382  instance, cursor_list, next_list, xctx);
1383  }
1384 
1385  /* (entry OR on_false) */
1386  else if (AFW_QUERY_CRITERIA_CONTINUE(entry->on_false))
1387  {
1388  /* conjunction of cursors, so we must use both */
1389  next_list = afw_adaptor_impl_index_cursor_list(instance,
1390  object_type_id, entry->on_false, xctx);
1391 
1392  cursor_list = afw_adaptor_impl_index_cursor_list_merge(instance,
1393  cursor_list, next_list, xctx);
1394  }
1395 
1396  /* bug?? */
1397  else {
1398  AFW_THROW_ERROR_Z(general,
1399  "Error: unexpected condition while parsing filter expression.", xctx);
1400  }
1401 
1402  return cursor_list;
1403 }
1404 
1405 /*
1406  * int afw_adaptor_impl_index_compare()
1407  *
1408  * This routine compares a value to the filter criteria entry
1409  * and returns the comparison result as:
1410  *
1411  * <0 less than
1412  * = equivalent
1413  * >= grater than
1414  *
1415  */
1416 static int afw_adaptor_impl_index_compare(
1417  const afw_adaptor_impl_index_t * instance,
1418  const afw_query_criteria_filter_entry_t * entry,
1419  const afw_value_t * value,
1420  afw_xctx_t * xctx)
1421 {
1422  const afw_utf8_t *string;
1423  const afw_value_t * const *values, *v;
1424  int i;
1425  const afw_utf8_t *property_value;
1426 
1427  /* use the internal utf-8 string representation */
1429  property_value = afw_value_as_utf8(entry->value, xctx->p, xctx);
1430 
1431  /* can't compare Objects */
1432  if (afw_value_is_object(value)) {
1433  AFW_THROW_ERROR_Z(general,
1434  "Error: property value cannot be of type object.", xctx);
1435  }
1436 
1437  /* all we can do with Lists and Bags is check for equivalence */
1438  else if (afw_value_is_list(value)) {
1439  values = afw_value_as_array_of_values(value, xctx->p, xctx);
1440  for (i = 0; values[i]; i++) {
1441  v = values[i];
1442  string = afw_value_as_utf8(v, xctx->p, xctx);
1443 
1444  if (afw_utf8_equal(string, property_value))
1445  return 0;
1446  }
1447  }
1448 
1449  else {
1450  /* scalars are easy */
1451  string = afw_value_as_utf8(value, xctx->p, xctx);
1452  return afw_utf8_compare(string, property_value);
1453  }
1454 
1455  /* not sure what else to do here, but say they definitely aren't equal */
1456  return -1;
1457 }
1458 
1459 /*
1460  * afw_boolean_t afw_adaptor_impl_index_applies()
1461  *
1462  * This routine determines whether a known object applies to
1463  * a specific cursor. It's used to eliminate duplicates during
1464  * a cursor conjunction operation.
1465  *
1466  * We could ask the cursor if the object applies, but that is often
1467  * more expensive than testing the criteria ourselves, since there
1468  * are typically more objects in a cursor than there are properties
1469  * in an object.
1470  *
1471  */
1472 static afw_boolean_t afw_adaptor_impl_index_applies(
1473  const afw_adaptor_impl_index_t * instance,
1474  const afw_adaptor_impl_index_cursor_t * cursor,
1475  const afw_object_t * object,
1476  afw_xctx_t * xctx)
1477 {
1478  afw_boolean_t contains = false;
1479  const afw_query_criteria_filter_entry_t *entry = cursor->filter_entry;
1480  const afw_value_t *value;
1481 
1482  value = afw_object_get_property(object, entry->property_name, xctx);
1483  if (value) {
1484  switch (entry->op_id) {
1485  case afw_query_criteria_filter_op_id_eq:
1486  if (afw_adaptor_impl_index_compare(instance,
1487  entry, value, xctx) == 0)
1488  return true;
1489  break;
1490  case afw_query_criteria_filter_op_id_ne:
1491  if (afw_adaptor_impl_index_compare(instance,
1492  entry, value, xctx) != 0)
1493  return true;
1494  break;
1495  case afw_query_criteria_filter_op_id_lt:
1496  if (afw_adaptor_impl_index_compare(instance,
1497  entry, value, xctx) < 0)
1498  return true;
1499  break;
1500  case afw_query_criteria_filter_op_id_le:
1501  if (afw_adaptor_impl_index_compare(instance,
1502  entry, value, xctx) <= 0)
1503  return true;
1504  break;
1505  case afw_query_criteria_filter_op_id_gt:
1506  if (afw_adaptor_impl_index_compare(instance,
1507  entry, value, xctx) > 0)
1508  return true;
1509  break;
1510  case afw_query_criteria_filter_op_id_ge:
1511  if (afw_adaptor_impl_index_compare(instance,
1512  entry, value, xctx) >= 0)
1513  return true;
1514  break;
1515 
1516  case afw_query_criteria_filter_op_id_na:
1517  break;
1518 
1519  case afw_query_criteria_filter_op_id_match:
1520  case afw_query_criteria_filter_op_id_contains:
1521  case afw_query_criteria_filter_op_id_in:
1522  case afw_query_criteria_filter_op_id_differ:
1523  case afw_query_criteria_filter_op_id_excludes:
1524  case afw_query_criteria_filter_op_id_out:
1525  case afw_query_criteria_filter_op_id_and:
1526  case afw_query_criteria_filter_op_id_or:
1527  AFW_THROW_ERROR_Z(query_too_complex,
1528  "Filter op not implemented", xctx);
1529 
1530  default:
1531  AFW_THROW_ERROR_Z(general, "Filter op invalid", xctx);
1532  }
1533  } else {
1534  /* only "ne" operator is meaningful if the value doesn't exist */
1535  if (entry->op_id == afw_query_criteria_filter_op_id_ne)
1536  return true;
1537  }
1538 
1539  return contains;
1540 }
1541 
1542 /*
1543  * afw_adaptor_impl_index_query()
1544  *
1545  * This routine will use the indexes to filter and create
1546  * a list, which the caller can iterate over.
1547  *
1548  * A query criteria filter converts our set of cursors into
1549  * conjunctive normal form (CNF). Each cursor in the set
1550  * is the result of a disjunctive join (AND) clauses. The
1551  * final result must compute the union, eliminating duplicate
1552  * entries that may be contained within each cursor.
1553  */
1554 AFW_DEFINE(void) afw_adaptor_impl_index_query(
1555  const afw_adaptor_impl_index_t * instance,
1556  const afw_utf8_t * object_type_id,
1557  const afw_query_criteria_t * criteria,
1558  afw_object_cb_t callback,
1559  void * context,
1560  const afw_pool_t * pool,
1561  afw_xctx_t * xctx)
1562 {
1563  apr_array_header_t *cursors;
1564  const afw_adaptor_impl_index_cursor_t *current_cursor;
1565  const afw_adaptor_impl_index_cursor_t *next_cursor;
1566  const afw_object_t *object = NULL;
1567  const afw_pool_t *p;
1568  int cursor_index = 0;
1569  int i;
1570 
1571  /* Recursively compute our cursors */
1572  cursors = afw_adaptor_impl_index_cursor_list(instance,
1573  object_type_id, (criteria ? criteria->filter : NULL), xctx);
1574 
1575  if (apr_is_empty_array(cursors)) {
1576  AFW_THROW_ERROR_Z(general,
1577  "Error: unable to parse filter into indexable cursors.", xctx);
1578  }
1579 
1580  current_cursor =
1581  ((const afw_adaptor_impl_index_cursor_t**)cursors->elts)[0];
1582 
1583  while (1)
1584  {
1585  p = afw_pool_create(pool, xctx);
1586 
1587  /* get the next value from this cursor */
1589  current_cursor, p, xctx);
1590 
1591  if (object == NULL) {
1592  /* No more objects left on this index cursor */
1593  afw_adaptor_impl_index_cursor_release(current_cursor, xctx);
1594 
1595  cursor_index++;
1596  if (cursors->nelts == cursor_index) {
1597  /* we're at the end of our cursors */
1598  callback(NULL, context, xctx);
1599 
1600  afw_pool_release(p, xctx);
1601  return;
1602  }
1603 
1604  current_cursor =
1606  cursors->elts)[cursor_index];
1607 
1608  afw_pool_release(p, xctx);
1609  continue;
1610  }
1611 
1612  /*
1613  * The reason we may need to test the object is because some of
1614  * of the predicates may have specified non-indexed properties
1615  * that we couldn't exclude automatically through joins.
1616  */
1617  if (!current_cursor->inner_join &&
1618  !afw_query_criteria_test_object(object, criteria, p, xctx))
1619  {
1620  /* this object can be discarded/ignored, so release its memory */
1621  afw_pool_release(p, xctx);
1622  continue;
1623  }
1624 
1625  /*
1626  * We may have duplicates that we need to eliminate. We do this
1627  * by simply looking ahead at the following cursors. This
1628  * does require some additional CPU, but cuts down on memory.
1629  */
1630  afw_boolean_t duplicate = false;
1631  for (i = cursor_index+1; i < cursors->nelts; i++) {
1632  next_cursor = ((const afw_adaptor_impl_index_cursor_t**)
1633  cursors->elts)[i];
1634  /*
1635  The "applies" routine will check the filter entry for this
1636  cursor, along with the object's matching property value,
1637  and determine whether this cursor contains the object.
1638  */
1639  if (afw_adaptor_impl_index_applies(instance,
1640  next_cursor, object, xctx)) {
1641  /* we have a duplicate, which we skip for now and let the
1642  future cursor provide instead. */
1643  duplicate = true;
1644  break;
1645  }
1646  }
1647 
1648  if (duplicate) {
1649  /* release this object to save memory, and move onto the next */
1650  afw_pool_release(p, xctx);
1651  continue;
1652  }
1653 
1654  /* finally, we have an object that satisfies all conditions, and is unique */
1655  callback(object, context, xctx);
1656  }
1657 
1658  /* should never get here */
1659  callback(NULL, context, xctx);
1660 }
afw_boolean_t afw_adaptor_impl_index_filter_applicable(const afw_object_t *object, const afw_object_t *indexDefinition, afw_xctx_t *xctx)
AFW_DEFINE(const afw_object_t *)
apr_array_header_t * afw_adaptor_impl_index_cursor_list(const afw_adaptor_impl_index_t *instance, const afw_utf8_t *object_type_id, const afw_query_criteria_filter_entry_t *entry, afw_xctx_t *xctx)
Helpers for afw_adaptor implementation index development.
Adaptive Framework Core Internal.
#define afw_adaptor_impl_index_cursor_get_next_object(instance, pool, xctx)
Call method get_next_object of interface afw_adaptor_impl_index_cursor.
#define afw_adaptor_impl_index_cursor_release(instance, xctx)
Call method release of interface afw_adaptor_impl_index_cursor.
#define afw_adaptor_impl_index_cursor_get_count(instance, count, xctx)
Call method get_count of interface afw_adaptor_impl_index_cursor.
#define afw_adaptor_impl_index_drop(instance, object_type_id, key, pool, xctx)
Call method drop of interface afw_adaptor_impl_index.
#define afw_adaptor_impl_index_open_cursor(instance, object_type_id, index_key, operator,value, unique, pool, xctx)
Call method open_cursor of interface afw_adaptor_impl_index.
#define afw_adaptor_impl_index_update_index_definitions(instance, indexDefinitions, xctx)
Call method update_index_definitions of interface afw_adaptor_impl_index.
#define afw_adaptor_impl_index_add(instance, object_type_id, object_id, key, value, unique, pool, xctx)
Call method add of interface afw_adaptor_impl_index.
#define afw_adaptor_impl_index_delete(instance, object_type_id, object_id, key, value, pool, xctx)
Call method delete of interface afw_adaptor_impl_index.
#define afw_adaptor_impl_index_open(instance, object_type_id, key, integer, unique, reverse, pool, xctx)
Call method open of interface afw_adaptor_impl_index.
#define afw_adaptor_session_retrieve_objects(instance, impl_request, object_type_id, criteria, context, callback, adaptor_type_specific, p, xctx)
Call method retrieve_objects of interface afw_adaptor_session.
#define afw_adaptor_session_begin_transaction(instance, xctx)
Call method begin_transaction of interface afw_adaptor_session.
#define afw_adaptor_session_get_index_interface(instance, xctx)
Call method get_index_interface of interface afw_adaptor_session.
#define afw_adaptor_transaction_release(instance, xctx)
Call method release of interface afw_adaptor_transaction.
#define afw_adaptor_transaction_commit(instance, xctx)
Call method commit of interface afw_adaptor_transaction.
afw_adaptor_session_get_cached(const afw_utf8_t *adaptor_id, afw_boolean_t begin_transaction, afw_xctx_t *xctx)
Get/create an active cached session for adaptor_id.
Definition: afw_adaptor.c:375
#define afw_value_is_boolean(A_VALUE)
Macro to determine if value is evaluated boolean.
afw_value_as_boolean(const afw_value_t *value, afw_xctx_t *xctx)
Typesafe cast of data type boolean.
afw_object_set_property_as_integer(const afw_object_t *object, const afw_utf8_t *property_name, afw_integer_t internal, afw_xctx_t *xctx)
Set property function for data type integer values.
#define afw_value_is_list(A_VALUE)
Macro to determine if value is evaluated list.
#define afw_object_old_get_property_as_list(object, property_name, xctx)
Get property function for data type list value.
afw_object_set_property_as_list(const afw_object_t *object, const afw_utf8_t *property_name, const afw_list_t *internal, afw_xctx_t *xctx)
Set property function for data type list values.
#define afw_value_is_null(A_VALUE)
Macro to determine if value is evaluated null.
#define afw_value_is_object(A_VALUE)
Macro to determine if value is evaluated object.
afw_object_set_property_as_object(const afw_object_t *object, const afw_utf8_t *property_name, const afw_object_t *internal, afw_xctx_t *xctx)
Set property function for data type object values.
#define afw_object_old_get_property_as_object(object, property_name, xctx)
Get property function for data type object value.
#define afw_object_old_get_next_property_as_object(object, iterator, property_name, xctx)
Get next property function for data type object value.
afw_value_create_object(const afw_object_t *internal, const afw_pool_t *p, afw_xctx_t *xctx)
Create function for unmanaged data type object value.
afw_value_create_string(const afw_utf8_t *internal, const afw_pool_t *p, afw_xctx_t *xctx)
Create function for unmanaged data type string value.
#define afw_object_old_get_property_as_string(object, property_name, xctx)
Get property function for data type string value.
#define afw_list_of_string_get_next(list, iterator, xctx)
Get next value from list of string.
#define afw_value_is_string(A_VALUE)
Macro to determine if value is evaluated string.
afw_object_set_property_as_string(const afw_object_t *object, const afw_utf8_t *property_name, const afw_utf8_t *internal, afw_xctx_t *xctx)
Set property function for data type string values.
afw_boolean_t(* afw_object_cb_t)(const afw_object_t *object, void *context, afw_xctx_t *xctx)
Typedef for afw_adaptor_session_object callback.
Definition: afw_common.h:1176
struct afw_iterator_s afw_iterator_t
_Bool afw_boolean_t
Definition: afw_common.h:373
int afw_rc_t
Definition: afw_common.h:663
#define afw_compile_to_value(string, source_location, compile_type, parent, shared, p, xctx)
Compile string to adaptive value.
Definition: afw_compile.h:189
#define AFW_FINALLY
Always executed regardless of error.
Definition: afw_error.h:702
#define AFW_ENDTRY
Ends an AFW try block.
Definition: afw_error.h:727
#define AFW_TRY
Begin an AFW TRY block.
Definition: afw_error.h:634
#define AFW_THROW_ERROR_Z(code, message_z, xctx)
Macro used to set error and 0 rv in xctx and throw it.
Definition: afw_error.h:283
#define afw_list_get_next_value(instance, iterator, p, xctx)
Call method get_next_value of interface afw_list.
#define afw_object_get_property(instance, property_name, xctx)
Call method get_property of interface afw_object.
#define afw_object_release(instance, xctx)
Call method release of interface afw_object.
#define afw_object_meta_get_object_type_id(instance, xctx)
Get object's object_type_id.
afw_object_meta_get_object_id(const afw_object_t *instance, afw_xctx_t *xctx)
Get entity object's object id.
afw_object_remove_property(const afw_object_t *instance, const afw_utf8_t *property_name, afw_xctx_t *xctx)
Remove a property from object.
Definition: afw_object.c:35
#define afw_object_create_managed(p, xctx)
Create an empty entity object in its own pool.
Definition: afw_object.h:913
afw_object_set_property_as_string_from_utf8_z(const afw_object_t *instance, const afw_utf8_t *property_name, const afw_utf8_z_t *string_z, afw_xctx_t *xctx)
Set an string property from utf8_z.
Definition: afw_object.c:194
#define afw_pool_get_apr_pool(instance)
Call method get_apr_pool of interface afw_pool.
#define afw_pool_release(instance, xctx)
Call method release of interface afw_pool.
const afw_pool_t * afw_pool_create(const afw_pool_t *parent, afw_xctx_t *xctx)
Create a new pool.
afw_query_criteria_test_object(const afw_object_t *obj, const afw_query_criteria_t *criteria, const afw_pool_t *p, afw_xctx_t *xctx)
Test object against query criteria.
#define AFW_QUERY_CRITERIA_FALSE
#define AFW_QUERY_CRITERIA_TRUE
int afw_utf8_compare(const afw_utf8_t *s1, const afw_utf8_t *s2)
Compare two strings.
afw_boolean_t afw_utf8_equal(const afw_utf8_t *s1, const afw_utf8_t *s2)
Check to see if a string equals another string.
const afw_utf8_t * afw_utf8_to_lower(const afw_utf8_t *s, const afw_pool_t *p, afw_xctx_t *xctx)
Convert utf-8 sting to lower case in specified pool.
#define afw_value_evaluate(value, p, xctx)
Evaluate value if needed using specific pool.
Definition: afw_value.h:841
afw_value_as_utf8(const afw_value_t *value, const afw_pool_t *p, afw_xctx_t *xctx)
Definition: afw_value.c:456
#define afw_value_is_defined_and_evaluated(A_VALUE)
Macro to determine if value is defined and evaluated.
Definition: afw_value.h:481
afw_value_as_array_of_values(const afw_value_t *value, const afw_pool_t *p, afw_xctx_t *xctx)
Return a NULL terminated list of values in a specified pool.
Definition: afw_value.c:817
int afw_xctx_begin_stack_frame(afw_xctx_t *xctx)
Begin stack frame.
Definition: afw_xctx.h:329
afw_xctx_set_local_variable(const afw_utf8_t *name, const afw_value_t *value, afw_xctx_t *xctx)
Set a variable then current xctx frame.
Definition: afw_xctx.c:289
void afw_xctx_end_stack_frame(int top, afw_xctx_t *xctx)
Set stack top.
Definition: afw_xctx.h:343
Interface afw_adaptor_impl_index_cursor public struct.
Interface afw_adaptor_impl_index public struct.
Interface afw_adaptor_session public struct.
Interface afw_adaptor_transaction public struct.
Interface afw_list public struct.
Interface afw_object public struct.
Interface afw_pool public struct.
Parsed filter entry from query string.
Parsed query criteria.
NFC normalized UTF-8 string.
Definition: afw_common.h:545
Interface afw_value public struct.
Interface afw_xctx public struct.