Indexes and Constraints
Unique node property constraints
Unique node property constraints map to @unique
directives used in your type definitions, which has the following definition:
"""Informs @neo4j/graphql that there should be a uniqueness constraint in the database for the decorated field."""
directive @unique(
"""The name which should be used for this constraint. By default; type name, followed by an underscore, followed by the field name."""
constraintName: String
) on FIELD_DEFINITION
Additionally, the usage of the @id
directive by default implies that there should be a unique node property constraint in the database for that property.
Using this directive does not automatically ensure the existence of these constraints, and you will need to run a function on server startup. See the section Asserting constraints below for details.
@unique
directive usage
@unique
directives can only be used in GraphQL object types representing nodes, and will only be applied for the "main" label for the node. You can find some examples below.
In the following example, a unique constraint will be asserted for the label Colour
and the property hexadecimal
:
type Colour {
hexadecimal: String! @unique
}
In the next example, a unique constraint with name unique_colour
will be asserted for the label Colour
and the property hexadecimal
:
type Colour {
hexadecimal: String! @unique(constraintName: "unique_colour")
}
The @node
directive is used to change the database label mapping in this next example, so a unique constraint will be asserted for the first label in the list, Color
, and the property hexadecimal
:
type Colour @node(labels: ["Color"]) {
hexadecimal: String! @unique
}
In the following example, all labels specified in the labels
argument of the @node
directive are also checked when asserting constraints.
If there is a unique constraint specified for the hexadecimal
property of nodes with the Hue
label, but not the Color
label, no error will be thrown and no new constraints created when running assertIndexesAndConstraints
.
type Colour @node(labels: ["Color", "Hue"]) {
hexadecimal: String! @unique
}
Fulltext indexes
You can use the @fulltext
directive to add a Full text index inside Neo4j.
input FullTextInput {
indexName: String
queryName: String
fields: [String]!
name: String # Deprecated and will be removed in version 4.0. of the library. Use indexName instead.
}
"""
Informs @neo4j/graphql that there should be a fulltext index in the database, allows users to search by the index in the generated schema.
"""
directive @fulltext(indexes: [FullTextInput]!) on OBJECT
Using this directive does not automatically ensure the existence of these indexes, and you will need to run a function on server startup. See the section Asserting constraints below for details.
Specifying the @fulltext
directive
The directive can be used on nodes. Here we add a Fulltext index, called 'ProductName', for the name field, on the Product node.
type Product @fulltext(indexes: [{ indexName: "ProductName", fields: ["name"] }]) {
name: String!
color: Color! @relationship(type: "OF_COLOR", direction: OUT)
}
When you run Asserting constraints this shall do the index creation like so:
CALL db.index.fulltext.createNodeIndex("ProductName", ["Product"], ["name"])
Using the @fulltext
directive
For every index specified, a new top level query will be generated by the library. For example, for the type definitions above, the following query and types would be generated:
type Query {
productsFulltextProductName(phrase: String!, where: ProductFulltextWhere, sort: [ProductFulltextSort!],
limit: Int, offset: Int): [ProductFulltextResult!]!
}
"""The result of a fulltext search on an index of Product"""
type ProductFulltextResult {
score: Float
product: Product
}
"""The input for filtering a fulltext query on an index of Product"""
input ProductFulltextWhere {
score: FloatWhere
product: ProductWhere
}
"""The input for sorting a fulltext query on an index of Product"""
input ProductFulltextSort {
score: SortDirection
product: ProductSort
}
"""The input for filtering the score of a fulltext search"""
input FloatWhere {
min: Float
max: Float
}
This query can then be used to perform a Lucene full-text query to match and return products. Below is an example of this:
query {
productsFulltextProductName(phrase: "Hot sauce", where: { score: { min: 1.1 } } sort: [{ product: { name: ASC } }]) {
score
product {
name
}
}
}
The above query would produce results in the following format:
{
"data": {
"productsFulltextProductName": [
{
"score": 2.1265015602111816,
"product": {
"name": "Louisiana Fiery Hot Pepper Sauce"
}
},
{
"score": 1.2077560424804688,
"product": {
"name": "Louisiana Hot Spiced Okra"
}
},
{
"score": 1.3977186679840088,
"product": {
"name": "Northwoods Cranberry Sauce"
}
}
]
}
}
Additionally, it is possible to define a custom query name as part of the @fulltext
directive, using the queryName
argument as shown below:
type Product @fulltext(indexes: [{ queryName: "CustomProductFulltextQuery", indexName: "ProductName", fields: ["name"] }]) {
name: String!
color: Color! @relationship(type: "OF_COLOR", direction: OUT)
}
This would then produce the following top-level query:
type Query {
CustomProductFulltextQuery(phrase: String!, where: ProductFulltextWhere, sort: [ProductFulltextSort!],
limit: Int, offset: Int): [ProductFulltextResult!]!
}
This query can then be used as shown below:
query {
CustomProductFulltextQuery(phrase: "Hot sauce", sort: [{ score: ASC }]) {
score
product {
name
}
}
}
Deprecated usage
Querying full-text indexes in the following ways has been deprecated and will be removed in version 4.0. |
Once you specify the index, you will now gain a 'Top Level' fulltext
key on the following operations:
-
read
-
count
-
aggregate
Here we use the fulltext
key, and the phrase is using Lucene’s full-text query language to match and return Products:
query {
products(fulltext: { ProductName: { phrase: "beer OR cerveza" } }) {
name
}
}
Note that you can only query one Fulltext index at once and that the fulltext key is only available on 'Top Level' queries.
Asserting constraints
In order to ensure that the specified constraints exist in the database, you will need to run the function assertIndexesAndConstraints
, the full details of which can be found in the API reference. A simple example to create the necessary constraints might look like the following, assuming a valid driver instance in the variable driver
. This will create two constraints, one for each field decorated with @id
, @unique
and apply the indexes specified in @fulltext
:
const typeDefs = gql`
type Colour {
id: ID! @id
hexadecimal: String! @unique
}
type Product @fulltext(indexes: [{ indexName: "ProductName", fields: ["name"] }]) {
name: String!
color: Color! @relationship(type: "OF_COLOR", direction: OUT)
}
`;
const neoSchema = new Neo4jGraphQL({ typeDefs, driver });
await neoSchema.assertIndexesAndConstraints({ options: { create: true }});
Was this page helpful?