Kafka GraphDB Connector¶
What’s in this document?
Note
This feature requires a GraphDB Enterprise license.
Note
GraphDB supports full-text search options as well.
The Kafka connector provides a means to synchronize RDF model changes to any Kafka consumer, staying automatically up-to-date with the GraphDB repository data.
GraphDB’s Connectors provide synchronization at the entity level, where an entity is defined as having a unique identifier (an IRI) and a set of properties and property values. In terms of RDF, this corresponds to a set of triples that have the same subject. In addition to simple properties (defined by a single triple), the Connectors support property chains. A property chain is a sequence of triples where each triple’s object is the subject of the following triple.
On the Kafka side, the RDF entities are translated to JSON documents.
The main features of the Kafka Connector are:
maintenance of a Kafka topic that is always in sync with the data stored in GraphDB
multiple independent instances per repository
entity synchronization defined by:
a list of fields on the Kafka side and property chains on the GraphDB side whose values will be synchronized
a list of
rdf:typevalues of the entities for synchronizationa list of languages for synchronization with all languages being the default
additional filtering by property and value
Unlike the Elasticsearch, OpenSearch, Solr, and Lucene connectors, the Kafka Connector does not have a query interface since Kafka is a simple message queue and does not provide a search feature.
In terms of Kafka terminology and behavior:
Each connector instance must be assigned to a fixed Kafka topic.
The connector is a Kafka producer, and does not have any information about the Kafka consumers.
The partitions are assigned by the Kafka framework and not the connector.
Usage¶
All interactions with the Kafka GraphDB Connector are done through SPARQL queries.
There are three types of SPARQL queries:
INSERTfor creating, updating, and deleting connector instancesSELECTfor listing connector instances and querying their configuration parametersINSERTandSELECTfor storing and querying data as part of the normal GraphDB data workflow
In general, this corresponds to INSERT update requests that add or modify data and to SELECT queries of existing data.
Each connector implementation uses its own namespace IRI to distinguish it from other connectors. For the Kafka GraphDB Connector, this is http://www.ontotext.com/connectors/kafka#. Each command or predicate executed by the connector uses terms from this namespace (for example, http://www.ontotext.com/connectors/kafka#createConnector) to create a connector instance for Kafka.
Individual instances of a connector are distinguished by unique names that are also IRIs. They have their own namespace to avoid clashing with any of the command predicates. For Kafka, the instance IRI is http://www.ontotext.com/connectors/kafka/instance#.
- Sample data
All examples use the following
sample datathat describes five fictitious wines: Yoyowine, Franvino, Noirette, Blanquito, and Rozova, as well as the grape varieties required to make these wines. The minimum required ruleset level in GraphDB is RDFS.@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix wine: <http://www.example.com/demo/wine#> . wine:RedWine rdfs:subClassOf wine:Wine . wine:WhiteWine rdfs:subClassOf wine:Wine . wine:RoseWine rdfs:subClassOf wine:Wine . wine:Merlot rdf:type wine:Grape ; rdfs:label "Merlot" . wine:CabernetSauvignon rdf:type wine:Grape ; rdfs:label "Cabernet Sauvignon" . wine:CabernetFranc rdf:type wine:Grape ; rdfs:label "Cabernet Franc" . wine:PinotNoir rdf:type wine:Grape ; rdfs:label "Pinot Noir" . wine:Chardonnay rdf:type wine:Grape ; rdfs:label "Chardonnay" . wine:Yoyowine rdf:type wine:RedWine ; wine:madeFromGrape wine:CabernetSauvignon ; wine:hasSugar "dry" ; wine:hasYear "2013"^^xsd:integer . wine:Franvino rdf:type wine:RedWine ; wine:madeFromGrape wine:Merlot ; wine:madeFromGrape wine:CabernetFranc ; wine:hasSugar "dry" ; wine:hasYear "2012"^^xsd:integer . wine:Noirette rdf:type wine:RedWine ; wine:madeFromGrape wine:PinotNoir ; wine:hasSugar "medium" ; wine:hasYear "2012"^^xsd:integer . wine:Blanquito rdf:type wine:WhiteWine ; wine:madeFromGrape wine:Chardonnay ; wine:hasSugar "dry" ; wine:hasYear "2012"^^xsd:integer . wine:Rozova rdf:type wine:RoseWine ; wine:madeFromGrape wine:PinotNoir ; wine:hasSugar "medium" ; wine:hasYear "2013"^^xsd:integer .
Setup and maintenance¶
Prerequisites¶
- Third-party component versions
This version of the Kafka GraphDB Connector uses Kafka version 3.9.1.
Creating a connector instance¶
Create a connector instance by sending a SPARQL query with the following configuration data:
the name of the connector instance, such as
my_indexa Kafka node and topic to synchronize to
classes to synchronize
properties to synchronize
The configuration data must be provided as a JSON string representation and passed together with the create command.
You can create connectors via Workbench or by using a SPARQL update query (create command).
If you create the connector using Workbench, no matter which way you use, you will be presented with a pop-up screen showing you the connector creation progress.
Using Workbench¶
Go to .
Click New Connector in the tab of the respective Connector type you want to create.
Fill out the configuration form.
Execute the
CREATEstatement from the form by clicking OK. Alternatively, you can view its SPARQL query by clicking View SPARQL Query, and then copy it to execute it manually or integrate it in automation scripts.
Using the create command¶
The create command is triggered by a SPARQL INSERT with the kafka:createConnector predicate. In the example, it creates a connector instance called my_index, which synchronizes the wines from the sample data above.
To be able to use newlines and quotes without the need for escaping, the update request uses SPARQL’s multi-line string delimiter consisting of 3 apostrophes: '''...'''. You can also use 3 quotes instead: """...""".
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:createConnector '''
{
"kafkaNode": "localhost:9092",
"kafkaTopic": "my_index",
"types": [
"http://www.example.com/demo/wine#Wine"
],
"fields": [
{
"fieldName": "grape",
"propertyChain": [
"http://www.example.com/demo/wine#madeFromGrape",
"http://www.w3.org/2000/01/rdf-schema#label"
]
},
{
"fieldName": "sugar",
"propertyChain": [
"http://www.example.com/demo/wine#hasSugar"
]
},
{
"fieldName": "year",
"propertyChain": [
"http://www.example.com/demo/wine#hasYear"
]
}
]
}
''' .
}
The above command creates a new Kafka connector instance that connects to the Kafka instance accessible at port 9200 on the localhost as specified by the kafkaNode key.
The "types" key defines the RDF type of the entities to synchronize. In the example, it is only entities of the type http://www.example.com/demo/wine#Wine and its subtypes if RDFS or higher-level reasoning is enabled. The "fields" key defines the mapping from RDF to Kafka. The basic building block is the property chain: a sequence of RDF properties where the object of each property is the subject of the following property. In the example, three bits of information are mapped — the grape the wines are made of, sugar content, and year. Each chain is assigned a short and convenient field name: “grape”, “sugar”, and “year”. The field names are used later in the queries.
The field grape is an example of a property chain composed of more than one property. First, we take the wine’s madeFromGrape property, the object of which is an instance of the type Grape, and then we take the rdfs:label of this instance. The fields sugar and year are both composed of a single property that links the value directly to the wine.
Working with a secured Kafka broker¶
GraphDB can connect to a secured Kafka broker using the SASL/PLAIN authentication mechanism. To configure it, set the kafkaPlainAuthUsername and kafkaPlainAuthPassword parameters. Since the password will be transmitted in clear text, it’s best to enable SSL on the Kafka broker, and accordingly set the kafkaSSL parameter to true.
Instead of supplying the username and password as part of the connector instance configuration, you can also implement a custom authenticator class and set it via the authenticationConfiguratorClass option. See GraphDB’s connector authenticator examples for more information and example projects that implement such a custom class.
There is no explicitly configurable support for other authentication mechanism supported by Kafka. It should be possible to configure most of them by supplying the relevant Kafka producer properties using the kafkaProducerConfig parameter.
Dropping a connector instance¶
Dropping a connector instance removes all references to its external store from GraphDB as well as the Kafka index associated with it.
The drop command is triggered by a SPARQL INSERT with the name of the connector instance in the subject position and kafka:dropConnector as the predicate. For example, this removes the connector my_index:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:dropConnector [] .
}
You can also force drop a connector if a normal delete operation does not work. The force delete will remove the connector even if part of the operation fails. Go to where you will see the already existing connectors that you have created. Click the delete icon , check Force delete in the dialog box, and click that dialog box’s Delete button.
Retrieving the create options for a connector instance¶
You can view the options string that was used to create a particular connector instance with the following query:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
SELECT ?createString {
kafka-inst:my_index kafka:listOptionValues ?createString .
}
Listing available connector instances¶
In the Connectors management view¶
Existing instances of a given Connector type are shown above the New connector button for that connector type. Click the name of an instance to view its configuration and SPARQL query or click the Repair or Delete
icons to perform these operations. Click the Copy
icon to copy the connector definition query to your clipboard.
With a SPARQL query¶
Listing connector instances returns all previously created instances. It is a SELECT query with the listConnectors predicate:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
SELECT ?cntUri ?cntStr {
?cntUri kafka:listConnectors ?cntStr .
}
In this example, ?cntUri is bound to the IRI of the connector instance that was used during creation such as http://www.ontotext.com/connectors/kafka/instance#my_index, while ?cntStr is bound to a string representing the local name such as “my_index” in this example.
Instance status check¶
You can query the internal state of each connector instance using a SELECT query with the connectorStatus predicate:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
SELECT ?cntUri ?cntStatus {
?cntUri kafka:connectorStatus ?cntStatus .
}
In this example, ?cntUri is bound to the IRI of the connector instance, while ?cntStatus is bound to a string representation of the status of the connector represented by this IRI. The status is key-value based.
Working with data¶
Adding, updating, and deleting data¶
From the user point of view, all synchronization happens transparently without using any additional predicates or naming a specific store explicitly. You simply execute standard SPARQL INSERT and DELETE update requests. This is achieved by intercepting all changes in the plugin and determining which Kafka documents need to be updated.
List of creation parameters¶
The creation parameters define how the kafka:createConnector predicate creates a connector instance. Some are required and some are optional. All parameters are provided together in a JSON object, where the parameter names are the object keys. Parameter values may be simple JSON values such as a string or a boolean, or they can be lists or objects.
All of the creation parameters can also be set conveniently from the Create Connector user interface without any knowledge of JSON.
readonly(boolean), optional, read-only modeA read-only connector will index all existing data in the repository at creation time, but, unlike non-read-only connectors, it will:
Not react to updates. Changes will not be synced to the connector.
Not keep any extra structures (such as the internal data structures for tracking updates to chains).
The only way to index changes in data after the connector has been created is to repair (or drop and recreate) the connector.
importGraph(boolean), optional, specifies that the RDF data from which to create the connector is in a special virtual graphUsed to make a Kafka index from temporary RDF data inserted in the same transaction. It requires read-only mode and creates a connector whose data will come from statements inserted into a special virtual graph instead of data contained in the repository. The virtual graph is
kafka:graph, where the prefixkafka:represents the IRIhttp://www.ontotext.com/connectors/kafka#as before. The data must be inserted into this graph before the connector create statement is executed.Both the insertion into the special graph and the create statement must be in the same transaction. In Workbench, you can do this by pasting them one after another in the SPARQL editor and putting a semicolon at the end of the first INSERT. This feature requires read-only mode.
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#> INSERT { GRAPH kafka:graph { ... } } WHERE { ... }; PREFIX kafka: <http://www.ontotext.com/connectors/kafka#> PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#> INSERT DATA { kafka-inst:my_index kafka:createConnector ''' { "readonly": true, "importGraph": true, "fields": [], "languages": [], "types": [], } ''' . }
importFile(string), optional, an RDF file with data from which to create the connectorCreates a connector whose data will come from an RDF file on the file system instead of data contained in the repository. The value must be the full path to the RDF file. This feature requires readonly mode.
detectFields(boolean), optional, detects fieldsThis mode introduces automatic field detection when creating a connector. You can omit the specification of
fieldsin JSON. Instead, you will get automaticfields, each corresponding to a single predicate with a field name the same as the predicate, so you must use escaping when issuing Kafka queries.In this mode, specifying types is also optional. If types are not provided, then all types will be indexed. This mode requires importGraph or importFile.
Once the connector is created, you can inspect the detected fields in the Connector management section of Workbench.
kafkaNode(string), required, the Kafka instance to sync toAs Kafka is a third-party service, you have to specify the node where it is running. The node value takes the form
http://hostname.domain:portorhttps://hostname.domain:port. No default value. Can be updated at runtime with no need to rebuild the index.
kafkaTopic(string), required, the Kafka topic to send documents to.No default value.
kafkaSSL(boolean), optional, controls whether to use an SSL connection to the Kafka broker.Falseby default. Can be updated at runtime with no need to rebuild the index.
kafkaPlainAuthUsername(string), optional, supplies the username for Kafka SASL PLAIN authentication.No default value. Can be updated at runtime with no need to rebuild the index.
kafkaPlainAuthPassword(string), optional, supplies the password for Kafka SASL PLAIN authentication.No default value. Can be updated at runtime with no need to rebuild the index.
kafkaUseTransaction(boolean), optional, controls whether to use a kafka transaction on entity updates.trueby default. Can be updated at runtime with no need to rebuild the index. When false, entities go to kafka immediately regardless of the GraphDB transaction that triggers the update.
bulkUpdateBatchSize(integer), controls the maximum batch size in bytes and corresponds to Kafka producer configbatch.size.Default value is
1,048,576(1 megabyte). Can be updated at runtime with no need to rebuild the index.
bulkUpdateRequestSize(integer), controls the maximum request size (and consequently the maximum size per document) in bytes.Default value is
1,048,576(1 megabyte). Can be updated at runtime with no need to rebuild the index.
authenticationConfiguratorClassprovides custom authentication behaviorOptional.
kafkaCompressionType(string), sets the compression to use when sending documents to Kafka.One of
none,gzip,lz4,snappy), the default issnappy. This corresponds to Kafka producer config propertycompression.type. Can be updated at runtime with no need to rebuild the index.
kafkaProducerId(string), an optional identifier that allows for separate Kafka producers with different options to the same Kafka broker.No default — all instances to the same Kafka broker will use a shared Kafka producer and thus must have the same options. See also Producer sharing and Conflict resolution.
kafkaProducerConfig(JSON), optional, the settings for creating the Kafka producer.This option is passed directly to the Kafka producer when it is instantiated. Each key is a Kafka producer configuration property. Some configuration keys such as
transactional.idare not allowed here. The following producer configuration defaults are set:transaction.timeout.ms: 900000 = 15 minutes and equals the default transaction.max.timeout.ms of a kafka brokerbatch.size: 1024 * 1024 = 1MBmax.request.size: 1024 * 1024 = 1MBlinger.ms: 50ack: all
Can be updated at runtime with no need to rebuild the index.
kafkaIgnoreDeleteAll(boolean), optional, a flag that, when selected, will not notify Kafka when all repository statements are removed.GraphDB handles the removal of all statements as a special operation that is manifested as sending a Kafka record with NULL key and NULL value. If this flag is
true, no such record will be sent.Falseby default.
kafkaPropagateConfig(boolean), optional, a non-persisted flag that, when selected, will force propagation of the Kafka configuration to other connector instances using the same Kafka broker.Falseby default. See also Producer sharing and Conflict resolution. Can be updated at runtime with no need to rebuild the index.
types(list of IRIs), required, specifies the types of entities to syncThe RDF types of entities to sync are specified as a list of IRIs. At least one type IRI is required.
Use the pseudo-IRI
$anyto sync entities that have at least one RDF type.Use the pseudo-IRI
$untypedto sync entities regardless of whether they have any RDF type.
languages(list of strings), optional, valid languages for literalsRDF data is often multilingual, but only some of the languages represented in the literal values can be mapped. You can do this by specifying a list of language ranges to be matched to the language tags of literals according to RFC 4647, Section 3.3.1. Basic Filtering. You can also use an empty range to include literals that have no language tag. The list of language ranges maps all existing literals that have matching language tags.
fields(list of field objects), required, defines the mapping from RDF to KafkaThe fields specify exactly which parts of each entity will be synchronized as well as the specific details on the connector side. The field is the smallest synchronization unit, and it maps a property chain from GraphDB to a field in Kafka. The fields are specified as a list of field objects. At least one field object is required. Each field object has further keys that specify details.
fieldName(string), required, the name of the field in KafkaThe name of the field defines the mapping on the connector side. It is specified by the key
fieldNamewith a string value. The field name is used as the key in the JSON document that will be sent to Kafka.
fieldNameTransform(one ofnone,predicate, orpredicate.localName), optional,noneby defaultDefines an optional transformation of the field name. Although
fieldNameis always required, it is ignored iffieldNameTransformispredicateorpredicate.localName.none: The field name is supplied via thefieldNameoption.predicate: The field name is equal to the full IRI of the last predicate of the chain. For example, if the last predicate washttp://www.w3.org/2000/01/rdf-schema#label, then the field name will also behttp://www.w3.org/2000/01/rdf-schema#label.predicate.localName: The field name is derived from the local name of the IRI of the last predicate of the chain. For example, if the last predicate washttp://www.w3.org/2000/01/rdf-schema#comment, then the field name will becomment.
See Indexing all literals in distinct fields for an example.
propertyChain(list of IRIs), required, defines the property chain to reach the valueThe property chain defines the mapping on the GraphDB side. A property chain is a sequence of triples where the entity IRI is the subject of the first triple, its object is the subject of the next triple, etc. In this model, a property chain with a single element corresponds to a direct property defined by a single triple. Property chains are specified as a list of IRIs where at least one IRI must be provided.
The IRI of the document will be synchronized as the key in the Kafka record.
See Copy fields for defining multiple fields with the same property chain.
See Multiple property chains per field for defining a field whose values are populated from more than one property chain.
See Indexing language tags for defining a field whose values are populated with the language tags of literals.
See Indexing the IRI of an entity for defining a field whose values are populated with the IRI of the indexed entity.
See Wildcard literal indexing for defining a field whose values are populated with literals regardless of their predicate.
valueFilter(string), optional, specifies the value filter for the fieldSee also Entity filtering.
documentFilter(string), optional, specifies the nested document filter for the fieldOnly for fields that define nested documents. See also Entity filtering.
defaultValue(string), optional, specifies a default value for the fieldProvides a means to specify a default value for the field when the property chain has no matching values in GraphDB. The default value can be a plain literal, a literal with a datatype (
xsd:prefix supported), a literal with language, or a IRI. It has no default value.
indexed(boolean), optional, defaulttrueIf indexed, a field will be included in the JSON document sent to Kafka.
trueby default.If
true, this option corresponds to"index" = true. Iffalse, it corresponds to"index" = false.
multivalued(boolean), optional, defaulttrueRDF properties and synchronized fields may have more than one value. If
multivaluedis set totrue, all values will be synchronized to Kafka. If set tofalse, only a single value will be synchronized.trueby default.
ignoreInvalidValues(boolean), optional, defaultfalsePer-field option that controls what happens when a value cannot be converted to the requested (or previously detected) type.
Falseby default.Example use: when an invalid date literal like
"2021-02-29"^^xsd:date(2021 is not a leap year) needs to be indexed as a date, or when an IRI needs to be indexed as a number.Note
Some conversions are always valid: any literal to an FTS field, any non-literal (IRI, blank node, embedded triple) to a non-analyzed field. When
true, such values will be skipped with a note in the logs. Whenfalse, such values will break the transaction.
array(boolean), optional, defaultfalseNormally, Kafka creates an array only if more than value is present for a given field. If
arrayis set totrue, Kafka will always create an array even for single values. If set tofalse, Kafka will create arrays for multiple values only.Falseby default.
datatype(string), optional, the manual datatype overrideBy default, the Kafka GraphDB Connector uses datatype of literal values to determine how they should be mapped to Kafka types. For more information on the supported datatypes, see Datatype mapping.
You can override the datatype mapping with the
"datatype"property, which can be specified per field. The value ofdatatypecan be any of thexsd:types supported by the automatic mapping or a native Kafka type prefixed bynative:, e.g., bothxsd:longandnative:longmap to the long type in Kafka.
objectFields(objects array), optional, nested object mappingWhen
native:objectis used as a datatype value, this provides a mapping for the nested object’s fields. Ifdatatypeis not provided, thennative:objectwill be assumed.Nested objects support further nested objects with a limit of five levels of nesting.
startFromParent(integer), optional, default0Start processing the property chain from the N-th parent instead of the root of the current nested object. 0 is the root of the current nested object, 1 is the parent of the nested object, 2 is the parent of the parent and so on.
valueFilter(string), optional, specifies the top-level value filter for the documentSee also Entity filtering.
documentFilter(string), optional, specifies the top-level document filter for the documentSee also Entity filtering.
Updating parameters at runtime¶
As mentioned above, you can update the following connection parameters at runtime with no need to rebuild the index:
kafkaNodekafkaSSLkafkaProducerConfigkafkaCompressionTypekafkaPlainAuthUsernamekafkaPlainAuthPasswordkafkaUseTransactionbulkUpdateBatchSizekafkaPropagateConfig
The following SPARQL update request demonstrates this by updating the user and password values:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:updateConnector '''
{
"kafkaPlainAuthUsername": "foo"
"kafkaPlainAuthPassword": "bar"
}
''' .
}
Special field definitions¶
Nested objects¶
Nested objects are JSON objects that are used as values in the main document or in other nested objects. Up to five levels of nesting is possible. They are defined with the objectFields option.
Let’s look at an example that uses the following data, which consists of children and grandchildren relations:
<urn:John>
a <urn:Person> ;
<urn:name> "John" ;
<urn:gender> <urn:Male> ;
<urn:age> 60 ;
<urn:hasSpouse> <urn:Mary> ;
<urn:hasChild> <urn:Billy> ;
<urn:hasChild> <urn:Annie> .
<urn:Mary>
a <urn:Person> ;
<urn:name> "Mary" ;
<urn:gender> <urn:Female> ;
<urn:age> 58 ;
<urn:hasSpouse> <urn:John> ;
<urn:hasChild> <urn:Billy> .
<urn:Eva>
a <urn:Person> ;
<urn:name> "Eva" ;
<urn:gender> <urn:Female> ;
<urn:age> 45 ;
<urn:hasChild> <urn:Annie> .
<urn:Billy>
a <urn:Person> ;
<urn:name> "Billy" ;
<urn:gender> <urn:Male> ;
<urn:age> 35 ;
<urn:hasChild> <urn:Tylor> ;
<urn:hasChild> <urn:Melody> .
<urn:Annie>
a <urn:Person> ;
<urn:name> "Annie" ;
<urn:gender> <urn:Female> ;
<urn:age> 28 ;
<urn:hasChild> <urn:Sammy> .
<urn:Tylor>
a <urn:Person> ;
<urn:name> "Tylor" ;
<urn:gender> <urn:Male> ;
<urn:age> 5 .
<urn:Melody>
a <urn:Person> ;
<urn:name> "Melody" ;
<urn:gender> <urn:Female> ;
<urn:age> 2 .
<urn:Sammy>
a <urn:Person> ;
<urn:name> "Sammy" ;
<urn:gender> <urn:Male> ;
<urn:age> 10 .
<urn:Male> <urn:label> "male" .
<urn:Female> <urn:label> "female" .
We can create a nested objects index that consists of children and grandchildren with their corresponding fields defining their gender and age:
{
"fields": [
{
"fieldName": "name",
"propertyChain": [
"urn:name"
]
},
{
"fieldName": "age",
"propertyChain": [
"urn:age"
],
"datatype": "xsd:long"
},
{
"fieldName": "hasSpouse",
"propertyChain": [
"urn:hasSpouse"
]
},
{
"fieldName": "gender",
"propertyChain": [
"urn:gender",
"urn:label"
]
},
{
"fieldName": "children",
"propertyChain": [
"urn:hasChild"
],
"datatype": "native:object",
"objectFields": [
{
"fieldName": "id",
"propertyChain": [
"$self"
]
},
{
"fieldName": "name",
"propertyChain": [
"urn:name"
]
},
{
"fieldName": "age",
"propertyChain": [
"urn:age"
],
"datatype": "xsd:long"
},
{
"fieldName": "gender",
"propertyChain": [
"urn:gender",
"urn:label"
]
},
{
"fieldName": "children",
"propertyChain": [
"urn:hasChild"
],
"objectFields": [
{
"fieldName": "id",
"propertyChain": [
"$self"
]
},
{
"fieldName": "name",
"propertyChain": [
"urn:name"
]
},
{
"fieldName": "age",
"propertyChain": [
"urn:age"
],
"datatype": "xsd:long"
}
]
}
]
},
{
"fieldName": "grandChildren",
"valueFilter": "$this -> type in (<urn:Person>)",
"propertyChain": [
"urn:hasChild",
"urn:hasChild"
],
"datatype": "native:object",
"objectFields": [
{
"fieldName": "id",
"propertyChain": [
"$self"
]
},
{
"fieldName": "name",
"propertyChain": [
"urn:name"
]
},
{
"fieldName": "age",
"propertyChain": [
"urn:age"
],
"datatype": "xsd:long"
},
{
"fieldName": "gender",
"propertyChain": [
"urn:gender",
"urn:label"
]
}
]
},
],
"types": [
"urn:Person"
],
"kafkaNode": ...,
"kafkaTopic": ...
}
Copy fields¶
It is often convenient to synchronize one and the same data multiple times with different settings to accommodate for different use cases. The Kafka GraphDB Connector has explicit support for fields that copy their value from another field. To do this, specify a single element in the property chain of the form @otherFieldName, where otherFieldName is another non-copy field. Take the following example:
...
"fields": [
{
"fieldName": "grape",
"facet": false,
"propertyChain": [
"http://www.ontotext.com/example/wine#madeFromGrape",
"http://www.w3.org/2000/01/rdf-schema#label"
]
},
{
"fieldName": "whiteGrape",
"propertyChain": [
"@grape"
]
}
],
"entityFilter": "?whiteGrape -> type = <wine:WhiteGrape>"
...
The snippet creates a field “grape” containing all grapes, and another field “whiteGrape”. Both fields are populated with the same values initially and “whiteGrape” is defined as a copy field that refers to the field “grape”. The field “whiteGrape” is additionally filtered so that only certain grape varieties will be synchronized.
Note
The connector handles copy fields in a more optimal way than specifying a field with exactly the same property chain as another field.
Multiple property chains per field¶
Sometimes, you have to work with data models that define the same concept (in terms of what you want to index in Kafka) with more than one property chain — for example, the concept of “name” could be defined as a single canonical name, multiple historical names and some unofficial names. If you want to index these together as a single field in Kafka, you can define this as a multiple property chains field.
Fields with multiple property chains are defined as a set of separate virtual fields that will be merged into a single physical field when indexed. Virtual fields are distinguished by the suffix $xyz, where xyz is any convenient alphanumeric sequence. For example, we can define the fields name$1 and name$2 like this:
...
"fields": [
{
"fieldName": "name$1",
"propertyChain": [
"http://www.example.com/demo#canonicalName"
],
"fieldName": "name$2",
"propertyChain": [
"http://www.example.com/demo#historicalName"
]
...
},
...
The values of the fields name$1 and name$2 will be merged and synchronized to the name field in Kafka.
Note
You cannot mix suffixed and unsuffixed fields with the same name. For example, if you defined myField$new and myField$old, you cannot have a field called just myField.
Filters and fields with multiple property chains¶
You can use filters with fields defined with multiple property chains. Both the physical field values and the individual virtual field values are available:
Physical fields are specified without the suffix (for example,
?myField)Virtual fields are specified with the suffix (for example,
?myField$2or?myField$alt)
Note
Physical fields cannot be combined with parent() as their values come from different property chains. If you really need to filter the same parent level, you can rewrite parent(?myField) in (<urn:x>, <urn:y>) as parent(?myField$1) in (<urn:x>, <urn:y>) || parent(?myField$2) in (<urn:x>, <urn:y>) || parent(?myField$3) ... and surround it with parentheses if it is a part of a bigger expression.
Indexing language tags¶
The language tag of an RDF literal can be indexed by specifying a property chain whose last element is the pseudo-IRI lang(). The property preceding lang() must lead to a literal value. For example:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:createConnector '''
{
"kafkaNode": "localhost:9092",
"kafkaTopic": "my_index",
"types": ["http://www.example.com/demo#gadget"],
"fields": [
{
"fieldName": "name",
"propertyChain": [
"http://www.example.com/demo#name"
]
},
{
"fieldName": "nameLanguage",
"propertyChain": [
"http://www.example.com/demo#name",
"lang()"
]
}
],
}
''' .
}
The above connector will index the language tag of each literal value of the property http://www.example.com/demo#name into the field nameLanguage.
Indexing named graphs¶
You can index a given value’s named graph by ending a property chain with the special pseudo-URI graph(). Indexing the named graph of the value instead of the value itself allows searching by named graph.
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:createConnector '''
{
"kafkaNode": "localhost:9092",
"kafkaTopic": "my_index",
"types": ["http://www.example.com/demo#gadget"],
"fields": [
{
"fieldName": "name",
"propertyChain": [
"http://www.example.com/demo#name"
]
},
{
"fieldName": "nameGraph",
"propertyChain": [
"http://www.example.com/demo#name",
"graph()"
]
}
],
}
''' .
}
The above connector will index the named graph of each value of the property http://www.example.com/demo#name into the field nameGraph.
Indexing local names¶
You can index the local name of a given IRI value by ending a property chain with the special pseudo-URI localName(). Indexing the local name instead of the full IRI is convenient when the local name is a human-readable meaningful string.
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:createConnector '''
{
"kafkaNode": "localhost:9092",
"kafkaTopic": "my_index",
"types": ["http://www.example.com/demo#gadget"],
"fields": [
{
"fieldName": "name",
"propertyChain": [
"http://www.example.com/demo#name"
]
},
{
"fieldName": "feature",
"propertyChain": [
"http://www.example.com/demo#feature",
"localName()"
]
}
],
}
''' .
}
The above connector will index the local name of each IRI value of the property http://www.example.com/demo#feature into the field feature.
Wildcard literal indexing¶
In this mode, the last element of a property chain is a wildcard that will match any predicate that leads to a literal value. Use the special pseudo-IRI $literal as the last element of the property chain to activate it.
Note
Currently, it really means any literal, including literals with data types.
For example:
{
"fields" : [ {
"propertyChain" : [ "$literal" ],
"fieldName" : "name"
}, {
"propertyChain" : [ "http://example.com/description", "$literal" ],
"fieldName" : "description"
}
...
}
See Indexing all literals for a detailed example.
Indexing the IRI of an entity¶
Sometimes you may need the IRI of each entity (for example, http://www.example.com/demo/wine#Franvino from our small example dataset) indexed as a regular field. Do this by specifying a property chain with a single property referring to the pseudo-IRI $self. For example:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:createConnector '''
{
"kafkaNode": "localhost:9092",
"kafkaTopic": "my_index",
"types": [
"http://www.example.com/demo/wine#Wine"
],
"fields": [
{
"fieldName": "entityId",
"propertyChain": [
"$self"
],
},
{
"fieldName": "grape",
"propertyChain": [
"http://www.example.com/demo/wine#madeFromGrape",
"http://www.w3.org/2000/01/rdf-schema#label"
]
},
]
}
''' .
}
The above connector will index the IRI of each wine into the field entityId.
Note
Note that GraphDB will also use the IRI of each entity as the ID of each document in Kafka, which is represented by the field id.
Datatype mapping¶
The Kafka GraphDB Connector maps different types of RDF values to different types of Kafka values according to the basic type of the RDF value (IRI or literal) and the datatype of literals. The auto-detection uses the following mapping:
RDF value |
RDF datatype |
JSON type |
|---|---|---|
IRI |
n/a |
|
literal |
any type not explicitly mentioned below |
|
literal |
|
|
literal |
|
|
literal |
|
|
literal |
|
|
literal |
|
|
literal |
|
|
literal |
|
|
literal |
|
|
literal |
|
|
literal |
|
|
Note
For any given field, the automatic mapping uses the first value it sees. This works fine for clean datasets but might lead to problems if your dataset has non-normalized data — for example, the first value has no datatype but other values do.
Because of this, it’s best to set the datatype to a fixed value such as xsd:date.
Please note that the commonly used xsd:integer and xsd:decimal datatypes are not indexed as numbers because they represent infinite precision numbers. You can override that by using the datatype option to cast them to xsd:long, xsd:double, or xsd:float as appropriate.
Date and time conversion¶
RDF and ISO use slightly different models for representing dates and times, even though the values might look very similar.
Years in RDF values use the XSD format and are era years, where positive values denote the common era and negative values denote years before the common era. There is no year zero.
Years in the ISO format are proleptic years: positive values denote years from the common era with any previous eras just going down by one mathematically so there is year zero.
In short:
year 2020 CE = year 2020 in XSD = year 2020 in ISO.
…
year 1 CE = year 1 in XSD = year 1 in ISO.
year 1 BCE = year -1 in XSD = year 0 in ISO.
year 2 BCE = year -2 in XSD = year -1 in ISO.
…
All years coming from RDF literals will be converted to ISO before sending to Kafka.
Both XSD and ISO date and time values support timezones. In addition to that, XSD defines the lack of a timezone as undetermined. Since we do not want to have any undetermined state in the indexing system, GraphDB defines the undetermined time zone as UTC. For example, "2020-02-14T12:00:00"^^xsd:dateTime is equivalent to "2020-02-14T12:00:00Z"^^xsd:dateTime (Z is the UTC timezone, also known as +00:00).
Also note that XSD dates and partial dates, e.g., xsd:gYear values, may have a timezone, which leads to additional complications. For example, "2020+02:00"^^xsd:gYear (the year 2020 in the +02:00 timezone) will be normalized to 2019-12-31T22:00:00Z (the previous year!) if GraphDB followed strict timezone adherence. We have chosen to ignore the timezone on any values that do not have an associated time value. For example, all of the following values will be treated as if they specified UTC as their timezone:
"2020-02-15+02:00"^^xsd:date"2020-02+02:00"^^xsd:gYearMonth"2020+02:00"^^xsd:gYear
Entity filtering¶
The Kafka connector supports four kinds of entity filters used to fine-tune the set of entities and individual values for the configured fields, based on the field value. Entities and field values are synchronized to Kafka if, and only if, they pass the filter. The filters are similar to a SPARQL query’s FILTER() but not exactly the same. You can reference each configured field in them by prefixing its name with a ?, much like referencing a SPARQL variable.
Types of filters¶
- Top-level value filter
The top-level value filter is specified with the
valueFilterparameter. It is evaluated prior to anything else when only the document ID is known, and it may not refer to any field names but only to the special field$thisthat contains the current document ID. Failing to pass this filter removes the entire document early in the indexing process. It can be used to introduce more restrictions similar to the built-in filtering by type via thetypesproperty.- Top-level document filter
The top-level document filter is specified with the
documentFilterparameter. This filter is evaluated last when all of the document has been collected and it decides whether to include the document in the index. You can use it to enforce global document restrictions such as that certain fields are required or a document needs to be indexed only if a certain field value meets specific conditions.- Per-field value filter
The per-field value filter is specified with the
valueFilterparameter inside the field definition of the field whose values are to be filtered. The filter is evaluated while collecting the data for the field when each field value becomes available.The variable that contains the field value is
$this. You can use other field names to filter the current field’s value based on the value of another field. For example,$this > ?agewill compare the current field value to the value of the field age (see also Two-variable filtering). Failing to pass the filter will remove the current field value.On nested documents, the per-field value filter can be used to remove the entire nested document early in the indexing process, e.g., by checking the type of the nested document via next hop with
rdf:type.- Nested document filter
The nested document filter is specified with the
documentFilterparameter inside the field definition of the field that defines the root of a nested document. The filter is evaluated after the entire nested document has been collected. Failing to pass this filter removes the entire nested document.Inside a nested document filter, the field names are within the context of the nested document and not within the context of the top-level document. For example, if you have a
childrenfield that defines a nested document and you use a filter like?age < "10"^^xsd:int, it will refer to the fieldchildren.age. You can use the prefix$outer.one or more times to refer to field values from the outer document from the viewpoint of the nested document. For example,$outer.age > "25"^^xsd:intwill refer to theagefield that is a sibling of thechildrenfield.Other than the above differences, the nested document filter is equivalent to the top-level document filter from the viewpoint of the nested document.
See also Migrating from GraphDB 9.x.
Filter operators¶
The filter operators test whether a given field’s value satisfies a certain condition.
Field comparisons are done on original RDF values before they are converted to Kafka values using datatype mapping.
Operator |
Meaning |
|---|---|
|
Tests if the Example:
?status in ("active", "new") |
|
The negated version of the Example:
?status not in ("archived") |
|
Tests if the field Example:
bound(?name) |
|
Tests if the Example:
isExplicit(?name) |
?var = value (equal to)?var != value (not equal to)?var > value (greater than)?var >= value (greater than or
equal to)?var < value (less than)?var <= value (less than or
equal to) |
RDF value comparison operators that compare RDF values similarly to the equivalent SPARQL operators.
The
var field’s value will be compared to the specified RDF value. When comparing RDF values that are
literals, their datatypes must be compatible such as xsd:integer and xsd:long but not xsd:string
and xsd:date. Values that do not match are treated as if they were not present in the repository.Examples:
Given that
height has a value of "150"^^xsd:int and dateOfBirth has avalue of
"1989-12-31"^^xsd:date, then:?height = "150"^^xsd:int is true?height = "150"^^xsd:long is true?height = "150" is false?height != "151"^^xsd:int is true?height != "150" is true?height > "150"^^xsd:int is false?height >= "150"^^xsd:int is true?dateOfBirth < "1990-01-01"^^xsd:date is true |
|
Tests if the
var field’s value matches the given regular expression pattern.The “i” flag tells the function to ignore case when matching. Values that do
not match are treated as if they were not present in the repository.
Example:
regex(?name, "^mrs?", "i") |
|
Logical disjunction of expressions Examples:
bound(?name) || bound(?company)bound(?name) or bound(?company) |
|
Logical conjunction of expressions Examples:
bound(?status) && ?status in ("active", "new")bound(?status) and ?status in ("active", "new") |
|
Logical negation of expression Example:
!bound(?company) |
|
Grouping of expressions Example:
(bound(?name) or bound(?company)) && bound(?address) |
Filter modifiers¶
Along with the filter operators, additional constructions let you write filters based not on the values of a field but on values related to them.
- Accessing the previous element in the chain
The construction
parent(?var)goes to a previous level in a property chain. You can apply it recursively as many times as as needed; for example,parent(parent(parent(?var)))goes back in the chain three times. You can use the effective value ofparent(?var)with theinornot inoperators (for example,parent(?company) in (<urn:a>, <urn:b>), or in theboundoperator (for example,parent(bound(?var))).
- Accessing an element beyond the chain
The construction
?var -> uri(alternatively,?var o urior just?var uri) accesses additional values through theuriproperty. This construction roughly corresponds to the triple patternvalueuri?effectiveValue, where?valueis a value bound by thevarfield. You can use the effective value of?var -> uriwith theinornot inoperator like this:?company -> rdf:type in (<urn:c>, <urn:d>). You can combine it withparent()like this:parent(?company) -> rdf:type in (<urn:c>, <urn:d>). You can apply the same construction to theboundoperator like this:bound(?company -> <urn:hasBranch>), or you can combine it withparent()like this:bound(parent(?company) -> <urn:hasGroup>).The IRI parameter can be a full IRI within
< >or the special stringrdf:type(or justtype), which will be expanded tohttp://www.w3.org/1999/02/22-rdf-syntax-ns#type.
- Filtering by RDF graph
The construction
graph(?var)gives you access to the RDF graph of a field’s value. A typical use case is to sync only explicit values:graph(?a) not in (<http://www.ontotext.com/implicit>)but usingisExplicit(?a)is the recommended way.You can combine this construction with the
parent()function like this:graph(parent(?a)) in (<urn:a>).
- Filtering by language tags
The construction
lang(?var)gives you access to the language tag of field’s value. (Only RDF literals can have a language tag.) The typical use case is to sync only values written in a given language:lang(?a) in ("de", "it", "no"). You can combine this construction with theparent()function and an element beyond the chain like this:lang(parent(?a) -> <http://www.w3.org/2000/01/rdf-schema#label>) in ("en", "bg"). You can filter literal values without language tags by using an empty tag:"".
- Current context variable
$this You can use the special field variable
$this(and not?this,?$this, or$?this) to refer to the current context. In the top-level value filter and the top-level document filter, it refers to the document. In the per-field value filter, it refers to the currently filtered field value. In the nested document filter, it refers to the nested document.
ALL()quantifierIn the context of document-level filtering, a match is
trueif at least one of potentially many field values match. For example,?location = <urn:Europe>would returntrueif the document contains{ "location": ["<urn:Asia>", "<urn:Europe>"] }.You can also use the
ALL()quantifier when you need all values to match. For example,ALL(?location) = <urn:Europe>would not match with the above document because<urn:Asia>does not match.
- Entity filters and default values
You can combine entity filters with default values for more flexible behavior.
If a field has no values in the RDF database, the
defaultValueis used. But if a field has some values,defaultValueis NOT used, even if all values are filtered out. See an example in Basic entity filter.A typical use case for an entity filter is having soft deletes — instead of deleting an entity, it is marked as deleted by the presence of a specific value for a given property.
Two-variable filtering¶
Besides comparing a field value to one or more constants, or running an existential check on the field value, some use cases also require the ability to compare the field value with the value of another field in order to produce the desired result. GraphDB solves this by supporting two-variable filtering in the per-field value filter, the top-level document filter, and the nested document filter.
Note
This type of filtering is not possible in the top-level value filter because the only variable that is available there is $this.
In the top-level document filter and the nested document filter, there are no restrictions because all values are available at the time of evaluation.
In the per-field value filter, two-variable filtering will reorder the defined fields such that values for other fields are already available when the current field’s filter is evaluated. For example, let’s say we defined a filter $this > ?salary for the field price. This will force the connector to process the field salary first, apply its per-field value filter if any, and only then start collecting and filtering the values for the field price.
Cyclic dependencies will be detected and reported as an invalid filter. For example, if in addition to the above we define a per-field value filter ?price > "1000"^^xsd:int for the field salary, a cyclic dependency will be detected as both price and salary will require the other field to be indexed first.
Basic entity filter example¶
Let’s look at an example that uses the following RDF data.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix example: <http://www.example.com/demo#> .
# the entity below will be synchronised because it has a matching value for city: ?city in ("London")
example:alpha
rdf:type example:gadget ;
example:name "John Synced" ;
example:city "London" .
# the entity below will not be synchronised because it lacks the property completely: bound(?city)
example:beta
rdf:type example:gadget ;
example:name "Peter Syncfree" .
# the entity below will not be synchronized because it has a different city value:
# ?city in ("London") will remove the value "Liverpool" so bound(?city) will be false
example:gamma
rdf:type example:gadget ;
example:name "Mary Syncless" ;
example:city "Liverpool" .
If you create a connector instance such as:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:createConnector '''
{
"kafkaNode": "localhost:9092",
"kafkaTopic": "my_index",
"types": ["http://www.example.com/demo#gadget"],
"fields": [
{
"fieldName": "name",
"propertyChain": ["http://www.example.com/demo#name"]
},
{
"fieldName": "city",
"propertyChain": ["http://www.example.com/demo#city"],
"valueFilter": "$this = \\"London\\""
}
],
"documentFilter": "bound(?city)"
}
''' .
}
The :beta entity is not synchronized because it has no value for city.
To handle such cases, you can modify the connector configuration to specify a default value for city:
...
{
"fieldName": "city",
"propertyChain": ["http://www.example.com/demo#city"],
"defaultValue": "London"
}
...
}
The default value is used for the entity :beta as it has no value for city in the repository. As the value is “London”, the entity is synchronized.
Advanced entity filter example¶
Sometimes, data represented in RDF does not map directly to a non-RDF format. For example, if you have news articles and they can be tagged with different concepts such as locations, persons, and events, one possible way to model this is a single property :taggedWith. Consider the following RDF data:
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix example2: <http://www.example.com/demo2#> .
example2:Berlin
rdf:type example2:Location ;
rdfs:label "Berlin" .
example2:Mozart
rdf:type example2:Person ;
rdfs:label "Wolfgang Amadeus Mozart" .
example2:Einstein
rdf:type example2:Person ;
rdfs:label "Albert Einstein" .
example2:Cannes-FF
rdf:type example2:Event ;
rdfs:label "Cannes Film Festival" .
example2:Article1
rdf:type example2:Article ;
rdfs:comment "An article about a film about Einstein's life while he was a professor in Berlin." ;
example2:taggedWith example2:Berlin ;
example2:taggedWith example2:Einstein ;
example2:taggedWith example2:Cannes-FF .
example2:Article2
rdf:type example2:Article ;
rdfs:comment "An article about Berlin." ;
example2:taggedWith example2:Berlin .
example2:Article3
rdf:type example2:Article ;
rdfs:comment "An article about Mozart's life." ;
example2:taggedWith example2:Mozart .
example2:Article4
rdf:type example2:Article ;
rdfs:comment "An article about classical music in Berlin." ;
example2:taggedWith example2:Berlin ;
example2:taggedWith example2:Mozart .
example2:Article5
rdf:type example2:Article ;
rdfs:comment "A boring article that has no tags." .
example2:Article6
rdf:type example2:Article ;
rdfs:comment "An article about the Cannes Film Festival in 2013." ;
example2:taggedWith example2:Cannes-FF .
Assume you want to map this data to Kafka, so that the property example2:taggedWith x is mapped to separate fields taggedWithPerson and taggedWithLocation, according to the type of x (where we are not interested in Events). You can map taggedWith twice to different fields and then use an entity filter to get the desired values:
PREFIX kafka: <http://www.ontotext.com/connectors/kafka#>
PREFIX kafka-inst: <http://www.ontotext.com/connectors/kafka/instance#>
INSERT DATA {
kafka-inst:my_index kafka:createConnector '''
{
"kafkaNode": "localhost:9092",
"kafkaTopic": "my_index",
"types": ["http://www.example.com/demo2#Article"],
"fields": [
{
"fieldName": "comment",
"propertyChain": ["http://www.w3.org/2000/01/rdf-schema#comment"]
},
{
"fieldName": "taggedWithPerson",
"propertyChain": ["http://www.example.com/demo2#taggedWith"],
"valueFilter": "$this -> type = <http://www.example.com/demo2#Person>"
},
{
"fieldName": "taggedWithLocation",
"propertyChain": ["http://www.example.com/demo2#taggedWith"],
"valueFilter": "$this -> type = <http://www.example.com/demo2#Location>"
}
]
}
''' .
}
Note
type is the short way to write <http://www.w3.org/1999/02/22-rdf-syntax-ns#type>.
The six articles in the RDF data above will be mapped as such:
Article IRI |
Value in taggedWithPerson |
Value in taggedWithLocation |
Explanation |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
Overview of connector predicates¶
The following diagram shows a summary of all predicates that can administer (create, drop, check the status of) connector instances or issue queries and retrieve results. It can be used as a quick reference of what a particular predicate needs to be attached to.
Variables that are bound as a result of a query are shown in green, blank helper nodes are shown in blue, literals in red, and IRIs in orange. The predicates are represented by labeled arrows.
Caveats¶
Producer sharing¶
The Kafka connector aims to minimize resource usage and provide smooth transactional operation. It does this by using a single Kafka producer object for each connector instance that is connected to the same Kafka broker node. This has the following benefits:
It uses less memory because each Kafka producer requires a certain amount of buffer memory.
A failed transaction in one Kafka connector instance will be reverted in all other Kafka connector instances together with the GraphDB transaction.
Due to the nature of Kafka producers it imposes a restriction as well:
All connector instances must use the same Kafka options. For example, they must have the same values for the
bulkUpdateBatchSizeandkafkaCompressionTypeoptions.
Once you have created at least one Kafka connector instance and attempted to create another instance, the following are possible scenarios:
- Different Kafka broker
The new connector instance specifies a different Kafka broker.
The connector instance will be created and a new Kafka producer will be instantiated.
- Same Kafka broker + same Kafka options
The new connector instance specifies the same Kafka broker as one of the existing connectors and the same options as the existing connector.
The connector instance will be created and the existing Kafka producer will be reused.
- Same Kafka broker + different Kafka options
The new connector instance specifies the same Kafka broker as one of the existing connectors and different options than the existing connector.
The connector instance will not be created and an error explaining the reason will be thrown.
See Conflict resolution for possible workarounds.
Note
The Kafka broker for two connector instances is considered to be the same if at least one of the host-port pairs supplied via the kafkaNode option is the same.
Conflict resolution¶
If the attempt to create a new Kafka connector instance was denied because another instance was already created with different options, there are several possible ways to resolve the conflict:
- Manual resolution
Examine the options of the new connector instance you want to create.
Make the options the same as of the existing connector instance.
- Propagate the new options to the existing instances
Set the option kafkaPropagateConfig of the new instance to
true.The new options will be propagated to all existing instances that share the same Kafka broker node.
- Force the allocation of a new producer
Set the option kafkaProducerId of the new instance to some non-empty identifier.
This will override the producer sharing mechanism and allocate a new producer associating it with the supplied producer ID.
The new connector will use the new options.
All existing instances will continue using their previous options.
Upgrading from previous versions¶
Migrating from GraphDB 9.x¶
GraphDB 10.0 introduces major changes to the filtering mechanism of the connectors. Existing connector instances will not be usable and attempting to use them for queries or updates will throw an error.
If your GraphDB 9.x (or older) connector definitions do not include an entity filter, you can simply repair them.
If your GraphDB 9.x (or older) connector definitions do include an entity filter with the entityFilter option, you need to rewrite the filter with one of the current filter types:
Save your existing connector definition.
Drop the connector instance.
In general, most older connector filters can be easily rewritten using the per-field value filter and top-level document filter. Rewrite the filters as follows.
Rule of thumb:
If you want to remove individual values, i.e., if the operand is not
BOUND()–> rewrite with per-field value filter.If you want to remove entire documents, i.e., if the operand is
BOUND()–> rewrite with top-level document filter.
So if we take the example:
?location = <urn:Europe> AND BOUND(?location) AND ?type IN (<urn:Foo>, <urn:Bar>)
It needs to be rewritten like this:
Per-field rule on field
location:$this = <urn:Europe>Per-field rule on field
type:$this IN (<urn:Foo>, <urn:Bar>)Top-level document filter:
BOUND(?location)
Recreate the connector instance using the new definition.