I want to pass the Query dynamically while running the Dataflow job. I am using SQLTransform which works fine when I pass Query within code.
My use case requires passing the Query at Runtime, is it possible with SqlTransform in Apache Beam?
This works if I hard-code it in code.
String PQuery = "SELECT col1, max(col2) as max_watermark FROM PCOLLECTION GROUP BY col1";
PCollection<Row> rows1 = rows.apply(SqlTransform.query(PQuery));
But with valueProvider input, it gives compile time error.
PCollection<Row> rows1 = rows.apply(SqlTransform.query(options.getQuery()))
Error
The method query(String) in the type SqlTransform is not applicable for the arguments (ValueProvider<String>)
To solve your issue, you need to get the value inside the ValueProvider :
PCollection<Row> rows1 = rows.apply(SqlTransform.query(options.getQuery().get()))
The query method takes a String as parameter, that's why you need to get the String value of the ValueProvider option.
You should use FlexTemplates which allow dynamic graph construction (such as SqlTransform) uses based on template parameters.
Related
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.
Using the JOOQ parser API, I'm able to parse the following query and get the parameters map from the resulting Query object. From this, I can tell that there is one parameter, and it's name is "something".
However, I haven't been able to figure out how to determine that the parameter "something" is assigned to a column named "BAZ" and that column is part of the table "BAR".
Does the parser API have a way to get the table/column metadata associated to each parameter?
String sql = "SELECT A.FOO FROM BAR A WHERE A.BAZ = :something";
DSLContext context = DSL.using...
Parser parser = context.parser();
Query query = parser.parseQuery(sql);
Map<String, Param<?>> params = query.getParams();
Starting from jOOQ 3.16
jOOQ 3.16 introduced a new, experimental (as of 3.16) query object model API, which can be traversed, see:
The manual
A blog post about traversing jOOQ expression trees
Specifically, you can write:
List<QueryPart> parts = query.$traverse(
Traversers.findingAll(q -> q instanceof Param)
);
Or, to conveniently produce exactly the type you wanted:
Map<String, Param<?>> params = query.$traverse(Traversers.collecting(
Collectors.filtering(q -> q instanceof Param,
Collectors.toMap(
q -> ((Param<?>) q).getParamName(),
q -> (Param<?>) q
)
)
));
The Collectors.toMap() call could include a mergeFunction, in case you have the same param name twice.
Pre jOOQ 3.16
As of jOOQ 3.11, the SPI that can be used to access the internal expression tree is the VisitListener SPI, which you have to attach to your context.configuration() prior to parsing. It will then be invoked whenever you traverse that expression tree, e.g. on your query.getParams() call.
However, there's quite a bit of manual plumbing that needs to be done. For example, the VisitListener will only see A.BAZ as a column reference without knowing directly that A is the renamed table BAR. You will have to keep track of such renaming yourself when you visit the BAR A expression.
Spring Data Cassandra 1.5.0 comes with a streaming API in CassandraTemplate. I'm using spring-data-cassandra 1.5.1. I have a code like:
String tableName = cassandraTemplate.getTableName(MyEntity.class).toCql();
Select select = QueryBuilder.select()
.all()
.from(tableName);
// In real world, WHERE statement is much more complex
select.where(eq(ENTITY_FIELD_NAME, expectedField))
List<MyEntity> result = cassandraTemplate.select(select, MyEntity.class);
and want to replace this code with iterable or Java 8 Stream in order to avoid fetching a big list of results to memory at once.
What I'm looking for is a method signature like CassandraOperations.stream(Select query, Class<T> entityClass), but it is not available.
The only available method in CassandraOperations accepts query string: stream(String query, Class<T> entityClass). I tried to pass here a string generated by Select like
cassandraTemplate.stream(select.getQueryString(), MyEntity.class)
But that fails with InvalidQueryException: Invalid amount of bind variables, because getQueryString() returns query with question mark placeholders instead of variables.
I see 3 options to get what I want, but every option looks bad:
Use Spring Query creation mechanism with Stream/Iterator expected return type (good only for simple queries) http://docs.spring.io/spring-data/cassandra/docs/current/reference/html/#repositories.query-methods.query-creation
Use raw CQL query and not to use QueryBuilder
Call select.getQueryString() and then substitute parameters again via BoundStatement
Is there any better way to stream selection results?
Thanks.
So, as of now the answer on my question is to wait until stable version of spring-data-cassandra 2.0.0 comes out:
https://github.com/spring-projects/spring-data-cassandra/blob/2.0.x/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java#L208
Is it possible to pass both an SQL query, e.g. (<![CDATA[$P!{SQL}]]>), and a variable $V as parameters to a Jasper report at the same time?
Also, how can I pass the variable $V to my Jasper report using Java?
Often it is desired to just transform some parameter in order to use it in a query (in this case, the following works :) ).
E.g. (pseudo code - Groovy syntax!)
parameter:
filtParamUserName = "foo"
"variable" (technically not: in this case another parameter following it)1:
sqlCondUserName = $P{filtParamUserName} ? " and user_name = '$P{filtParamUserName}'" : ""
thus you could easily use it in your query like:
select * from bar where 1=1 $P{sqlCondUserName}
1: you have to set the Default Value Expression of the sqlCondUserName and Is For Prompting = false
A variable is something that varies during the report generation, like the number of pages for example. So a variable ($V) is not something you can pass to a report. You may pass any value from your Java code to the report as a parameter ($P), though.
I have a double precision array field dblArrayFld in a table myTable and I'd like to update it using Spring's NamedParameterJdbcTemplate (I'm using Postgres).
I'm running code like this:
SqlParameterSource params = (new MapSqlParameterSource())
.addValue("myarray", myDblArrayListVar)
.addValue("myid", 123);
namedJdbcTemplate.update("UPDATE myTable SET dblArrayFld = :myarray WHERE idFld = :myid", params);
This returns an error that reads syntax error at or near "$2"
I'm assuming my syntax on :myarray is at fault here. I've also tried encasing :myarray in the following ways:
dblArrayFld={:myarray}
dblArrayFld={ :myarray }
dblArrayFld=[:myarray]
dblArrayFld=ARRAY[:myarray]
dblArrayFld=(:myarray)
What's the correct syntax here?
Wehn you try to bind Collection or array as named parameter, NamedParameterJdbcTemplate explodes the appropriate named parameter in your statement into a number of positional parameters matching the length of your array / collection. This is useful for WHERE column IN (:param) statements, but is not going to work in this case.
In order to set an actual Postgres array you have to supply your parameter as java.sql.Array. You can create its instance using Connection#createArrayOf() method.