The idea behind parametrized queries is to re-use (cache) execution plans.
If a node label or a relationship type do not vary, the execution plan would be the same at all, thus benefits can be achieved of execution plan caching.
Currently, I'm my complete Cypher Query is built using Java String Building. Instead of creating the entire Cypher Query using String building I want to pass the values of the Properties as Parameter Values along with Property Names as Parameters or not. I need sample code, guidance for the same.
My Current Code
import org.neo4j.driver.v1.AuthTokens;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.Session;
public class ForStackoverflowQuestion {
public static void main(String[] args) {
Driver driver = GraphDatabase.driver(
"bolt://localhost:7687", AuthTokens.basic("neo4j", "12345"));
Session session = driver.session();
String Node1 = "Software_Engineer";
String Node2 = "Programming_Language";
String relationBetweenNode1andNode2 = "LEARNS";
String PersonNameAttribute = "name";
String PersonNameValue = "Jaykant";
String ProgrammingLanguageAttribute = "version";
String ProgrammingLanguageValue = "Neo4j";
String t = "MERGE(n1:"+Node1+"{"+PersonNameAttribute+":\""+PersonNameValue+"\"})"+"-[:"+relationBetweenNode1andNode2+"]->(n2:" + Node2 +" {"+ProgrammingLanguageAttribute+":'"+ProgrammingLanguageValue+"'})";
System.out.println(t);
session.run(t);
session.close();
driver.close();
}
}
I understand that my above code is not using Parameterized Cypher Query; so it will not generate any Query Plan in neo4j.
In order to use and benefit from the Query Plan, I need to use the Parametrized Query.
If not Node1, Node2, and relationBetweenNode1andNode2 as Parameters then at least following values can be passed as parameters.
PersonNameAttribute = "name";
PersonNameValue = "Jaykant";
ProgrammingLanguageAttribute = "version";
ProgrammingLanguageValue = "Neo4j";
Any sample code? Tutorial?
You can pass parameters along with query in session.run() method.
Ex.
session.run(query, parameters)
Parameters should be a Map.
HashMap<String, Object> parameters = new HashMap<String, Object>();
parameters.put("PersonNameValue", "Jaykant");
parameters.put("ProgrammingLanguageValue", "Neo4j");
Query can be modified as:
String t = "MERGE (n1:"+Node1+"{"+PersonNameAttribute+":{PersonNameValue}})"+"-[:"+relationBetweenNode1andNode2+"]->(n2:" + Node2 +" {"+ProgrammingLanguageAttribute+": {ProgrammingLanguageValue}})";
Finally run statement:
session.run(t, parameters);
Related
The ask: a function to retrieve a single Entity from the Google App Engine Datastore based on a property that is not its Key or otherwise return null if no such object is found.
Here is the function I have currently:
public Entity find(DatastoreService datastore, String kind, String property, String value) {
Filter propertyFilter =
new FilterPredicate(property, FilterOperator.EQUAL, value);
Query q = new Query(kind).setFilter(propertyFilter);
List<Entity> results =
datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());
if (results.isEmpty()) {
return null;
}
return results.get(0);
}
Is there a one-shot API I could use instead or are there any optimization suggestions?
You could use PreparedQuery#asSingleEntity():
public Entity find(DatastoreService datastore,
String kind, String property, String value)
throws TooManyResultsException {
Filter propertyFilter =
new FilterPredicate(property, FilterOperator.EQUAL, value);
Query q = new Query(kind).setFilter(propertyFilter);
return datastore.prepare(q).asSingleEntity();
}
The only real difference between this and your code is that the underlying query sets a LIMIT of 2.
I'm working on neo4vertx, a module that makes it possible to talk to a Neo4j database using Vert.x. Specifically, I'm working on feature called "query" which allows a Vert.x user
to send a Cypher query as an eventbus message and get a JSON resultset back.
However, I seem to run into an unexpected problem when serializing to JSON using JsonHelper.createJsonFrom() with certain queries.
A quick example (database has stuff in it of course):
// This Fails with JsonMappingException (see below):
String query="MATCH (n) RETURN n";
// This Succeeds:
String query="MATCH (n) RETURN n.something";
//Rest of code:
engine = new ExecutionEngine(graphDatabaseService);
ExecutionResult result;
result = engine.execute(query);
Object object = result.iterator();
String foo = JsonHelper.createJsonFrom(object);
System.out.println("DEBUG (foo): " + foo);
Does this look familiar to anyone? We essentially want to be able to
send any kind of query and either return an empty json string or a json
representation not unlike the result.json that you can retrieve from the
web interface of neo4j!
The Exception:
testQuery(org.openpcf.neo4vertx.neo4j.Neo4jGraphTest) Time elapsed: 2.362 sec <<< ERROR!
org.neo4j.server.rest.domain.JsonBuildRuntimeException: org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.neo4j.kernel.InternalAbstractGraphDatabase$DependencyResolverImpl and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: scala.collection.convert.MapWrapper["content"]->org.neo4j.kernel.impl.core.NodeProxy["graphDatabase"]->org.neo4j.test.["dependencyResolver"])
...
Although the ExecutionResult is an implementation of Iterator<Map<String,Object>> it is currently just a java wrapper around scala classes, as well as Neo4j classes (like Node, Relationship, Path).
So you have probably to do 2 things:
recursively replace neo4j classes with appropriate maps and lists
probably: recursively replace scala lists and maps with java lists and maps, e.g. new LinkedHashMap(row)
I did some of that some time ago here in my cypher-websocket-experiments
You can serialize the ExecutionResult of Neo4J to JSON this way:
ExecutionResult result ...
CypherResultRepresentation repr = new CypherResultRepresentation(result, false, false);
OutputFormat format = new OutputFormat(new JsonFormat(), null, null);
String json = format.assemble(repr);
The next thing you have to do is to create the appropriate vert.x JsonObject objects.
#Rubin: I'm currently adding this to neo4vertx ;)
By using RESTAPI:
String query = "MATCH (a)-[r]-(b) RETURN a,r,b";
RestAPI restAPI = new RestAPIFacade(URI);
CypherResult result = restAPI.query(query, Collections.<String, Object>emptyMap());
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
String stringResult = gson.toJson(result.asMap());
JSONObject jsonResult = new JSONObject(stringResult);
By using Bolt
String query = "MATCH (a)-[r]-(b) RETURN a,r,b";
String stringConnection = "bolt://" + HOST + ":7687";
Driver driver = GraphDatabase.driver(stringConnection, AuthTokens.basic(USER, PASS));
Session session = driver.session();
StatementResult result = session.run(query);
session.close();
driver.close();
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
while (result.hasNext()) {
Record record = result.next();
stringResult = gson.toJson(record.asMap());
}
JSONObject jsonResult = new JSONObject(stringResult);
The following code haven't worked for me:
public void addFieldWithValueToDoc(String DBName, String collName, String docID, String key, String value) {
BasicDBObject setNewFieldQuery = new BasicDBObject().append("$set", new BasicDBObject().append(key, value));
mongoClient.getDB(DBName).getCollection(collName).update(new BasicDBObject().append("_id", docID), setNewFieldQuery);
}
Where mongoClient variable's type is MongoClient.
It's inspired by Add new field to a collection in MongoDB .
What's wrong and how to do it right?
Thanks.
I've written a JUnit test to prove that your code does work:
#Test
public void shouldUpdateAnExistingDocumentWithANewKeyAndValue() {
// Given
String docID = "someId";
collection.save(new BasicDBObject("_id", docID));
assertThat(collection.find().count(), is(1));
// When
String key = "newKeyName";
String value = "newKeyValue";
addFieldWithValueToDoc(db.getName(), collection.getName(), docID, key, value);
// Then
assertThat(collection.findOne().get(key).toString(), is(value));
}
public void addFieldWithValueToDoc(String DBName, String collName, String docID, String key, String value) {
BasicDBObject setNewFieldQuery = new BasicDBObject().append("$set", new BasicDBObject().append(key, value));
mongoClient.getDB(DBName).getCollection(collName).update(new BasicDBObject().append("_id", docID), setNewFieldQuery);
}
So your code is correct, although I'd like to point out some comments on style that would make it more readable:
Parameters and variables should start with a lower-case letter. DBName should be dbName,
You don't need new BasicDBObject().append(key, value) use new BasicDBObject(key, value)
This code does the same thing as your code, but is shorter and simpler:
public void addFieldWithValueToDoc(String dbName, String collName, String docID, String key, String value) {
mongoClient.getDB(dbName).getCollection(collName).update(new BasicDBObject("_id", docID),
new BasicDBObject("$set", new BasicDBObject(key, value)));
}
To update existing documents in a collection, you can use the collection’s updateOne() or updateMany methods.
updateOne method has the following form:
db.collection.updateOne(filter, update, options)
filter - the selection criteria for the update. The same query selectors as in the find() method are available.
Specify an empty document { } to update the first document returned in
the collection.
update - the modifications to apply.
So, if you want to add one more field using Mongodb Java driver 3.4+, it will be:
collection.updateOne(new Document("flag", true),
new Document("$set", new Document("title", "Portable Space Ball")));
The following operation updates a single document where flag:true
Or in the same logic:
collection.updateOne(eq("flag", true),
new Document("$set", new Document("title", "Portable Space Ball")));
If the title field does not exist, $set will add a new field with the specified value, provided that the new field does not violate a type constraint. If you specify a dotted path for a non-existent field, $set will create the embedded documents as needed to fulfill the dotted path to the field.
currently have a cypher query method
public static void RunQuery(String _query)
{
Properties prop = new Properties();
final String DB_PATH = "path/to/db"
GraphDatabaseService graphDb = new GraphDatabaseFactory().newEmbeddedDatabase(DB_PATH);
ExecutionEngine engine = new ExecutionEngine(graphDb);
ExecutionResult result = engine.execute(_query);
for(Map<String,Object> map : result)
{
System.out.println(map.toString());
}
graphDb.shutdown();
}
However this only allows me to get results like this:
{a=Node[11303]}
{a=Node[11341]}
{a=Node[11343]}
{a=Node[11347]}
{a=Node[11349]}
{a=Node[11378]}
How can I augment it to spit out the entire query results like the cypher shell does?
What does your query look like. The Map<String, Object> that is returned will have a key of the variable you return. The Object can be a Path, Node, or Relationship, and this will just call the native toString() on them, which in Java code just returns the Node ID. You have to build your own printer, something that will get the property keys and iterate through each of them.
for (String key : node.getPropertyKeys()) {
System.out.println("Key: " + key + ", Value: " + node.getProperty(key));
}
It depends on the RETURN of your query..
For example, if you have a query like this:
_query="Start x= node(someIndex) Match x-[rel:SOMETHING]-n Return n";
So, your code can be
ExecutionResult result = engine.execute(_query);
Iterator<Node> n_column = result.columnAs("n");
for (Node outputNode: IteratorUtil.asIterable(n_column)) {
System.out.println(outputNode.getProperty("yourKey","defaultValueIfNull"));
}
I've got a MySQL table with Foos. Each Foo has a numeric non-unique code and a name. Now I need to find if any Foo with one of certain codes happens to have a name that starts with a given string. In normal SQL this would be trivial:
select * from FOO where CODE in (2,3,5) and NAME like 'bar%';
But how would I properly do this in Spring now? Without the need for the 'like' operator I'd do it like this:
public List<Foo> getByName(List<Integer> codes, String namePart) {
String sql = "select * from FOO where CODE in (:codes) and NAME=:name"
Map<String,Object> params = new HashMap<String,Object>();
params.put("codes", codes);
params.put("name", namePart);
return getSimpleJdbcTemplate().query(sql, new FooRowMapper(), params);
}
However, with 'like' nothing seems to work: NAME like :name%, NAME like ':name%', or NAME like ?% when using the placeholders instead of named parameters.
I could be brutal and enter it as
String sql = "select * from FOO where CODE in (:codes) and NAME like '"+namePart+"%'";`
but obviously it would be more than nice if Spring would sanitize the input parameters properly etc, you know...
You'd think Spring would support this somehow but I cannot figure it out.
Wait, of course I had to "try one more final thing" before calling it a day, and lo and behold, all my unit tests suddenly pass:
public List<Foo> getByName(List<Integer> codes, String namePart) {
String sql = "select * from FOO where CODE in (:codes) and NAME like :name"
Map<String,Object> params = new HashMap<String,Object>();
params.put("codes", codes);
params.put("name", namePart+"%");
return getSimpleJdbcTemplate().query(sql, new FooRowMapper(), params);
}
I didn't think of entering the "%" in the parameter, I was certain Spring would automatically escape it. I wonder if I'm doing it right?
For named parameters to work, you need to use NamedParameterJdbcTemplate
params.put("name", "Joe%");
jdbcTemplate.query("select * from FOO where CODE in (:codes) and NAME like :name"
In another form, I encountered the same problem, and I tried to solve it via this manner:
public List<MyEntity> getMyEntityValuesBySearchText(String searchText) {
String query = "SELECT * FROM MY_ENTITY_TABLE WHERE NAME LIKE ?";
return this.getJdbcTemplate().query(query, new String[] { "%" + searchText + "%" },
(rs, rowNum) -> new MyEntity(rs.getLong("PK"), rs.getString("NAME")));
}
There is a problem with the code above. The code structure is correct but there is a problem in mapping the variable. You will get the error message as "Index Out of Bound" SQL Exception error.
To avoid that error we map our variable properly using the class "MySqlParameterSource". We needed to create an object for that class and pass our variable inside to map out variables.
Follow this as an example.
public List<Products> getParticular2(#RequestParam String charc){
String sql ="select * from products where name like :name";
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", charc+"%");
MapSqlParameterSource param = new MapSqlParameterSource(params);
List <Products> list = template.query(sql, param, new
BeanPropertyRowMapper<>(Products.class));
return list;
}