iOS 12.2 or higher / macOS 10.14 or higher / Ubuntu Linux 16.04 or higher
Xcode 10.2.1 or newer for iOS or macOS
Swift 5.0
Feedback
Because this framework is open source it is best for most situations to post on Stack Overflow and tag it Theo. If you do
find a bug please file an issue or issue a PR for any features or fixes.
You are also most welcome to join the conversation in the #neo4j-swift channel in the neo4j-users Slack
Installation
You can install Theo in a number of ways
Swift Package Manager
Add the following line to your Package dependencies array:
Run swift build to build your project, now with Theo included and ready to be used from your source
CococaPods
Add the following to your Podfile:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, ‘12.2’
use_frameworks!
target '<Your Target Name>' do
pod ‘Theo’
end
Run pod install to configure your updated workspace. Open the .xcworkspace generated, your project is now ready to use Theo
git submodule
Add it as a submodule to your existing project. git submodule add git@github.com:Neo4j-Swift/Neo4j-Swift.git
Through Terminal, navigate to the submodule directory and run swift package fetch. Theo has other dependencies and they need to be fetched.
Open the Theo folder, and drag Theo.xcodeproj into the file navigator of your Xcode project.
In Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the “Targets” heading in the sidebar.
In the tab bar at the top of that window, open the “Build Phases” panel.
Expand the “Link Binary with Libraries” group, Copy Frameworks and add Theo.framework, Bolt.framework, SSLService.framework, Socket.framework, PacketStream.framework.
Click on the + button at the top left of the panel and select “New Copy Files Phase”. Rename this new phase to “Copy Frameworks”, set the “Destination” to “Frameworks”, and add the frameworks.
Usage
If you prefer just code-examples to get started, check out theo-example that is updated to match the current version of Theo.
Initalization
To get started, you need to set up a BoltClient with the connection information to your Neo4j instance. You could for instance load a JSON into a dictionary, and then pass any values that should overrid the defaults, like this:
let config = ["password": "<passcode>"]
let client = try BoltClient(JSONClientConfiguration(json: config))
Or you can provide your on ClientConfiguration-based class, or even set them all manually:
// Create the node
let node = Node(label: "Character", properties: ["name": "Thomas Anderson", "alias": "Neo" ])
// Save the node
let result = client.createNodeSync(node: node)
// Verify the result of the save
switch result {
case let .failure(error):
print(error.localizedDescription)
case .success(_):
print("Node saved successfully")
}
createNodeSync() above has an async sibling createNode(), and they in turn have siblings that return the created node: createAndReturnNode() and creaetAndReturnNodeSync(). Finally, multiple nodes can be created at the same time, giving you the functions createNodes(), createNodesSync(), createAndReturnNodes() and createAndReturnNodesSync() to choose between.
Fetch a node via id
client.nodeBy(id: 42) { result in
switch result {
case let .failure(error):
print(error.localizedDescription)
case let .success(foundNode):
if let foundNode = foundNode {
print("Successfully found node \(foundNode)")
} else {
print("There was no node with id 42")
}
}
}
So, finding the node with id 42 is easy, but there is a little routine work in handling that there could be an error with connecting to the database, or there might not be a node with id 42.
Living dangerously and ignoring both error scenarios would look like this:
client.nodeBy(id: 42) { result in
let foundNode = result.value!!
print("Successfully found node \(foundNode)")
}
Updating a node
Given the variable ‘node’ with an existing node, we might want to update it. Let’s add a label:
node.add(label: "AnotherLabel")
or add a few properties:
node["age"] = 42
node["color"] = "white"
and then
let result = client.updateNodeSync(node: node)
switch result {
case let .failure(error):
print(error.localizedDescription)
case .success(_):
print("Node updated successfully")
}
Deleting a node
Likewise, given the variable ‘node’ with an existing node, when we no longer want the data,
we might want to delete it all together:
let result = client.deleteNodeSync(node: node)
switch result {
case let .failure(error):
print(error.localizedDescription)
case .success(_):
print("Node deleted successfully")
}
Note that in Neo4j, to delete a node all relationships this node participates in should be deleted first. However, you can force a delete by calling “DETACH DELETE”, and it will then remove all the relationships the node participates in as well. Since this is an exception to the rule, there is no helper function for this. But with Theo, running an arbitrary Cypher statement is easy:
guard let id = node.id else { return }
let query = """
MATCH (n) WHERE id(n) = {id} DETACH DELETE n
"""
if client.executeCypherSync(query, params: [ "id": Int64(id)] ).isSuccess {
print("Node deleted successfully")
} else {
print("Something went wrong while deleting the node")
}
Fetch nodes matching a labels and property values
let labels = ["Father", "Husband"]
let properties: [String:PackProtocol] = [
"firstName": "Niklas",
"age": 38
]
client.nodesWith(labels: labels, andProperties: properties) { result in
print("Found \(result.value?.count ?? 0) nodes")
}
Create a relationship
Given two nodes reader and writer, making a relationship with the type “follows” is easy as
let result = client.relateSync(node: reader, to: writer, type: "follows")
if result.isSuccess {
print("Relationship successfully created")
}
Again, there is an async version of relateSync() called relate() that takes the same parameters and a callback block with the same result as relateSync returned
You can also make a relationship directly and create that:
let relationship = Relationship(fromNode: from, toNode: to, type: "Married to")
client.createAndReturnRelationship(relationship: relationship) { result in
switch result {
case let .failure(error):
print(error.localizedDescription)
case let .success(relationship):
print("Successfully created relationship \(relationship)")
}
}
Do note that if one or both of the nodes in a relationship have not been created in advance, they will be created together with the relationship
Updating properties on a relationship
Having fetched a relationship as part of a query, you can now edit properties on that relationship:
relationship["someKey"] = "someValue"
relationship["otherKey"] = 42
let result = client.updateAndReturnRelationshipSync(relationship: relationship)
switch result {
case let .failure(error):
print(error.localizedDescription)
case let .success(relationship):
print("Successfully updated relationship \(relationship)")
}
Deleting a relationship
And finally, you can remove the relationship alltogether:
let result = client.deleteRelationshipSync(relationship: relationship)
switch result {
case let .failure(error):
print(error.localizedDescription)
case .success(_):
print("Successfully deleted the relationship")
}
Execute a transaction
It is easy to make a transaction, and to roll it back if you are not happy with its results. Simply call executeAsTransaction() and pass in a block. This block has a parameter, tx in the example below, where you can invalidate the transaction at any point. If it has not been invalidated, it is considered successful and committed at the end of the transaction block.
try client.executeAsTransaction() { tx in
client.executeCypherSync("MATCH (n) SET n.abra = \"kadabra\"")
client.executeCypherSync("MATCH (n:Person) WHERE n.name = 'Guy' SET n.likeable = true")
let finalResult = client.executeCypherSync("MATCH (n:Person) WHERE n.name = 'Guy' AND n.abra='kadabra' SET n.starRating = 5")
if (finalResult.value?.stats.propertiesSetCount ?? 0) == 0 {
tx.markAsFailed()
}
}
Execute a cypher query
In the example above, we already executed a few cypher queries. In the following example, we execute a longer cypher example with named parameters, where we’ll supply the parameters along side the query:
let query = """
MATCH (u:User {username: {user} }) WITH u
MATCH (u)-[:FOLLOWS*0..1]->(f) WITH DISTINCT f,u
MATCH (f)-[:LASTPOST]-(lp)-[:NEXTPOST*0..3]-(p)
RETURN p.contentId as contentId, p.title as title, p.tagstr as tagstr, p.timestamp as timestamp, p.url as url, f.username as username, f=u as owner
"""
let params: [String:PackProtocol] = ["user": "ajordan"]
let result = client.executeCypherSync(query, params: params)
if result.isSuccess {
print("Successfully ran query")
} else {
print("Got an error")
}
Integration Tests
Setup
There is a file called, TheoBoltConfig.json.example which you should copy to TheoBoltConfig.json. You can edit this configuration with connection settings to your Neo4j instance, and the test classes using these instead of having to modify any actual class files. TheoBoltConfig.json is in the .gitignore so you don’t have to worry about creds being committed.
Theo
Thomas Anderson is a computer programmer who maintains a double life as “Neo” the hacker. - Combination of Neo and Thomas
Summary
Theo is an open-source Neo4j Swift interface.
Features
Requirements
Feedback
Because this framework is open source it is best for most situations to post on Stack Overflow and tag it Theo. If you do find a bug please file an issue or issue a PR for any features or fixes. You are also most welcome to join the conversation in the #neo4j-swift channel in the neo4j-users Slack
Installation
You can install Theo in a number of ways
Swift Package Manager
Add the following line to your Package dependencies array:
Run
swift build
to build your project, now with Theo included and ready to be used from your sourceCococaPods
Add the following to your Podfile:
Run
pod install
to configure your updated workspace. Open the .xcworkspace generated, your project is now ready to use Theogit submodule
git submodule add git@github.com:Neo4j-Swift/Neo4j-Swift.git
swift package fetch
. Theo has other dependencies and they need to be fetched.Usage
If you prefer just code-examples to get started, check out theo-example that is updated to match the current version of Theo.
Initalization
To get started, you need to set up a BoltClient with the connection information to your Neo4j instance. You could for instance load a JSON into a dictionary, and then pass any values that should overrid the defaults, like this:
Or you can provide your on ClientConfiguration-based class, or even set them all manually:
Create and save a node
createNodeSync() above has an async sibling createNode(), and they in turn have siblings that return the created node: createAndReturnNode() and creaetAndReturnNodeSync(). Finally, multiple nodes can be created at the same time, giving you the functions createNodes(), createNodesSync(), createAndReturnNodes() and createAndReturnNodesSync() to choose between.
Fetch a node via id
So, finding the node with id 42 is easy, but there is a little routine work in handling that there could be an error with connecting to the database, or there might not be a node with id 42.
Living dangerously and ignoring both error scenarios would look like this:
Updating a node
Given the variable ‘node’ with an existing node, we might want to update it. Let’s add a label:
or add a few properties:
and then
Deleting a node
Likewise, given the variable ‘node’ with an existing node, when we no longer want the data, we might want to delete it all together:
Note that in Neo4j, to delete a node all relationships this node participates in should be deleted first. However, you can force a delete by calling “DETACH DELETE”, and it will then remove all the relationships the node participates in as well. Since this is an exception to the rule, there is no helper function for this. But with Theo, running an arbitrary Cypher statement is easy:
Fetch nodes matching a labels and property values
Create a relationship
Given two nodes reader and writer, making a relationship with the type “follows” is easy as
Again, there is an async version of relateSync() called relate() that takes the same parameters and a callback block with the same result as relateSync returned
You can also make a relationship directly and create that:
Do note that if one or both of the nodes in a relationship have not been created in advance, they will be created together with the relationship
Updating properties on a relationship
Having fetched a relationship as part of a query, you can now edit properties on that relationship:
Deleting a relationship
And finally, you can remove the relationship alltogether:
Execute a transaction
It is easy to make a transaction, and to roll it back if you are not happy with its results. Simply call executeAsTransaction() and pass in a block. This block has a parameter, tx in the example below, where you can invalidate the transaction at any point. If it has not been invalidated, it is considered successful and committed at the end of the transaction block.
Execute a cypher query
In the example above, we already executed a few cypher queries. In the following example, we execute a longer cypher example with named parameters, where we’ll supply the parameters along side the query:
Integration Tests
Setup
There is a file called,
TheoBoltConfig.json.example
which you should copy toTheoBoltConfig.json
. You can edit this configuration with connection settings to your Neo4j instance, and the test classes using these instead of having to modify any actual class files.TheoBoltConfig.json
is in the.gitignore
so you don’t have to worry about creds being committed.Execution
CMD-U
Authors
Special thanks to