grails gorm mongodb `like` functionality in criteria - grails

Is like or rlike supported for searching a string in a collection's property value?
Does the collection need to define text type index for this to work? Unfortunately I can not create a text index for the property. There are 100 million documents and text index killed the performance (MongoDB is on single node). If this is not do-able without text index, its fine with me. I will look for alternatives.
Given below collection:
Message {
'payload' : 'XML or JSON string'
//few other properties
}
In grails, I created a Criteria to return me a list of documents which contain a specific string in the payload
Message.list {
projections {
like('payload' : searchString)
}
}
I tried using rlike('payload' : ".*${searchString}.*") as well. It did not result in any doc to me.
Note: I was able to get the document when I fired the native query on Mongo shell.
db.Message.find({payload : { $regex : ".*My search string.*" }}).pretty()

I got it working in a round about way. I believe there is a much better grails solution. Criteria approach did not work. So used the low level API converted the DBObjects to Domain objects.
def query = ['payload' : [ '$regex' : /${searchString}/ ] ]
def dbObjects = Message.collection.find(query).skip(offset).limit(defaultPageSize).toArray()
dbObjects?.collect { new Message(new JsonSlurper().parseText(it.toString()))}

Related

Neo4j+PopotoJS: filter graph based-on predefined constraints

I have a question about the query based on the predefined constraints in PopotoJs. In this example, the graph can be filtered based on the constraints defined in the search boxes. The sample file in this example visualizations folder, constraint is only defined for "Person" node. It is specified in the sample html file like the following:
"Person": {
"returnAttributes": ["name", "born"],
"constraintAttribute": "name",
// Return a predefined constraint that can be edited in the page.
"getPredefinedConstraints": function (node) {
return personPredefinedConstraints;
},
....
In my graph I would like to apply that query function for more than one node. For example I have 2 nodes: Contact (has "name" attribute) and Delivery (has "address" attribute)
I succeeded it by defining two functions for each nodes. However, I also had to put two search box forms with different input id (like constraint1 and constraint2). And I had to make the queries in the associated search boxes.
Is there a way to make queries which are defined for multiple nodes in one search box? For example searching Contact-name and/or Delivery-adress in the same search box?
Thanks
First I’d like to specify that the predefined constraints feature is still experimental (but fully functional) and doesn’t have any documentation yet.
It is intended to be used in configuration to filter data displayed in nodes and in the example the use of search boxes is just to show dynamically how it works.
A common use of this feature would be to add the list of predefined constraint you want in the configuration for every node types.
Let's take an example:
With the following configuration example the graph will be filtered to show only Person nodes having "born" attribute and only Movie nodes with title in the provided list:
"Person": {
"getPredefinedConstraints": function (node) {
return ["has($identifier.born)"];
},
...
}
"Movie": {
"getPredefinedConstraints": function (node) {
return ["$identifier.title IN [\"The Matrix\", \"The Matrix Reloaded\", \"The Matrix Revolutions\"]"];
},
...
}
The $identifier variable is then replaced during query generation with the corresponding node identifier. In this case the generated query would look like this:
MATCH (person:`Person`) WHERE has(person.born) RETURN person
In your case if I understood your question correctly you are trying to use this feature to implement a search box to filter the data. I'm still working on that feature but it won't be available soon :(
This is a workaround but maybe it could work in your use case, you could keep the search box value in a variable:
var value = d3.select("#constraint")[0][0].value;
inputValue = value;
Then use it in the predefined constraint of all the nodes type you want.
In this example Person will be filtered based on the name attribute and Movie on title:
"Person": {
"getPredefinedConstraints": function (node) {
if (inputValue) {
return ["$identifier.name =~ '(?i).*" + inputValue + ".*'"];
} else {
return [];
}
},
...
}
"Movie": {
"getPredefinedConstraints": function (node) {
if (inputValue) {
return ["$identifier.title =~ '(?i).*" + inputValue + ".*'"];
} else {
return [];
}
},
...
}
Everything is in the HTML page of this example so you can view the full source directly on the page.
#Popoto, thanks for the descriptive reply. I tried your suggestion and it worked pretty much well. With the actual codes, when I make a query it was showing only the queried node and make the other node amount zero. I wanted to make a query which queries only the related node while the number of other nodes are still same.
I tried a temporary solution for my problem. What I did is:
Export the all the node data to JSON file, search my query constraint in the exported JSONs, if the file is existing in JSON, then run the query in the related node; and if not, do nothing.
With that way, of course I needed to define many functions with different variable names (as much as the node amount). Anyhow, it is not a propoer way, bu it worked for now.

How to use must_not with an empty JSON attribute with ElasticSearch + Grails?

I'm using Grails plugin to work with ElasticSearch over MySQL. I have a domain column mapped in my domain class as follows:
String updateHistoryJSON
(...)
static mapping = {
updateHistoryJSON type: 'text', column: 'update_history'
}
In MySQL, this basically maps to a TEXT column, which purpose is to store JSON content.
So, in both DB and ElasticSearch index, I have 2 instances:
- instance 1 has updateHistoryJSON = '{"zip":null,"street":null,"name":null,"categories":[],"city":null}'
- instance 2 has updateHistoryJSON = '{}'
Now, what I need is an ElasticSearch query that returns only instance 2.
I've been doing a closure like this, using Groovy DSL:
{
bool {
must_not = term(updateHistoryJSON: "{}")
minimum_should_match = 1
}
}
And ElasticSearch seems to ignore it, it keeps bringing back both instances.
On the other hand, if I use a filter like "missing":{"field":"updateHistoryJSON"}, it gives back no documents. The same goes for "exists": {"field":"updateHistoryJSON"}.
Any idea about what am I doing wrong here?
I'm still not sure about what was the problem, but at least I found a workaround.
Since the search based on updateHistoryJSON contents was not working, I decided to use a script to search based on updateHistoryJSON contents size, meaning, instead of looking for documents that had a non-empty JSON, I just look for documents which updateHistoryJSON size is greater than 2 ({} == size 2).
The closure I used is like this:
{script = {
script = "doc['updateHistoryJSON'].size() > 2"
}

Groovy name queried

I got a domain like this:
ZZPartAndTeam
String parts
String team
Parts may have many team.
For ex: part:part1 team:10
part:part1 team:20
part:part2 team:30
How can I query in the domain that get all parts which have multi team?
result:part:part1 team:10
part:part1 team:20
Thanks.
The HAVING clause is not supported by Hibernate Criteria. A way around is to use DetachedCriteria.
import org.hibernate.criterion.DetachedCriteria as HDetachedCriteria
query: { builder, params ->
// This query counts the number of teams per part
HDetachedCriteria innerQry = HDetachedCriteria.forClass(ZZPartAndTeam.class)
innerQry.setProjection(Projections.projectionList()
.add(Projections.count('team').as('teamCount'))
)
innerQry.add(HRestrictions.eqProperty('part', 'outer.part')
// Using innerQuery, this criteria returns the parts having more than one team.
HDetachedCriteria outerQry = HDetachedCriteria.forClass(ZZPartAndTeam.class, 'outer')
outerQry.setProjection(Projections.projectionList()
.add(Projections.distinct(Projections.property('part').as('part')))
)
outerQry.add(Subqueries.gt(1, innerQry))
builder.addToCriteria(Property.forName('part').in(outerQry))
}

how to use ilike with Integer in grails

I use EasyGrid plugin and must find values where integer field like '%001%'
initialCriteria {
ilike('id', "%"+params.id+"%")
}
But ilike doesn't work with Integer. How to do it?
I tried to do:
initialCriteria {
ilike('id'.toString(), "%"+params.id+"%")
}
initialCriteria {
ilike('str(id)', "%"+params.id+"%")
}
but it's not work.
If id is an integer in the database, then ilike doesn't really make much sense and there is probably a better way to do what you are trying to do (like adding a type field or something to the domain object, and filter by type)
However, you should be able to do something like this (untested):
initialCriteria {
sqlRestriction "cast( id AS char( 256 ) ) like '%001%'"
}
Following criteria not working if you search from your textbox when user search any text character by mistake like 12dfdsf as your searchable id. It will give you an exception
initialCriteria {
ilike('id', "%"+params.id+"%")
}
For better use you can use following criteria
initialCriteria {
sqlRestriction "id like '%${params?.id}%'"
}
You could do:
String paddedId = params.id.toString().padLeft(3,'0')
initialCriteria {
ilike('id', "%$paddedId%")
}
The solution offered by tim_yates with the sqlRestriction would work in version 1.5.0 of easygrid.
One of the main differences from 1.4.x is that the gorm datasource no longer uses DetachedCriteria, but Criteria - which maps directly to Hibernate's Criteria API.
So you can try it on the last version.
(Keep in mind that the upgrade might break your existing grids. There's also many other changes)
Another small observation is that 'initialCriteria' is not the right place to do stuff like that. (it's not wrong, but there is a 'globalFilterClosure' property for applying column independent filters)
I mixed the code posted by #tim_yates and mine:
String paddedId = params.id.toString().padLeft(3,'0')
def crit = Book.withCriteria {
sqlRestriction "lpad(cast( id AS char( 256 ) ), 3, '0') like '%${paddedId}%'"
}
I've tried with a h2 in-memory db and it works, but I am not sure about two things:
the real usefulness of that
lpad syntax consistence across all db engines
YMMV

Storing graph-like structure in Couch DB or do include_docs yourself

I am trying to store network layout in Couch DB, but my solution provides rather randomized graph.
I store a nodes with a document:
{_id ,
nodeName,
group}
and storing links in traditional:
{_id, source_id, target_id, value}
Following multiple tutorials on handling joins and multiple relationship in Couch DB I created view:
function(doc) {
if(doc.type == 'connection') {
if (doc.source_id)
emit("source", {'_id': doc.source_id});
if(doc.target_id)
emit("target", {'_id': doc.target_id});
}
}
which should have emitted sequence of source and target id, then I pass it to the list function with include_docs=true, assumes that source and target come in pairs stitches everything back in a structure like this:
{
"nodes":[
{"nodeName":"Name 1","group":"1"},
{"nodeName":"Name 2","group":"1"},
],
"links": [
{"source":7,"target":0,"value":1},
{"source":7,"target":5,"value":1}
]
}
Although my list produce a proper JSON, view map returns number of rows of source docs and then target docs.
So far I don't have any ideas how to make this thing working properly - I am happy to fetch additional values from document _id in the list, but so far I havn't find any good examples.
Alternative ways of achieving the same goal are welcome. _id values are standard for CouchDB so far.
Update: while writing a question I came up with different view which sorted my immediate problem, but I still would like to see other options.
updated map:
function(doc) {
if(doc.type == 'connection') {
if (doc.source_id)
emit([doc._id,0,"source"], {'_id': doc.source_id});
if(doc.target_id)
emit([doc._id,1,"target"], {'_id': doc.target_id});
}
}
Your updated map function makes more sense. However, you don't need 0 and 1 in your key since you have already "source"and "target".

Resources