docs: protected PlanNode attachCriteria( final QueryContext context,
                                         PlanNode plan,
                                         Constraint constraint,
                                         List<? extends Column> columns,
                                         Map<String, Subquery> subqueriesByVariableName ) {
          if (constraint == null) return plan;
          context.getHints().hasCriteria = true;
  
          // Extract the list of Constraint objects that all must be satisfied ...
          LinkedList<Constraint> andableConstraints = new LinkedList<Constraint>();
          separateAndConstraints(constraint, andableConstraints);
          assert !andableConstraints.isEmpty();
  
          // Build up the map of aliases for the properties used in the criteria ...
          Map<String, String> propertyNameByAlias = new HashMap<String, String>();
          for (Column column : columns) {
              if (column.getColumnName() != null && !column.getColumnName().equals(column.getPropertyName())) {
                  propertyNameByAlias.put(column.getColumnName(), column.getPropertyName());
              }
          }
  
          // For each of these constraints, create a criteria (SELECT) node above the supplied (JOIN or SOURCE) node.
          // Do this in reverse order so that the top-most SELECT node corresponds to the first constraint.
          while (!andableConstraints.isEmpty()) {
              Constraint criteria = andableConstraints.removeLast();
  
              // Replace any subqueries with bind variables ...
              criteria = PlanUtil.replaceSubqueriesWithBindVariables(context, criteria, subqueriesByVariableName);
  
              // Replace any use of aliases with the actual properties ...
              criteria = PlanUtil.replaceAliasesWithProperties(context, criteria, propertyNameByAlias);
  
              // Create the select node ...
              PlanNode criteriaNode = new PlanNode(Type.SELECT);
              criteriaNode.setProperty(Property.SELECT_CRITERIA, criteria);
  
              // Add selectors to the criteria node ...
              criteriaNode.addSelectors(Visitors.getSelectorsReferencedBy(criteria));
  
              // Is there at least one full-text search or subquery ...
              Visitors.visitAll(criteria, new Visitors.AbstractVisitor() {
                  @Override
                  public void visit( FullTextSearch obj ) {
                      context.getHints().hasFullTextSearch = true;
                  }
              });
  
              criteriaNode.addFirstChild(plan);
              plan = criteriaNode;
          }
  
          if (!subqueriesByVariableName.isEmpty()) {
              context.getHints().hasSubqueries = true;
  
          }
          return plan;
      }