I am building a query based on passed parameters. For example, if pass p1='a' and p2='b', my query will look something like this:
g.V()
.has("p1","a")
.has("p2","b")
If let's say p2 is not passed, then I won't have a second check:
g.V()
.has("p1","a")
Is it possible to perform parameter check inside the query instead of creating conditional checks for parameters before creating query?
Edit: Use case is based RESTful web service where I have something like, /server/myEndpoint?p1=a and endpoint implementation would build gremlin query with .has() steps solely based on presence of p1 or p2, so if p2 is not passed, query would look like in second snippet, and if passed would look like the one in first.
One possible approach is to build GraphTraversal until it's not executed:
Map<String, String> map = new HashMap<>();
map.put("p1", "a");
map.put("p2", null);
final GraphTraversal<Vertex, Vertex> v = g.V();
map.forEach((k,v) -> {
if(v != null) {
v.has(k,v);
}
});
return v.toStream().collect(Collectors.toList());
As a suggested improvement where you are creating the search, the Gremlin-optimized way to approach this would be through a parameterized script. In theory this would mean that you are building the query in a way which is looking for each parameter.
However, I don't believe there is an inherent "if" statement, per-say, so if you wanted to use the query to optionally handle your "Has" cases, it could be done with an "Or Step", an "And Step", or a "Choose Step". I would discourage it as a pattern, though. It is similar to how MySQL has the options to handle cases in Select queries: it would almost always be more performant and a better separation of concerns to bind the parameters right in the first place than to have the query builder sort out the programmatic logic.
Related
I'm working on a gremlin query that navigates along several edges and eventually produces a String. Depending on the graph content, this traversal may be empty. In case that the traversal ends up being empty, I want to return a default value instead.
Here's what I am currently doing:
GraphTraversal<?, ?> traversal = g.traversal().V().
// ... fairly complex navigation here...
// eventually, we arrive at the target vertex and use its name
.values("name")
// as we don't know if the target vertex is present, lets add a default
.union(
identity(), // if we found something we want to keep it
constant("") // empty string is our default
)
// to make sure that we do not use the default if we have a value...
.order().by(s -> ((String)s).length(), Order.decr)
.limit(1)
This query works, but it is fairly convoluted - all I want is a default if the traversal ends up not finding anything.
Does anybody have a better proposal? My only restriction is that it has to be done within gremlin itself, i.e. the result must be of type GraphTraversal.
You can probably use coalesce() in some way:
gremlin> g.V().has('person','name','marko').coalesce(has('person','age',29),constant('nope'))
==>v[1]
gremlin> g.V().has('person','name','marko').coalesce(has('person','age',231),constant('nope'))
==>nope
If you have more complex logic in mind for determining if something is found or not then consider choose() step.
Lets say we have a message containing ID of some record in the database
message Record {
uint64 id = 1;
}
We also have an rpc call that returns all of the rows from table DATA that said record is mentioned in.
rpc GetDataForRecord(Record) returns (Data) {}
If we, for example, wrap Record in
RqData{
Record id = 1;
}
then once we need to only return, for example, "active" data, we won't need to make
GetActiveDataForRecord
instead we could extend RqData as:
RqData{
Record id = 1;
bool use_active = 2;
}
and use
rpc GetDataForRecord(RqData) returns (Data) {}
and clients that know of this new functionality will be able to call it, while older clients will just use it as it was passing only Record part within the Rq wrapper, without specifying active or not.
Here's the question: is there really a reason to use this kind of wrapping of everything into a separate request, or am I overthinking things and just passing plain structures will do?
I am kinda trying to think about the future, but not sure if I am not overcomplicating things.
In general, making a method-specific request and response is a Good Thing™ and is encouraged. For a Foo method you'd have FooRequest and FooResponse. Having specialized messages for the method allows you to add new "arguments," as you mentioned.
But for some cases it turns out fine to break the pattern and avoid the wrapping; it's a judgement call. Although you're asking from a different perspective, you may be interested in this answer about related methods.
I recognized that (insert/delete)-XQueries executed with the BaseX client always returning an empty string. I find this very confusing or unintuitive.
Is there a way to find out if the query was "successful" without querying the database again (and using potentially buggy "transitive" logic like "if I deleted a node, there must be 'oldNodeCount-1' nodes in the XML")?
XQuery Update statements do not return anything -- that's how they are defined. But you're not the only one who does not like those restrictions, and BaseX added two ways around this limitation:
Returning Results
By default, it is not possible to mix different types of expressions
in a query result. The outermost expression of a query must either be
a collection of updating or non-updating expressions. But there are
two ways out:
The BaseX-specific update:output() function bridges this gap: it caches the results of its arguments at runtime and returns them after
all updates have been processed. The following example performs an
update and returns a success message:
update:output("Update successful."), insert node <c/> into doc('factbook')/mondial
With the MIXUPDATES option, all updating constraints will be turned off. Returned nodes will be copied before they are modified by
updating expressions. An error is raised if items are returned within
a transform expression.
If you want to modify nodes in main memory, you can use the transform
expression.
The transform expression will not help you, as you seem to modify the data on disk. Enabling MIXUPDATES allows you to both update the document and return something at the same time, for example running something like
let $node := <c/>
return ($node, insert node $node into doc('factbook')/mondial)
MIXUPDATES allows you to return something which can be further processed. Results are copied before being returned, if you run multiple updates operations and do not get the expected results, make sure you got the concept of the pending update list.
The db:output() function intentionally breaks its interface contract: it is defined to be an updating function (not having any output), but at the same time it prints some information to the query info. You cannot further process these results, but the output can help you debugging some issues.
Pending Update List
Both ways, you will not be able to have an immediate result from the update, you have to add something on your own -- and be aware updates are not visible until the pending update list is applied, ie. after the query finished.
Compatibility
Obviously, these options are BaseX-specific. If you strongly require compatible and standard XQuery, you cannot use these expressions.
I recognized that (insert/delete)-XQueries executed with the BaseX client always returning an empty string. I find this very confusing or unintuitive.
Is there a way to find out if the query was "successful" without querying the database again (and using potentially buggy "transitive" logic like "if I deleted a node, there must be 'oldNodeCount-1' nodes in the XML")?
XQuery Update statements do not return anything -- that's how they are defined. But you're not the only one who does not like those restrictions, and BaseX added two ways around this limitation:
Returning Results
By default, it is not possible to mix different types of expressions
in a query result. The outermost expression of a query must either be
a collection of updating or non-updating expressions. But there are
two ways out:
The BaseX-specific update:output() function bridges this gap: it caches the results of its arguments at runtime and returns them after
all updates have been processed. The following example performs an
update and returns a success message:
update:output("Update successful."), insert node <c/> into doc('factbook')/mondial
With the MIXUPDATES option, all updating constraints will be turned off. Returned nodes will be copied before they are modified by
updating expressions. An error is raised if items are returned within
a transform expression.
If you want to modify nodes in main memory, you can use the transform
expression.
The transform expression will not help you, as you seem to modify the data on disk. Enabling MIXUPDATES allows you to both update the document and return something at the same time, for example running something like
let $node := <c/>
return ($node, insert node $node into doc('factbook')/mondial)
MIXUPDATES allows you to return something which can be further processed. Results are copied before being returned, if you run multiple updates operations and do not get the expected results, make sure you got the concept of the pending update list.
The db:output() function intentionally breaks its interface contract: it is defined to be an updating function (not having any output), but at the same time it prints some information to the query info. You cannot further process these results, but the output can help you debugging some issues.
Pending Update List
Both ways, you will not be able to have an immediate result from the update, you have to add something on your own -- and be aware updates are not visible until the pending update list is applied, ie. after the query finished.
Compatibility
Obviously, these options are BaseX-specific. If you strongly require compatible and standard XQuery, you cannot use these expressions.
I have a simple task ahead of me, yet I find myself pretty much incapable of completing it.
My model is pretty complex, so I'll try to simplify for the sake of being specific.
I have two entities, Call and Caller, with entity repositories that I access via custom services. I am using JMS Serializer Bundle. All entities are mapped correctly, the the whole thing is working pretty fine. But I have this idea that I just can't make happen. To the point...
These are my entities described:
--Call--
#call_id
location
date_created
Caller
--Caller--
#caller_id
phone_num
The idea is to have a list of all calls with their fields for example:
New York, 2015.12.12. 20:07:06, Novak Djokovic, 3816976548 [YY]
London, 2015.12.13. 20:07:06, Jelena Jankovic, 3811116333 [XX]
Fields YY and XX represent the number of calls already in a database with that specific number.
I have a query that returns the list without YY and XX values, and I also have a separate query that returns number of calls from a specific number. The thing gets complicated when I try to join them. Not sure how to do that.
I read about VirtualProperty annotation for JMS, but failed to actually see how to use it this time (since it's not a good practice to access your repository or service from an Entity).
These are my methods:
1 - get list of all calls with callers
public function findAllCalls()
{
$callerAlias = "c";
//getAlias method returns the alias of the current entity (call)
$qb = $this->createQueryBuilder($this->getAlias());
$qb->leftJoin($this->getAlias() . '.caller', $callerAlias);
return $qb->getQuery()->getResult();
}
2 - get number of calls from a specific number based on a call as a parameter
public function getNumberOfCalls(Call $call) {
$callerAlias = "c";
$qb = $this->createQueryBuilder($this->getAlias());
$qb->leftJoin($this->getAlias() . '.caller', $callerAlias);
$qb->select("COUNT(" . $this->getAlias() . ".call_id)");
$qb->where($callerAlias.".phonenbr = ".$call->getPhoneNumber());
return $qb->getQuery()->getScalarResult();
}
Hoping to hear your opinions on this, 'cause I really struggled to find the sollution.