Can I write a Neo4j Plugin to intercept and modify CYPHER queries - neo4j

In my system, I would like to intercept and change Cypher queries as they come in, one alternative is to modify them before sending them from my middle layer to the graph - but is there a way to have a plugin do the conversion for me in the graph itself?
I'd like to do some of the following:
If someone identifying themselves as members of group A, imagine I'd like to change their request from:
MATCH(f:Film)-[r:REVIEWED_BY]-(u:User {id:"1337"})
to:
MATCH(p:Product)-[p:PURCHASED_BY]-(u:User {id:"1337"})
Is something like this possible? Or do I have to write the traversals in Java directly to achieve this?

Of course you can. You can do ANYTHING in Neo4j. Just grab the cypher string in an unmanaged extension that receives a post request, alter it any way you want, execute it with the graphdb.execute method and return the result as normal.
#POST
#Path("/batch")
public Response alterCypher(String body, #Context GraphDatabaseService db) throws IOException, InterruptedException {
ArrayList<Result> results = new ArrayList<>();
// Validate our input or exit right away
HashMap input = Validators.getValidCypherStatements(body);
ArrayList<HashMap> statements = (ArrayList<HashMap>)input.get("statements");
for (HashMap statement : statements) {
// write the alterQuery method to change the queries.
String alteredQuery = alterQuery((String)statement.get("statement"));
Result result = db.execute(alteredQuery, (Map)statement.getOrDefault("parameters", new HashMap<>()));
results.add(result);
}
// or go the results and return them however you want
// see https://github.com/dmontag/neo4j-unmanaged-extension-template/blob/master/src/main/java/org/neo4j/example/unmanagedextension/MyService.java#L36
return Response.ok().build();
}

At this time it's not possible to extend or modify Cypher queries.
If you need that I recommend you to use Transaction Event API - http://graphaware.com/neo4j/transactions/2014/07/11/neo4j-transaction-event-api.html
With that you should be able to change what query returns.

Related

How to get GraphDatabaseService instance from Bolt Driver

I wrote some codes that used java Api of GraphDatabaseService to access an embedded database before. But now I want to switch the database to a remote one, so I have to use Driver and Session class to write and run cyphers. I don't like cyphers and I don't want to change the old codes.
So I'm looking for a way so that I can get GraphDatabaseService from Driver, but none is found.
I think a possible way is to make up a GraphDataBaseService delegate that wraps a Driver and converts Api calls to cyphers. Is that feasible? Is there already some libraries which can do this?
You won't get a direct equivalent of GraphDatabaseService since that's the embedded API.
You can however run Cypher queries like this:
var driver = GraphDatabase.driver(
"some://address",
AuthTokens.basic("username", "password")
);
// [...]
try (var session = driver.session() {
return session.writeTransaction(tx -> {
Result result = tx.run(
"CYPHER QUERY",
Map.of(/* query parameters */)
);
// do something with result
});
}
// [...]
driver.close(); // (driver is usually a singleton - closes when application shuts down)
I tried to make a project to solve this problem by wrapping Driver to GraphDatabaseService.

Language-ext: chain Either<L, R> with Option?

I am just starting with language-ext, trying to use it in my Azure Function.
In this function, I first parse/validate the POSTed data from the HTTP request using some validator.
This validator returns an Either<ValidationErrors, RequestModel>.
Then I would like to chain onto the either result a service call that should use the request model to grab some data from an API an return an Option.
At the end of the chain I would then like to return an IActionResult BadRequest if there were ValidationErrors in the first step, or otherwise perform a Match on the result of the service call Option to either return a NotFoundResult or ObjectResult.
The issue I run into is that if I want to chain my service call (using Bind, or BiBind) after the Either<ValidationErrors, GetRequestModel>, then the signature of my service method must be some Either<ValidationErrors, ...>, which is incorrect, since my service method has nothing to do with ValidationErrors. It should just return an Option.
So I guess my question is how can preserve any ValidationErrors until the end of the chain, and be able to chain my service call with a Option signature onto an Either?
You have to decide what the result of your chained expression is.
Option:
var maybeResult = from validated in GetValidationResult(...).ToOption()
from apiResult in ApiCall(...)
select apiResult;
Either:
var resultOrError = from validated in GetValidationResult(...)
from apiResult in ApiCall(...).ToEither(*LEFT*)
select apiResult;
You have to replace *LEFT* by some error value or error generating function returning same type like left type of GetValidationResult.
Replace above pseudo code with your own code and look at the return types of the functions used above to see what's going on.
The reason why you need a common left type is that the bind operation can return some left (error) of first (GetValidationResult) or second (ApiCall) function call -- or right of your last (ApiCall) function if your reach successful end of your chain.
Recommendation: If you mix different left (error) return types you might want to use some thing like LanguageExt's built-in Error type or maybe just a plain string (or Exception).
Either with string as error type:
var resultOrError = from validated in GetValidationResult(...).MapLeft(Prelude.toString)
from apiResult in ApiCall(...).ToEither("api call failed")
select apiResult;
Additional note: I use LINQ style here, you can use method style:
var resultOrError = GetValidationResult(...)
.MapLeft(Prelude.toString)
.Bind(validated => ApiCall(...)
.ToEither("api call failed"));

How do we generate a cypher query by using Java and APOC for Neo4j?

I am trying to create my own procedure in Java in order to use it for Neo4j.I wanted to know how we can execute Cypher code in Java ?
I tried to use graphDB.execute() function but it doesn't work.
I just want to execute a basic code in Java by using Neo4j libraries.
Example of a basic code I want to execute:
[EDIT]
public class Test
{
#Context public GraphDatabaseService graphDb;
#UserFunction
public Result test() {
Result result = graphDb.execute("MATCH (n:Actor)\n" +
"RETURN n.name AS name\n" +
"UNION ALL MATCH (n:Movie)\n" +
"RETURN n.title AS name", new HashMap<String, Object>());
return result;
}
}
If you want to display nodes (as in the graphical result view in the browser), then you have to return the nodes themselves (and/or relationships and/or paths), not the properties alone (names and titles). You'll also need this to be a procedure, not a function. Procedures can yield streams of nodes, functions can only return single values.
Change this to a procedure, and change your return type to be something like Stream<NodeResult> where NodeResult is a POJO that has a public Node field.
You'll need to change your return accordingly.

Dropwizard Stored Procedure Example

I am new to dropwizard. I am going through different tutorial but I couldnt find good example/tutorial on how to call stored procedure in dropwizard using JDBI.
With JDBI it is possible to specify an object SQL query as a method annotation. This string is treated by the DB as normal SQL. An example DAO to execute a stored procedure might look like:
public interface SomeQueries
{
#SqlQuery("call find_name_procedure(:id)")
String findName(#Bind("id") int id);
}
Where find_name_procedure has been previously defined.
For more information see http://jdbi.org/sql_object_api_queries/
Jooq is an elegant way to call stored procedures from java (See https://dzone.com/articles/using-stored-procedures-with-jpa-jdbc-meh-just-use). You also have a thirdparty module integrating Jooq into Dropwizard
(See http://modules.dropwizard.io/thirdparty/)
So if you're familiar with Maven, you can set up the combination quickly and start validating if it works in your case.
DropWizard JDBI provides two options to make a stored procedure call. Depending on what you want from Stored proc.
If you want resultset from Stored Proc, Andy has answered that already:
public interface DAO
{
#SqlQuery("call sp_name(:id)")
String query(#Bind("id") int id);
}
The other scenario is that your stored proc doesn't return resultset but gives out some output parameters. Here is what you can do:
#SqlCall("{ CALL sp_name(:id, :id2) }")
#OutParameter(name = "out", sqlType = Types.INTEGER)
OutParameters spCal(#Bind("id") long id, #Bind("id2") long id2);
OutParameter annotation may not work in DropWizard as the annotation was introduced in v3, you can use the following for accessing OutParameters:
try (Handle handle = dbi.open()) {
parameters = handle.createCall("CALL sp_name(:IN_Field1,:OUT_field2)")
.bind("IN_Field1", filed1)
.registerOutParameter("OUT_field2", Types.INTEGER)
.invoke();
}
And for some real freaky situations where you will need both Outparameters and resultset - DropWizard currently doesn't provide any direct way (Because the JDBI version with dropwizard is old). So, you can get connection by doing below:
handle = jdbi.open()
handle.getConnection()
And then do what you want to do with the connection.
Also, one more thing - I will not try to use Hibernate for Stored Proc call. Its just more pain.
I used Zalando Sprocwrapper. I like it and was widely used in the Zalando company to call PostgreSQL functions.
https://github.com/zalando-incubator/java-sproc-wrapper

Dynamic Cypher Search Query

I know this may sound nonsense but I will try my best to explain the problem that I am facing with neo4jClient.
I developing a Social TaskManagment Software. and for the server side code and data Storage I choose to have neo4j (neo4jClient) and C# in place.
in Our software Imagine a User : (mhs) Post a Task of "#somebody please help me on #Neo4j #cypher" the task would be decorated with some fancy character including (# # + /) the obove post will be save in neo4j as graph like this :
(post:Post {Text : #somebody please help me on #Neo4j})-[Has_HashTag]-(neo4j:HashTag)
(post:Post)-[Has_HashTag]-(Cypher: HashTag)
(post:Post)-[Has_Author)-[mhs:User]
(post:Post)-[Has_MentionedUser)-[Somebody:User]
Now Imagine User #mhs is trying to search the Post that Have HashTag of #Neo4j and mentioned to #somebody
Here I am building (hand Craft) a Cypher Query Including the 2 seach Paramerts in Cypher with Some Fancy C# code which result the blow Cypher Query:
MATCH (nodes)-[r]-(post:Post),
(post:Post)-[:HAS_MentionedUsers]->(assignee1307989068:User),
(nodes)-[r]-(post:Post)-[:HAS_HashTags]->(Hashtag1482870844:HashTag)
WHERE (assignee1307989068.UserName = "somebody") AND (Hashtag1482870844.Value = "neo4j")
RETURN post AS Post, Collect(nodes) as nodes
ORDER BY post.creationDate
the above cypher will return a post with just all the nodes of the post that is not included in Where clause.
my question is how to include all the Nodes related to the Targeted (post) without including them in Return part of the cypher. Something like return (*).
The Other problem is How can I deserialize the result set into C# without knowing what shape it may have.
The Search method that I am producing the mentioned Cypher is as fallow:
public List<PostNode> Search(string searchterm)
{
List<string> where = new List<string>();
var tokenizedstring = searchterm.Split(' ');
var querystring = new StringBuilder();
var relatedNodes = new List<string>();
var q = new CypherFluentQuery(_graphClient) as ICypherFluentQuery;
foreach (var t in tokenizedstring)
{
_commandService.BuildPostQueystring(t, ref querystring, ref where, ref relatedNodes);
}
if (querystring[querystring.Length - 1] == ',')
querystring = querystring.Remove(querystring.Length - 1, 1);
q = q.Match(querystring.ToString());
int i = 1;
if (where.Count > 0)
q = q.Where(where[0]);
while (i < where.Count)
{
q = q.AndWhere(where[i]);
i++;
}
var rq = q.Return(
(post, nodes) => new PostNode
{
Post = post.As<Node<string>>(),
Nodes = nodes.CollectAs<string>()
})
.OrderBy("post.creationDate");
var results = rq.Results.ToList();
//foreach (var result in results)
//{
// //dynamic p = JsonConvert.DeserializeObject<dynamic>(result.Post.Data);
// //dynamic d = JsonConvert.DeserializeObject<dynamic>(result.Nodes.Data);
//}
return results;
}
}
//Some Helper Class just to cast the result.
public class PostNode
{
public Node<string> Post { get; set; }
public IEnumerable<Node<string>> Nodes { get; set; }
}
But as you may noticed it does not have the nodes that is included in the search term via Where Clause in Cypher Query.
I am really stopped here for a while, as I can not provide any decent solution for this. so your help may save me a lot.
may be i am totally in a wrong direction so please help in any way you can think of.
It appears that a list of UNKNOWN related nodes are being provisioned, so one of this:
Scenario A
It doesn't matter what EXACTLY those related nodes are, I just want them.
Question: What is intention of retrieving those unknown but related nodes? By answering this chances are this query could be improved for good.
Scenario B
These unknown related nodes are actually known! It's just they are not fully known at time of query execution however down the road somewhere in C# code we will have things like this
if (nodes.Any(_ => _ is HashTag) {...}
Question:
This type of behaviour requires to KNOW the type. Even with reflection or C# dynamic stuff that requirement is still there because Neo4jClient has no way of correlating a bag of JSON data coming from Neo4j into any local type. When Neo4jClient receives bulk of data over wire somehow it should know what type would YOU prefer to represent. This is why queries are always like this:
Return((a, p) => new
{
Author = a.As<Author>(), //we expect node content to be represented as Author
Post = p.As<Post>()
})
Neo4jClient does NOT preserve C# types inside your Neo4j database. It would have been nasty to do so. However, idea behind it is that you shouldn't find yourself desperate for it and if you do so then I would recommend looking for problem somewhere else i.e. why would you rely on your client library to describe your domain for you?

Resources