Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignite-23251 : Calcite. Validate number of passed query parameters. #11547

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ public ExecutionService<Object[]> executionService() {
String sql,
Object... params
) throws IgniteSQLException {
return parseAndProcessQuery(qryCtx, executionSvc::executePlan, schemaName, sql, params);
return parseAndProcessQuery(qryCtx, executionSvc::executePlan, schemaName, sql, true, params);
}

/** {@inheritDoc} */
Expand All @@ -399,7 +399,7 @@ public ExecutionService<Object[]> executionService() {
String schemaName,
String sql
) throws IgniteSQLException {
return parseAndProcessQuery(ctx, (qry, plan) -> fieldsMeta(plan, true), schemaName, sql);
return parseAndProcessQuery(ctx, (qry, plan) -> fieldsMeta(plan, true), schemaName, sql, false);
}

/** {@inheritDoc} */
Expand All @@ -408,7 +408,7 @@ public ExecutionService<Object[]> executionService() {
String schemaName,
String sql
) throws IgniteSQLException {
return parseAndProcessQuery(ctx, (qry, plan) -> fieldsMeta(plan, false), schemaName, sql);
return parseAndProcessQuery(ctx, (qry, plan) -> fieldsMeta(plan, false), schemaName, sql, false);
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -484,6 +484,7 @@ private <T> List<T> parseAndProcessQuery(
BiFunction<RootQuery<Object[]>, QueryPlan, T> action,
@Nullable String schemaName,
String sql,
boolean validateParamsCnt,
Object... params
) throws IgniteSQLException {
SchemaPlus schema = schemaHolder.schema(schemaName);
Expand Down Expand Up @@ -513,23 +514,28 @@ private <T> List<T> parseAndProcessQuery(

List<T> res = new ArrayList<>(qryList.size());
List<RootQuery<Object[]>> qrys = new ArrayList<>(qryList.size());
int qryIdx = 0;

for (final SqlNode sqlNode: qryList) {
int qryIdx0 = qryIdx;

T singleRes = processQuery(qryCtx, qry -> {
QueryPlan plan0;
if (qryList.size() == 1) {
plan0 = queryPlanCache().queryPlan(
// Use source SQL to avoid redundant parsing next time.
new CacheKey(schema.getName(), sql, contextKey(qryCtx), params),
() -> prepareSvc.prepareSingle(sqlNode, qry.planningContext())
() -> prepareSvc.prepareSingle(sqlNode, qry.planningContext(validateParamsCnt, 0, 1))
);
}
else
plan0 = prepareSvc.prepareSingle(sqlNode, qry.planningContext());
plan0 = prepareSvc.prepareSingle(sqlNode, qry.planningContext(validateParamsCnt, qryIdx0, qryList.size()));

return action.apply(qry, plan0);
}, schema.getName(), removeSensitive(sqlNode), qrys, params);

++qryIdx;

res.add(singleRes);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,27 @@ public void run(ExecutionContext<RowT> ctx, ExecutionPlan plan, FieldsMetadata m
tryClose(queryCanceledException());
}

/** */
/**
* Provides a planning context with disabled validation of the parameters number.
*
* @return Planning context.
*/
public PlanningContext planningContext() {
return planningContext(false, 0, 0);
}

/**
* Provides a planning context with optionally enabled validation of the parameters number.
*
* @param validateParamsCnt If {@code true}, enables validation of {@link #parameters()} number.
* @param curQryNum Current query number in the queries sharing {@link #parameters()}. Ignored if
* {@code validateParamsCnt} is {@code false}.
* @param totalQueriesCnt Total count of th queries sharing {@link #parameters()}. Ignored if
* {@code validateParamsCnt} is {@code false}.
* @return Planning context.
* @see PlanningContext#validateParamsNumber()
*/
public PlanningContext planningContext(boolean validateParamsCnt, int curQryNum, int totalQueriesCnt) {
synchronized (mux) {
if (state == QueryState.CLOSED || state == QueryState.CLOSING)
throw queryCanceledException();
Expand All @@ -308,12 +327,18 @@ public PlanningContext planningContext() {
if (pctx == null) {
state = QueryState.PLANNING;

pctx = PlanningContext.builder()
PlanningContext.Builder builder = PlanningContext.builder()
.parentContext(ctx)
.query(sql)
.parameters(params)
.plannerTimeout(plannerTimeout)
.build();
.plannerTimeout(plannerTimeout);

if (validateParamsCnt) {
builder.validateParametersQueryNumber(curQryNum);
builder.validateParametersTotalQueries(totalQueriesCnt);
}

pctx = builder.build();

try {
cancel.add(() -> pctx.unwrap(CancelFlag.class).requestCancel());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ private static boolean isAsCall(SqlNode node) {
}

CalciteCatalogReader catalogReader = this.catalogReader.withSchemaPath(schemaPath);
SqlValidator validator = new IgniteSqlValidator(operatorTbl, catalogReader, typeFactory, validatorCfg, ctx.parameters());
SqlValidator validator = new IgniteSqlValidator(operatorTbl, catalogReader, typeFactory, validatorCfg, ctx);
SqlToRelConverter sqlToRelConverter = sqlToRelConverter(validator, catalogReader, sqlToRelConverterCfg);
RelRoot root = sqlToRelConverter.convertQuery(sqlNode, true, false);
root = root.withRel(sqlToRelConverter.decorrelate(sqlNode, root.rel));
Expand Down Expand Up @@ -413,7 +413,7 @@ public String dump() {
/** */
private SqlValidator validator() {
if (validator == null)
validator = new IgniteSqlValidator(operatorTbl, catalogReader, typeFactory, validatorCfg, ctx.parameters());
validator = new IgniteSqlValidator(operatorTbl, catalogReader, typeFactory, validatorCfg, ctx);

return validator;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlNumericLiteral;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.SqlOrderBy;
import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlUpdate;
import org.apache.calcite.sql.SqlUtil;
Expand All @@ -57,7 +58,6 @@
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SelectScope;
import org.apache.calcite.sql.validate.SqlQualified;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorImpl;
import org.apache.calcite.sql.validate.SqlValidatorNamespace;
import org.apache.calcite.sql.validate.SqlValidatorScope;
Expand Down Expand Up @@ -101,8 +101,23 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
HUMAN_READABLE_ALIASES_FOR = Collections.unmodifiableSet(kinds);
}

/** Dynamic parameters. */
Object[] parameters;
/** Passed query arguments. */
private final Object[] parameters;

/** If {@code true}, enables validation of {@link #parameters}'s number against the query's dynamic parameters. */
private final boolean validateParamsNum;

/**
* Number of current query being validated with shared {@link #parameters}.
* Is not actual if {@link #validateParamsNum} is {@code false}.
*/
private final int qryNum;

/** Total number of queries being validated one by one with shared {@link #parameters}. */
private final int qryCnt;

/** Maximal number of detected query's dynamic parameters. Is not actual if {@link #validateParamsNum} is {@code false}. */
private int dynParCnt;

/**
* Creates a validator.
Expand All @@ -111,13 +126,26 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
* @param catalogReader Catalog reader
* @param typeFactory Type factory
* @param config Config
* @param parameters Dynamic parameters
* @param planningCtx Planning context
*/
public IgniteSqlValidator(SqlOperatorTable opTab, CalciteCatalogReader catalogReader,
IgniteTypeFactory typeFactory, SqlValidator.Config config, Object[] parameters) {
IgniteTypeFactory typeFactory, Config config, PlanningContext planningCtx) {
super(opTab, catalogReader, typeFactory, config);

this.parameters = parameters;
parameters = planningCtx.parameters();

validateParamsNum = planningCtx.validateParamsNumber();

if (validateParamsNum) {
qryNum = planningCtx.currentQueryNumber();
qryCnt = planningCtx.queriesCnt();

assert qryNum >= 0 && qryCnt > qryNum : "Wrong query number or total queries number.";
}
else {
qryNum = 0;
qryCnt = 0;
}
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -522,8 +550,24 @@ private boolean isSystemFieldName(String alias) {
|| QueryUtils.VAL_FIELD_NAME.equalsIgnoreCase(alias);
}

/** {@inheritDoc} */
@Override public SqlNode validate(SqlNode topNode) {
extractDynamicParameters(topNode);

SqlNode res = super.validate(topNode);

if (validateParamsNum && (parameters.length < dynParCnt || (parameters.length > dynParCnt && qryNum == qryCnt - 1))) {
throw newValidationError(res, IgniteResource.INSTANCE.unexpectedParameter(dynParCnt,
parameters == null ? 0 : parameters.length));
}

return res;
}

/** {@inheritDoc} */
@Override protected void inferUnknownTypes(RelDataType inferredType, SqlValidatorScope scope, SqlNode node) {
extractDynamicParameters(node);

if (node instanceof SqlDynamicParam && inferredType.equals(unknownType)) {
if (parameters.length > ((SqlDynamicParam)node).getIndex()) {
Object param = parameters[((SqlDynamicParam)node).getIndex()];
Expand Down Expand Up @@ -578,6 +622,39 @@ else if (operandTypeChecker instanceof FamilyOperandTypeChecker) {
super.inferUnknownTypes(inferredType, scope, node);
}

/** */
private void extractDynamicParameters(SqlNode node) {
if (!validateParamsNum)
return;

if (node instanceof SqlDynamicParam)
findMaximalDynamicParameterNumber((SqlDynamicParam)node);
if (node instanceof SqlOrderBy) {
SqlOrderBy orderBy = (SqlOrderBy)node;

if (orderBy.offset instanceof SqlDynamicParam)
findMaximalDynamicParameterNumber((SqlDynamicParam)orderBy.offset);

if (orderBy.fetch instanceof SqlDynamicParam)
findMaximalDynamicParameterNumber((SqlDynamicParam)orderBy.fetch);
}
else if (node instanceof SqlSelect) {
SqlSelect select = (SqlSelect)node;

if (select.getOffset() instanceof SqlDynamicParam)
findMaximalDynamicParameterNumber((SqlDynamicParam)select.getOffset());

if (select.getFetch() instanceof SqlDynamicParam)
findMaximalDynamicParameterNumber((SqlDynamicParam)select.getFetch());
}
}

/** */
private void findMaximalDynamicParameterNumber(SqlDynamicParam dynamicParam) {
if (dynamicParam.getIndex() >= dynParCnt)
dynParCnt = dynamicParam.getIndex() + 1;
}

/** {@inheritDoc} */
@Override public SqlLiteral resolveLiteral(SqlLiteral literal) {
if (literal instanceof SqlNumericLiteral && literal.createSqlType(typeFactory).getSqlTypeName() == SqlTypeName.BIGINT) {
Expand Down
Loading