I use a query scope that should append a orWhere condition but it appends a normal where (with AND). If I remove the query scope and put it directly it does return the OR condition.
The query looks like this:
$query = MyModel::firstQueryScope()->secondQueryScope();
FirstQueryScope:
public function scopeFirstQueryScope($query){
return $query->where('someAttribute', 0);
}
Second Query Scope:
public function scopeSecondQueryScope($query){
return $query->orWhere('someOtherAttribute', 0);
}
Building the query this way should be the same as:
$query = MyModel::where('someAttribute', 0)->orWhere('someOtherAttribute', 0);
But the first approach returns this query:
select * from MyModelTable where someAttribute = 0 AND (someOtherAttribute = 0).
The second approach returns:
select * from MyModelTable where someAttribute = 0 OR someOtherAttribute = 0
I've have notice two things:
No matter in what order you call FirstQueryScope or SecondQueryScope, it always surrounds the orWhere scope with parenthesis.
The orWhere method only ads the parenthesis when called from the query scope.
What am I doing wrong? or can this be an Eloquent's bug?
Note: the query that I'm building is more elaborated but I've break it down to this simplicity to verify that I'm not doing anything strange.
Update
I try to debug Eloquent's building process and found that it is adding a nested where and inside it the orWhere scope. Again this only happens when putting the orWhere inside an scope but does not happen when adding a simple where. The nested where is the responsable for surrounding the orWhere expression within parenthisis.
I think scopes are isolated by itself.
Simply add a third scope.
public function scopeFirstQueryOrSecondQuery($query){
return $query->where('someAttribute', 0)
->orWhere('someOtherAttribute', 0);
}
-
MyModel::firstQueryOrSecondQuery()->get();
Related
Since we upgraded from Hibernate Search 5.11 to Hibernate Search 6 we are having problems with Scrollable results
When we get a chunk of hits from the SearchScroll object each hit is stored in an Arrays.ArrayList
What we expected is that each chunk hits would be an ArrayList of say for example of type long
What we get is an ArrayList where where each hit is an Arrays.ArrayList with the Long value
Current code
SearchScroll scroll = searchSession
.search(scope)
.select(projectionArray)
.where(searchPredicate)
.sort(getSort(resultType))
.scroll(20);
Old code with Hibernate Search 5
FullTextQuery fullTextQuery = fullTextSession
.createFullTextQuery(query, resultType)
.setSort(getSort(resultType));
fullTextQuery.setProjection(fields);
ScrollableResults scrollableResults = fullTextQuery.scroll();
Any suggestions welcome
At worst we can loop through the results and convert the Arrays.ArrayList item to a long but cannot find a way to make that work either
The acual search results are correct just coming back in a different format that what we expect
Changing the code to
SearchScroll<Long> scroll = searchSession
.search(scope)
.select(projectionArray)
.where(searchPredicate)
.sort(getSort(resultType))
.scroll(20);
Makes no difference which seems to match the example in the docs
try ( SearchScroll<Book> scroll = searchSession.search(
Book.class )
.where( f -> f.matchAll() )
.scroll( 20 ) ) {
for ( SearchScrollResult<Book> chunk = scroll.next();
chunk.hasHits(); chunk = scroll.next() ) {
for ( Book hit : chunk.hits() ) {
// ... do something with the hits ...
}
totalHitCount = chunk.total().hitCount();
entityManager.flush();
entityManager.clear();
}
}
Not sure if the projection is what is causing the problem
Tested further if I remove the projection I get the results as an ArrayList of the object as expected so obviously I am doing something wrong with the use of projections in Hibernate Search 6
Without projection everything is good
With projection the results are Arrays.ArrayList
If I understand correctly, you are surprised that you get a List for each hit instead of just a Long.
First, I would recommend that you don't ignore raw type warnings.
I'll wager that your projectionArray is defined this way:
SearchProjection[] projectionArray = new SearchProjection[1];
That's wrong because you're using a "raw" type for SearchProjection, which basically disables all kinds of type-checking for all the code afterwards.
The correct way of defining that array is as follows:
SearchProjection<?>[] projectionArray = new SearchProjection<?>[1];
If you do that, then you'll get a compile-time error with the following code, telling you something like "cannot convert SearchScroll<List<?>> to SearchScroll<Long>":
SearchScroll<Long> scroll = searchSession
.search(scope)
.select(projectionArray)
.where(searchPredicate)
.sort(getSort(resultType))
.scroll(20);
Now, the reason you're getting a SearchScroll<List<?>> is you're passing an array of projections to .select(), so you're calling this method from SearchQuerySelectStep:
SearchQueryWhereStep<?, List<?>, LOS, ?> select(SearchProjection<?>... projections);
This method takes an array of projections as an argument, and (ultimately) returns a query whose hits are lists, with the results of requested projections in the same order as your array of projections.
You want to call that method instead:
<P> SearchQueryWhereStep<?, P, LOS, ?> select(SearchProjection<P> projection);
That method takes a single projection as an argument, and (ultimately) returns a query whose hits are directly the result of the requested projection.
To call that method, pass a single projection instead of an array of projections; then you will get the Long values you expect instead of Lists:
SearchProjection<Long> projection = ...;
SearchScroll<Long> scroll = searchSession
.search(scope)
.select(projection)
.where(searchPredicate)
.sort(getSort(resultType))
.scroll(20);
I am using Groovy Sql in Grails with named parameters to get results from a Postgres DB. My statement is generated dynamically, i.e. concatenated to become the final statement, with the params being added to a map as I go along.
sqlWhere += " AND bar = :namedParam1"
paramsMap.namedParam1 = "blah"
For readability, I am using the groovy string syntax which allows me to write my sql statement over multiple lines, like this:
sql = """
SELECT *
FROM foo
WHERE 1=1
${sqlWhere}
"""
The expression is evaluated as a string containing the linebreaks as \n:
SELECT *\n ...
This is not a problem when I pass params like this
results = sql.rows(sqlString, paramsMap)
but it does become one if paramsMap is empty (which happens since AND bar = :namedParam1 is not always concatenated into the query). I then get an error
org.postgresql.util.PSQLException: No hstore extension installed
which does not really seem to relate to the true nature of the problem. I have for now fixed this with an if...else
if (sqlQuery.params.size() > 0) {
results = sql.rows(sqlString, paramsMap)
} else {
results = sql.rows(sqlString.replace('\n',' '))
}
But this seems a bit weird (especially since it does not work if I use the replace in the if-branch as well).
My question is: why do I really get this error message and is there a better way to prevent it from occuring?
It's certainly a bug in groovy.sql.SQL implementation. The method rows() can't deal with an empty map passed as params. As a workaround, you can test for it and pass an empty list instead.
def paramsMap = [:]
...
if (paramsMap.isEmpty())
paramsMap= []
Issue created at https://issues.apache.org/jira/browse/GROOVY-8082
This is the query I am trying to run in PostgreSQL:
SELECT * FROM message WHERE id IN (
SELECT unnest(message_ids) "mid"
FROM session_messages WHERE session_id = '?' ORDER BY "mid" ASC
);
However, I am not able do something:
create.selectFrom(Tables.MESSAGE).where(Tables.MESSAGE.ID.in(
create.select(DSL.unnest(..))
Because DSL.unnest is a Table<?>, which makes sense since it is trying to take a List-like object (mostly a literal) and convert it to table.
I have a feeling that I need to find a way to wrap the function around my field name, but I have no clue as to how to proceed.
NOTE. The field message_ids is of type bigint[].
EDIT
So, this is how I am doing it now, and it works exactly as expected, but I am not sure if this is the best way to do it:
Field<Long> unnestMessageIdField = DSL.field(
"unnest(" + SESSION_MESSAGES.MESSAGE_IDS.getName() + ")",
Long.class)
.as("mid");
Field<Long> messageIdField = DSL.field("mid", Long.class);
MESSAGE.ID.in(
ctx.select(messageIdField).from(
ctx.select(unnestMessageIdField)
.from(Tables.CHAT_SESSION_MESSAGES)
.where(Tables.CHAT_SESSION_MESSAGES.SESSION_ID.eq(sessionId))
)
.where(condition)
)
EDIT2
After going through the code on https://github.com/jOOQ/jOOQ/blob/master/jOOQ/src/main/java/org/jooq/impl/DSL.java I guess the right way to do this would be:
DSL.function("unnest", SQLDataTypes.BIGINT.getArrayType(), SESSION_MESSAGES.MESSAGE_IDS)
EDIT3
Since as always lukas is here for my jOOQ woes, I am going to capitalize on this :)
Trying to generalize this function, in a signature of sort
public <T> Field<T> unnest(Field<T[]> arrayField) {
return DSL.function("unnest", <??>, arrayField);
}
I don't know how I can fetch the datatype. There seems to be a way to get DataType<T[]> from DataType<T> using DataType::getArrayDataType(), but the reverse is not possible. There is this class I found ArrayDataType, but it seems to be package-private, so I cannot use it (and even if I could, it does not expose the field elementType).
Old PostgreSQL versions had this funky idea that it is OK to produce a table from within the SELECT clause, and expand it into the "outer" table, as if it were declared in the FROM clause. That is a very obscure PostgreSQL legacy, and this example is a good chance to get rid of it, and use LATERAL instead. Your query is equivalent to this one:
SELECT *
FROM message
WHERE id IN (
SELECT "mid"
FROM session_messages
CROSS JOIN LATERAL unnest(message_ids) AS t("mid")
WHERE session_id = '?'
);
This can be translated to jOOQ much more easily as:
DSL.using(configuration)
.select()
.from(MESSAGE)
.where(MESSAGE.ID).in(
select(field(name("mid"), MESSAGE.ID.getDataType()))
.from(SESSION_MESSAGES)
.crossJoin(lateral(unnest(SESSION_MESSAGES.MESSAGE_IDS)).as("t", "mid"))
.where(SESSION_MESSAGES.SESSION_ID.eq("'?'"))
)
The Edit3 in the question is quite close to a decent solution for this problem.
We can create a custom generic unnest method for jOOQ which accepts Field and use it in jOOQ query normally.
Helper method:
public static <T> Field<T> unnest(Field<T[]> field) {
var type = (Class<T>) field.getType().getComponentType();
return DSL.function("unnest", type, field);
}
Usage:
public void query(SessionId sessionId) {
var field = unnest(SESSION_MESSAGES.MESSAGE_IDS, UUID.class);
dsl.select().from(MESSAGE).where(
MESSAGE.ID.in(
dsl.select(field).from(SESSION_MESSAGES)
.where(SESSION_MESSAGES.SESSION_ID.eq(sessionId.id))
.orderBy(field)
)
);
}
I'm trying to replace a function call like (simplified) Utility.GetString(MyEntity.SomePropertyWithRelatedEntity)=="abc" with an expression visitor into something like p => p.SubRelatedEntities.FirstOrDefault(sre => sre.SomeFlag==true).SomePropertyWithRelatedEntity.
It means, the datamodel goes like:
MyEntity -> RelatedEntity -> SubRelatedEntity
I'm trying to return a string value from the SubRelatedEntity, based on some rules in the RelatedEntity, so I don't have to re-write / copy/paste the whole filtering rules in every usage; that's why I put inside a "call-signature", so my expression visitor can identify it and replace the fake-call to Utility.GetString to some complicated lambda expressions.
My expression visitor contains something like:
public override Expression Visit(Expression node)
{
if (node == null)
return null;
Expression result = null;
if (node.NodeType == ExpressionType.Call)
{
MethodCallExpression mce = node as MethodCallExpression;
if (mce.Method.DeclaringType == typeof(Utility) && mce.Method.Name == "GetString")
{
Expression<Func<RelatedEntity, string>> exp = re => re.SubRelatedEntities.FirstOrDefault(sre => sre.SomeFlag == true).SomeStringValue;
result = exp.Body;
}
else
result = base.Visit(node);
}
else
result = base.Visit(node);
return result;
}
Now, the problem is, the "sre" parameter is not bound when called the injected lambda expression. After much research, I see the lambda parameters should be replaced with another expression visitor, specifically searching for the new parameters and replacing them with the old ones. In my situation, however, I don't have an "old parameter" - I have the expression MyEntity.SomePropertyWithRelatedEntity (e.g. an property filled with the related entities) which I need to insert somehow in the generated lambda.
I hope my problem is understandable. Thank you for any insights!
After getting no answers for long time and trying hard to find a solution, I've solved it at the end :o)! It goes like this:
The newly injected lambda expression gets an ParameterExpression - well, this is a 'helper', used when directly calling the lambda, what I don't want (hence, 'parameter not bound' exception when ToEnumerable is called). So, the clue is to make a specialized ExpressionVisitor, which replaces this helper with the original expression, which is of course available in the Arguments[] for the method call, which I try to replace.
Works like a charm, like this you can reuse the same LINQ expressions, something like reusable sub-queries, instead of writing all the same LINQ stuff all time. Notice as well, that expression calling a method is not allowed in EF, in Linq2Sql it worked. Also, all the proposed web articles only replace the parameter instances, when constructing/merging more LINQ expressions together - here, I needed to replace a parameter with an faked-method-call argument, e.g. the method should not be called, it only stands for a code-marker, where I need to put my LINQ sub-query.
Hope this helps somebody, at the end it's pretty simple and logical, when one knows how the expression trees are constructed ;-).
Bye,
Andrej
I'm trying to use a select object to filter the results of a many to many rowset. This call works great:
$articles = $this->model->findArticlesViaArticlesUsers();
This however does not:
$articles = new Default_Model_Articles();
$articleSelect = $articles->select();
$articleSelect->where("status = 'published'")
->order("date_published DESC")
->limit(1);
$articles = $this->model->findArticlesViaArticlesUsers($articleSelect);
That throws the following error:
exception 'Zend_Db_Select_Exception'
with message 'You cannot define a
correlation name 'i' more than once'
I can't figure out how to successfully get "articles that have the status of 'published'" using the magic many-to-many relationship (nor findManyToManyRowset). I'm at the end of my rope and thinking of just writing the sql manually. Any ideas?
When defining the select statement, you must use the same object that you call findManyToManyRowset (or whatever magic function you use) on.
Ex:
$articles = new Default_Model_Articles();
$user = $articles->find($userId)->current();
$select = $user->select();
$select->where('status = ?', 'published');
$articles = $user->findArticlesViaArticlesUsers($select);
Notice the select statement and findArticlesViaArticlesUsers are both extending $user. Thats the key.
I think you've misunderstood how the relationships work.
See this manual page - you should call the magic method, findArticlesViaArticlesUsers, on a Row object. In this case, I think you want to find a User, and then call findArticles... on that.