在 Marklogic 自定义约束中使用关系运算符进行搜索

Searching with relational operators in a Marklogic custom constraint

有没有办法使用关系运算符使用 MarkLogic 自定义约束进行搜索?

对于正常范围查询,我可以搜索完全匹配 ("thing:123") 或使用关系运算符 ("thing LT 123")。但是,当我编写自定义约束时,只有完全匹配才有效。如果我使用关系运算符,它永远不会进入 'parse' 方法。

一个非常简单的例子,如果以任何方式触发约束,它只是 returns 什么都没有:

xquery 库模块(它使用 'Multi-Format' 指定的签名 here,但其他人做同样的事情):

xquery version "1.0-ml";
module namespace test = "http://test/search";
import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";

declare function test:parse(
  $query as item(), 
  $right-or-option as element())
as schema-element(cts:query) {
  <cts:false-query/> (: don't return anything :)
};

在qconsole中可以运行的XQuery:

xquery version "1.0-ml";
import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";

declare variable $OPTIONS := <options xmlns="http://marklogic.com/appservices/search">
    <debug>true</debug>
    <constraint name="test">
        <custom facet="false">
            <parse apply="parse" ns="http://test/search" at="/ext/search/test-search.xqy"/>
        </custom>
    </constraint>
</options>;

search:search("test GT foo",$OPTIONS)

搜索 "test:foo" 使用 cts:false-query() 创建正确的查询。正在搜索 "test GT foo" returns 所有文档。

使用关系运算符时的响应包含:

<search:qtext>test GT foo</search:qtext>
<search:report id="SEARCH-FLWOR">(cts:search(fn:collection(), cts:and-query((), ()), ("score-logtfidf",cts:score-order("descending")), 1))[1 to 10]</search:report>
<search:warning id="SEARCH-IGNOREDQTEXT">[test GT foo]</search:warning>

它似乎将其识别为不仅仅是纯文本搜索,但并未将其发送到我的解析函数中。我怎样才能让它调用我的函数?

以防万一,真正的约束是使用三重范围查询,它确实支持运算符(假设我可以让它们进入)。如果有更好的方法通过 search:search 进行三重查询,这将解决我当前的问题,但我想知道如何使自定义约束无论如何都能识别关系运算符。

关于你的测试约束,它被标记为facet="false"。我还没有验证,但我怀疑这就是为什么它不作用于 test GT foo,而是作用于 test:foo 的原因。实现一个假的小平面完成(我认为开始是可选的),并切换标志以查看您的测试约束是否也适用于 GT 案例。

关于三重范围查询,有这个三重范围约束可供使用,但最适合结构化查询(比如使用 REST-api /v1/search 等) .它可能会给你一些灵感:

https://github.com/patrickmcelwee/triple-range-constraint

HTH!

简短版本:自定义约束不支持不等式运算符(LT、GT 等),但有解决方法。

更长的版本:

我认为这可行,所以我做了一些测试(为了记录,我使用的是 MarkLogic 8.0-5.8)。我相信您的搜索选项与默认选项混合在一起(否则 "test:foo" 情况将不起作用)。这些选项包括:

<search:grammar>
  <search:joiner strength="50" apply="constraint" compare="LT" tokenize="word">LT</search:joiner>
</search:grammar>

这表示,当您看到两个由 "LT" 分隔的标记时,apply the constraint function 想知道如何处理它们。这促使我查找 constraint 函数的作用。在深入挖掘几层之后,我在搜索 API 库中发现了这些代码行:

else if ($compare and (empty($matched-constraint/opt:range) or $is-bucketed)) 
then <search:annotation warning="SEARCH-IGNOREDQTEXT:[{concat($matched-constraint/@name/string()," ",$compare," ",$qtext-rhs)}]"/>

我相信你认得那个注解。在我的测试中,$compare 是 "LT" 而 $matched-constraint 是我的测试约束。此行要求解析器 the constraint be a range type 将 LT 应用于此。

解决方法

我看到两个选择。

  1. 实施您可以在搜索 API 语法 (apply="my-constraint") 的 joiner 部分中指定的不同函数,并使其适用于自定义约束。
  2. 实施多个版本的解析函数并设置多个约束:约束 test-lt 将应用 parse-lt,等等

注意:您可能想深入研究搜索 API 的实现,然后破解它以适合您的情况(毕竟它只是一个 XQuery 库)。不。如果这样做,您的更改将在您升级 MarkLogic 后立即消失,如果该升级包括对该库的任何更改,您就会给自己找麻烦。

如果您与 MarkLogic 的某个人有关系,您可以要求该人提交 RFE 以允许这些加入者使用自定义约束。如果你不这样做,请告诉我。

如果其他人需要这个,我得到了一个自定义连接器函数来处理常规范围索引和自定义范围索引,以便用户可以对两者使用相同的运算符。

选项约束中有一些配置告诉它在搜索时要查找什么,还有一个语法部分将关系运算符从默认 'constraint' 更改为自定义函数:

<options xmlns="http://marklogic.com/appservices/search" xmlns:ps="http://my.namespace">
    <constraint name="quantity">
        <custom facet="false">
            <parse apply="parse" ns="http://test/search" at="/ext/search/test-search.xqy"/>
        </custom>
        <annotation>
            <ps:config compareSupported="true">
                <ps:triple-constraint type='xs:int' predicate='http://example.com/quantity'/>
            </ps:config>
        </annotation>
    </constraint>

    <grammar xmlns="http://marklogic.com/appservices/search">
      <quotation>"</quotation>
      <implicit>
        <cts:and-query strength="20" xmlns:cts="http://marklogic.com/cts"/>
      </implicit>
      <starter strength="30" apply="grouping" delimiter=")">(</starter>
      <starter strength="40" apply="prefix" element="cts:not-query">-</starter>
      <joiner strength="10" apply="infix" element="cts:or-query" tokenize="word">OR</joiner>
      <joiner strength="20" apply="infix" element="cts:and-query" tokenize="word">AND</joiner>
      <joiner strength="30" apply="infix" element="cts:near-query" tokenize="word">NEAR</joiner>
      <joiner strength="30" apply="near2" consume="2" element="cts:near-query">NEAR/</joiner>
      <joiner strength="32" apply="boost" element="cts:boost-query" tokenize="word">BOOST</joiner>
      <joiner strength="35" apply="not-in" element="cts:not-in-query" tokenize="word">NOT_IN</joiner>
      <joiner strength="50" apply="constraint">:</joiner>
      <joiner strength="50" apply="joiner-constraint" ns="http://test/search" at="/ext/search/test-search.xqy" compare="LT" tokenize="word">LT</joiner>
      <joiner strength="50" apply="joiner-constraint" ns="http://test/search" at="/ext/search/test-search.xqy" compare="LE" tokenize="word">LE</joiner>
      <joiner strength="50" apply="joiner-constraint" ns="http://test/search" at="/ext/search/test-search.xqy" compare="GT" tokenize="word">GT</joiner>
      <joiner strength="50" apply="joiner-constraint" ns="http://test/search" at="/ext/search/test-search.xqy" compare="GE" tokenize="word">GE</joiner>
      <joiner strength="50" apply="joiner-constraint" ns="http://test/search" at="/ext/search/test-search.xqy" compare="NE" tokenize="word">NE</joiner>
    </grammar>
</options>

接下来是自定义连接函数。它使用自定义查询选项配置来决定是否将其视为可以处理比较运算符的自定义约束。如果是这样,它会绕过内部 MarkLogic 代码和 returns a custom-constraint-query 与运算符。如果没有,它会让 MarkLogic 处理它。顺便说一下,我还没有详尽地测试传递,但它适用于我正在使用的简单范围索引。它也不是特别面向未来,因为它直接调用一些未记录的 MarkLogic 函数。

import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";
import module namespace searchimpl = "http://marklogic.com/appservices/search-impl" at "/MarkLogic/appservices/search/search-impl.xqy";
import module namespace ast = "http://marklogic.com/appservices/search-ast" at "/MarkLogic/appservices/search/ast.xqy";
import module namespace tdop     = "http://marklogic.com/parser/tdop" at "/MarkLogic/appservices/utils/tdop.xqy";

declare namespace ps = "http://my.namespace";
declare function test:joiner-constraint(
  $ps as map:map,
  $left as element()?,
  $opts as element()?
) as element()? {
    if ($left instance of element(cts:query)) then
        (: it doesn't seem to call this function with a cts:query for a custom constraint, but the pass-through for normal range indexes does :)
        searchimpl:joiner-constraint($ps, $left)
    else 
        let $term := ast:textonly($left)
        let $symbol := searchimpl:symbol-lookup($ps)
        let $compare := ($symbol/@compare/string(), "EQ")[1]
        let $constraint := $opts/search:constraint[@name=$term]
        let $is-custom := $constraint//search:custom and $constraint//ps:config[@compareSupported=fn:true()]

        return
        if ($is-custom) then 
            let $_ := tdop:advance($ps)
            let $right := tdop:expression($ps, $symbol/@strength - 1)
            let $value := ast:textonly($right)

            return
            <search:custom-constraint-query>
                <search:constraint-name>{$term}</search:constraint-name>
                <search:text>{$value}</search:text>
                <search:range-operator>{$compare}</search:range-operator>
            </search:custom-constraint-query>
        else
            ast:joiner-constraint($ps, $left)
};

在解析方法中,我切换到结构化查询参数,因为这将通过 REST 调用,并且如何使用字符串查询参数获取所有部分并不明显。 joiner函数创建的custom-constraint-query作为$query-elem参数传入

declare function test:parse(
  $query-elem as element(),
  $options as element(search:options)
) as schema-element(cts:query) {
...
};