docs: protected PlanNode createPlanNode( QueryContext context,
                                         Source source,
                                         Map<SelectorName, Table> usedSelectors ) {
          if (source instanceof Selector) {
              // No join required ...
              assert source instanceof AllNodes || source instanceof NamedSelector;
              Selector selector = (Selector)source;
              PlanNode node = new PlanNode(Type.SOURCE);
              if (selector.hasAlias()) {
                  node.addSelector(selector.alias());
                  node.setProperty(Property.SOURCE_ALIAS, selector.alias());
                  node.setProperty(Property.SOURCE_NAME, selector.name());
              } else {
                  node.addSelector(selector.name());
                  node.setProperty(Property.SOURCE_NAME, selector.name());
              }
              // Validate the source name and set the available columns ...
              NameFactory nameFactory = context.getExecutionContext().getValueFactories().getNameFactory();
              // Always use the qualified form when searching for tables
              Table table = context.getSchemata().getTable(selector.name().qualifiedForm(nameFactory));
              if (table != null) {
                  if (table instanceof View) context.getHints().hasView = true;
                  if (usedSelectors.put(selector.aliasOrName(), table) != null) {
                      // There was already a table with this alias or name ...
                      I18n msg = GraphI18n.selectorNamesMayNotBeUsedMoreThanOnce;
                      context.getProblems().addError(msg, selector.aliasOrName().getString());
                  }
                  node.setProperty(Property.SOURCE_COLUMNS, table.getColumns());
              } else {
                  context.getProblems().addError(GraphI18n.tableDoesNotExist, selector.name());
              }
              return node;
          }
          if (source instanceof Join) {
              Join join = (Join)source;
              JoinCondition joinCondition = join.getJoinCondition();
              // Set up new join node corresponding to this join predicate
              PlanNode node = new PlanNode(Type.JOIN);
              node.setProperty(Property.JOIN_TYPE, join.type());
              node.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
              node.setProperty(Property.JOIN_CONDITION, joinCondition);
  
              context.getHints().hasJoin = true;
              if (join.type() == JoinType.LEFT_OUTER) {
                  context.getHints().hasOptionalJoin = true;
              }
  
              // Handle each child
              Source[] clauses = new Source[] {join.getLeft(), join.getRight()};
              for (int i = 0; i < 2; i++) {
                  PlanNode sourceNode = createPlanNode(context, clauses[i], usedSelectors);
                  node.addLastChild(sourceNode);
              }
  
              // Add selectors to the joinNode
              for (PlanNode child : node.getChildren()) {
                  node.addSelectors(child.getSelectors());
              }
              return node;
          }
          // should not get here; if we do, somebody added a new type of source
          assert false;
          return null;
      }