private void fdbQueryPair(QueryPair query) throws ElasticException, LSHException {
		ResponsePair response = query.pairResponse;

		double aveSim = 0.0;
		double aveSimSquare = 0.0;
		double aveSig = 0.0;
		double aveSigSquare = 0.0;
		int pairCount = 0;
		int missedExe = 0;
		int missedFunc = 0;
		int missedVector = 0;

		List<FunctionDescription> aFuncList = new ArrayList<>();
		List<FunctionDescription> bFuncList = new ArrayList<>();
		DescriptionManager resManage = new DescriptionManager();
		TreeMap<ExeSpecifier, ExecutableRecord> nameMap = new TreeMap<>();
		for (PairInput pairInput : query.pairs) {
			FunctionDescription funcA = null;
			FunctionDescription funcB = null;
			ExecutableRecord erec = findSingleExeWithMap(pairInput.execA, resManage, nameMap);
			if (erec == null) {
				missedExe += 1;
			}
			else {
				funcA = queryByNameAddress(resManage, erec, pairInput.funcA.funcName,
					pairInput.funcA.address, true);
				if (funcA == null) {
					missedFunc += 1;
				}
			}

			erec = findSingleExeWithMap(pairInput.execB, resManage, nameMap);
			if (erec == null) {
				missedExe += 1;
			}
			else {
				funcB = queryByNameAddress(resManage, erec, pairInput.funcB.funcName,
					pairInput.funcB.address, true);
				if (funcB == null) {
					missedFunc += 1;
				}
			}
			aFuncList.add(funcA);
			bFuncList.add(funcB);
		}

		Iterator<FunctionDescription> bIter = bFuncList.iterator();
		VectorCompare vectorData = new VectorCompare();
		for (FunctionDescription funcA : aFuncList) {
			FunctionDescription funcB = bIter.next();
			if (funcA == null || funcB == null) {
				continue;
			}
			SignatureRecord sigA = funcA.getSignatureRecord();
			if (sigA == null) {
				missedVector += 1;
				continue;
			}
			SignatureRecord sigB = funcB.getSignatureRecord();
			if (sigB == null) {
				missedVector += 1;
				continue;
			}
			double sim = sigA.getLSHVector().compare(sigB.getLSHVector(), vectorData);
			double signif = vectorFactory.calculateSignificance(vectorData);
			PairNote pairNote = new PairNote(funcA, funcB, sim, signif, vectorData.dotproduct,
				vectorData.acount, vectorData.bcount, vectorData.intersectcount);
			response.notes.add(pairNote);
			pairCount += 1;
			aveSim += sim;
			aveSimSquare += sim * sim;
			aveSig += signif;
			aveSigSquare += signif * signif;
		}
		response.averageSim = aveSim / pairCount;
		response.averageSig = aveSig / pairCount;
		double simVariance = (aveSimSquare / pairCount) - response.averageSim * response.averageSim;
		response.simStdDev = Math.sqrt(simVariance);
		double sigVariance = (aveSigSquare / pairCount) - response.averageSig * response.averageSig;
		response.sigStdDev = Math.sqrt(sigVariance);
		response.scale = vectorFactory.getSignificanceScale();
		response.pairCount = pairCount;
		response.missedExe = missedExe;
		response.missedFunc = missedFunc;
		response.missedVector = missedVector;
	}
Entry point for the Elasticsearch version of QueryPair command:
  Query for pairs functions in the database, and compute the similarity
  and significance of their feature vectors
@param query is command parameters
@throws ElasticException for communication problems with the server
@throws LSHException for problems adding records to the response
Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticDatabase.java