I'm trying to execute an expression that returns a set of results where the collection attribute is not empty. Here are my classes
A.class
#Document
public class A {
#Id String id;
String name;
List<B> matches;
}
B.class
#Document
public class B {
String name;
}
My test case
#Test
public void testFindWhereCollectionNotEmpty() {
B b1 = new B();
b1.name = "b1";
B b2 = new B();
b2.name = "b2";
template.save(b1);
template.save(b2);
A a1 = new A();
a1.id = "p1";
a1.matches = Arrays.asList(b1, b2);
A a2 = new A();
a2.id = "a2";
a2.matches = new ArrayList<B>();
A a3 = new A();
a3.id = "a3";
a3.matches = null;
template.save(a1);
template.save(a2);
template.save(a3);
QA qa = QA.a;
BooleanExpression expr = qa.matches.isNotEmpty();
Iterable<A> result = aRepo.findAll(expr);
assertThat(result, is(not((emptyIterable()))));
}
When I execute this test I get the following error:
com.mongodb.MongoException: Can't canonicalize query: BadValue $or needs an array
at com.mongodb.QueryResultIterator.throwOnQueryFailure(QueryResultIterator.java:214)
at com.mongodb.QueryResultIterator.init(QueryResultIterator.java:198)
at com.mongodb.QueryResultIterator.initFromQueryResponse(QueryResultIterator.java:176)
at com.mongodb.QueryResultIterator.<init>(QueryResultIterator.java:64)
at com.mongodb.DBCollectionImpl.find(DBCollectionImpl.java:86)
at com.mongodb.DBCollectionImpl.find(DBCollectionImpl.java:66)
at com.mongodb.DBCursor._check(DBCursor.java:458)
at com.mongodb.DBCursor._hasNext(DBCursor.java:546)
at com.mongodb.DBCursor.hasNext(DBCursor.java:571)
at com.mysema.query.mongodb.MongodbQuery.list(MongodbQuery.java:253)
at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.findAll(QueryDslMongoRepository.java:102)
You can try this for yourself at https://github.com/tedp/spring-querydsl-test
Am I doing something wrong?
This issue has now been fixed in version 3.6.3 of QueryDSL. See https://github.com/querydsl/querydsl/issues/1264 for further info.
Many thanks to all those who were involved in the fix!
Related
I have a class in which I get the number of ids per year with a soql query on a custom object:
public static Integer numberRecords(Integer year) {
List<AggregateResult> numbersOfRecords = [
SELECT Keeper__c
FROM Month_Expense_Application__c
WHERE calendar_year(MonthDate__c) =: year
];
Set<Id> keepers = new Set<Id>();
for (AggregateResult result : numbersOfRecords) {
keepers.add((Id)result.get('Keeper__c'));
}
Integer value = keepers.size();
return value;
}
I'm trying to write a test for this method by creating an object and filling in the fields it needs, then I try to get the id with a soql request and compare:
#isTest
public class ExpenseAdminControllerTests {
#isTest
public static void numberRecordsTests() {
Month_Expense_Application__c monthExpenseTest = new Month_Expense_Application__c(
Balance__c = 12.3,
Income__c = 11,
Keeper__c = '0034x00001K7kGCAAZ',
MonthDate__c = date.newInstance(2022, 11, 21)
);
Integer year = 2022;
List<Month_Expense_Application__c> numbersOfRecords = [
SELECT Keeper__c
FROM Month_Expense_Application__c
WHERE calendar_year(MonthDate__c) =: year AND Id =: monthExpenseTest.Id
];
Set<Id> keepers = new Set<Id>();
keepers.add(numbersOfRecords[0].Keeper__c);
Integer value = keepers.size();
System.assertEquals(1, value);
}
}
But i cant tell me what am i doing wrong
I guess you just forgot to insert "monthExpenseTest" in actual database, by means that you have just created a object by using below lines as you mentioned
Month_Expense_Application__c monthExpenseTest = new Month_Expense_Application__c(
Balance__c = 12.3,
Income__c = 11,
Keeper__c = '0034x00001K7kGCAAZ',
MonthDate__c = date.newInstance(2022, 11, 21)
);
by writing above lines you just created object which is present in memory only. it is not stored in database, so that it is not fetched when you try to fetch it via SOQL Query.
Now you may want to add one more line after above code.which is given as below
insert monthExpenseTest ;
so after inserting your object is stored in database. now you can fetch it via SOQL query.
So your test class will be looks like below
#isTest
public class ExpenseAdminControllerTests {
#isTest
public static void numberRecordsTests() {
Month_Expense_Application__c monthExpenseTest = new Month_Expense_Application__c(
Balance__c = 12.3,
Income__c = 11,
Keeper__c = '0034x00001K7kGCAAZ',
MonthDate__c = date.newInstance(2022, 11, 21)
);
insert monthExpenseTest;
Integer year = 2022;
Integer keepers = ExpenseAdminController.numberRecords(year);
System.assertEquals(1, keepers);
}
}
So when you are testing ExpenseAdminController then you just need to call that method because you want to test it. in test class which is mentioned in question it not testing numberRecords() method instead of you are rewriting logic.
I am at 71%, 4 lines of code cannot be run in the test for some reason.
When I test myself in Salesforce it works (those lines of code are running).
How can I get these lines of code to run in the test?
Lines not running, in second for loop
nextId=Integer.Valueof(c.next_id__c);
Lines not running, in third for loop
btnRecord.next_id__c = newid + 1;
btnRecord.last_id__c = newId;
btnRecord.last_assigned_starting_id__c = nextId;
btnRecord.last_assigned_ending_id__c = newId;
Below is my code:
trigger getNextId on tracking__c (before insert, before update) {
Integer newId;
Integer lastId;
Integer nextId;
newId=0;
lastId=0;
nextId =0;
//add the total accounts to the last_id
for (tracking__c bt: Trigger.new) {
//get the next id
List<tracking_next_id__c> btnxtid = [SELECT next_id__c FROM tracking_next_id__c];
for (tracking_next_id__c c : btnxtid )
{
nextId=Integer.Valueof(c.next_id__c);
}
newId = Integer.Valueof(bt.total_account__c) + nextId;
bt.starting_id__c = nextId;
bt.ending_id__c = newId;
tracking_next_id__c[] nextIdToUpdate = [SELECT last_id__c, next_id__c, last_assigned_starting_id__c, last_assigned_ending_id__c FROM tracking_next_id__c];
for(tracking_next_id__c btnRecord : nextIdToUpdate ){
btnRecord.next_id__c = newid + 1;
btnRecord.last_id__c = newId;
btnRecord.last_assigned_starting_id__c = nextId;
btnRecord.last_assigned_ending_id__c = newId;
}
update nextIdToUpdate ;
}
}
Even though test coverage is increased by using seeAllData=true, it is not best practice to use seeAllData unless and until it is really required. Please find the blog here for details.
Another way to increase the coverage is by creating test Data for tracking_next_id__c object.
#isTest
private class getNextIdTest {
static testMethod void validateOnInsert(){
tracking_next_id__c c = new tracking_next_id__c(next_id__c='Your next_id',
last_id__c='Your last_id', last_assigned_starting_id__c='Your last_assigned_starting_id',
last_assigned_ending_id__c='last_assigned_ending_id');
insert c;
tracking__c b = new tracking__c(total_account__c=Integer.Valueof(99));
System.debug('before insert : ' + b.total_account__c);
insert b;
System.debug('after insert : ' + b.total_account__c);
List<tracking__c> customObjectList =
[SELECT total_account__c FROM tracking__c ];
for(bid_tracking__c ont : customObjectList){
ont.total_account__c = 5;
}
update customObjectList;
}
}
I have added below line so that, when 2 queries get executed before FOR loops (which were not covered previously) it will fetch data as we have inserted it in test class now.
tracking_next_id__c c = new tracking_next_id__c(next_id__c='Your next_id',
last_id__c='Your last_id', last_assigned_starting_id__c='Your last_assigned_starting_id',
last_assigned_ending_id__c='last_assigned_ending_id');
insert c;
Just an observation, it is best to avoid SOQL query in FOR loop to avoid Runtime Exception (101:Too many SOQL query)
#isTest
private class getNextIdTest {
static testMethod void validateOnInsert(){
tracking__c b = new tracking__c(total_account__c=Integer.Valueof(99));
System.debug('before insert : ' + b.total_account__c);
insert b;
System.debug('after insert : ' + b.total_account__c);
List<tracking__c> customObjectList =
[SELECT total_account__c FROM tracking__c ];
for(bid_tracking__c ont : customObjectList){
ont.total_account__c = 5;
}
update customObjectList;
}
}
Added #isTest(SeeAllData=true) and it moved to 100%
https://developer.salesforce.com/forums/ForumsMain?id=9060G000000I5f8
I'm having some difficulty to solve this kind of problem:
I Have some nested entities and I'm trying to persist them in the same transaction (HRD enable).
Entity A:
#Entity
public class A {
#Id Long id;
List<B> children;
}
Entity B:
#Entity
public class B {
#Id Long id;
}
When I try to persist 6 instances (just two Entity Groups, A e B) ...
public void testOfy() {
ofy.getFactory().register(A.class);
ofy.getFactory().register(B.class);
List<B> list = new ArrayList<B>();
final A a0 = new A();
final B b1 = new B();
final B b2 = new B();
final B b3 = new B();
final B b4 = new B();
final B b5 = new B();
Ofy o = ofy.transaction();
try {
o.save().entities(b1).now(); list.add(b1);
o.save().entities(b2).now(); list.add(b2);
o.save().entities(b3).now(); list.add(b3);
o.save().entities(b4).now(); list.add(b4);
o.save().entities(b5).now(); list.add(b5);
a0.children = list;
o.save(a0);
o.getTxn().commit();
}
finally {
if (o.getTxn().isActive())
o.getTxn().rollback();
}
}
I get the Exception:
java.lang.IllegalArgumentException: operating on too many entity groups in a single transaction.
at com.google.appengine.api.datastore.DatastoreApiHelper.translateError(DatastoreApiHelper.java:36)
However if I put just 5 instances everything works...
public void testOfy() {
ofy.getFactory().register(A.class);
ofy.getFactory().register(B.class);
List<B> list = new ArrayList<B>();
final A a0 = new A();
final B b1 = new B();
final B b2 = new B();
final B b3 = new B();
final B b4 = new B();
final B b5 = new B();
Ofy o = ofy.transaction();
try {
o.save().entities(b1).now(); list.add(b1);
o.save().entities(b2).now(); list.add(b2);
o.save().entities(b3).now(); list.add(b3);
o.save().entities(b4).now(); list.add(b4);
// o.save().entities(b5).now(); list.add(b5);
a0.children = list;
o.save(a0);
o.getTxn().commit();
}
finally {
if (o.getTxn().isActive())
o.getTxn().rollback();
}
}
I'm using Objectify 4.0b3, does anyone have any suggestion?
Thank you!
You have misunderstood what an entity group is. An entity group is not a class, it is an instance (or a group of instances).
Each of those entities represent a separate entity group. XG transactions allow a maximum of five EGs per transaction. The 6th produces the error you see.
I want to pass a collection of ids to a stored procedure that will be mapped using NHibernate. This technique was introduced in Sql Server 2008 ( more info here => Table-Valued Parameters ). I just don't want to pass multiple ids within an nvarchar parameter and then chop its value on the SQL Server side.
My first, ad hoc, idea was to implement my own IType.
public class Sql2008Structured : IType {
private static readonly SqlType[] x = new[] { new SqlType(DbType.Object) };
public SqlType[] SqlTypes(NHibernate.Engine.IMapping mapping) {
return x;
}
public bool IsCollectionType {
get { return true; }
}
public int GetColumnSpan(NHibernate.Engine.IMapping mapping) {
return 1;
}
public void NullSafeSet(DbCommand st, object value, int index, NHibernate.Engine.ISessionImplementor session) {
var s = st as SqlCommand;
if (s != null) {
s.Parameters[index].SqlDbType = SqlDbType.Structured;
s.Parameters[index].TypeName = "IntTable";
s.Parameters[index].Value = value;
}
else {
throw new NotImplementedException();
}
}
#region IType Members...
#region ICacheAssembler Members...
}
No more methods are implemented; a throw new NotImplementedException(); is in all the rest. Next, I created a simple extension for IQuery.
public static class StructuredExtensions {
private static readonly Sql2008Structured structured = new Sql2008Structured();
public static IQuery SetStructured(this IQuery query, string name, DataTable dt) {
return query.SetParameter(name, dt, structured);
}
}
Typical usage for me is
DataTable dt = ...;
ISession s = ...;
var l = s.CreateSQLQuery("EXEC some_sp #id = :id, #par1 = :par1")
.SetStructured("id", dt)
.SetParameter("par1", ...)
.SetResultTransformer(Transformers.AliasToBean<SomeEntity>())
.List<SomeEntity>();
Ok, but what is an "IntTable"? It's the name of SQL type created to pass table value arguments.
CREATE TYPE IntTable AS TABLE
(
ID INT
);
And some_sp could be like
CREATE PROCEDURE some_sp
#id IntTable READONLY,
#par1 ...
AS
BEGIN
...
END
It only works with Sql Server 2008 of course and in this particular implementation with a single-column DataTable.
var dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
It's POC only, not a complete solution, but it works and might be useful when customized. If someone knows a better/shorter solution let us know.
A simpler solution than the accepted answer would be to use ADO.NET. NHibernate allows users to enlist IDbCommands into NHibernate transactions.
DataTable myIntsDataTable = new DataTable();
myIntsDataTable.Columns.Add("ID", typeof(int));
// ... Add rows to DataTable
ISession session = sessionFactory.GetSession();
using(ITransaction transaction = session.BeginTransaction())
{
IDbCommand command = new SqlCommand("StoredProcedureName");
command.Connection = session.Connection;
command.CommandType = CommandType.StoredProcedure;
var parameter = new SqlParameter();
parameter.ParameterName = "IntTable";
parameter.SqlDbType = SqlDbType.Structured;
parameter.Value = myIntsDataTable;
command.Parameters.Add(parameter);
session.Transaction.Enlist(command);
command.ExecuteNonQuery();
}
For my case, my stored procedure needs to be called in the middle of an open transaction.
If there is an open transaction, this code works because it is automatically reusing the existing transaction of the NHibernate session:
NHibernateSession.GetNamedQuery("SaveStoredProc")
.SetInt64("spData", 500)
.ExecuteUpdate();
However, for my new Stored Procedure, the parameter is not as simple as an Int64. It's a table-valued-parameter (User Defined Table Type)
My problem is that I cannot find the proper Set function.
I tried SetParameter("spData", tvpObj), but it's returning this error:
Could not determine a type for class: …
Anyways, after some trial and error, this approach below seems to work.
The Enlist() function is the key in this approach. It basically tells the SQLCommand to use the existing transaction. Without it, there will be an error saying
ExecuteNonQuery requires the command to have a transaction when the
connection assigned to the command is in a pending local transaction…
using (SqlCommand cmd = NHibernateSession.Connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = "MyStoredProc";
NHibernateSession.Transaction.Enlist(cmd); // Because there is a pending transaction
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("#wiData", SqlDbType.Structured) { Value = wiSnSqlList });
int affected = cmd.ExecuteNonQuery();
}
Since I am using the SqlParameter class with this approach, SqlDbType.Structured is available.
This is the function where wiSnList gets assigned:
private IEnumerable<SqlDataRecord> TransformWiSnListToSql(IList<SHWorkInstructionSnapshot> wiSnList)
{
if (wiSnList == null)
{
yield break;
}
var schema = new[]
{
new SqlMetaData("OriginalId", SqlDbType.BigInt), //0
new SqlMetaData("ReportId", SqlDbType.BigInt), //1
new SqlMetaData("Description", SqlDbType.DateTime), //2
};
SqlDataRecord row = new SqlDataRecord(schema);
foreach (var wi in wiSnList)
{
row.SetSqlInt64(0, wi.OriginalId);
row.SetSqlInt64(1, wi.ShiftHandoverReportId);
if (wi.Description == null)
{
row.SetDBNull(2);
}
else
{
row.SetSqlString(2, wi.Description);
}
yield return row;
}
}
You can pass collections of values without the hassle.
Example:
var ids = new[] {1, 2, 3};
var query = session.CreateQuery("from Foo where id in (:ids)");
query.SetParameterList("ids", ids);
NHibernate will create a parameter for each element.
I'm using Spring JdbcTemplate, and I'm stuck at the point where I have a query that updates a column that is actually an array of int. The database is postgres 8.3.7.
This is the code I'm using :
public int setUsersArray(int idUser, int idDevice, Collection<Integer> ids) {
int update = -666;
int[] tipi = new int[3];
tipi[0] = java.sql.Types.INTEGER;
tipi[1] = java.sql.Types.INTEGER;
tipi[2] = java.sql.Types.ARRAY;
try {
update = this.jdbcTemplate.update(setUsersArrayQuery, new Object[] {
ids, idUser, idDevice }, tipi);
} catch (Exception e) {
e.printStackTrace();
}
return update;
}
The query is "update table_name set array_column = ? where id_user = ? and id_device = ?".
I get this exception :
org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [update acotel_msp.users_mau set denied_sub_client = ? where id_users = ? and id_mau = ?]; The column index is out of range: 4, number of columns: 3.; nested exception is org.postgresql.util.PSQLException: The column index is out of range: 4, number of columns: 3.
Caused by: org.postgresql.util.PSQLException: The column index is out of range: 4, number of columns: 3.
I've looked into spring jdbc template docs but I can't find any help, I'll keep looking, anyway could someone point me to the right direction? Thanks!
EDIT :
Obviously the order was wrong, my fault...
I tried both your solutions, in the first case I had this :
org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [update users set denied_sub_client = ? where id_users = ? and id_device = ?]; nested exception is org.postgresql.util.PSQLException: Cannot cast an instance of java.util.ArrayList to type Types.ARRAY
Trying the second solution I had this :
org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [update users set denied_sub_client = ? where id_users = ? and id_device = ?]; nested exception is org.postgresql.util.PSQLException: Cannot cast an instance of [Ljava.lang.Object; to type Types.ARRAY
I suppose i need an instance of java.sql.Array, but how can I create it using JdbcTemplate?
After struggling with many attempts, we settled to use a little helper ArraySqlValue to create Spring SqlValue objects for Java Array Types.
usage is like this
jdbcTemplate.update(
"UPDATE sometable SET arraycolumn = ?",
ArraySqlValue.create(arrayValue))
The ArraySqlValue can also be used in MapSqlParameterSource for use with NamedParameterJdbcTemplate.
import static com.google.common.base.Preconditions.checkNotNull;
import java.sql.Array;
import java.sql.JDBCType;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Locale;
import org.springframework.jdbc.core.StatementCreatorUtils;
import org.springframework.jdbc.support.SqlValue;
public class ArraySqlValue implements SqlValue {
private final Object[] arr;
private final String dbTypeName;
public static ArraySqlValue create(final Object[] arr) {
return new ArraySqlValue(arr, determineDbTypeName(arr));
}
public static ArraySqlValue create(final Object[] arr, final String dbTypeName) {
return new ArraySqlValue(arr, dbTypeName);
}
private ArraySqlValue(final Object[] arr, final String dbTypeName) {
this.arr = checkNotNull(arr);
this.dbTypeName = checkNotNull(dbTypeName);
}
#Override
public void setValue(final PreparedStatement ps, final int paramIndex) throws SQLException {
final Array arrayValue = ps.getConnection().createArrayOf(dbTypeName, arr);
ps.setArray(paramIndex, arrayValue);
}
#Override
public void cleanup() {}
private static String determineDbTypeName(final Object[] arr) {
// use Spring Utils similar to normal JdbcTemplate inner workings
final int sqlParameterType =
StatementCreatorUtils.javaTypeToSqlParameterType(arr.getClass().getComponentType());
final JDBCType jdbcTypeToUse = JDBCType.valueOf(sqlParameterType);
// lowercasing typename for Postgres
final String typeNameToUse = jdbcTypeToUse.getName().toLowerCase(Locale.US);
return typeNameToUse;
}
}
this code is provided in the Public Domain
private static final String ARRAY_DATATYPE = "int4";
private static final String SQL_UPDATE = "UPDATE foo SET arr = ? WHERE d = ?";
final Integer[] existing = ...;
final DateTime dt = ...;
getJdbcTemplate().update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(final Connection con) throws SQLException {
final PreparedStatement ret = con.prepareStatement(SQL_UPDATE);
ret.setArray(1, con.createArrayOf(ARRAY_DATATYPE, existing));
ret.setDate(2, new java.sql.Date(dt.getMillis()));
return ret;
}
});
This solution is kind of workaround using postgreSQL built-in function, which definitely worked for me.
reference blog
1) Convert String Array to Comma Separated String
If you are using Java8, it's pretty easy. other options are here
String commaSeparatedString = String.join(",",stringArray); // Java8 feature
2) PostgreSQL built-in function string_to_array()
you can find other postgreSQL array functions here
// tableName ( name text, string_array_column_name text[] )
String query = "insert into tableName(name,string_array_column_name ) values(?, string_to_array(?,',') )";
int[] types = new int[] { Types.VARCHAR, Types.VARCHAR};
Object[] psParams = new Object[] {"Dhruvil Thaker",commaSeparatedString };
jdbcTemplate.batchUpdate(query, psParams ,types); // assuming you have jdbctemplate instance
The cleanest way I found so far is to first convert the Collection into an Integer[] and then use the Connection to convert that into an Array.
Integer[] idArray = ids.toArray(new Integer[0]);
Array idSqlArray = jdbcTemplate.execute(
(Connection c) -> c.createArrayOf(JDBCType.INTEGER.getName(), idArray)
);
update = this.jdbcTemplate.update(setUsersArrayQuery, new Object[] {
idSqlArray, idUser, idDevice })
This is based on information in the documentation: https://jdbc.postgresql.org/documentation/head/arrays.html
The argument type and argument is not matching.
Try changing the argument type order
int[] tipi = new int[3];
tipi[0] = java.sql.Types.ARRAY;
tipi[1] = java.sql.Types.INTEGER;
tipi[2] = java.sql.Types.INTEGER;
or use
update = this.jdbcTemplate.update(setUsersArrayQuery, new Object[] {
ids.toArray(), idUser, idDevice })
and see if it works
http://valgogtech.blogspot.com/2009/02/passing-arrays-to-postgresql-database.html explains how to create java.sql.Array postgresql
basically Array.getBaseTypeName should return int and Array.toString should return the array content in "{1,2,3}" format
after you create the array you can set it using preparedstatement.setArray(...)
from PreparedStatementCreator e.g.
jdbcTemplate.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
Good Luck ..
java.sql.Array intArray = connection.createArrayOf("int", existing);
List<Object> values= new ArrayList<Object>();
values.add(intArray);
values.add(dt);
getJdbcTemplate().update(SQL_UPDATE,values);