All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.tools.Hoist Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hazelcast.org.apache.calcite.tools;

import com.hazelcast.org.apache.calcite.sql.SqlLiteral;
import com.hazelcast.org.apache.calcite.sql.SqlNode;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParseException;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParser;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParserPos;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParserUtil;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeName;
import com.hazelcast.org.apache.calcite.sql.util.SqlShuttle;

import com.hazelcast.com.google.common.base.Preconditions;
import com.hazelcast.com.google.common.collect.ImmutableList;
import com.hazelcast.com.google.common.collect.Lists;

import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;
import org.immutables.value.Value;

import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

/**
 * Utility that extracts constants from a SQL query.
 *
 * 

Simple use: * *

* final String sql =
* "select 'x' from emp where deptno < 10";
* final Hoist.Hoisted hoisted =
* Hoist.create(Hoist.config()).hoist();
* print(hoisted); // "select ?0 from emp where deptno < ?1" *
* *

Calling {@link Hoisted#toString()} generates a string that is similar to * SQL where a user has manually converted all constants to bind variables, and * which could then be executed using {@link PreparedStatement#execute()}. * That is not a goal of this utility, but see * [CALCITE-963] * Hoist literals. * *

For more advanced formatting, use {@link Hoisted#substitute(Function)}. * *

Adjust {@link Config} to use a different parser or parsing options. */ @Value.Enclosing public class Hoist { private final Config config; /** Creates a Config. */ public static Config config() { return ImmutableHoist.Config.builder() .withParserConfig(SqlParser.config()) .build(); } /** Creates a Hoist. */ public static Hoist create(Config config) { return new Hoist(config); } private Hoist(Config config) { this.config = Objects.requireNonNull(config, "config"); } /** Converts a {@link Variable} to a string "?N", * where N is the {@link Variable#ordinal}. */ public static String ordinalString(Variable v) { return "?" + v.ordinal; } /** Converts a {@link Variable} to a string "?N", * where N is the {@link Variable#ordinal}, * if the fragment is a character literal. Other fragments are unchanged. */ public static String ordinalStringIfChar(Variable v) { if (v.node instanceof SqlLiteral && ((SqlLiteral) v.node).getTypeName() == SqlTypeName.CHAR) { return "?" + v.ordinal; } else { return v.sql(); } } /** Hoists literals in a given SQL string, returning a {@link Hoisted}. */ public Hoisted hoist(String sql) { final List variables = new ArrayList<>(); final SqlParser parser = SqlParser.create(sql, config.parserConfig()); final SqlNode node; try { node = parser.parseQuery(); } catch (SqlParseException e) { throw new RuntimeException(e); } node.accept(new SqlShuttle() { @Override public @Nullable SqlNode visit(SqlLiteral literal) { variables.add(new Variable(sql, variables.size(), literal)); return super.visit(literal); } }); return new Hoisted(sql, variables); } /** Configuration. */ @Value.Immutable(singleton = false) public interface Config { /** Returns the configuration for the SQL parser. */ SqlParser.Config parserConfig(); /** Sets {@link #parserConfig()}. */ Config withParserConfig(SqlParser.Config parserConfig); } /** Variable. */ public static class Variable { /** Original SQL of whole statement. */ public final String originalSql; /** Zero-based ordinal in statement. */ public final int ordinal; /** Parse tree node (typically a literal). */ public final SqlNode node; /** Zero-based position within the SQL text of start of node. */ public final int start; /** Zero-based position within the SQL text after end of node. */ public final int end; private Variable(String originalSql, int ordinal, SqlNode node) { this.originalSql = Objects.requireNonNull(originalSql, "originalSql"); this.ordinal = ordinal; this.node = Objects.requireNonNull(node, "node"); final SqlParserPos pos = node.getParserPosition(); start = SqlParserUtil.lineColToIndex(originalSql, pos.getLineNum(), pos.getColumnNum()); end = SqlParserUtil.lineColToIndex(originalSql, pos.getEndLineNum(), pos.getEndColumnNum()) + 1; Preconditions.checkArgument(ordinal >= 0); Preconditions.checkArgument(start >= 0); Preconditions.checkArgument(start <= end); Preconditions.checkArgument(end <= originalSql.length()); } /** Returns SQL text of the region of the statement covered by this * Variable. */ public String sql() { return originalSql.substring(start, end); } } /** Result of hoisting. */ public static class Hoisted { public final String originalSql; public final List variables; Hoisted(String originalSql, List variables) { this.originalSql = originalSql; this.variables = ImmutableList.copyOf(variables); } @Override public String toString() { return substitute(Hoist::ordinalString); } /** Returns the SQL string with variables replaced according to the * given substitution function. */ public String substitute(Function fn) { final StringBuilder b = new StringBuilder(originalSql); for (Variable variable : Lists.reverse(variables)) { final String s = fn.apply(variable); b.replace(variable.start, variable.end, s); } return b.toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy