1"use strict";(self.webpackChunkottoman_documentation=self.webpackChunkottoman_documentation||[]).push([[1162],{2160:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>h});const s=JSON.parse('{"id":"advanced/fts","title":"Full Text Search","description":"You can use the Full Text Search service (FTS) to create queryable full-text indexes in Couchbase Server.","source":"@site/docs/advanced/fts.md","sourceDirName":"advanced","slug":"/advanced/fts","permalink":"/docs/advanced/fts","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"sidebar_position":0,"title":"Full Text Search"},"sidebar":"tutorialSidebar","previous":{"title":"Query Builder","permalink":"/docs/basic/query-builder"},"next":{"title":"Transactions","permalink":"/docs/advanced/transactions"}}');var r=t(4848),a=t(8453);const i={sidebar_position:0,title:"Full Text Search"},o=void 0,c={},h=[{value:"Examples",id:"examples",level:2},{value:"Match",id:"match",level:4},{value:"Match Phrase",id:"match-phrase",level:4},{value:"Date Range",id:"date-range",level:4},{value:"Conjunction",id:"conjunction",level:4},{value:"Disjunction",id:"disjunction",level:4},{value:"Working with Results",id:"working-with-results",level:2},{value:"Iterating over Hits",id:"iterating-over-hits",level:4},{value:"Facets",id:"facets",level:4},{value:"Scan Consistency and ConsistentWith",id:"scan-consistency-and-consistentwith",level:2},{value:"Scan consistency example:",id:"scan-consistency-example",level:4}];function l(e){const n={a:"a",admonition:"admonition",code:"code",h2:"h2",h4:"h4",p:"p",pre:"pre",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.p,{children:"You can use the Full Text Search service (FTS) to create queryable full-text indexes in Couchbase Server.\nFTS allows you to create, manage, and query full-text indexes on JSON documents stored in Couchbase buckets."}),"\n",(0,r.jsx)(n.p,{children:"It uses natural language processing for querying documents, provides relevance scoring on the results of your queries, and has fast indexes for querying a wide range of possible text searches."}),"\n",(0,r.jsx)(n.p,{children:"Supported query types include simple queries like Match and Term queries; range queries like Date Range and Numeric Range; and compound queries for conjunctions, disjunctions, and/or boolean queries."}),"\n",(0,r.jsx)(n.p,{children:"Ottoman exposes an API for performing FTS queries which abstracts some of the complexity of using the Node.js SDK."}),"\n",(0,r.jsx)(n.h2,{id:"examples",children:"Examples"}),"\n",(0,r.jsx)(n.p,{children:"Search queries are executed at the cluster level (not bucket or collection). All examples below will console log our returned documents along with their metadata and rows, each returned document has an index, id, score and sort value."}),"\n",(0,r.jsx)(n.h4,{id:"match",children:"Match"}),"\n",(0,r.jsxs)(n.p,{children:["Using the ",(0,r.jsx)(n.code,{children:"travel-sample"})," ",(0,r.jsx)(n.a,{href:"https://docs.couchbase.com/server/7.1/manage/manage-settings/install-sample-buckets.html",children:"Sample Bucket"}),", we define an FTS SearchQuery using the ",(0,r.jsx)(n.code,{children:"match()"}),' method to search for the specified term: "five-star".']}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"import { searchQuery, SearchQuery } from 'ottoman';\n\nasync function ftsMatchWord(term) {\n return await searchQuery('index-hotel-description', SearchQuery.match(term), { limit: 5 });\n}\n\nvar result = await ftsMatchWord('five-star');\nconsole.log('RESULT:', result);\n"})}),"\n",(0,r.jsx)(n.h4,{id:"match-phrase",children:"Match Phrase"}),"\n",(0,r.jsxs)(n.p,{children:["An FTS SearchQuery using the ",(0,r.jsx)(n.code,{children:"matchPhrase()"})," method to find a specified phrase: ",(0,r.jsx)(n.code,{children:'"10-minute walk from the"'}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"import { searchQuery, SearchQuery } from 'ottoman';\n\nasync function ftsMatchPhrase(phrase) {\n return await searchQuery('index-hotel-description', SearchQuery.matchPhrase(phrase), { limit: 10 });\n}\n\nresult = await ftsMatchPhrase('10-minute walk from the');\nconsole.log('RESULT:', result);\n"})}),"\n",(0,r.jsxs)(n.p,{children:["When searching for a phrase we get some additional benefits outside of the ",(0,r.jsx)(n.code,{children:"match()"})," method. The match phrase query for ",(0,r.jsx)(n.code,{children:'"10-minute walk from the"'})," will produce the following hits from our travel-sample dataset:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:'hits:\n hotel_11331: "10-minute walk from village"\n hotel_15915: "10 minute walk from Echo Arena"\n hotel_3606: "10 minute walk to the centre"\n hotel_28259: "10 minute walk to the coastal path"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["If you run this code, notice that we matched ",(0,r.jsx)(n.code,{children:'"10-minute"'})," with three additional hits on ",(0,r.jsx)(n.code,{children:'"10 minute"'})," (without the dash).\nSo, we get some of the same matches on variations of that term just as we would with a regular ",(0,r.jsx)(n.code,{children:"match()"})," method search,\nhowever; notice that ",(0,r.jsx)(n.code,{children:'"walk from the"'})," hits on several variations of this phrase:\n",(0,r.jsx)(n.code,{children:'"walk from"'})," (where ",(0,r.jsx)(n.code,{children:'"the"'})," was removed) and ",(0,r.jsx)(n.code,{children:'"walk to the"'})," (where ",(0,r.jsx)(n.code,{children:'"from"'})," was removed).\nThis is specific to searching phrases and helps provide us with various matches relevant to our search."]}),"\n",(0,r.jsx)(n.h4,{id:"date-range",children:"Date Range"}),"\n",(0,r.jsxs)(n.p,{children:["Here we define an FTS SearchQuery that uses the ",(0,r.jsx)(n.code,{children:"dateRange()"})," method to search for hotels where the updated field (",(0,r.jsx)(n.code,{children:"datetime"}),") falls within a specified date range."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"import { searchQuery, SearchQuery, Schema, model } from 'ottoman';\n\nasync function ftsHotelByDateRange(startDate, endDate) {\n const schema = new Schema({\n name: String,\n updated: Date,\n description: String\n });\n \n const Hotel = model('hotel', schema, {modelKey: 'type', keyGeneratorDelimiter: '_'});\n \n await Hotel.updateById('hotel_fts_123', {\n name: 'HotelFTS',\n updated: new Date('2010-11-10 18:33:50 +0300'),\n description: 'a fancy hotel',\n })\n\n return searchQuery(\n 'index-hotel-description',\n SearchQuery.dateRange().start(startDate).end(e
1ndDate),\n {\n limit: 5,\n }\n )\n}\n\nresult = await ftsHotelByDateRange('2010-11-10', '2010-11-20')\nconsole.log('RESULT:', result)\n"})}),"\n",(0,r.jsx)(n.h4,{id:"conjunction",children:"Conjunction"}),"\n",(0,r.jsxs)(n.p,{children:["A query satisfying multiple child queries. The example below will only return two documents hitting on the term ",(0,r.jsx)(n.code,{children:'"five-star"'}),"\nand the phrase ",(0,r.jsx)(n.code,{children:'"luxury hotel"'})," while no other documents match both criteria."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"import { searchQuery, SearchQuery } from 'ottoman';\n\nasync function ftsConjunction() {\n return await searchQuery(\n 'index-hotel-description',\n SearchQuery.conjuncts(\n SearchQuery.match('five-star'),\n SearchQuery.matchPhrase('luxury hotel')\n )\n )\n}\n\nvar result = await ftsConjunction()\nconsole.log('RESULT:', result)\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"info",children:(0,r.jsxs)(n.p,{children:["Our match for ",(0,r.jsx)(n.code,{children:'"five-star"'})," was not exact, but still produced a result because a similar term was found ",(0,r.jsx)(n.code,{children:'"Five star"'}),",\nwe could have potentially matched ",(0,r.jsx)(n.code,{children:'"5 star"'})," or the word ",(0,r.jsx)(n.code,{children:'"five"'}),".\nWhen you work with any full-text search the number of hits you get and their score are variable."]})}),"\n",(0,r.jsx)(n.h4,{id:"disjunction",children:"Disjunction"}),"\n",(0,r.jsxs)(n.p,{children:["A query satisfying (by default) one query or another.\nIf a conjunction query can be thought of like using an ",(0,r.jsx)(n.code,{children:"AND"})," operator, a disjunction would be like using an ",(0,r.jsx)(n.code,{children:"OR"})," operator.\nThe example below will return seven documents hitting on the term ",(0,r.jsx)(n.code,{children:'"Louvre"'})," and five hits on the term ",(0,r.jsx)(n.code,{children:'"Eiffel"'})," returning a total of 12 rows together as part of a disjunction query."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"import { searchQuery, SearchQuery, TermSearchFacet } from 'ottoman';\n\nasync function ftsDisjunction() {\n return await searchQuery(\n 'index-hotel-description',\n SearchQuery.disjuncts(\n SearchQuery.match('Louvre'),\n SearchQuery.match('Eiffel')\n ),\n {\n facets: {\n Descriptions: new TermSearchFacet('description', 5),\n },\n limit: 12,\n }\n )\n}\n\nresult = await ftsDisjunction()\nconsole.log('RESULT:', result)\n"})}),"\n",(0,r.jsx)(n.h2,{id:"working-with-results",children:"Working with Results"}),"\n",(0,r.jsx)(n.p,{children:"As with all query result types in Ottoman, the search query results object contains two properties. The hits reflecting the documents that matched your query, emitted as rows. Along with the metadata available in the meta property."}),"\n",(0,r.jsx)(n.p,{children:"Metadata holds additional information not directly related to your query, such as success total hits and how long the query took to execute in the cluster."}),"\n",(0,r.jsx)(n.h4,{id:"iterating-over-hits",children:"Iterating over Hits"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"result.rows.forEach((hit, index) => {\n const docId = hit.id\n const score = hit.score\n const resultNum = index + 1\n console.log(`Result #${resultNum} ID: ${docId} Score: ${score}`)\n})\n"})}),"\n",(0,r.jsx)(n.h4,{id:"facets",children:"Facets"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"var facets = result.meta.facets\nconsole.log('Descriptions facet:', facets.Descriptions)\n"})}),"\n",(0,r.jsx)(n.h2,{id:"scan-consistency-and-consistentwith",children:"Scan Consistency and ConsistentWith"}),"\n",(0,r.jsx)(n.p,{children:"By default, all Search queries will return the data from whatever is in the index at the time of query.\nThese semantics can be tuned if needed so that the hits returned include the most recently performed mutations,\nat the cost of slightly higher latency since the index needs to be updated first."}),"\n",(0,r.jsxs)(n.p,{children:["There are two ways to control consistency: either by supplying a custom ",(0,r.jsx)(n.code,{children:"SearchScanConsistency"})," or using ",(0,r.jsx)(n.code,{children:"consistentWith"}),".\nAt the moment the cluster only supports ",(0,r.jsx)(n.code,{children:"consistentWith"}),", which is why you only see ",(0,r.jsx)(n.code,{children:"SearchScanConsistency.NotBounded"}),'\nin the enum which is the default setting.\nThe way to make sure that recently written documents show up in the search works as follows (commonly referred to "',(0,r.jsx)(n.code,{children:'read your own writes"'}),"\u2009\u2014\u2009RYOW):"]}),"\n",(0,r.jsx)(n.h4,{id:"scan-consistency-example",children:"Scan consistency example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"import { searchQuery, SearchQuery, SearchScanConsistency } from 'ottoman';\n\nresult = await searchQuery(\n 'index-hotel-description',\n SearchQuery.match('swanky'),\n { consistency: SearchScanConsistency.NotBounded }\n) \n"})})]})}function d(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>i,x:()=>o});var s=t(6540);const r={},a=s.createContext(r);function i(e){const n=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),s.createElement(a.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.