tantivy_go

package module
v1.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 6, 2026 License: MIT Imports: 7 Imported by: 1

README

Go Tantivy Bindings

This project provides Go bindings for the Tantivy search engine library. Tantivy is a full-text search engine library written in Rust, and this project aims to make its powerful search capabilities available to Go developers.

The library is thread safe and can be used in a concurrent environment

Why

The only available FTS engine in the Golang community is Bleve, which is surprisingly slow compared to Tantivy. Check out the last link for details on the performance comparison.

Search Benchmark Credits for the image to the Tantivy team

Our Journey with Tantivy

We've been running it in Anytype for over a year across all major platforms and architectures without issues on 32-bit and 64-bit systems, x86 and ARM64, iOS, Android, PC, macOS, and Linux.

Features

Jieba Tokenizer

This library includes the Jieba feature by default, which provides Chinese text segmentation. However, if you do not need this functionality, you can build the library without it to save approximately 5MB of the dictionary.

Golang API to Create Custom Queries for Tantivy

See searchquerybuilder.go

Search quality testing

Test quality

Installation

go get github.com/anyproto/tantivy-go

Ensure your libraries are in your ld path.

Example Run
  • Run make download-tantivy-all inside the rust folder
  • Run main.go in the example folder

Development

Development and compilation are done on MacBooks and for Apple platforms. Therefore, the development steps provided are for macOS.

Install environment
  • Install rustup
  • Install Rust architectures: make setup
  • Add Android libraries to your path: export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$ANDROID_HOME/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin
  • Install Windows compiler: brew install mingw-w64
  • Install musl: brew tap messense/macos-cross-toolchains && brew install x86_64-unknown-linux-musl
Install rust libraries

Run inside the rust folder:

make install-all - install release versions for all platforms

make install-debug-all - install debug versions for all platforms

make install-ARCH-GOOS - install release version for ARCH GOOS

make install-debug-ARCH-GOOS - install debug version for ARCH GOOS

GCC support

To be done

Validate min macos version

otool -l libtantivy_go.a | rg LC_BUILD_VERSION -A4 | rg minos | sort | uniq -c Expected output:

 880     minos 11.0
Possible troubleshooting

If you experience SIGSEGV issues with musl or windows, try adding these flags to the linker:

-extldflags '-static -Wl,-z stack-size=1000000'
Nix

flake.nix currently provides two versions of devShell: musl and gcc.

This command will make a bash shell with all required build dependencies:

nix develop .

Each devShell also contains a script which:

  • builds rust into .a lib
  • copies it to ../anytype-heart
  • builds anytype-heart grpcServer
  • copies grpcServer to ../anytype-ts anytypeHelper

[!TIP] To enable musl, set musl = true; in flake.nix.

If you want to debug tantivy from anytype-ts, with musl or gcc, this scripts automates all the flow.

All together it would look like:

nix develop .
tantivy_compile_all_gcc
# or
tantivy_compile_all_musl

To check that it works, run anytype-ts and try to search something.

[!NOTE] MacOS (Darwin) nix shell is not supported yet

Documentation

Index

Constants

View Source
const (
	// IndexRecordOptionBasic specifies that only basic indexing information should be used.
	IndexRecordOptionBasic = iota
	// IndexRecordOptionWithFreqs specifies that indexing should include term frequencies.
	IndexRecordOptionWithFreqs
	// IndexRecordOptionWithFreqsAndPositions specifies that indexing should include term frequencies and term positions.
	IndexRecordOptionWithFreqsAndPositions
)
View Source
const DefaultTokenizer = "default"
View Source
const TokenizerEdgeNgram = "edge_ngram"
View Source
const TokenizerJieba = "jieba"
View Source
const TokenizerNgram = "ngram"
View Source
const TokenizerRaw = "raw"
View Source
const TokenizerSimple = "simple_tokenizer"

Variables

This section is empty.

Functions

func GetSearchResults

func GetSearchResults[T any](
	searchResult *SearchResult,
	tc *TantivyContext,
	f func(json string) (T, error),
	includeFields ...string,
) ([]T, error)

GetSearchResults extracts search results from a SearchResult and converts them into a slice of models.

Parameters:

  • searchResult (*SearchResult): The search results to process.
  • schema (*Schema): The schema to use for converting documents to models.
  • f (func(json string) (T, error)): A function to convert JSON strings to models.
  • includeFields (...string): Optional list of fields to include in the result.

Returns:

  • ([]T, error): A slice of models obtained from the search results, and an error if something goes wrong.

func LibInit

func LibInit(cleanOnPanic, utf8Lenient bool, directive ...string) error

LibInit initializes the library with an optional directive.

Parameters:

  • directive: A variadic parameter that allows specifying an initialization directive. If no directive is provided, the default value "info" is used.

Returns: - An error if the initialization fails.

func ToModel

func ToModel[T any](doc *Document, tc *TantivyContext, includeFields []string, f func(json string) (T, error)) (T, error)

ToModel converts a document to a model of type T using the provided schema and a conversion function.

Parameters:

  • doc: the document to convert
  • schema: the schema to use for converting the document to JSON
  • includeFields: optional fields to include in the JSON output
  • f: a function that takes a JSON string and converts it to a model of type T

Returns:

  • T: the model of type T resulting from the conversion
  • error: an error if the conversion fails, or nil if the operation is successful

Types

type AllQueryStruct added in v1.0.5

type AllQueryStruct struct {
	Boost float64 `json:"boost"`
}

func (*AllQueryStruct) IsQuery added in v1.0.5

func (aq *AllQueryStruct) IsQuery()

type BooleanQuery added in v0.3.0

type BooleanQuery struct {
	Subqueries []QueryElement `json:"subqueries"`
	Boost      float64        `json:"boost"`
}

func (*BooleanQuery) IsQuery added in v0.3.0

func (bq *BooleanQuery) IsQuery()

type Document

type Document struct {
	// contains filtered or unexported fields
}

func NewDocument

func NewDocument() *Document

NewDocument creates a new instance of Document.

Returns:

  • *Document: a pointer to a newly created Document instance.

func (*Document) AddField

func (d *Document) AddField(fieldValue string, tc *TantivyContext, fieldName string) error

AddField adds a field with the specified name and value to the document using the given index. Returns an error if adding the field fails.

Parameters:

  • fieldValue: the value of the field to add
  • index: the index to use for adding the field
  • fieldName: the name of the field to add

Returns:

  • error: an error if adding the field fails, or nil if the operation is successful

func (*Document) AddFields added in v1.0.0

func (d *Document) AddFields(fieldValue string, tc *TantivyContext, fieldNames ...string) error

AddFields adds a field with the specified name and value to the document using the given index. Returns an error if adding the field fails.

Parameters:

  • fieldValue: the value of the field to add
  • index: the index to use for adding the field
  • fieldNames: the names of the fields to add

Returns:

  • error: an error if adding the field fails, or nil if the operation is successful

func (*Document) Free

func (d *Document) Free()

func (*Document) FreeStrings added in v0.2.0

func (d *Document) FreeStrings()

func (*Document) ToJson

func (d *Document) ToJson(tc *TantivyContext, includeFields ...string) (string, error)

ToJson converts the document to its JSON representation based on the provided schema. Optionally, specific fields can be included in the JSON output.

Parameters:

  • schema: the schema to use for converting the document to JSON
  • includeFields: optional variadic parameter specifying the fields to include in the JSON output

Returns:

  • string: the JSON representation of the document
  • error: an error if the conversion fails, or nil if the operation is successful

type FastFieldResult added in v1.0.5

type FastFieldResult struct {
	Values []string
	Scores []float32
}

FastFieldResult holds the results of a fast field search.

type FieldQuery added in v0.3.0

type FieldQuery struct {
	FieldIndex int     `json:"field_index"`
	TextIndex  int     `json:"text_index"`
	Boost      float64 `json:"boost"`
}

func (*FieldQuery) IsQuery added in v0.3.0

func (fq *FieldQuery) IsQuery()

type FinalQuery added in v0.3.0

type FinalQuery struct {
	Texts  []string      `json:"texts"`
	Fields []string      `json:"fields"`
	Query  *BooleanQuery `json:"query"`
}

type Language added in v1.0.1

type Language string
const (
	Arabic     Language = "ar"
	Armenian   Language = "hy"
	Basque     Language = "eu"
	Catalan    Language = "ca"
	Danish     Language = "da"
	Dutch      Language = "nl"
	English    Language = "en"
	Estonian   Language = "et"
	Finnish    Language = "fi"
	French     Language = "fr"
	German     Language = "de"
	Greek      Language = "el"
	Hindi      Language = "hi"
	Hungarian  Language = "hu"
	Indonesian Language = "id"
	Irish      Language = "ga"
	Italian    Language = "it"
	Lithuanian Language = "lt"
	Nepali     Language = "ne"
	Norwegian  Language = "no"
	Portuguese Language = "pt"
	Romanian   Language = "ro"
	Russian    Language = "ru"
	Serbian    Language = "sr"
	Spanish    Language = "es"
	Swedish    Language = "sv"
	Tamil      Language = "ta"
	Turkish    Language = "tr"
	Yiddish    Language = "yi"
)

type Query added in v0.3.0

type Query interface {
	IsQuery()
}

type QueryBuilder added in v0.3.0

type QueryBuilder struct {
	// contains filtered or unexported fields
}

func NewQueryBuilder added in v0.3.0

func NewQueryBuilder() *QueryBuilder

func (*QueryBuilder) AddField added in v0.3.0

func (qb *QueryBuilder) AddField(field string) int

func (*QueryBuilder) AddText added in v0.3.0

func (qb *QueryBuilder) AddText(text string) int

func (*QueryBuilder) AllQuery added in v1.0.5

func (qb *QueryBuilder) AllQuery(modifier QueryModifier, boost float64) *QueryBuilder

func (*QueryBuilder) BooleanQuery added in v0.3.0

func (qb *QueryBuilder) BooleanQuery(modifier QueryModifier, subBuilder *QueryBuilder, boost float64) *QueryBuilder

func (*QueryBuilder) Build added in v0.3.0

func (qb *QueryBuilder) Build() FinalQuery

func (*QueryBuilder) NestedBuilder added in v0.3.0

func (qb *QueryBuilder) NestedBuilder() *QueryBuilder

func (*QueryBuilder) Query added in v0.3.0

func (qb *QueryBuilder) Query(modifier QueryModifier, field string, text string, queryType QueryType, boost float64) *QueryBuilder

type QueryElement added in v0.3.0

type QueryElement struct {
	Query     Query         `json:"query"`
	Modifier  QueryModifier `json:"query_modifier"`
	QueryType QueryType     `json:"query_type"`
}

type QueryModifier added in v0.3.0

type QueryModifier int
const (
	Must QueryModifier = iota
	Should
	MustNot
)

type QueryType added in v0.3.0

type QueryType int
const (
	BoolQuery QueryType = iota
	PhraseQuery
	PhrasePrefixQuery
	TermPrefixQuery
	TermQuery
	EveryTermQuery
	OneOfTermQuery
	AllQuery
)

type Schema

type Schema struct {
	// contains filtered or unexported fields
}

type SchemaBuilder

type SchemaBuilder struct {
	// contains filtered or unexported fields
}

func NewSchemaBuilder

func NewSchemaBuilder() (*SchemaBuilder, error)

NewSchemaBuilder creates a new SchemaBuilder instance. Returns a pointer to the SchemaBuilder and an error if creation fails.

func (*SchemaBuilder) AddTextField

func (b *SchemaBuilder) AddTextField(
	name string,
	stored bool,
	isText bool,
	isFast bool,
	indexRecordOption int,
	tokenizer string,
) error

AddTextField adds a text field to the schema being built.

Parameters: - name: The name of the field. - stored: Whether the field should be stored in the index. - isText: Whether the field should be treated as tantivy text or string for full-text search. - isFast: Whether the field should be isText as tantivy quick field. - indexRecordOption: The indexing option to be used (e.g., basic, with frequencies, with frequencies and positions). - tokenizer: The name of the tokenizer to be used for the field.

Returns an error if the field could not be added.

func (*SchemaBuilder) BuildSchema

func (b *SchemaBuilder) BuildSchema() (*Schema, error)

BuildSchema finalizes the schema building process and returns the resulting Schema. Returns a pointer to the Schema and an error if the schema could not be built.

type SearchContext added in v0.2.0

type SearchContext interface {
	// GetQuery returns the search query string.
	GetQuery() string
	// GetDocsLimit returns the document limit as a uintptr.
	GetDocsLimit() uintptr
	// WithHighlights returns true if highlights are enabled.
	WithHighlights() bool
	// GetFieldAndWeights returns slices of field names and their corresponding weights.
	GetFieldAndWeights() ([]string, []float32)
}

SearchContext defines the interface for searchContext

type SearchContextBuilder added in v0.2.0

type SearchContextBuilder struct {
	// contains filtered or unexported fields
}

SearchContextBuilder is a builder structure for creating searchContext.

func NewSearchContextBuilder added in v0.2.0

func NewSearchContextBuilder() *SearchContextBuilder

NewSearchContextBuilder creates a new instance of SearchContextBuilder.

func (*SearchContextBuilder) AddField added in v0.2.0

func (b *SearchContextBuilder) AddField(field string, weight float32) *SearchContextBuilder

AddField adds a field with the specified weight to searchContext.

func (*SearchContextBuilder) AddFieldDefaultWeight added in v0.2.0

func (b *SearchContextBuilder) AddFieldDefaultWeight(field string) *SearchContextBuilder

AddFieldDefaultWeight adds a field with a default weight of 1.0 to searchContext.

func (*SearchContextBuilder) Build added in v0.2.0

Build returns the constructed searchContext as an interface.

func (*SearchContextBuilder) SetDocsLimit added in v0.2.0

func (b *SearchContextBuilder) SetDocsLimit(limit uintptr) *SearchContextBuilder

SetDocsLimit sets the docsLimit for searchContext.

func (*SearchContextBuilder) SetQuery added in v0.2.0

func (b *SearchContextBuilder) SetQuery(query string) *SearchContextBuilder

SetQuery sets the query for searchContext.

func (*SearchContextBuilder) SetQueryFromJson added in v0.3.0

func (b *SearchContextBuilder) SetQueryFromJson(query *FinalQuery) *SearchContextBuilder

SetQueryFromJson sets the query for searchContext.

func (*SearchContextBuilder) SetWithHighlights added in v0.2.0

func (b *SearchContextBuilder) SetWithHighlights(withHighlights bool) *SearchContextBuilder

SetWithHighlights sets the withHighlights flag for searchContext.

type SearchResult

type SearchResult struct {
	// contains filtered or unexported fields
}

func (*SearchResult) Free

func (r *SearchResult) Free()

func (*SearchResult) Get

func (r *SearchResult) Get(index uint64) (*Document, error)

Get retrieves a document from the search result at the specified index.

Parameters: - index: The index of the document to retrieve.

Returns: - A pointer to the Document if successful, or nil if not found. - An error if there was an issue retrieving the document.

func (*SearchResult) GetSize

func (r *SearchResult) GetSize() (uint64, error)

GetSize returns the number of documents in the search result.

Returns: - The size of the search result if successful. - An error if there was an issue getting the size.

type TantivyContext added in v0.1.0

type TantivyContext struct {
	// contains filtered or unexported fields
}

func NewTantivyContextWithSchema added in v0.1.0

func NewTantivyContextWithSchema(path string, schema *Schema) (*TantivyContext, error)

NewTantivyContextWithSchema creates a new instance of TantivyContext with the provided schema.

Parameters:

  • path: The path to the index as a string.
  • schema: A pointer to the Schema to be used.

Returns:

  • *TantivyContext: A pointer to a newly created TantivyContext instance.
  • error: An error if the index creation fails.

func (*TantivyContext) AddAndConsumeDocuments added in v0.1.0

func (tc *TantivyContext) AddAndConsumeDocuments(docs ...*Document) error

AddAndConsumeDocuments adds and consumes the provided documents to the index.

Parameters:

  • docs: A variadic parameter of pointers to Document to be added and consumed.

Returns:

  • error: An error if adding and consuming the documents fails.

func (*TantivyContext) AddAndConsumeDocumentsWithOpstamp added in v1.0.4

func (tc *TantivyContext) AddAndConsumeDocumentsWithOpstamp(docs ...*Document) (uint64, error)

AddAndConsumeDocumentsWithOpstamp adds and consumes the provided documents to the index and returns the commit opstamp.

Parameters:

  • docs: A variadic parameter of pointers to Document to be added and consumed.

Returns:

  • uint64: The opstamp from the commit operation. Returns 0 if no documents are provided.
  • error: An error if adding and consuming the documents fails.

func (*TantivyContext) BatchAddAndDeleteDocumentsWithOpstamp added in v1.0.4

func (tc *TantivyContext) BatchAddAndDeleteDocumentsWithOpstamp(addDocs []*Document, deleteFieldName string, deleteFieldValues []string) (uint64, error)

BatchAddAndDeleteDocumentsWithOpstamp performs batch add and delete operations within a single commit. This is more efficient than calling AddAndConsumeDocumentsWithOpstamp and DeleteDocumentsWithOpstamp separately as it only commits once, reducing I/O overhead.

Important: To update an existing document, you must include its field value in deleteFieldValues. Otherwise, the new document will be added without removing the old one, creating duplicates. The delete operation happens first, then the add operation.

Parameters:

  • addDocs: Documents to add to the index.
  • deleteFieldName: The field name to match against for deletion.
  • deleteFieldValues: Field values to delete from the index (documents where deleteFieldName matches these values).

Returns:

  • uint64: The opstamp from the commit operation. Returns 0 if both addDocs and deleteFieldValues are empty.
  • error: An error if the batch operation fails.

func (*TantivyContext) Close added in v1.0.2

func (tc *TantivyContext) Close() error

Close waits till the merging operations are finished and releases all the resources held by the indexWriter

func (*TantivyContext) CommitOpstamp added in v1.0.4

func (tc *TantivyContext) CommitOpstamp() uint64

CommitOpstamp gets the opstamp of the last commit.

Note: Due to a bug in Tantivy (https://github.com/quickwit-oss/tantivy/issues/2666), this returns the INITIAL commit opstamp, not the latest one. The value is only updated after the index is closed and reopened. During an active session, this will return 0 for a new index or the opstamp from when the index was opened.

func (*TantivyContext) DeleteDocuments added in v0.1.0

func (tc *TantivyContext) DeleteDocuments(fieldName string, deleteIds ...string) error

DeleteDocuments deletes documents from the index based on the specified field and IDs.

Parameters:

  • fieldName: The field name to match against the document IDs.
  • deleteIds: A variadic parameter of document IDs to be deleted.

Returns:

  • error: An error if deleting the documents fails.

func (*TantivyContext) DeleteDocumentsWithOpstamp added in v1.0.4

func (tc *TantivyContext) DeleteDocumentsWithOpstamp(fieldName string, deleteIds ...string) (uint64, error)

DeleteDocumentsWithOpstamp deletes documents from the index based on the specified field and IDs and returns the commit opstamp.

Parameters:

  • fieldName: The field name to match against the document IDs.
  • deleteIds: A variadic parameter of document IDs to be deleted.

Returns:

  • uint64: The opstamp from the delete operation. Returns 0 if no IDs are provided.
  • error: An error if deleting the documents fails.

func (*TantivyContext) Free deprecated added in v0.1.0

func (tc *TantivyContext) Free()

Deprecated: Use Close() instead.

func (*TantivyContext) GarbageCollectFiles added in v1.0.4

func (tc *TantivyContext) GarbageCollectFiles() (uint64, error)

GarbageCollectFiles performs garbage collection on unused index files. This method removes files that were created by tantivy and are no longer used by any segment.

Returns:

  • uint64: The number of files that were deleted.
  • error: An error if garbage collection fails.

func (*TantivyContext) NumDocs added in v0.1.0

func (tc *TantivyContext) NumDocs() (uint64, error)

NumDocs returns the number of documents in the index.

Returns:

  • uint64: The number of documents.
  • error: An error if retrieving the document count fails.

func (*TantivyContext) RegisterTextAnalyzerEdgeNgram added in v0.1.0

func (tc *TantivyContext) RegisterTextAnalyzerEdgeNgram(tokenizerName string, minGram, maxGram uintptr, limit uintptr) error

RegisterTextAnalyzerEdgeNgram registers a text analyzer using edge n-grams with the index.

Parameters:

  • tokenizerName (string): The name of the tokenizer to be used.
  • minGram (uintptr): The minimum length of the edge n-grams.
  • maxGram (uintptr): The maximum length of the edge n-grams.
  • limit (uintptr): The maximum number of edge n-grams to generate.

Returns:

  • error: An error if the registration fails.

func (*TantivyContext) RegisterTextAnalyzerJieba added in v0.2.0

func (tc *TantivyContext) RegisterTextAnalyzerJieba(tokenizerName string, textLimit uintptr) error

RegisterTextAnalyzerJieba registers a jieba text analyzer with the index.

Parameters:

  • tokenizerName (string): The name of the tokenizer to be used.
  • textLimit (uintptr): The limit on the length of the text to be analyzed.

Returns:

  • error: An error if the registration fails.

func (*TantivyContext) RegisterTextAnalyzerNgram added in v0.1.0

func (tc *TantivyContext) RegisterTextAnalyzerNgram(tokenizerName string, minGram, maxGram uintptr, prefixOnly bool) error

RegisterTextAnalyzerNgram registers a text analyzer using N-grams with the index.

Parameters:

  • tokenizerName (string): The name of the tokenizer to be used.
  • minGram (uintptr): The minimum length of the n-grams.
  • maxGram (uintptr): The maximum length of the n-grams.
  • prefixOnly (bool): Whether to generate only prefix n-grams.

Returns:

  • error: An error if the registration fails.

func (*TantivyContext) RegisterTextAnalyzerRaw added in v0.1.0

func (tc *TantivyContext) RegisterTextAnalyzerRaw(tokenizerName string) error

RegisterTextAnalyzerRaw registers a raw text analyzer with the index.

Parameters:

  • tokenizerName (string): The name of the raw tokenizer to be used.

Returns:

  • error: An error if the registration fails.

func (*TantivyContext) RegisterTextAnalyzerSimple added in v0.1.0

func (tc *TantivyContext) RegisterTextAnalyzerSimple(tokenizerName string, textLimit uintptr, lang Language) error

RegisterTextAnalyzerSimple registers a simple text analyzer with the index.

Parameters:

  • tokenizerName (string): The name of the tokenizer to be used.
  • textLimit (uintptr): The limit on the length of the text to be analyzed.
  • lang (string): The language code for the text analyzer.

Returns:

  • error: An error if the registration fails.

func (*TantivyContext) ReloadReader added in v1.0.4

func (tc *TantivyContext) ReloadReader() error

ReloadReader forces the index reader to reload and check for new commits.

Note: This method is called automatically during search operations (Search, SearchJson, NumDocs), so manual calls are typically not necessary. The reader uses ReloadPolicy::Manual internally, but reloading happens automatically when needed.

Returns:

  • error: An error if reloading the reader fails.

func (*TantivyContext) Search added in v0.1.0

func (tc *TantivyContext) Search(sCtx SearchContext) (*SearchResult, error)

Search performs a search query on the index and returns the search results.

Parameters:

  • sCtx (SearchContext): The context for the search, containing query string, document limit, highlight option, and field weights.

Returns:

  • *SearchResult: A pointer to the SearchResult containing the search results.
  • error: An error if the search fails.

func (*TantivyContext) SearchFastField added in v1.0.5

func (tc *TantivyContext) SearchFastField(sCtx SearchContext, fastFieldName string) (*FastFieldResult, error)

SearchFastField performs a search returning only fast field values without loading full documents. The field must be configured with isFast=true in the schema.

func (*TantivyContext) SearchFastFieldJson added in v1.0.6

func (tc *TantivyContext) SearchFastFieldJson(sCtx SearchContext, fastFieldName string) (*FastFieldResult, error)

SearchFastFieldJson performs a search using JSON query returning only fast field values. The field must be configured with isFast=true in the schema. Use this with AllQuery or other JSON-based queries.

func (*TantivyContext) SearchJson added in v0.3.0

func (tc *TantivyContext) SearchJson(sCtx SearchContext) (*SearchResult, error)

SearchJson performs a simplified search query on the index and returns the search results.

Parameters:

  • sCtx (SearchContext): The context for the search, containing query string, document limit, and highlight option.

Returns:

  • *SearchResult: A pointer to the SearchResult containing the search results.
  • error: An error if the search fails.

Directories

Path Synopsis
go module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL