In the mid-1990s, Tcl was my go-to dynamic programming language as it was cross-platform (you may recall the plethora of UNIX variants), easily deployed and readily extended. Although many other languages now carry favor (e.g., Python, JavaScript, Go, etc.), the Tcl community remains active, and v9.0 was recently released.
I was introduced to the world of relational databases via Tcl’s built-in SQLite extension, and with this article I’ll demonstrate how we can use Tcl’s HTTP functionality to query a graph database via the Neo4j Query API.
Tcl Extensions
In order to get the following examples to work, the base Tcl environment needs to be extended to include tcllib and tcl-tls. The following packages are required to make/secure HTTP requests and handle the JSON payload.
package require http
package require tls
package require json::write
package require json
HTTP(S) Request in Tcl
HTTP is bound to OpenSSL to encrypt the sessions for HTTPS:
http::register https 443 [list tls::socket -autoservername true]
The Neo4j Query API URL contains two components that define the NEO4J_SERVER_HOSTNAME and NEO4J_DATABASE_NAME. These, along with NEO4J_USERNAME and NEO4J_PASSWORD (for authentication), need to be substituted with appropriate values from your Neo4j database setup (check out this article to learn how to create a free Neo4j Aura instance).
CYPHER_QUERY defines the graph database query.
These are specified below with the header dictionary encoding the authorization and the Cypher query converted to JSON as json_query:
set url "https://NEO4J_SERVER_HOSTNAME/db/NEO4J_DATABASE_NAME/query/v2"
dict set header Authorization "Basic [binary encode base64 NEO4J_USERNAME:NEO4J_PASSWORD]"
set json_query [json::write object statement [json::write string CYPHER_QUERY]]
Perform the query and capture the response token:
set token [http::geturl $url -headers $header -query $json_query -type "application/json"]
Transform JSON response to the lists — headers and rows:
set json_response [json::json2dict [http::data $token]]
set data [dict get $json_response data]
set headers [dict get $data fields]
set rows [dict get $data values]
Query Procedure
We can combine the snippets above to yield a procedure to perform the Cypher query and return the result as a dictionary:
proc neo4j_query {cypher db} {
# Define URL
set url "https://[dict get $db server]/db/[dict get $db database]/query/v2"
# Create Authorization header
dict set header Authorization "Basic [binary encode base64 [dict get $db username]:[dict get $db password]]"
# Create JSON payload with cypher query
set json_query [json::write object statement [json::write string $cypher]]
# Perfom request
set token [http::geturl $url -headers $header -query $json_query -type "application/json"]
# Convert JSON response to dictionary
set json_response [json::json2dict [http::data $token]]
# Return 'data' element
return [dict get $json_response data]
}
We create a dictionary (neo4j) to hold the Neo4j settings and use this with the Cypher query string (cypher) to call the neo4j_query procedure:
dict set neo4j server NEO4J_SERVER_HOSTNAME
dict set neo4j username NEO4J_USERNAME
dict set neo4j password NEO4J_PASSWORD
dict set neo4j database NEO4J_DATABASE_NAME
set cypher {CYPHER_QUERY}
set data [neo4j_query $cypher $neo4j]
The returned dictionary (data) contains two keys:
- values: a list of lists (rows) with the query results
- fields: a list of column names (headers)
Please take care with this simple example. There is a lack of error-handling for Cypher query response or with the HTTPS connection.
Example
We can run a query against the Northwind database hosted on a Neo4j demo server. The query identifies all products in the ‘Dairy Products’ category:
dict set neo4j server demo.neo4jlabs.com:7473
dict set neo4j username northwind
dict set neo4j password northwind
dict set neo4j database northwind
set cypher {MATCH (p:Product)-[:PART_OF]->(:Category)-[:PARENT*0..]->(:Category {categoryName: "Dairy Products"}) RETURN p.productName as product}
set data [neo4j_query $cypher $neo4j]
puts "Headers: [dict get $data fields]"
puts "Values: [dict get $data values]"
Output from query:
$ tclsh northwind.tcl
Headers: product
Values: Geitost {{Raclette Courdavault}} {{Camembert Pierrot}} {{Mozzarella di Giovanni}} Flotemysost Gudbrandsdalsost {{Mascarpone Fabioli}} {{Gorgonzola Telino}} {{Queso Manchego La Pastora}} {{Queso Cabrales}}
Summary
We’ve demonstrated how the Neo4j Query API can bring graph databases to the venerable Tcl. There are many other features of the Neo4j Query API to be explored (query parameterization, explicit transactions, user impersonation, and token-based authentication). To learn more, check out the Query API documentation. You can also learn about moving to the Neo4j Query API and using the Neo4j Query API with R on Aura.
Query Neo4j From Tcl was originally published in Neo4j Developer Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.