1919
2020import static java .util .Objects .requireNonNull ;
2121
22+ import java .lang .invoke .MethodHandle ;
23+ import java .lang .invoke .MethodHandles ;
24+ import java .lang .invoke .MethodHandles .Lookup ;
25+ import java .lang .invoke .MethodType ;
2226import java .util .ArrayDeque ;
2327import java .util .ArrayList ;
2428import java .util .Deque ;
2529import java .util .List ;
2630import org .apache .calcite .plan .RelOptCluster ;
2731import org .apache .calcite .plan .RelOptTable ;
32+ import org .apache .calcite .plan .RelOptUtil ;
2833import org .apache .calcite .prepare .Prepare ;
2934import org .apache .calcite .rel .RelNode ;
3035import org .apache .calcite .rel .RelRoot ;
4348import org .apache .calcite .sql .SqlKind ;
4449import org .apache .calcite .sql .SqlMerge ;
4550import org .apache .calcite .sql .SqlNode ;
51+ import org .apache .calcite .sql .SqlSelect ;
4652import org .apache .calcite .sql .SqlUpdate ;
47- import org .apache .calcite .sql .util .SqlShuttle ;
4853import org .apache .calcite .sql .validate .SqlValidator ;
4954import org .apache .calcite .sql .validate .SqlValidatorScope ;
5055import org .apache .calcite .sql .validate .SqlValidatorUtil ;
5156import org .apache .calcite .sql2rel .InitializerContext ;
5257import org .apache .calcite .sql2rel .SqlRexConvertletTable ;
5358import org .apache .calcite .sql2rel .SqlToRelConverter ;
5459import org .apache .calcite .tools .RelBuilder ;
55- import org .apache .calcite .util .ControlFlowException ;
5660import org .apache .calcite .util .Pair ;
61+ import org .apache .calcite .util .Util ;
5762import org .apache .ignite .internal .sql .engine .schema .IgniteDataSource ;
5863import org .jetbrains .annotations .Nullable ;
5964
6065/** Converts a SQL parse tree into a relational algebra operators. */
6166public class IgniteSqlToRelConvertor extends SqlToRelConverter implements InitializerContext {
67+
68+ private static final MethodHandle REPLACE_SUB_QUERIES ;
69+
70+ private static final Throwable INIT_ERROR ;
71+
72+ static {
73+ // TODO https://issues.apache.org/jira/browse/IGNITE-27398 Remove this workaround
74+ MethodHandle handle ;
75+ Throwable err ;
76+ try {
77+ Lookup lookup = MethodHandles .privateLookupIn (SqlToRelConverter .class , MethodHandles .lookup ());
78+ Class <?> bbClass = lookup .findClass ("org.apache.calcite.sql2rel.SqlToRelConverter$Blackboard" );
79+ Class <?> logicClass = lookup .findClass ("org.apache.calcite.plan.RelOptUtil$Logic" );
80+ Class <?> sqlNodeClass = SqlNode .class ;
81+ MethodType tpe = MethodType .methodType (void .class , bbClass , sqlNodeClass , logicClass );
82+
83+ handle = lookup .findVirtual (SqlToRelConverter .class , "replaceSubQueries" , tpe );
84+ err = null ;
85+ } catch (Throwable e ) {
86+ // Postpone error reporting to have a better chance of logging an error.
87+ err = e ;
88+ handle = null ;
89+ }
90+
91+ INIT_ERROR = err ;
92+ REPLACE_SUB_QUERIES = handle ;
93+ }
94+
6295 private final Deque <SqlCall > datasetStack = new ArrayDeque <>();
6396
6497 private RelBuilder relBuilder ;
@@ -73,12 +106,19 @@ public class IgniteSqlToRelConvertor extends SqlToRelConverter implements Initia
73106 super (viewExpander , validator , catalogReader , cluster , convertletTable , cfg );
74107
75108 relBuilder = config .getRelBuilderFactory ().create (cluster , null );
109+
110+ if (INIT_ERROR != null ) {
111+ throw new IllegalStateException ("Failed to initialize " + IgniteSqlToRelConvertor .class .getName (), INIT_ERROR );
112+ }
76113 }
77114
78115 /** {@inheritDoc} */
79116 @ Override protected RelRoot convertQueryRecursive (SqlNode qry , boolean top , @ Nullable RelDataType targetRowType ) {
80117 if (qry .getKind () == SqlKind .MERGE ) {
81118 return RelRoot .of (convertMerge ((SqlMerge ) qry ), qry .getKind ());
119+ } else if (qry .getKind () == SqlKind .UPDATE ) {
120+ // TODO https://issues.apache.org/jira/browse/IGNITE-27398 Remove this workaround and use calcite's method.
121+ return RelRoot .of (convertUpdateFixed ((SqlUpdate ) qry ), qry .getKind ());
82122 } else {
83123 return super .convertQueryRecursive (qry , top , targetRowType );
84124 }
@@ -99,39 +139,30 @@ public SqlNode validateExpression(RelDataType rowType, SqlNode expr) {
99139 throw new UnsupportedOperationException ("Not implemented yet" );
100140 }
101141
102- private static class DefaultChecker extends SqlShuttle {
103- private boolean hasDefaults (SqlCall call ) {
104- try {
105- call .accept (this );
106- return false ;
107- } catch (ControlFlowException e ) {
108- return true ;
109- }
110- }
111-
112- @ Override public @ Nullable SqlNode visit (SqlCall call ) {
113- if (call .getKind () == SqlKind .DEFAULT ) {
114- throw new ControlFlowException ();
115- }
116-
117- return super .visit (call );
118- }
119- }
120-
121142 @ Override public RelNode convertValues (SqlCall values , RelDataType targetRowType ) {
122- DefaultChecker checker = new DefaultChecker ();
123-
124- boolean hasDefaults = checker .hasDefaults (values );
125-
126- if (hasDefaults ) {
143+ // TODO https://issues.apache.org/jira/browse/IGNITE-27396
144+ // FIX: Original convertValuesImpl adds additional type casts that are not correct
145+ // and break NOT NULL constraints.
146+ //
147+ // See these lines in convertValuesImpl:
148+ // if (!(def instanceof RexDynamicParam) && !def.getType().equals(fieldType)) {
149+ // def = rexBuilder.makeCast(operand.getParserPosition(), fieldType, def);
150+ // }
151+ // exps.add(def, SqlValidatorUtil.alias(operand, i));
152+ //
153+ // Example: INSERT INTO t1 VALUES(1, (SELECT NULL))
154+ //
155+ // if fieldType is NOT NULLABLE INT and def's type is NULLABLE INT then
156+ // resulting expression is wrapped into CAST(NULLABLE INT AS NOT NULLABLE INT)
157+ // but that cast expression always results in 0 (INT) thus breaking a NOT NULL constraint.
158+ if (datasetStack .peek () instanceof SqlInsert ) {
127159 SqlValidatorScope scope = validator .getOverScope (values );
128160 assert scope != null ;
129161 Blackboard bb = createBlackboard (scope , null , false );
130162
131163 convertValuesImplEx (bb , values , targetRowType );
132164 return bb .root ();
133165 } else {
134- // a bit lightweight than default processing one.
135166 return super .convertValues (values , targetRowType );
136167 }
137168 }
@@ -345,4 +376,60 @@ private List<RexNode> repairProject(LogicalJoin join, List<RexNode> actual) {
345376 public RelOptTable getTargetTable (SqlNode call ) {
346377 return super .getTargetTable (call );
347378 }
379+
380+ // TODO https://issues.apache.org/jira/browse/IGNITE-27398 Remove this workaround
381+ // This method is a copy of SqlToRelConverter convertUpdate.
382+ private RelNode convertUpdateFixed (SqlUpdate call ) {
383+ final SqlSelect sourceSelect =
384+ requireNonNull (call .getSourceSelect (),
385+ () -> "sourceSelect for " + call );
386+ final SqlValidatorScope scope = validator .getWhereScope (sourceSelect );
387+ Blackboard bb = createBlackboard (scope , null , false );
388+
389+ RelOptTable targetTable = getTargetTable (call );
390+
391+ // convert update column list from SqlIdentifier to String
392+ final List <String > targetColumnNameList = new ArrayList <>();
393+ final RelDataType targetRowType = targetTable .getRowType ();
394+ for (SqlNode node : call .getTargetColumnList ()) {
395+ SqlIdentifier id = (SqlIdentifier ) node ;
396+ RelDataTypeField field =
397+ SqlValidatorUtil .getTargetField (
398+ targetRowType , typeFactory , id , catalogReader , targetTable );
399+ if (field == null ) {
400+ throw new AssertionError ("column " + id + " not found" );
401+ }
402+ targetColumnNameList .add (field .getName ());
403+ }
404+
405+ // A call to replaceSubQueries(bb, call, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
406+ try {
407+ REPLACE_SUB_QUERIES .invoke (this , bb , call , RelOptUtil .Logic .TRUE_FALSE_UNKNOWN );
408+ } catch (Throwable e ) {
409+ throw new AssertionError ("Failed to replace subqueries" , e );
410+ }
411+
412+ // FIX: The condition below is incorrect, it ignores virtual columns that present is the target table.
413+ // sourceSelect` should contain target columns values plus source expressions
414+ //
415+ // if (sourceSelect.getSelectList().size()
416+ // != targetTable.getRowType().getFieldCount() + call.getSourceExpressionList().size()) {
417+ // throw new AssertionError(
418+ // "Unexpected select list size. Select list should contain both target table columns and "
419+ // + "set expressions");
420+ // }
421+
422+ RelNode sourceRel = convertSelect (sourceSelect , false );
423+ bb .setRoot (sourceRel , false );
424+
425+ // sourceRel already contains all source expressions. Only create references to those fields.
426+ List <RexNode > rexExpressionList =
427+ Util .transform (
428+ Util .last (sourceRel .getRowType ().getFieldList (), targetColumnNameList .size ()),
429+ expressionField -> new RexInputRef (expressionField .getIndex (),
430+ expressionField .getType ()));
431+
432+ return LogicalTableModify .create (targetTable , catalogReader , sourceRel ,
433+ LogicalTableModify .Operation .UPDATE , targetColumnNameList , rexExpressionList , false );
434+ }
348435}
0 commit comments