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 SwitchQParserPlugin
genau 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.