Apache Lucene Query Iterate through all queries / terms - java

I have an interface that enforces implementing classes to define and implement a function that returns an org.apache.lucene.search.Query. These classes create various queries like TermQuery, PhraseQuery, etc. Is it possible to take the org.apache.lucene.search.Query that gets returned and iterate over all of the queries and terms it's comprised of?
public interface BaseQuery {
public Query getQuery();
default Query toQuery() {
Query query = getQuery();
// iterate through Query and do things to each term
return query;
}
}
public class ContainsQuery implements BaseQuery {
#Override
protected Query getQuery() {
PhraseQuery.Builder queryBuilder = new PhraseQuery.Builder();
queryBuilder.add(new Term("field", "value");
return queryBuilder.build();
}
}

As you can't update the changes (setTerms or similar), not even in this implementation (PhraseQuery), maybe this works.
You could first retrieve them, and loop over them. Whatever modification you wish to do, update the term or create a new one, or even discard those unwanted.
Then, assign query to a newly constructed object with the modified terms. Something like a manual update/set for a Query object. In the example I just add the terms to the builder, but you could include the previous parameters as well (slop, ...).
default Query toQuery()
{
Query query = getQuery();
Term[] terms= query.getTerms();
List<Term> modifiedTerms = new ArrayList<>();
for (Term t : terms)
{
/*Your modifications here f.e --> you copy one, create two terms and discard one
Term toAdd=null;
toAdd= t.clone();
...
toAdd = new Term((t.field()+"suffix"), t.text());
...
toAdd = new Term("5","6");
...
(do nothing-discard)
if (toAdd!=null)
modifiedTerms.add(toAdd);*/
}
PhraseQuery.Builder builder = new PhraseQuery.Builder();
for (int i=0;i<modifiedTerms.size();i++)
builder.add(modifiedTerms.get(i),i);
query = builder.build();
return query;
}
/* override the reference and assign a new one very similar to s
set/update, if those where implemented.
The last query will be erased on the next GC
so there's not a big gap here. Also this is most surely
a not needed micro-optimization, no worries. */

The way to do this is using the QueryTermExtractor.
WeightedTerm[] terms = QueryTermExtractor.getTerms(query);
for (WeightedTerm term : terms) {
System.out.println("THE TERM: " + term.getTerm());
}
The issue I was having is that all examples I found were calling .getTerms() on a org.apache.lucene.search.Query but .getTerms() seems to no longer be implemented on the base Query class.
Also, #aran's suggested approach of constructing a new Query object is an appropriate method to "modifying" the terms on an already constructed immutable Query object.

Related

How to return a list of results in jOOQ Mocked Data

I'm using jOOQ's MockDataProvider to mock calls to the database. I've figured out how to return a single record using the information here: https://blog.jooq.org/2013/02/20/easy-mocking-of-your-database/
However, I want to return a list of results not just a single record for my query. How do I do that?
I can use the following (from the above link) to return a single result:
return new MockResult[] {
new MockResult(1, result)
};
However, I cannot figure out how to add multiple results, all of the constructors for MockResult only take a single result. Any hints? Am I missing something obvious?
For example if I query all bicycles that are road bikes:
SELECT * FROM bicycles WHERE type = "road";
how do I return a list of 10 bicycles instead of just one?
I can use the following (from the above link) to return a single result
But that's already it. You return a single result with several records. The result you pass to that MockResult constructor could look like this:
var result = ctx.newResult(BICYCLES.COL1, BICYCLES.COL2);
result.add(ctx.newRecord(BICYCLES.COL1, BICYCLES.COL2).values(1, 2));
result.add(ctx.newRecord(BICYCLES.COL1, BICYCLES.COL2).values(3, 4));
...

Efficient way of mimicking hibernate criteria on cached map

I have just wrote a code to cach a table in the memory (simple java hashmap). Now one of the code that i am trying to replace is the find the objects based on criteria. it receives multiple field parameters and if those fields are not empty and not null, they were being added as part of hibernate query criteria.
To replace this, what i am thinking to do is
For each valid param (not null and no empty) I will create a HashSet which will satisfy this criteria.
Once i am done making hashsets for all valid criteria, I will call Set.retainAll(second_set) on all sets. So that at the end, I will have only that set which is intersection of all valid criteria.
Does it sound like the best approach or is there any better way to implement this ?
EDIT
Though, My original post is still valid and I am looking for that answer. I ended up implementing it in the following way. The reason is that it was kind a cumbersome with sets since after creating all sets, I had to first figure out which set is non empty so that the retainAll could be called. it was resulting in lots of if-else statements. My current implementation is like this
private List<MyObj> getCachedObjs(Long criteria1, String criteria2, String criteria3) {
List<MyObj> results = new ArrayList<>();
int totalActiveFilters = 0;
if (criteria1 != null){
totalActiveFilters++;
}
if (!StringUtil.isBlank(criteria2)){
totalActiveFilters++;
}
if (!StringUtil.isBlank(criteria3)){
totalActiveFilters++;
}
for (Map.Entry<Long, MyObj> objEntry : objCache.entrySet()){
MyObj obj = objEntry.getValue();
int matchedFilters = 0;
if (criteria1 != null) {
if (obj.getCriteria1().equals(criteria1)) {
matchedFilters++;
}
}
if (!StringUtil.isBlank(criteria2)){
if (obj.getCriteria2().equals(criteria2)){
matchedFilters++;
}
}
if (!StringUtil.isBlank(criteria3)){
if (game.getCriteria3().equals(criteria3)){
matchedFilters++;
}
}
if (matchedFilters == totalActiveFilters){
results.add(obj);
}
}
return results;
}

What is the best way to read nested object from a join query

Assume I have a model like following
class Chest {
public Id id;
public List<Drawer> drawers;
public Price price;
}
class Drawer {
public Id id;
public Price price;
}
And a JOOQ query to fetch a Chest object with its Drawers:
dsl.selectFrom(CHEST.join(DRAWERS).onKey()).where(CHEST.ID.eq(1)).fetch()
What is the best way to construct the Chest object from the result of the query above?
Thanks.
In general, using JOIN to materialise object graphs won't really work well, as you're denormalising your database entities into a table (with duplicates) before you try to normalise the data again in a mapping algorithm. JPA hides these things from you by offering an alternative query language that doesn't expose so many SQL features.
In your particular case, however, you can get this to run via the jOOQ API by using the Result.intoGroups() methods. Thus:
Map<Record, Result<Record>> result =
dsl.selectFrom(...).fetch().intoGroups(CHEST.fields());
List<Chest> list = new ArrayList<>();
for (Entry<Record, Result<Record>> entry : result.entrySet()) {
Record chest = entry.getKey();
Result<Record> drawers = entry.getValue();
list.add(new Chest(
chest.into(Id.class), // These into(Class<?>) methods assume that you
drawers.into(Drawer.class) // want to use jOOQ's DefaultRecordMapper
));
}
The above algorithm is probably incomplete, or not exactly what you need. But it'll give you a general idea of what's possible out-of-the-box via jOOQ API.

Java create PreparedStatement with no SQL content

Is it possible to create a PreparedStatement in java without setting the initial SQL query?
Example code:
#Override
public List<AccountBean> search(AccountConstraint... c) {
if (c.length == 0) {
throw new IllegalArgumentException("dao.AccountDAO.search: c.length == 0");
}
try {
List<AccountBean> beans = new ArrayList<>();
for (AccountConstraint ac : c) {
PreparedStatement ps = connection.prepareStatement(null);
QueryBuilder queryBuilder = new QueryBuilder(ps, "SELECT * FROM accounts");
queryBuilder.add(ac.getAccountIdConstraint());
queryBuilder.add(ac.getUsernameConstraint());
queryBuilder.add(ac.getPasswordConstraint());
queryBuilder.add(ac.getEmailConstraint());
//INSERT QUERY INTO PS
ResultSet rs = ps.executeQuery();
while (rs.next()) {
beans.add(new AccountBean(rs));
}
}
return beans;
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
The trick is in QueryBuilder, this class is responsible for building parts of a query based on the initial SELECT part and then adds respective WHERE and AND clauses.
However to ensure that all data is safe, the actual arguments must also be put in the PreparedStatement, hence why it is being passed to the QueryBuilder.
Every QueryBuilder.add() adds some arguments into the PreparedStatement and appends a specific string to the end of the query.
I think some workarounds are possible, such as instead of giving a PreparedStatement to the QueryBuilder you would give a List<Object> and then you would write a custom function that puts them in the PreparedStatement later on.
But what are your thoughts, suggestions on this?
Regards.
Solution added
Few key changes first:
QueryBuilder now implements the Builder pattern properly.
QueryBuilder.add() accepts multiple Constraints at once.
AccountConstraint can give an array that gives all Constraints now.
#Override
public List<AccountBean> search(AccountConstraint... c) {
if (c.length == 0) {
throw new IllegalArgumentException("dao.AccountDAO.search: c.length == 0");
}
try {
List<AccountBean> beans = new ArrayList<>();
for (AccountConstraint ac : c) {
try (PreparedStatement ps = new QueryBuilder("SELECT * FROM accounts").add(ac.getConstraints()).build();ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
beans.add(new AccountBean(rs));
}
}
}
return beans;
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
ps. I get two statements in one try{ } because of the try-with-resources.
Preparing a statement means compiling it so you can efficiently execute it many times with different arguments. So, no it does not make sense to compile a query before it is defined.
As I understand, you want to use the Java compiler to assist you in dynamically defining the query. Why don't you just create the prepared statement in a compile() method, thus, as the result of your builder. Also, your code becomes more readable and more resembles a declarative query if you use the builder pattern such that each call to add() returns this. Then you can write your query like this:
PreparedStatement ps = new QueryBuilder()
.select("*")
.from("accounts")
.where()
.add(yourConstraint())
...
.compile();
However, you must create the prepared statement before the loop. Otherwise, if you keep a reference to the builder and call compile() in your loop you will get a new prepared statement on every call. So you won't get the benefit of reusing a precompiled query. In the loop you only assign values to the variables in your prepared statement.
You can't modify the prepared statement via the API after you crate it. You can't create it without an SQL statement either.
Why not create the query separately and then bind the parameters? You can use a Map to hold the parameter placeholders and their values so they can be set to the prepared statement.
Although I'd just use the Spring's JDBC templates to get the same thing done more quickly.
How to improve your SQL query builder
If you look at how popular query builders like jOOQ and others do it, the idea is that you separate your concerns more thoroughly. You should have:
An expression tree representation of your SQL statement (and ideally that doesn't directly operate on strings)
A way to construct that expression tree conveniently, e.g. by using a DSL
Some sort of execution lifecycle management that generates the SQL string, prepares the statement, binds the variables, etc.
Or in code (jOOQ example, but this could also apply to your own query builder):
Result<?> result =
// This constructs the expression tree through the jOOQ DSL
ctx.selectFrom(ACCOUNTS)
.where(ac.getAccountIdConstraint())
.and(ac.getUsernameConstraint())
.and(ac.getPasswordConstraint())
.and(ac.getEmailConstraint())
// This internally creates a PreparedStatement, binds variables, executes it, and maps results
.fetch();
Of course, your AccountConstraint.getXYZConstraint() methods would not return SQL string snippets, but again expression tree elements. In the case of jOOQ, this would be a Condition
(Disclaimer: I work for the vendor of jOOQ)
How to improve your SQL performance
I've noticed that you run N queries for N AccountConstraint values, and you mix the results in a way that it doesn't matter which AccountConstraint value produced which AccountBean. I strongly suggest you move that loop into the generated SQL query, as you're going to get much faster results on pretty much every database. I've blogged about this here.

How can I get Toplink generated query by using getTranslatedSQLString?

So what I'm doing is creating a subquery that gets a list of ID values, then the main query gets all the necessary values and adds ordering.
What I have is this:
ReportQuery querySub = new ReportQuery(Predmet.class, generatedExpression);
querySub.addAttribute("m_id");
DatabaseRow row = new DatabaseRow();
querySub.prepareCall(getSession(), row);
// This part is the problem
String sql = querySub.getTranslatedSQLString(getSession(), row);
The problem with this code is that it doesn't return TranslatedSQLString, it returns the same result as querySub.getSQLString(). Now in all the example code I saw, they either instanced row as a new object or didn't bother to write from where they got the reference but whatever the case, this doesn't work (TopLink version issue?). I'm guessing I need to populate the DatabaseRow object myself, but I can't find any example online.
I didn't manage to find any way to do this by using getTranslatedSQLString. I suppose the DatabaseRow needs to be populated, but I have yet to find the proper way. For now, I'm using "bruteforce" substitution, I "memorize" all of my parameters and do a find/replace on each "?" sign in the query.
you need get session like this:
JpaHelper.getDatabaseSession(getEntityManager().getEntityManagerFactory());
The Database that you need is:
TypedQuery<T> typedQuery = getEntityManager().createQuery(cq);
DatabaseQuery databaseQuery = typedQuery.unwrap(JpaQuery.class).getDatabaseQuery();
So the final example is:
Session session = JpaHelper.getDatabaseSession(getEntityManager().getEntityManagerFactory());
DatabaseQuery databaseQuery = null;
if(typedQuery instanceof EJBQueryImpl)
databaseQuery = ((EJBQueryImpl<T>)typedQuery).getDatabaseQuery();
else
databaseQuery = typedQuery.unwrap(JpaQuery.class).getDatabaseQuery();
sql = databaseQuery.getTranslatedSQLString(session, databaseQuery.getTranslationRow());
That work for me.

Categories