1. Introduction
In our daily development, we may encounter the need to verify whether SQL is correct, that is, we need to check SQL.
To determine whether SQL is correct, generally include the following points:
1. Whether the column used in sql exists
2. Is the sql syntax correct?
3. Whether the operators/functions used in sql exist and are there any correct use
We can use the following SQL example to explore how to verify SQL using Calcite
select
,
max()
from user u
inner join role r on u.role_id =
where = 1
group by
2. Maven
<dependency>
<groupId></groupId>
<artifactId>calcite-core</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
3. Verification
First, verify the correctness of SQL in calcite by using theSqlValidator
class is validated, butSqlValidator
is an interface, usually through(...)
The method is instantiated as follows:
public static SqlValidatorWithHints newValidator(SqlOperatorTable opTab,
SqlValidatorCatalogReader catalogReader,
RelDataTypeFactory typeFactory,
config)
{
return new SqlValidatorImpl(opTab, catalogReader, typeFactory, config);
}
- SqlOperatorTable: Used to provide the operators (SqlOperator) and functions (SqlFunction) required for SQL verification, for example: >, <, = or max(), in()
- SqlValidatorCatalogReader: Used to provide the metadata information required for verification, for example: schema, table, column
- RelDataTypeFactory: A factory class that processes data types, used to provide the creation and conversion of types, java types and collection types. For different interface forms, calcite supports two implementations of SQL and Java (SqlTypeFactoryImpl and JavaTypeFactoryImpl). Of course, users can expand on their own according to different situations.
- : You can customize some configurations, such as whether to enable implicit type conversion, whether to enable SQL rewrite, etc.
3.1 Creating SqlValidator
createSqlValidator
Before, we need to instantiate the above four parameter objects. Fortunately, calcite provides the default implementation of corresponding properties, which makes it easy for us to create.SqlValidator
Object
SqlValidator validator = (
(),
catalogReader, // catalog information needs to be created manually by yourself
new SqlTypeFactoryImpl(),
);
Except hereSqlValidatorCatalogReader
Objects need to be created extra by themselves. If there are no other special requirements, we can use the default implementation provided by calcite.
Let's talk about it here.SqlValidatorCatalogReader
How to create an object
firstSqlValidatorCatalogReader
If we use it to provide the catalog information required for verification, we need to provide the catalog information (because calcite needs to verify metadata, such as whether the table or field exists, and if metadata is not provided, how can we talk about verification?)
createSqlValidatorCatalogReader
There are two ways:
-
Through the data source, we know the server information of executing SQL, and give the connection information to Calcite, so that Calcite can obtain meta information and verify it by itself. That is, at this time, we need to connect to DB to verify it.
@SneakyThrows private static CalciteCatalogReader createCatalogReaderWithDataSource() { Connection connection = ("jdbc:calcite:"); CalciteConnection calciteConnection = (); SchemaPlus rootSchema = (); DataSource dataSource = ( "jdbc:mysql://localhost:3306/test", "", "root", "123456" ); JdbcSchema jdbcSchema = (rootSchema, "my_mysql", dataSource, null, null); ("my_mysql", jdbcSchema); ("my_mysql"); CalciteServerStatement statement = ().unwrap(); prepareContext = (); SqlTypeFactoryImpl factory = new SqlTypeFactoryImpl(); return new CalciteCatalogReader( (), (), factory, ()); }
-
Manually add catalog information, so you can verify it without connecting to the library
private static CalciteCatalogReader createCatalogReaderWithMeta() { SchemaPlus rootSchema = (true); RelDataTypeSystem relDataTypeSystem = ; RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(relDataTypeSystem); ("user", new AbstractTable() { @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { builder = (); ("id", new BasicSqlType(relDataTypeSystem, )); ("name", new BasicSqlType(relDataTypeSystem, )); ("age", new BasicSqlType(relDataTypeSystem, )); ("sex", new BasicSqlType(relDataTypeSystem, )); ("role_id", new BasicSqlType(relDataTypeSystem, )); return (); } }); ("role", new AbstractTable() { @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { builder = (); ("id", new BasicSqlType(relDataTypeSystem, )); ("name", new BasicSqlType(relDataTypeSystem, )); return (); } }); CalciteConnectionConfig connectionConfig = ; return new CalciteCatalogReader( (rootSchema), (rootSchema).path(null), typeFactory, connectionConfig); }
OK, create it here
SqlValidator
All required parameters are ready, but the required parameters are not SQL strings but(SqlNode topNode)
, SoSqlNode
How to create it?
3.2 Analyzing Sql
SqlNode
As the name suggests, it is a SQL node object, directly passedSqlParser
Object creation, as follows
config = ()
// Analysis factory
.withParserFactory()
// It can also be directly set as the lexical analyzer for the corresponding database
// .withLex()
// Insensitive to upper and lower case
.withCaseSensitive(false)
// Quotation symbol is backquoted
.withQuoting(Quoting.BACK_TICK)
// Unquoted identifiers are not processed during parsing
.withUnquotedCasing()
// Quoted identifiers are not processed during parsing
.withQuotedCasing()
// Use default syntax rules
.withConformance();
// sql parser
final SqlParser parser = (SQL, config);
// Convert sql to calcite SqlNode
SqlNode sqlNode = ();
3.3 Perform verification
Through the above steps we have been able to createSqlValidator
Objects and can create their validation requiredSqlNode
Objects are actually very simple. As long as there is no error reported during verification, sql is correct.
try{
// Verify sql
(sqlNode);
("sql is valid");
}
catch (Exception e) {
("sql is invalid", e);
}
4. Complete verification code
4.1 Verification via SqlValidator
package ;
import ;
import .slf4j.Slf4j;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Slf4j
public class SqlValidatorTest {
private static final String SQL = "select , max() from `user` u inner join role r on u.role_id = where = 1 group by ";
@Test
@SneakyThrows
public void given_sql_and_meta_then_validate_sql() {
config = ()
// Analysis factory
.withParserFactory()
// It can also be directly set as the lexical analyzer for the corresponding database
// .withLex()
// Insensitive to upper and lower case
.withCaseSensitive(false)
// Quotation symbol is backquoted
.withQuoting(Quoting.BACK_TICK)
// Unquoted identifiers are not processed during parsing
.withUnquotedCasing()
// Quoted identifiers are not processed during parsing
.withQuotedCasing()
// Use default syntax rules
.withConformance();
// sql parser
final SqlParser parser = (SQL, config);
// Convert SQL to Calcite's SqlNode
SqlNode sqlNode = ();
// Create SqlValidator for verification
SqlValidator validator = (
(),
// Use the method of providing meta information directly
createCatalogReaderWithMeta(),
// Use the method of providing data sources
//createCatalogReaderWithDataSource(),
new SqlTypeFactoryImpl(),
);
try{
// Verify sql
(sqlNode);
("sql is valid");
}
catch (Exception e) {
("sql is invalid", e);
}
}
private static CalciteCatalogReader createCatalogReaderWithMeta() {
SchemaPlus rootSchema = (true);
RelDataTypeSystem relDataTypeSystem = ;
RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(relDataTypeSystem);
("user", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
builder = ();
("id", new BasicSqlType(relDataTypeSystem, ));
("name", new BasicSqlType(relDataTypeSystem, ));
("age", new BasicSqlType(relDataTypeSystem, ));
("sex", new BasicSqlType(relDataTypeSystem, ));
("role_id", new BasicSqlType(relDataTypeSystem, ));
return ();
}
});
("role", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
builder = ();
("id", new BasicSqlType(relDataTypeSystem, ));
("name", new BasicSqlType(relDataTypeSystem, ));
return ();
}
});
CalciteConnectionConfig connectionConfig = ;
return new CalciteCatalogReader(
(rootSchema),
(rootSchema).path(null),
typeFactory,
connectionConfig);
}
@SneakyThrows
private static CalciteCatalogReader createCatalogReaderWithDataSource() {
Connection connection = ("jdbc:calcite:");
CalciteConnection calciteConnection = ();
SchemaPlus rootSchema = ();
DataSource dataSource = (
"jdbc:mysql://localhost:3306/test",
"",
"root",
"123456"
);
JdbcSchema jdbcSchema = (rootSchema, "my_mysql", dataSource, null, null);
("my_mysql", jdbcSchema);
("my_mysql");
CalciteServerStatement statement = ().unwrap();
prepareContext = ();
SqlTypeFactoryImpl factory = new SqlTypeFactoryImpl();
return new CalciteCatalogReader(
(),
(),
factory,
());
}
}
4.2 Verification using Planner object
In fact, the method is using the SqlValidator object for verification
package ;
import ;
import .slf4j.Slf4j;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Slf4j
public class SqlValidatorWithPlannerTest {
private static final String SQL = "select , max() from `user` u inner join role r on u.role_id = where = 1 group by ";
@Test
@SneakyThrows
public void given_sql_and_meta_then_validate_sql() {
// Create Calcite configuration
FrameworkConfig config = createFrameworkConfig();
// Create Planner
Planner planner = (config);
// parse SQL
final SqlNode parse = (SQL);
try {
// Get SqlValidator for verification
(parse);
("sql is valid");
} catch (ValidationException e) {
("sql is invalid", e);
}
}
private static FrameworkConfig createFrameworkConfig() {
SchemaPlus rootSchema = (true);
RelDataTypeSystem relDataTypeSystem = ;
("user", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
builder = ();
("id", new BasicSqlType(relDataTypeSystem, ));
("name", new BasicSqlType(relDataTypeSystem, ));
("age", new BasicSqlType(relDataTypeSystem, ));
("sex", new BasicSqlType(relDataTypeSystem, ));
("role_id", new BasicSqlType(relDataTypeSystem, ));
return ();
}
});
("role", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
builder = ();
("id", new BasicSqlType(relDataTypeSystem, ));
("name", new BasicSqlType(relDataTypeSystem, ));
return ();
}
});
config = ()
.withParserFactory()
.withQuoting(Quoting.BACK_TICK)
.withCaseSensitive(false)
.withUnquotedCasing()
.withQuotedCasing()
.withConformance();
return Frameworks
.newConfigBuilder()
.defaultSchema(rootSchema)
.parserConfig(config)
.build();
}
}