惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Proofpoint News Feed
D
DataBreaches.Net
D
Docker
云风的 BLOG
云风的 BLOG
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
J
Java Code Geeks
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
罗磊的独立博客
Martin Fowler
Martin Fowler
U
Unit 42
Engineering at Meta
Engineering at Meta
IT之家
IT之家
Vercel News
Vercel News
B
Blog RSS Feed
人人都是产品经理
人人都是产品经理
博客园 - Franky
博客园 - 【当耐特】
Stack Overflow Blog
Stack Overflow Blog
G
Google Developers Blog
MongoDB | Blog
MongoDB | Blog

博客园 - 黄洪波

安装openclaw 排查项目中依赖的mybatis 拦截器 ModelAttribute 老革命遇上新问题 OpenWebUI单点登录之解决动态参数问题 IDEA自带的Maven 3.9.x无法刷新http nexus私服(转) windows docker安装rocketmq之踩坑记 分布式系统设计经典论文(转载) 训练自己的yolo-v11数据集(二) 训练自己的yolo-v11数据集(一) 本地使用pycharm进行yolo推理 2024年的云原生架构需要哪些技术栈 (转) yolo v11学习,入门篇 OpenWebUI单点登录之深坑 AI工具验证 解决Win10无法进入睡眠模式(转) idea常用插件 内网离线模式下激活JRebel java导入json数据至doris 将SpringBoot打包之后的jar设为守护进程
使用calcite构造ddl建表语句
黄洪波 · 2025-03-26 · via 博客园 - 黄洪波

核心实现:

// 列定义
SqlNode columnDef = SqlDdlNodes.column(
SqlParserPos.ZERO,
columnName,
dataTypeSpec,
defaultValue,
defaultValue == null ? null : ColumnStrategy.DEFAULT
);

备注,这个地方卡了我三个小时以上,所有ai给出的示例最后一个参数都是传入的null,

一直报空指针异常:

java.lang.NullPointerException
at org.apache.calcite.sql.ddl.SqlColumnDeclaration.unparse(SqlColumnDeclaration.java:77)
直到某段AI的分解过程中出现:

Calcite SqlColumnDeclaration 类中 unparse 方法的实现细节

SqlColumnDeclaration 类中的 unparse 方法的具体实现细节并未直接在任何证据中提及。

但是,可以基于其他 SqlNode 类的 unparse 方法实现进行推测。

通常,unparse 方法负责将 SQL 节点转换回 SQL 字符串。

以下是一个可能的实现示例,基于其他 SqlNode 类的 unparse 方法实现:

@Override
public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
    // 输出列名
    name.unparse(writer, leftPrec, rightPrec);
    // 输出数据类型
    type.unparse(writer, leftPrec, rightPrec);
    // 如果有默认值,则输出默认值
    if (defaultValue != null) {
        writer.keyword("DEFAULT");
        defaultValue.unparse(writer, leftPrec, rightPrec);
    }
    // 如果有约束,则输出约束
    if (constraints != null) {
        for (SqlNode constraint : constraints) {
            constraint.unparse(writer, leftPrec, rightPrec);
        }
    }
}

我于是去翻源码,看到以下内容,源码第45行:

public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
        this.name.unparse(writer, 0, 0);
        this.dataType.unparse(writer, 0, 0);
        if (Boolean.FALSE.equals(this.dataType.getNullable())) {
            writer.keyword("NOT NULL");
        }

        SqlNode expression = this.expression;
        if (expression != null) {
            switch (this.strategy) {
                case VIRTUAL:
                case STORED:
                    writer.keyword("AS");
                    exp(writer, expression);
                    writer.keyword(this.strategy.name());
                    break;
                case DEFAULT:
                    writer.keyword("DEFAULT");
                    exp(writer, expression);
                    break;
                default:
                    throw new AssertionError("unexpected: " + this.strategy);
            }
        }

    }

反应过来,如果我想要设置默认值,那么第五个参数

ColumnStrategy 就必须传入
ColumnStrategy.DEFAULT

完整代码如下:

import org.apache.calcite.schema.ColumnStrategy;
import org.apache.calcite.sql.*;
import org.apache.calcite.sql.ddl.SqlColumnDeclaration;
import org.apache.calcite.sql.ddl.SqlCreateTable;
import org.apache.calcite.sql.dialect.PostgresqlSqlDialect;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.util.SqlBuilder;
import org.apache.calcite.sql.ddl.SqlDdlNodes;

import java.util.List;

public class DdlSqlGenerator {

    /**
     * 生成 CREATE TABLE 语句
     *
     * @param tableSchema 表模式(如 "public")
     * @param tableName   表名
     * @param columns     列定义列表
     * @return 生成的 SQL 字符串
     */
    public static String
    generateCreateTableSql(SqlDialect dialect, String tableSchema, String tableName, List<Column> columns) {
        // 创建表标识符(schema.tableName)
        SqlIdentifier tableIdentifier = new SqlIdentifier(tableName, SqlParserPos.ZERO);

        // 创建列定义列表
        SqlNodeList columnList = new SqlNodeList(SqlParserPos.ZERO);
        for (Column column : columns) {
            // 列名
            SqlIdentifier columnName = new SqlIdentifier(column.getColumnName(), SqlParserPos.ZERO);

            // 数据类型
            SqlDataTypeSpec dataTypeSpec = createDataTypeSpec(column);

            SqlNode defaultValue = createDefaultValue(column.getDataDefault());

            // 列定义
            SqlNode columnDef = SqlDdlNodes.column(
                    SqlParserPos.ZERO,
                    columnName,
                    dataTypeSpec,
                    defaultValue,
                    defaultValue == null ? null : ColumnStrategy.DEFAULT
            );


            columnList.add(columnDef);
        }

        // 创建 CREATE TABLE 语句
        SqlCreateTable createTable = SqlDdlNodes.createTable(
                SqlParserPos.ZERO,  // 位置
                false,              // 是否替换(ifNotExists)
                false,              // 是否临时表(isTemporary)
                tableIdentifier,    // 表名
                columnList,         // 列定义
                null                // 查询(这里无)
        );

        return createTable.toSqlString(dialect).getSql();
    }

    /**
     * 根据 Column 属性创建数据类型规格
     *
     * @param column 列对象
     * @return 数据类型规格
     */
    private static SqlDataTypeSpec createDataTypeSpec(Column column) {
        SqlTypeName typeName = column.getDataType();
        SqlDataTypeSpec dataTypeSpec;

        // 根据数据类型处理长度、精度和标度
        switch (typeName) {
            case CHAR:
            case VARCHAR:
                // 有长度的数据类型
                dataTypeSpec = new SqlDataTypeSpec(
                        new SqlBasicTypeNameSpec(typeName, (int) column.getDataLength(), SqlParserPos.ZERO),
                        SqlParserPos.ZERO
                );
                break;
            case DECIMAL:
                // 有精度和标度的数据类型
                dataTypeSpec = new SqlDataTypeSpec(
                        new SqlBasicTypeNameSpec(typeName, column.getDataPrecision(), column.getDataScale(), SqlParserPos.ZERO),
                        SqlParserPos.ZERO
                );
                break;
            default:
                // 无需长度、精度的数据类型
                dataTypeSpec = new SqlDataTypeSpec(
                        new SqlBasicTypeNameSpec(typeName, SqlParserPos.ZERO),
                        SqlParserPos.ZERO
                );
                break;
        }

        // 设置可空性
        dataTypeSpec = dataTypeSpec.withNullable(column.isNullable());



        return dataTypeSpec;
    }

    //创建默认值
    public static SqlNode createDefaultValue(Object defaultObj) {
        if (defaultObj == null) {
            return null;
        }

        try {
            if (defaultObj instanceof Number) {
                // 数字类型(整数、浮点数)
                return SqlLiteral.createExactNumeric(defaultObj.toString(), SqlParserPos.ZERO);
            } else if (defaultObj instanceof Boolean) {
                // 布尔类型
                return SqlLiteral.createBoolean((Boolean) defaultObj, SqlParserPos.ZERO);
            } else if (defaultObj instanceof String) {
                // 字符串类型
                // 添加额外的空字符串检查
                String strValue = (String) defaultObj;
                if (strValue.isEmpty()) {
                    return SqlLiteral.createCharString("", SqlParserPos.ZERO);
                }
                return SqlLiteral.createCharString(strValue, SqlParserPos.ZERO);
            } else {
                // 其他类型默认转换为字符串
                return SqlLiteral.createCharString(defaultObj.toString(), SqlParserPos.ZERO);
            }
        } catch (Exception e) {
            // 如果创建默认值失败,返回null或记录日志
            System.err.println("Failed to create default value: " + e.getMessage());
            return null;
        }
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Column {
    private String tableSchema;
    private String tableName;
    private String columnName;
    private SqlTypeName dataType;
    private long dataLength;
    private int dataPrecision;
    private int dataScale;
    private boolean nullable;
    private Object dataDefault;
    private int columnId;
    private String columnComment;

}

测试代码:

@Test
void testGenerateCreateTableSql() {
// 示例列定义
List<Column> columns = new ArrayList<>();
columns.add(new Column("public", "users", "id", SqlTypeName.BIGINT, 0, 0, 0, false, 11, 1, "Primary key"));
columns.add(new Column("public", "users", "name", SqlTypeName.VARCHAR, 50, 0, 0, true, "unknown", 2, "User name"));
columns.add(new Column("public", "users", "balance", SqlTypeName.DECIMAL, 0, 10, 2, true, 40, 3, "User balance"));

// 生成 SQL
String sql = DdlSqlGenerator.generateCreateTableSql(MysqlSqlDialect.DEFAULT,"public", "users", columns);
System.out.println(sql);
sql = DdlSqlGenerator.generateCreateTableSql(PostgresqlSqlDialect.DEFAULT,"public", "users", columns);
System.out.println(sql);
sql = DdlSqlGenerator.generateCreateTableSql(OracleSqlDialect.DEFAULT,"public", "users", columns);
System.out.println(sql);
sql = DdlSqlGenerator.generateCreateTableSql(MssqlSqlDialect.DEFAULT,"public", "users", columns);
System.out.println(sql);

}

输出结果:

CREATE TABLE `users` (`id` BIGINT NOT NULL DEFAULT (11), `name` VARCHAR(50) DEFAULT ('unknown'), `balance` DECIMAL(10, 2) DEFAULT (40))
CREATE TABLE "users" ("id" BIGINT NOT NULL DEFAULT (11), "name" VARCHAR(50) DEFAULT ('unknown'), "balance" DECIMAL(10, 2) DEFAULT (40))
CREATE TABLE "users" ("id" BIGINT NOT NULL DEFAULT (11), "name" VARCHAR(50) DEFAULT ('unknown'), "balance" DECIMAL(10, 2) DEFAULT (40))
CREATE TABLE [users] ([id] BIGINT NOT NULL DEFAULT (11), [name] VARCHAR(50) DEFAULT ('unknown'), [balance] DECIMAL(10, 2) DEFAULT (40))

posted on 2025-03-26 22:53  黄洪波  阅读(209)  评论()    收藏  举报