How to bind to IN parameters in Cloud Spanner Java API - java

Is it possible, using the Google Cloud Spanner Java SDK, to bind to the parameters supplied to the IN portion of a query?
e.g.
List<String> names = new ArrayList<String>();
names.add("Alice");
names.add("Bob");
String sql = "SELECT * FROM people WHERE name IN (#names)";
Statement statement = Statement
.newBuilder(sql)
.bind("names").to(names)
.build();
If we bind names using toStringArray, it errors. And if we set the following:
names = "'Alice','Bob'";
Then the generated SQL is:
SELECT * FROM people WHERE name IN ("'Alice','Bob'")
Note the extra quotations. Any idea how we can do this without %s string substitution to avoid inject attacks?

2 changes to your code:
List<String> names = new ArrayList<String>();
names.add("Alice");
names.add("Bob");
String sql = "SELECT * FROM people WHERE name IN UNNEST(#names)";
Statement statement = Statement
.newBuilder(sql)
.bind("names").toStringArray(names)
.build();
First is to make the condition IN UNNEST as we're going to be binding an array rather than repeated values.
Second is to change to to toStringArray to bind the array as you originally tried.
To better visualize this, the array that you bind is essentially this:
SELECT * FROM people WHERE name IN UNNEST(["Alice", "Bob"])

Related

Jdbc template named parameters for querying with IN clause

I need to copy the records of one table to another table based on some condition.
String query = "insert into public.ticket_booking_archive select * from public.ticket_booking where ticketId in (:ticketIds)";
So here the :ticketIds are dynamic, where i need to pass ticketIds to make sure whether it satisfies the condition. So it may be the matching and non matching ticket id's here at runtime.
The values of ticketIds are something like this
('f1fa3a42-5837-11ec-bf63-0242ac130002','516fd14d-3c9d-4b4b-91a0-b684d8592dfe','c9652f86-734c-4df5-8ef9-d407cb3eaf7a','df7f2812-b445-45b4-b731-da23c36d7738','f1fa3a42-5837-11ec-bf63-0242ac130002'). And this is just an example. And the list might goes on.
Since it is of type UUID, I'm storing it into a Set<UUID>
Set<UUID> tktIds = new HashSet<UUID>();
for(int i=0 ; i<ticketIds.size(); i++) {
String ticketId = ticketIds[i];
tktIds.add(UUID.fromString(ticketId));
}
Map<String, Object> params = new HashMap<>();
params.put("ticketIds", tktIds);
SqlParameterSource namedParameters =
new MapSqlParameterSource().addValue("ticketIds",params.get("ticketIds"));
Since I'm using NamedParameterJdbcTemplate, so I'm using like below
int res = writeNamedJdbcTemplate.update(query, namedParameters);
res = 3 when executed programmatically.
Here the problem is, as soon as it finds the first matching value in the IN clause it executes. And it is not considering the other matching values (ticketIds here)
But if I execute the same query in pgadmin it works fine
insert into public.ticket_booking_archive select * from public.ticket_booking where ticketId in ('f1fa3a42-5837-11ec-bf63-0242ac130002','516fd14d-3c9d-4b4b-91a0-b684d8592dfe','c9652f86-734c-4df5-8ef9-d407cb3eaf7a','df7f2812-b445-45b4-b731-da23c36d7738','f1fa3a42-5837-11ec-bf63-0242ac130002');
result is 6. Working as expected.
writeNamedJdbcTemplate.queryForObject(query, namedParameters, Integer.class); //. throws an error
Can anyone please assist? I'm really not sure where I'm making a mistake
I am not quite sure whether you are using the appropriate JDBC template for the named parameters, but you can do the following:
you can consult this article to use the right template and employ proper SQL query composition,
for string passing you can wrap the parameter mapping as shown here
after all your named parameter should work

Introducing a named parameter breaks jOOQ query

To query a PostgreSQL 10.11 database, I am using jOOQ 3.12.4, which comes bundled with Spring Boot 2.2.
Let's assume I have built a query using jOOQ like this:
final String[] ids = ...;
final var query = dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(ids));
final Map<String, List<MyTable>> changeDomains = query.fetch().intoGroups(MY_TABLE.ID, MyTable.class);
This code runs fine and produces the expected results. But when I refactor my query and introduce a named parameter (to reuse the query in multiple parts of my code), like this:
final String[] ids = ...;
final var query = dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(param("ids")));
final Map<String, List<MyTable>> changeDomains = query.bind("ids", ids).fetch().intoGroups(MY_TABLE.ID, MyTable.class);
I suddenly start to get the following error:
org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar ...; nested exception is org.postgresql.util.PSQLException: ERROR: operator does not exist: text = character varying[]
Hinweis: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Edit: I get the same error when I use
MY_TABLE.ID.in(param("ids", String[].class))
instead.
How can I solve or work around this problem?
A better solution to your code reuse approach
But when I refactor my query and introduce a named parameter (to reuse the query in multiple parts of my code)
While you could use jOOQ this way (be careful, when mutating and reusing jOOQ queries in a non-threadsafe way!), it is generally recommended to use jOOQ in a more functional way, see e.g.:
https://blog.jooq.org/2017/01/16/a-functional-programming-approach-to-dynamic-sql-with-jooq/
https://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql/
You don't gain much by re-using a jOOQ query, specifically, there's hardly any performance gain.
So, instead of this:
final var query = dslContext.selectFrom(MY_TABLE)
.where(MY_TABLE.ID.in(param("ids")));
final Map<String, List<MyTable>> changeDomains = query
.bind("ids", ids).fetch().intoGroups(MY_TABLE.ID, MyTable.class);
Write this:
public ResultQuery<MyTableRecord> query(String[] ids) {
return dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(ids));
}
// And then:
final Map<String, List<MyTable>> changeDomains = query(ids)
.fetch().intoGroups(MY_TABLE.ID, MyTable.class);
The actual problem you ran into:
jOOQ, JDBC, and SQL don't support single bind value IN lists. While it seems useful to write this:
SELECT * FROM t WHERE c IN (:bind_value)
And passing an array or list as a single bind value, this is not supported in SQL. Some APIs might pretend that this is supported (but behind the scenes replace the single bind value by multiple ?, ?, ..., ?
PostgreSQL supports the = ANY (:bind_value) operator with arrays
SELECT * FROM t WHERE c = ANY (:bind_value)
You could use it in jOOQ using
dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.eq(any(ids)));
That way, you could call the bind() method to replace the array prior to execution. However, I still recommend you write functions returning queries dynamically.

Parse SQL Statement and reconstructing after modifications using java

Does anyone know how to parse SQL statements, and again build in back using Java? This is required because I would need to add extra columns to WHERE clause based on the some conditions. FOr example, based on the Logon user, I would need to decide whether the user is restricted to see the records like it is restricted outside USA.
use jsqlparser
examples:
CCJSqlParserManager ccjSqlParserManager = new CCJSqlParserManager();
Select statement = (Select) ccjSqlParserManager.parse(new FileReader(path));
PlainSelect plainSelect = (PlainSelect) statement.getSelectBody();
Expression expression = plainSelect.getWhere();
See below example
String sqlstr= "select * from [table name] where [ column 1]='value' or ? "
If( your condition){
sqlstr= sqlstr+" [ column 2]=' value 2'";
}
// Now write your execution statement

setParameterList() does not return full result set

I want to make my small spring project effectively. So I use IN clause instead of using loops in hql.
01) Question in setParameterList()
To use setParameterList(), we have to pass list object
List<Department> listDeptmntId = reportService.listDepartmentID(companyId); //list of objects
String hql = "select s.department.departmentName, g.dateTime from Gauge g inner join g.survey s where s.department in (:dpts)";
Query query = sessionFactory.getCurrentSession().createQuery(hql);
query.setParameterList("dpts",listDeptmntId);
The query works fine. But this return only one (first object in listDeptmntId list) resultset, does not return other results.
I tries to pass integer list like [1,2,3] using following method also not working.
List<Integer> dptIds=listDeptmntId.stream().map((Department::getDepartmentId()).collect(Collectors.toList());
02) Question in setParameter()
int cId=10;
String hql="...... companyId=:id"
Query query = sessionFactory.getCurrentSession().createQuery(hql);
query.setParameter("id",cId);
Sometime when I use parameter passing ("=:") , it does not work for Integers . But directly setting variable to query like following is working
int cId=10;
String hql="...... companyId="+cId
Query query = sessionFactory.getCurrentSession().createQuery(hql);
My code may be wrong because I'm going through ebooks and referring materials to do the project. Thank you in advance.
I generally do IN(?, ..., ?) using an java.sql.Array.
long[] deptIds = listDeptmntId.toArray(new long[listDeptmntId.size()];
java.sql.Array array = conn.createArrayOf("LONG", deptIds);
query.setPameter("depts", array);
q1) Check the join (can't see any other reasons to return just one object). And passing [1,2,3] won't work with that query because in query you're dealing with department object. If you want [1,2,3] to work change the query to check for dept-ids like this -> "select s.department.departmentName, g.dateTime from Gauge g inner join g.survey s where s.department.id in (:id-list)"
q2) You've already created the query object when you set the parameter, so setting parameter at that point might not affect the query object. (Your "+" approach works since the parameter is set to the query when creating the query object.)

Proper way to keep a list of Java class members as strings

Probably the only way to do this is to keep a list of static final Strings but if anyone has any idea it is welcome.
I have a POJO and a database table. The columns(properties) are the same as the class members of the POJO. I want to avoid (it is a fairly big program) to write getColumn("username") but instead write something like getColumn(UserPOJO.getUsername().getActualMemberNameInAString())
The only way I can think of is to have a UserPOJO class with just static variables and update it when I add members in the class. The second option is to have all the properties as enums but that is bad I think in a number of levels.
What I would like to have is a way to get the member name that corresponds to the getter method. Some kind of reflection?
Problem to be solved is typos in a huge codebase. I overemphasize here because I have received comments and answers that are not what I am asking for.
Note that I am not using any framework or ORM tool. I am using a Graph database that maps all columns to a Vertex and then you need to do .getProperty(string s) to get an Object. And I do this many times in the code
My understanding of this question is you want minimum code change when your underlaying Database Table get changed.
so let me put it as , say I have a table Info as
CREATE TABLE Info (
field1 INTEGER ,
field2 VARCHAR(1)
);
and I am expecting field3 could be added in future then I need a solution where
I do not need to add extra code for getting information for field3.
Please confirm if the understanding is correct ? if yes then you can use below strategy
/**
* generic Method for executing Any RAW SQL Query.
* for e.g. sql = "select * from Info";
*/
public List<Map<String,String>> executeQuery(String sql) throws Exception {
logger.info("executing SQL = "+sql);
List<Map<String,String>> resultList = new ArrayList<Map<String, String>>();
Statement statement=null;
ResultSet resultSet=null;
ResultSetMetaData metaData = null;
try{
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
metaData = resultSet.getMetaData();
int noOfColumns = metaData.getColumnCount();
while(resultSet.next()){
Map<String,String> row = new HashMap<String, String>();
for(int i =1;i<=noOfColumns;i++){
row.put(metaData.getColumnName(i), resultSet.getString(metaData.getColumnName(i)));
}
resultList.add(row);
}
} catch (SQLException e) {
throw new Exception(" Exception while executing query on database. Reason "+e.getMessage());
}
finally {
closeResources(statement, resultSet);
}
return resultList;
}
for input String sql = "select * from Info"; Here you will get all records of Info Table with key-value pair where key suggests columnName and value suggests value of that column.
And this is as good as dynamic ,even if we add extra columns we will get that information in our Map also it does not require to use any framework
You have multiple options:
If you only need the name once, I wouldn't bother to create constants for them.
In one of your comments you stated you will use it a lot and in different places, you could create a class that stores all those strings as constants. At least you will avoid typos, and your code wont be littered with strings.
You could use reflection but that would overcomplicate things (in my opinion).
I wouldn't exclude using enums as an option. This SO answer explains it better, then I could do...

Categories

Resources