Call MongoDB function from Java - java

I'm trying to call a stored JavaScript function from the MongoDB Java driver.
I have been following this guide to store the function on the DB server and I'm able to call the function from the mongo shell and have the result returned.
However I cannot figure out how to call the same function in Java?
According to this http://api.mongodb.org/java/current/com/mongodb/DB.html#doEval-java.lang.String-java.lang.Object...- there's a method called doEval
I have also tried to use it with this method:
public static String callFunction() {
try (MongoClient client = new MongoClient("localhost")) {
com.mongodb.DB db = client.getDB("TestDB");
return db.doEval("echoFunction", 3).toString();
}
}
But when I call the method this is what I get:
{ "retval" : { "$code" : "function (x) {\n return x;\n}"} , "ok" : 1.0}
and I would expect to get the number 3 back in this case.
Another problem with the above code is that the method client.getDB() is deprecated. As I understand it the new method to call is client.getDatabase() and it returns a MongoDatabase object, but according to the API there is no method to execute a function.
So my question is: Is it possible to execute a stored JavaScript function on the database server from Java and get back the result of that function? And if it is possible, I would appreciate some help on how to do it?
Thank you.
Edit:
According to a comment on Calling server js function on mongodb from java:
"It seems like getNextSequence is a function written in the mongo
javascript shell. Neither the database (mongod) nor the Java side
knows this function exists and neither is able to interprete the
Javascript code the function contains. You will have to reimplement it
in Java. "
The function I'm trying to implement is a bit more complex than the example above - it's supposed to return a collection of documents and that does not seems to be working using the db.doEval method.
So I guess the comment is correct?

You can do all this with java driver.
MongoClient mongoClient = new MongoClient();
MongoDatabase mdb = mongoClient.getDatabase("TestDB");
/* run this <code snippet> in bootstrap */
BsonDocument echoFunction = new BsonDocument("value",
new BsonJavaScript("function(x1) { return x1; }"));
BsonDocument myAddFunction = new BsonDocument("value",
new BsonJavaScript("function (x, y){ return x + y; }"));
mdb.getCollection("system.js").updateOne(
new Document("_id", "echoFunction"),
new Document("$set", echoFunction),
new UpdateOptions().upsert(true));
mdb.getCollection("system.js").updateOne(
new Document("_id", "myAddFunction"),
new Document("$set", myAddFunction),
new UpdateOptions().upsert(true));
mdb.runCommand(new Document("$eval", "db.loadServerScripts()"));
/* end </code snippet> */
Document doc1 = mdb.runCommand(new Document("$eval", "echoFunction(5)"));
System.out.println(doc1);
The result is also:
Document{{retval=5.0, ok=1.0}}

You should do this instead:
return db.doEval("echoFunction(3)").toString();
If you use just function name in eval you only refer to JavaScript variable on server side storing code of function. It doesn't execute it. When you use parentheses you request to actually execute a function. If you need to send something more complex than a number I would advise to use JSON serializer.

I resolved the same issue in the following way:
I run a command in mongoShell to create my stored JavaScript functions:
db.system.js.save(
{
_id: "echoFunction" ,
value : function(x1) { return x1; }
}
)
db.system.js.save(
{
_id : "myAddFunction" ,
value : function (x, y){ return x + y; }
}
);
db.system.js.save(
{
_id: "fullFillCollumns" ,
value : function () {
for (i = 0; i < 2000; i++) {
db.numbers.save({num:i}); } }
}
);
To execute this functions from MongoDB Java Driver:
MongoClient mongoClient = new MongoClient();
MongoDatabase db = mongoClient.getDatabase("databaseName");
db.runCommand(new Document("$eval", "fullFillCollumns()"));
Document doc1 = db.runCommand(new Document("$eval", "echoFunction(5)"));
System.out.println(doc1);
Document doc2 = db.runCommand(new Document("$eval", "myAddFunction(5,8)"));
System.out.println(doc2);
I see that the collection numbers were created and filled with values. In the IntellijIdea console I see:
Document{{retval=5.0, ok=1.0}}
Document{{retval=13.0, ok=1.0}}

Related

How to query mongoDB with TEXT (no java code) from Java, REST forbidden by OPS

I need a solution to parse a MongoDB shell query (or something close to) using Java...
I maintain a tool written in Java, used to move and transform data to and from different applications and tools.
I have been asked to implement something to query MongoDB... but am a total newbie in MongoDB, and...
It's a mess: Ops decided to not allow the rest communication, and deploying new specific Java code for each demand is not an option.
I try for some days to find a generic solution to request MongoDB from Java, simply giving a "request text" (yes, something like SQL) in order to make the solution easily reusable and maintainable.
One not bad solution is to use Nashorn and write a MongoDB dedicated code/request to be run. I already made the script module, it works on simple request but becomes a real pain in case of complex multi-level requests, and my users are not developers.
The second trail is to find a "Java accessible" MongoDB method to parse a "MongoDB shell" request, whatever it is. I have some results in a case of "Document" like the request but found nothing concerning "pipelines" starting with "[" as a JSON array... I saw here that hacking the mongoShell was not such a good idea, and I agree.
Any fresh idea ?
Thanks
//----------------------------
// Nashorn script
// inputObject contains configuration data
var credential= MongoCredential.createCredential( inputObject.user, inputObject.baseName, inputObject.passWord.toCharArray() );
var mongoClient= null;
try
{
mongoClient= new MongoClient( mongoServerList, java.util.Arrays.asList(credential) );
var mongoBase= mongoClient.getDatabase( inputObject.baseName );
var nbDocs= mongoBase.getCollection("messages_pnr").count();
print("nb docs= " + nbDocs );
var resultingDocuments= mongoBase.getCollection("messages").aggregate ( java.util.Arrays.asList (
new Document("$match", new Document("version", 2))
, new Document("$sort", new Document("_id", 1))
, new Document("$limit", 5)
, new Document("$project", new Document("_id", 0).append("identifier", "$identifier"))
) );
print("step 1");
var it_resultingDocuments= resultingDocuments.iterator();
print("step 2");
while( it_resultingDocuments.hasNext() )
{
print("step 3");
var currentResultingDocument= it_resultingDocuments.next();
print( currentResultingDocument.toJson() );
}
}
finally
{ if( mongoClient != null)
{ mongoClient.close();
}
}
//----------------------------------
// Java code for Document (found nothing for arrays)
MongoClient mongoClient= null;
try
{ // cnx
MongoCredential credential= MongoCredential.createCredential( this.getUser(), this.getBaseName(), this.getPassWord().toCharArray() );
mongoClient= new MongoClient( this.getMongoServersList(), Arrays.asList(credential) );
MongoDatabase mongoBase= mongoClient.getDatabase( this.getBaseName() );
long counter= 0;
Document requestDocument= Document.parse(mongoRequest);
MongoCollection<Document> collection= mongoBase.getCollection("messages_pnr");
AggregateIterable<Document> aggregate= collection.aggregate( Arrays.asList(requestDocument));
MongoCursor<Document> cursorIterable= aggregate.iterator();
while(cursorIterable.hasNext())
{
Document currentResult= cursorIterable.next();
String currentResultAsString= currentResult.toJson();
counter++;
System.out.println(counter + " " + currentResultAsString);
Utilities4Stream.copyFromByteArrayToOutputStreamsList(currentResultAsString.getBytes(), this.getResultOutputStreams());
}
}
finally
{
if(mongoClient != null)
{ mongoClient.close();
}
}
Our dev support department found that people from "dbschema.com" wrote a jdbc compliant layer above mongo client, and it works fine for me !
https://www.dbschema.com/mongodb-jdbc-driver.html

createUserDefinedFunction : if already exists?

I'm using azure-documentdb java SDK in order to create and use "User Defined Functions (UDFs)"
So from the official documentation I finally find the way (with a Java client) on how to create an UDF:
String regexUdfJson = "{"
+ "id:\"REGEX_MATCH\","
+ "body:\"function (input, pattern) { return input.match(pattern) !== null; }\","
+ "}";
UserDefinedFunction udfREGEX = new UserDefinedFunction(regexUdfJson);
getDC().createUserDefinedFunction(
myCollection.getSelfLink(),
udfREGEX,
new RequestOptions());
And here is a sample query :
SELECT * FROM root r WHERE udf.REGEX_MATCH(r.name, "mytest_.*")
I had to create the UDF one time only because I got an exception if I try to recreate an existing UDF:
DocumentClientException: Message: {"Errors":["The input name presented is already taken. Ensure to provide a unique name property for this resource type."]}
How should I do to know if the UDF already exists ?
I try to use "readUserDefinedFunctions" function without success. Any example / other ideas ?
Maybe for the long term, should we suggest a "createOrReplaceUserDefinedFunction(...)" on azure feedback
You can check for existing UDFs by running query using queryUserDefinedFunctions.
Example:
List<UserDefinedFunction> udfs = client.queryUserDefinedFunctions(
myCollection.getSelfLink(),
new SqlQuerySpec("SELECT * FROM root r WHERE r.id=#id",
new SqlParameterCollection(new SqlParameter("#id", myUdfId))),
null).getQueryIterable().toList();
if (udfs.size() > 0) {
// Found UDF.
}
An answer for .NET users.
`var collectionAltLink = documentCollections["myCollection"].AltLink; // Target collection's AltLink
var udfLink = $"{collectionAltLink}/udfs/{sampleUdfId}"; // sampleUdfId is your UDF Id
var result = await _client.ReadUserDefinedFunctionAsync(udfLink);
var resource = result.Resource;
if (resource != null)
{
// The UDF with udfId exists
}`
Here _client is Azure's DocumentClient and documentCollections is a dictionary of your documentDb collections.
If there's no such UDF in the mentioned collection, the _client throws a NotFound exception.

How do I execute a MongoDB js script using the Java MongoDriver

I have implemented a JavaScript function inside the Mongodb server using this example.
It works fine when I use mongo shell, but I want to run it from inside a Java program. This is the code:
public String runFunction() {
CommandResult commandResult1 = db.command("db.loadServerScripts()");
CommandResult commandResult2 = db.command("echoFunction(3)");
return commandResult2.toString();
}
I don't understand the result.
The previous answers does not work in MongoDB 3.4+. Th proper way to do this in version 3.4 and above is to create a BasicDBObject and use it as the parameter of Database.runCommand(). Here's an example.
final BasicDBObject command = new BasicDBObject();
command.put("eval", String.format("function() { %s return;}}, {entity_id : 1, value : 1, type : 1}).forEach(someFun); }", code));
Document result = database.runCommand(command);
Since MongoDB 4.2 the eval command is removed (it was deprecated in 3.0).
There is no more way to eval JavaScript scripts into MongoDB from the Java Driver.
See https://docs.mongodb.com/manual/reference/method/db.eval/
You should use DB.eval(), see the api docs and make sure that you don't do string concatenation. Pass the variables through instead.
I think your answer is probably the same answer as this other one on StackOverflow.
#Autowired
private MongoOperations mongoOperations;
private final BasicDBObject basicDBObject = new BasicDBObject();
#PostConstruct
private void initialize() {
basicDBObject.put("eval", "function() { return db.loadServerScripts(); }");
mongoOperations.executeCommand(basicDBObject);
}
private void execute() {
basicDBObject.put("eval", "function() { return echoFunction(3); }");
CommandResult result = mongoOperations.executeCommand(basicDBObject);
}
And then you can use something like:
ObjectMapper mapper = new ObjectMapper();
And MongoOperation's:
mongoOperations.getConverter().read(CLASS, DBOBJECT);
Just try to have some workaround depends on your requirements

Parse Cloud Code in Java and Calling the code in Swift

I am trying to implement this code using java and the cloud code option from Parse.
Does anyone know how to implement this code in Java and then correctly call it from my function using PFCloud?
//Search for submissions & groups
var findUsersInGroupData:PFQuery = PFQuery(className: "GameInfo")
findUsersInGroupData.whereKey("Group", equalTo:groupName)
findUsersInGroupData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in
if error == nil{
for object in objects{
let usersInGroup:PFObject = object as PFObject
self.timelineData.addObject(usersInGroup)
var findTimelineData:PFQuery = PFQuery(className: "Submissions")
findTimelineData.whereKey("User", equalTo: usersInGroup.objectForKey("User"))
findTimelineData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in
if error == nil{
for object in objects{
let submission:PFObject = object as PFObject
self.timelineData.addObject(submission)
}
let array:NSArray = self.timelineData.reverseObjectEnumerator().allObjects
self.timelineData = NSMutableArray(array: array)
self.tableView.reloadData()
}
}
}
}
}

Neo4j ExecutionEngine does not return valid results

Trying to use a similar example from the sample code found here
My sample function is:
void query()
{
String nodeResult = "";
String rows = "";
String resultString;
String columnsString;
System.out.println("In query");
// START SNIPPET: execute
ExecutionEngine engine = new ExecutionEngine( graphDb );
ExecutionResult result;
try ( Transaction ignored = graphDb.beginTx() )
{
result = engine.execute( "start n=node(*) where n.Name =~ '.*79.*' return n, n.Name" );
// END SNIPPET: execute
// START SNIPPET: items
Iterator<Node> n_column = result.columnAs( "n" );
for ( Node node : IteratorUtil.asIterable( n_column ) )
{
// note: we're grabbing the name property from the node,
// not from the n.name in this case.
nodeResult = node + ": " + node.getProperty( "Name" );
System.out.println("In for loop");
System.out.println(nodeResult);
}
// END SNIPPET: items
// START SNIPPET: columns
List<String> columns = result.columns();
// END SNIPPET: columns
// the result is now empty, get a new one
result = engine.execute( "start n=node(*) where n.Name =~ '.*79.*' return n, n.Name" );
// START SNIPPET: rows
for ( Map<String, Object> row : result )
{
for ( Entry<String, Object> column : row.entrySet() )
{
rows += column.getKey() + ": " + column.getValue() + "; ";
System.out.println("nested");
}
rows += "\n";
}
// END SNIPPET: rows
resultString = engine.execute( "start n=node(*) where n.Name =~ '.*79.*' return n.Name" ).dumpToString();
columnsString = columns.toString();
System.out.println(rows);
System.out.println(resultString);
System.out.println(columnsString);
System.out.println("leaving");
}
}
When I run this in the web console I get many results (as there are multiple nodes that have an attribute of Name that contains the pattern 79. Yet running this code returns no results. The debug print statements 'in loop' and 'nested' never print either. Thus this must mean there are not results found in the Iterator, yet that doesn't make sense.
And yes, I already checked and made sure that the graphDb variable is the same as the path for the web console. I have other code earlier that uses the same variable to write to the database.
EDIT - More info
If I place the contents of query in the same function that creates my data, I get the correct results. If I run the query by itself it returns nothing. It's almost as the query works only in the instance where I add the data and not if I come back to the database cold in a separate instance.
EDIT2 -
Here is a snippet of code that shows the bigger context of how it is being called and sharing the same DBHandle
package ContextEngine;
import ContextEngine.NeoHandle;
import java.util.LinkedList;
/*
* Class to handle streaming data from any coded source
*/
public class Streamer {
private NeoHandle myHandle;
private String contextType;
Streamer()
{
}
public void openStream(String contextType)
{
myHandle = new NeoHandle();
myHandle.createDb();
}
public void streamInput(String dataLine)
{
Context context = new Context();
/*
* get database instance
* write to database
* check for errors
* report errors & success
*/
System.out.println(dataLine);
//apply rules to data (make ContextRules do this, send type and string of data)
ContextRules contextRules = new ContextRules();
context = contextRules.processContextRules("Calls", dataLine);
//write data (using linked list from contextRules)
NeoProcessor processor = new NeoProcessor(myHandle);
processor.processContextData(context);
}
public void runQuery()
{
NeoProcessor processor = new NeoProcessor(myHandle);
processor.query();
}
public void closeStream()
{
/*
* close database instance
*/
myHandle.shutDown();
}
}
Now, if I call streamInput AND query in in the same instance (parent calls) the query returns results. If I only call query and do not enter ANY data in that instance (yet web console shows data for same query) I get nothing. Why would I have to create the Nodes and enter them into the database at runtime just to return a valid query. Shouldn't I ALWAYS get the same results with such a query?
You mention that you are using the Neo4j Browser, which comes with Neo4j. However, the example you posted is for Neo4j Embedded, which is the in-process version of Neo4j. Are you sure you are talking to the same database when you try your query in the Browser?
In order to talk to Neo4j Server from Java, I'd recommend looking at the Neo4j JDBC driver, which has good support for connecting to the Neo4j server from Java.
http://www.neo4j.org/develop/tools/jdbc
You can set up a simple connection by adding the Neo4j JDBC jar to your classpath, available here: https://github.com/neo4j-contrib/neo4j-jdbc/releases Then just use Neo4j as any JDBC driver:
Connection conn = DriverManager.getConnection("jdbc:neo4j://localhost:7474/");
ResultSet rs = conn.executeQuery("start n=node({id}) return id(n) as id", map("id", id));
while(rs.next()) {
System.out.println(rs.getLong("id"));
}
Refer to the JDBC documentation for more advanced usage.
To answer your question on why the data is not durably stored, it may be one of many reasons. I would attempt to incrementally scale back the complexity of the code to try and locate the culprit. For instance, until you've found your problem, do these one at a time:
Instead of looping through the result, print it using System.out.println(result.dumpToString());
Instead of the regex query, try just MATCH (n) RETURN n, to return all data in the database
Make sure the data you are seeing in the browser is not "old" data inserted earlier on, but really is an insert from your latest run of the Java program. You can verify this by deleting the data via the browser before running the Java program using MATCH (n) OPTIONAL MATCH (n)-[r]->() DELETE n,r;
Make sure you are actually working against the same database directories. You can verify this by leaving the server running. If you can still start your java program, unless your Java program is using the Neo4j REST Bindings, you are not using the same directory. Two Neo4j databases cannot run against the same database directory simultaneously.

Categories

Resources