现象

使用 apache calicte 整合 mysql 测试,使用日期类型的 between and,发现无法查询到数据。

反复测试了多次,发现确实是 apache calicte 的兼容性问题,记录一下。

mysql 准备

版本

mysql> select @@version;
+------------+
| @@version  |
+------------+
| 5.7.31-log |
+------------+
1 row in set (0.00 sec)

建表

use test;

create table user_info(
    id INT NOT NULL,
    name varchar(32),
    update_time datetime(6)
);

insert into user_info (id, name, update_time) values (1, 'u-1', now());
insert into user_info (id, name, update_time) values (2, 'u-2', now());

数据确认:

mysql> select * from user_info;
+----+------+----------------------------+
| id | name | update_time                |
+----+------+----------------------------+
|  1 | u-1  | 2024-03-21 17:26:45.000000 |
|  2 | u-2  | 2024-03-21 17:26:46.000000 |
+----+------+----------------------------+
2 rows in set (0.00 sec)

maven 依赖

<dependencies>
<!--        jdbc 应该是 core 的一部分-->
    <dependency>
        <groupId>org.apache.calcite</groupId>
        <artifactId>calcite-core</artifactId>
        <version>1.36.0</version>
    </dependency>
    <!-- MySQL JDBC Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version> <!-- 或者最新版本 -->
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.15</version>
    </dependency>
</dependencies>

mysql 正常查询

java 代码

package com.github.houbb.calcite.learn.mysql;

import com.alibaba.druid.pool.DruidDataSource;

import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;

/**
 * druid 整合 mysql 使用
 */
public class DruidMySQLExampleWhereDate {

    public static void main(String[] args) {
        // 初始化 Druid 数据源
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
        dataSource.setUsername("admin");
        dataSource.setPassword("123456");

        try (PreparedStatement preparedStatement = dataSource.getConnection().prepareStatement("SELECT id, update_time FROM user_info WHERE update_time BETWEEN ? AND ?")) {
            Timestamp startOfDay = Timestamp.valueOf(LocalDate.now().atStartOfDay());
            Timestamp endOfDay = Timestamp.valueOf(LocalDateTime.now().withHour(23).withMinute(59).withSecond(59));

            // 设置参数
            preparedStatement.setTimestamp(1, startOfDay);
            preparedStatement.setTimestamp(2, endOfDay);
            // 执行查询
            ResultSet resultSet = preparedStatement.executeQuery();

            // 处理结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                Timestamp updateTime = resultSet.getTimestamp("update_time");
                System.out.println("ID: " + id + ", Update Time: " + updateTime);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

}

效果

ID: 1, Update Time: 2024-03-21 17:26:45.0
ID: 2, Update Time: 2024-03-21 17:26:46.0

可以正常查询。

v1-calcite 不正常的版本

代码

package com.github.houbb.calcite.learn.mysql;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import org.apache.calcite.adapter.jdbc.JdbcSchema;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaPlus;

import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Properties;

/**
 * https://blog.csdn.net/a17816876003/article/details/125592222
 *
 */
public class CalciteMySQLExampleNoPrefixAndWhereDateNotWork {

    public static void main(String[] args) throws Exception {
        // check driver exist
        Class.forName("org.apache.calcite.jdbc.Driver");
        Class.forName("com.mysql.jdbc.Driver");

        // the properties for calcite connection
        Properties info = new Properties();
        //LEX: 使用传统的 SQL 解析器,也称为 "LEX" 解析器。这是 Calcite 默认的解析器类型,能够处理大多数 SQL 语法。
        //JAVA: 使用 Java 解析器,也称为 "JAVA" 解析器。这个解析器是针对 Calcite 提供的 SQL 格式进行解析。
        info.setProperty("lex", "JAVA");
        // remarks 是指定是否应该包含数据库的备注信息(也称为注释)。当设置为 true 时,表示在元数据中包含数据库对象的备注信息;当设置为 false 时,不包含备注信息。
        info.setProperty("remarks","true");
        // SqlParserImpl can analysis sql dialect for sql parse
        info.setProperty("parserFactory","org.apache.calcite.sql.parser.impl.SqlParserImpl#FACTORY");

        // create calcite connection and schema
        Connection connection = DriverManager.getConnection("jdbc:calcite:", info);
        CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
        System.out.println(calciteConnection.getProperties());

        // code for mysql datasource
        MysqlDataSource dataSource = new MysqlDataSource();
        // please change host and port maybe like "jdbc:mysql://127.0.0.1:3306/test"
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
        dataSource.setUser("admin");
        dataSource.setPassword("123456");
        // mysql schema, the sub schema for rootSchema, "test" is a schema in mysql
        SchemaPlus rootSchema = calciteConnection.getRootSchema();
        Schema schema = JdbcSchema.create(rootSchema, "test", dataSource, null, "test");
        rootSchema.add("test", schema);

        // Set default schema for unqualified table names,这样可以让 sql 不加前缀?
        calciteConnection.setSchema("test");

        // 执行SQL查询
        try (PreparedStatement preparedStatement = connection.prepareStatement("SELECT id, update_time FROM user_info WHERE update_time BETWEEN ? AND ?")) {
            Timestamp startOfDay = Timestamp.valueOf(LocalDate.now().atStartOfDay());
            Timestamp endOfDay = Timestamp.valueOf(LocalDateTime.now().withHour(23).withMinute(59).withSecond(59));

            // 设置参数
            preparedStatement.setTimestamp(1, startOfDay);
            preparedStatement.setTimestamp(2, endOfDay);
            // 执行查询
            ResultSet resultSet = preparedStatement.executeQuery();

            // 处理结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                Timestamp updateTime = resultSet.getTimestamp("update_time");
                System.out.println("ID: " + id + ", Update Time: " + updateTime);
            }
        }

        connection.close();
    }
}

现象

没有任何报错,但是同时也没有任何结果。

一开始以为是写错了,但是把 where 条件去掉,发现数据就可以正常返回。

猜测最可能的原因应该是类型匹配,导致底层直接被忽略掉了?

这种也不报错,真的麻烦。

v2-可行的方式1

java

// 执行SQL查询
try (PreparedStatement preparedStatement = connection.prepareStatement("SELECT id, update_time FROM user_info WHERE update_time BETWEEN TIMESTAMP '2024-03-2100:00:00' AND TIMESTAMP '2024-03-21 23:59:59'")) {
    // 执行查询
    ResultSet resultSet = preparedStatement.executeQuery();
    // 处理结果集
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        Timestamp updateTime = resultSet.getTimestamp("update_time");
        System.out.println("ID: " + id + ", Update Time: " + updateTime);
    }
}

这种写法是可行的。

v3-可行方案2

TIMESTAMPADD 计算

// 执行SQL查询
try (PreparedStatement preparedStatement = connection.prepareStatement("SELECT id, update_time FROM user_info WHERE update_time BETWEEN TIMESTAMPADD(MINUTE,-30, CURRENT_TIMESTAMP) AND CURRENT_TIMESTAMP")) {
    // 执行查询
    ResultSet resultSet = preparedStatement.executeQuery();
    // 处理结果集
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        Timestamp updateTime = resultSet.getTimestamp("update_time");
        System.out.println("ID: " + id + ", Update Time: " + updateTime);
    }
}

v4-可行版本 CAST

java 代码

// 执行SQL查询
try (PreparedStatement preparedStatement = connection.prepareStatement("SELECT id, update_time FROM user_info WHERE update_time BETWEEN CAST(? AS TIMESTAMP) ANDCAST(? AS TIMESTAMP)")) {

    // 获取当前时间和当天的开始时间
    Timestamp startOfDay = Timestamp.valueOf(LocalDate.now().atStartOfDay());
    Timestamp endOfDay = Timestamp.valueOf(LocalDateTime.now().withHour(23).withMinute(59).withSecond(59));
    // 设置参数
    preparedStatement.setString(1, startOfDay.toString());
    preparedStatement.setString(2, endOfDay.toString());
    // 执行查询
    ResultSet resultSet = preparedStatement.executeQuery();
    // 处理结果集
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        Timestamp updateTime = resultSet.getTimestamp("update_time");
        System.out.println("ID: " + id + ", Update Time: " + updateTime);
    }

}

最后还是决定用 v4 CAST 的方式,可能这个更加接近原来的占位符的方式。

参考资料

https://blog.csdn.net/a17816876003/article/details/125592222