class Context {
            private final HashMap<Reference, ArrayList<GeneratedReference>> referencedRefsToGeneratedColumn;
            private final NodeContext nodeCtx;

            public Context(HashMap<Reference, ArrayList<GeneratedReference>> referencedRefsToGeneratedColumn,
                           NodeContext nodeCtx) {
                this.referencedRefsToGeneratedColumn = referencedRefsToGeneratedColumn;
                this.nodeCtx = nodeCtx;
            }
        }

        ComparisonReplaceVisitor() {
            super();
        }

        Symbol addComparisons(Symbol symbol,
                              List<GeneratedReference> generatedCols,
                              List<Reference> expansionCandidates,
                              NodeContext nodeCtx) {
            HashMap<Reference, ArrayList<GeneratedReference>> referencedSingleReferences =
                extractGeneratedReferences(generatedCols, expansionCandidates);
            if (referencedSingleReferences.isEmpty()) {
                return symbol;
            } else {
                Context ctx = new Context(referencedSingleReferences, nodeCtx);
                return symbol.accept(this, ctx);
            }
        }

        @Override
        public Symbol visitFunction(Function function, Context context) {
            if (Operators.COMPARISON_OPERATORS.contains(function.name())) {
                Reference reference = null;
                Symbol otherSide = null;
                for (int i = 0; i < function.arguments().size(); i++) {
                    Symbol arg = function.arguments().get(i);
                    arg = Symbols.unwrapReferenceFromCast(arg);
                    if (arg instanceof Reference ref) {
                        reference = ref;
                    } else {
                        otherSide = arg;
                    }
                }
                if (reference != null
                    && otherSide != null
                    && !SymbolVisitors.any(Symbols.IS_GENERATED_COLUMN, otherSide)) {
                    return addComparison(function, reference, otherSide, context);
                }
            }
            return super.visitFunction(function, context);
        }


        private Symbol addComparison(Function function, Reference reference, Symbol comparedAgainst, Context context) {
            ArrayList<GeneratedReference> genColInfos = context.referencedRefsToGeneratedColumn
                .computeIfAbsent(reference, (k) -> new ArrayList<>());
            List<Function> comparisonsToAdd = new ArrayList<>(genColInfos.size());
            comparisonsToAdd.add(function);
            for (GeneratedReference genColInfo : genColInfos) {
                Function comparison = createAdditionalComparison(
                    function,
                    genColInfo,
                    comparedAgainst,
                    context.nodeCtx
                );
                if (comparison != null) {
                    comparisonsToAdd.add(comparison);
                }
            }
            return AndOperator.join(comparisonsToAdd);
        }

        @Nullable
        private Function createAdditionalComparison(Function function,
                                                    GeneratedReference generatedReference,
                                                    Symbol comparedAgainst,
                                                    NodeContext nodeCtx) {<FILL_FUNCTION_BODY>

            if (generatedReference != null &&
                generatedReference.generatedExpression().symbolType().equals(SymbolType.FUNCTION)) {

                Function generatedFunction = (Function) generatedReference.generatedExpression();

                String operatorName = function.name();
                if (!operatorName.equals(EqOperator.NAME)) {
                    if (!generatedFunction.signature().hasFeature(Scalar.Feature.COMPARISON_REPLACEMENT)) {
                        return null;
                    }
                    // rewrite operator
                    if (ROUNDING_FUNCTIONS.contains(generatedFunction.name())) {
                        String replacedOperatorName = ROUNDING_FUNCTION_MAPPING.get(operatorName);
                        if (replacedOperatorName != null) {
                            operatorName = replacedOperatorName;
                        }
                    }
                }

                Symbol wrapped = wrapInGenerationExpression(comparedAgainst, generatedReference);
                var funcImpl = nodeCtx.functions().get(
                    null,
                    operatorName,
                    List.of(generatedReference, wrapped),
                    SearchPath.pathWithPGCatalogAndDoc()
                );
                return new Function(
                    funcImpl.signature(),
                    List.of(generatedReference, wrapped),
                    funcImpl.boundSignature().returnType()
                );
            }
            return null;
        
<no_super_class>