5151import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .rel .metadata .ReflectiveRelMetadataProvider ;
5252import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .rel .metadata .RelMetadataProvider ;
5353import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .rel .metadata .RelMetadataQuery ;
54+ import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .rel .type .RelDataType ;
55+ import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .rex .RexBuilder ;
56+ import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .rex .RexDynamicParam ;
57+ import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .rex .RexNode ;
58+ import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .rex .RexShuttle ;
5459import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .schema .SchemaPlus ;
5560import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .sql .SqlNode ;
5661import org .apache .beam .vendor .calcite .v1_40_0 .org .apache .calcite .sql .SqlOperatorTable ;
@@ -180,8 +185,8 @@ public SqlNode parse(String sqlStatement) throws ParseException {
180185 public BeamRelNode convertToBeamRel (String sqlStatement , QueryParameters queryParameters )
181186 throws ParseException , SqlConversionException {
182187 Preconditions .checkArgument (
183- queryParameters .getKind () == Kind .NONE ,
184- "Beam SQL Calcite dialect does not yet support query parameters." );
188+ queryParameters .getKind () == Kind .NONE || queryParameters . getKind () == Kind . POSITIONAL ,
189+ "Beam SQL Calcite dialect only supports positional query parameters." );
185190 BeamRelNode beamRelNode ;
186191 try {
187192 SqlNode parsed = planner .parse (sqlStatement );
@@ -191,28 +196,35 @@ public BeamRelNode convertToBeamRel(String sqlStatement, QueryParameters queryPa
191196
192197 // root of original logical plan
193198 RelRoot root = planner .rel (validated );
199+ RelNode relNode = root .rel ;
200+ if (queryParameters .getKind () == Kind .POSITIONAL ) {
201+ relNode =
202+ bindParameters (
203+ relNode ,
204+ new ParameterBinder (root .rel .getCluster ().getRexBuilder (), queryParameters ));
205+ }
194206 LOG .info ("SQLPlan>\n {}" , BeamSqlRelUtils .explainLazily (root .rel ));
195207 RelTraitSet desiredTraits =
196- root . rel
208+ relNode
197209 .getTraitSet ()
198210 .replace (BeamLogicalConvention .INSTANCE )
199211 .replace (root .collation )
200212 .simplify ();
201213 // beam physical plan
202- root . rel
214+ relNode
203215 .getCluster ()
204216 .setMetadataProvider (
205217 ChainedRelMetadataProvider .of (
206218 ImmutableList .of (
207219 NonCumulativeCostImpl .SOURCE ,
208220 RelMdNodeStats .SOURCE ,
209- root . rel .getCluster ().getMetadataProvider ())));
221+ relNode .getCluster ().getMetadataProvider ())));
210222
211- root . rel .getCluster ().setMetadataQuerySupplier (BeamRelMetadataQuery ::instance );
223+ relNode .getCluster ().setMetadataQuerySupplier (BeamRelMetadataQuery ::instance );
212224 RelMetadataQuery .THREAD_PROVIDERS .set (
213- JaninoRelMetadataProvider .of (root . rel .getCluster ().getMetadataProvider ()));
214- root . rel .getCluster ().invalidateMetadataQuery ();
215- beamRelNode = (BeamRelNode ) planner .transform (0 , desiredTraits , root . rel );
225+ JaninoRelMetadataProvider .of (relNode .getCluster ().getMetadataProvider ()));
226+ relNode .getCluster ().invalidateMetadataQuery ();
227+ beamRelNode = (BeamRelNode ) planner .transform (0 , desiredTraits , relNode );
216228 LOG .info ("BEAMPlan>\n {}" , BeamSqlRelUtils .explainLazily (beamRelNode ));
217229 } catch (RelConversionException | CannotPlanException e ) {
218230 throw new SqlConversionException (
@@ -225,6 +237,15 @@ public BeamRelNode convertToBeamRel(String sqlStatement, QueryParameters queryPa
225237 return beamRelNode ;
226238 }
227239
240+ private static RelNode bindParameters (RelNode rel , RexShuttle binder ) {
241+ RelNode newRel = rel .accept (binder );
242+ java .util .List <RelNode > newInputs = new java .util .ArrayList <>();
243+ for (RelNode input : newRel .getInputs ()) {
244+ newInputs .add (bindParameters (input , binder ));
245+ }
246+ return newRel .copy (newRel .getTraitSet (), newInputs );
247+ }
248+
228249 // It needs to be public so that the generated code in Calcite can access it.
229250 public static class NonCumulativeCostImpl
230251 implements MetadataHandler <BuiltInMetadata .NonCumulativeCost > {
@@ -265,4 +286,58 @@ public RelOptCost getNonCumulativeCost(RelNode rel, RelMetadataQuery mq) {
265286 return ((BeamRelNode ) rel ).beamComputeSelfCost (rel .getCluster ().getPlanner (), bmq );
266287 }
267288 }
289+
290+ private static class ParameterBinder extends RexShuttle {
291+ private final RexBuilder rexBuilder ;
292+ private final List <?> positionalParams ;
293+
294+ ParameterBinder (RexBuilder rexBuilder , QueryParameters params ) {
295+ this .rexBuilder = rexBuilder ;
296+ this .positionalParams = params .getKind () == Kind .POSITIONAL ? params .positional () : null ;
297+ }
298+
299+ @ Override
300+ public RexNode visitDynamicParam (RexDynamicParam dynamicParam ) {
301+ if (positionalParams != null ) {
302+ int index = dynamicParam .getIndex ();
303+ if (index < 0 || index >= positionalParams .size ()) {
304+ throw new IllegalArgumentException (
305+ "Index out of bounds for positional parameter: " + index );
306+ }
307+ Object val = positionalParams .get (index );
308+ return makeLiteral (cleanValue (val ), dynamicParam .getType ());
309+ }
310+ return super .visitDynamicParam (dynamicParam );
311+ }
312+
313+ private RexNode makeLiteral (Object val , RelDataType type ) {
314+ if (val == null ) {
315+ return rexBuilder .makeNullLiteral (type );
316+ }
317+ return rexBuilder .makeLiteral (val , type , true );
318+ }
319+
320+ @ SuppressWarnings ("JavaUtilDate" ) // explicit java.util.Date support
321+ private Object cleanValue (Object value ) {
322+ if (value instanceof org .joda .time .ReadableInstant ) {
323+ return ((org .joda .time .ReadableInstant ) value ).getMillis ();
324+ }
325+ if (value instanceof java .time .LocalDate ) {
326+ return (int ) ((java .time .LocalDate ) value ).toEpochDay ();
327+ }
328+ if (value instanceof java .time .LocalTime ) {
329+ return (int ) (((java .time .LocalTime ) value ).toNanoOfDay () / 1_000_000L );
330+ }
331+ if (value instanceof java .time .LocalDateTime ) {
332+ return ((java .time .LocalDateTime ) value ).toInstant (java .time .ZoneOffset .UTC ).toEpochMilli ();
333+ }
334+ if (value instanceof java .sql .Timestamp ) {
335+ return ((java .sql .Timestamp ) value ).getTime ();
336+ }
337+ if (value instanceof java .util .Date ) {
338+ return ((java .util .Date ) value ).getTime ();
339+ }
340+ return value ;
341+ }
342+ }
268343}
0 commit comments