Erstellen von benutzerdefinierten Solr-Anfrageparametern

Kürzlich hat mir jemand im IRC-Kanal #solr eine Frage zur Verwendung mehrerer Filterabfragen in einer Anfrage gestellt, wobei in der solrconfig.xml ein Standardwert für eine dieser Abfragen angegeben wurde. In diesem Beitrag möchte ich über ein einfaches Muster sprechen, das ich in der Vergangenheit verwendet habe und das das spezifische Ziel des Benutzers gelöst hat, sowie über ein neues Solr-Plugin, das ich geschrieben habe, um eine breitere Palette ähnlicher Anwendungsfälle zu lösen.

Einige Hintergründe

Während die meisten Solr-Benutzer wissen, dass Sie mit dem SearchHandler von Solr Standard-Anfrageparameter in der Datei solrconfig.xml festlegen können, wissen viele unerfahrene Benutzer nicht, dass der SearcHandler eigentlich 3 Abschnitte von „init“-Parametern unterstützt: „defaults“, „appends“ und „invariants“.

Ein „defaults“-Init-Parameter wird ignoriert, wenn der gleiche Parametername in einer Solr-Anfrage verwendet wird, aber „appends“-Parameter werden zusätzlich zu allen Parametern mit dem gleichen Namen in der Anfrage verwendet. „invariants“-Parameter treiben die Sache auf die Spitze – gleichnamige Anfrageparameter werden vollständig ignoriert.

Zum Beispiel würde eine Anfrage für /select?facet.field=author&fq=cat:books&rows=10000 nur 10 Dokumente pro Seite zurückgeben (nicht 10000), nur das Feld „Autor“ (nicht „Kategorie“) und nur Dokumente, die sich in der Kategorie „Bücher“ UND im Bestand…. befinden.

  <requestHandler name="/select" class="solr.SearchHandler">
     <lst name="defaults">
       <bool name="facet">true</bool>
       <str name="facet.field">category</bool>
       <str name="q">*:*</str>
     </lst>
    <lst name="appends">
      <str name="fq">inStock:true</str>
    </lst>
    <lst name="invariants">
      <int name="rows">10</int>
    </lst>
  </requestHandler>

Die Frage

Der im IRC angesprochene Anwendungsfall lässt sich wie folgt zusammenfassen:

  • Standardmäßig sollte eine spezielle Abfrage verwendet werden, um die Menge der für alle Abfragen verfügbaren Dokumente einzuschränken
  • Kunden müssen mehrere „fq“-Parameter angeben, um die Ergebnisse ihrer Abfragen aufzuschlüsseln
  • Die Kunden müssen in der Lage sein, die eingeschränkte Teilmenge der verfügbaren Dokumente pro Anfrage zu überschreiben.

Oder um es anhand des Solr-Beispielschemas konkreter auszudrücken:

  • Standardmäßig werden bei jeder Abfrage nur Dokumente zurückgegeben, die mit inStock:true übereinstimmen.
  • Die Kunden senden ‚fq‘-Parameter, um nach Feldern wie ‚Preis‘ und ‚Katze‘ zu filtern.
  • Kunden müssen die Möglichkeit haben, das Standardverhalten von inStock:true außer Kraft zu setzen, um alle Dokumente (*:*) oder Dokumente, die nicht auf Lager sind (inStock:false), zu durchsuchen.

Der springende Punkt des Problems ist: Wir brauchen eine Möglichkeit, in unserer Konfiguration ein „fq“ anzugeben, das zur Anfragezeit explizit überschrieben werden kann, aber nicht automatisch von einem fq=price:[* TO 100] oder fq=cat:electronics überschrieben wird.

Wir können nicht einfach <str name="fq">inStock:true</str> in unseren Request Handler „defaults“ angeben, sonst wird er ignoriert, wenn ein anderer „fq“-Parameter vom Client angegeben wird. Genauso wenig können wir ihn in unsere „Anhänge“ (oder „Invarianten“) aufnehmen, denn dann kann der Client ihn nicht mehr überschreiben.

Die Lösung

Die Lösung für diese Art von Problem ist überraschend einfach, aber nicht sofort offensichtlich.

Indem wir das De-Referenzieren von Variablen in Local Params nutzen, können wir einen fq-Filter „appends“ angeben, der an einen benutzerdefinierten Parameternamen unserer Wahl delegiert. Diesen benutzerdefinierten Parameternamen können wir dann in unseren „Standardeinstellungen“ angeben und es den Kunden dennoch ermöglichen, ihn bei Bedarf zu überschreiben.

In der Beispielkonfiguration unten habe ich einen benutzerdefinierten Parameter namens „base_set“ definiert, der als Variable in einer angehängten fq verwendet wird. Anfragen wie /select?q=video und /select?q=ipod&fq=cat:music werden automatisch durch den Standardwert „base_set“ von inStock:true eingeschränkt, aber eine Anfrage wie /select?q=ipod&fq=cat:connector&base_set=*:* überschreibt den Standardwert „base_set“ mit dem vom Kunden angegebenen Wert.

  <requestHandler name="/select" class="solr.SearchHandler">
    <lst name="defaults">
      <str name="base_set">inStock:true</str>
    </lst>
    <lst name="appends">
      <str name="fq">{!v=$base_set}</str>
    </lst>
  </requestHandler>

Idee für eine bessere Lösung

Die obige Lösung funktioniert zwar, setzt aber voraus, dass Sie Ihren Kunden zutrauen, eine beliebige Abfrage für den Parameter „base_set“ anzugeben. Als ich diese Lösung im IRC erläuterte, kam mir in den Sinn, dass es wirklich schön wäre, wenn es eine einfache Möglichkeit gäbe, benutzerdefinierte Parameter wie diesen zu erstellen, die auf eine Reihe von festen möglichen Werten beschränkt sind, auf die auch mit benutzerdefinierten Namen verwiesen werden kann.

Mit diesem Gedanken im Hinterkopf begann ich mir vorzustellen, wie ein switch QParser nützlich sein könnte.

Die Grundidee ist, dass der Switch-Parser eine beliebige Anzahl von beliebig benannten Parametern unterstützen sollte, die „Switch-Fälle“ angeben. Jeder Fall kann eine andere Abfrage bezeichnen, die der Switch-Parser abhängig vom (gekürzten) Wert der Abfragezeichenfolge an den Parser zurückgibt. Ein optionaler „Standardwert“-Parameter kann verwendet werden, um eine Abfrage für den Fall festzulegen, dass die an den Parser übergebene Abfragezeichenfolge keinem der konfigurierten Fälle entspricht. In den folgenden Beispielen würde das Ergebnis jeder Abfrage XXX lauten…

q = {!switch s.foo=XXX s.bar=zzz s.yak=qqq}foo
q = {!switch s.foo=qqq s.bar=XXX s.yak=zzz} bar     // extra whitespace
q = {!switch defSwitch=XXX s.foo=qqq s.bar=zzz}asdf // fallback on default
q = {!switch s=XXX s.bar=zzz s.yak=qqq}             // blank input

(Die Verwendung des Präfixes „s.“ würde nicht nur sicherstellen, dass es nicht versehentlich zu Konflikten zwischen switch-Werten und anderen speziellen Parameternamen wie defSwitch kommt, sondern es würde uns auch ermöglichen, einen switch-Fall von s für einen völlig leeren Abfrage-String zu haben).

Angenommen, wir hätten einen solchen Switch-Parser, dann könnten wir einige Standardwerte deklarieren und Parameter an unseren Request-Handler anhängen, die es unseren Kunden ermöglichen würden, das in ihren Abfragen verwendete „base_set“ auszuwählen, aber nur aus einer vorkonfigurierten Liste von Optionen….

  <requestHandler name="/select" class="solr.SearchHandler">
    <lst name="defaults">
      <str name="base_set">in_stock</str>
    </lst>
    <lst name="appends">
      <str name="fq">{!switch s.all='*:*'
                              s.in_stock='inStock:true'
                              s.not_in_stock='inStock:false'
                              v=$base_set}</str>
    </lst>
  </requestHandler>

Mit einer solchen Konfiguration würden Abfragen standardmäßig auf Dokumente beschränkt, die mit inStock:true übereinstimmen, aber die Kunden könnten einen von 3 zulässigen Werten für den Parameter base_set angeben, um die Menge der zu durchsuchenden Dokumente zu überschreiben – ohne dass sie die Implementierungsdetails wissen müssen, wie diese base_set Werte umgesetzt werden. Jeder Client, der versucht, einen nicht zulässigen Wert für base_set anzugeben, sollte eine Fehlermeldung erhalten (wir könnten jedoch jederzeit den lokalen Parameter defSwitch hinzufügen, wenn wir eine automatische Voreinstellung für nicht zulässige Eingaben haben möchten).

Die Umsetzung dieser Idee

Implementierung einer SwitchQParserPlugin ist eigentlich ziemlich einfach. Mein üblicher Rat, um zu verstehen, wie man ein neues QParserPlugin schreibt, ist normalerweise, mit dem Quelltext für TermQParserPlugin zu beginnen, da es die einfachste bestehende Implementierung ist. Da wir aber bereits wissen, dass wir unseren QParser an einen Sub-Parser delegieren wollen, dient das BoostQParserPlugin als besseres Beispiel.

Das Kernstück unseres Plugins ist die Rückgabe einer QParser, deren parse() Methode drei wichtige Dinge tut:

  • Prüfen Sie den lokalen Parameter v auf den Abfrage-String und trimmen Sie ihn, falls er existiert.
  • Verwenden Sie den Abfrage-String, um den Abfragewert aus den Switch-Parametern abzurufen, und greifen Sie auf den Standard-Parameter zurück, wenn dieser gesetzt ist.
  • Erstellen Sie einen Unterparser für den resultierenden Abfragewert oder geben Sie einen Fehler aus, wenn es keinen gibt.
public Query parse() throws SyntaxError {
  String val = localParams.get(QueryParsing.V);
  String subQ = localParams.get(SWITCH_DEFAULT);
  subQ = StringUtils.isBlank(val)
    ? localParams.get(SWITCH_CASE, subQ)
    : localParams.get(SWITCH_CASE + "." + val.trim(), subQ);
  if (null == subQ) {
    throw new SyntaxError( "Error: didn't match a switch case" );
  }
  subParser = subQuery(subQ, null);
  return subParser.getQuery();
}

All das ist hoffentlich ziemlich einfach.

Abgesehen von ein paar grundlegenden Java-Klischees ist der einzige andere Code, um den wir uns wirklich kümmern müssen, das Überschreiben einiger Standardmethoden der Basisklasse QParser, um sicherzustellen, dass wir sie an die entsprechenden Methoden in der oben ausgewählten subParser delegieren, die auf dem verwendeten switch case basieren. Auf diese Weise verhält sich unser SwitchQParserPlugingenau so wie der Parser, den es umhüllt.

Das Endergebnis, mit einigen Tests und Dokumentationen, finden Sie in SOLR-4481. Nach einer Überprüfung und Diskussion in der Gemeinschaft hoffe ich, dass es in Solr 4.2 aufgenommen wird.

UPDATE: SwitchQParserPlugin ist jetzt Teil von SOlr 4.2, aber bitte beachten Sie, dass sich die Namen der Parameter in der endgültigen Version gegenüber der Beschreibung in diesem Beitrag leicht geändert haben.

You Might Also Like

KI-Agenten dominieren den Einkauf. Ist Ihre Website auf die KI-gestützte Suche vorbereitet?

Generative KI-Agenten wie ChatGPT definieren die Produktsuche neu. Erfahren Sie, wie Sie...

Read More

Vom Suchunternehmen zum praktischen KI-Pionier: Unsere Vision für 2025 und darüber hinaus

CEO Mike Sinoway gibt Einblicke in die Zukunft der KI und stellt...

Read More

Wenn KI schief geht: Fehlschläge in der realen Welt und wie man sie vermeidet

Lassen Sie nicht zu, dass Ihr KI-Chatbot einen 50.000 Dollar teuren Tahoe...

Read More

Quick Links