Compare commits

...

5 Commits

Author SHA1 Message Date
gaattc c8843ecf39
Merge b5e431e1a3 into 7629916024 2025-07-17 09:39:54 +08:00
Gladys-111 7629916024
Fix multiline problem (#6485)
Java CI / Test JDK ${{ matrix.java }}, ${{ matrix.os }} (11, ubuntu-latest) (push) Has been cancelled Details
Java CI / Test JDK ${{ matrix.java }}, ${{ matrix.os }} (17, ubuntu-latest) (push) Has been cancelled Details
Java CI / Test JDK ${{ matrix.java }}, ${{ matrix.os }} (21, ubuntu-latest) (push) Has been cancelled Details
Java CI / Test JDK ${{ matrix.java }}, ${{ matrix.os }} (8, ubuntu-latest) (push) Has been cancelled Details
2025-07-17 09:38:13 +08:00
gaattc b5e431e1a3 #6467 supporting PG syntax 'ALTER COLUMN ... SET DATA TYPE ... USING ...'
Signed-off-by: gaattc <gaattc000@gmail.com>
2025-07-16 11:23:34 +08:00
gaattc 2f11505ce7 #6467 supporting PG syntax 'ALTER COLUMN ... SET DATA TYPE ...' add test
Signed-off-by: gaattc <gaattc000@gmail.com>
2025-07-05 16:45:00 +08:00
gaattc 924cd088b7 #6467 supporting PG syntax 'ALTER COLUMN ... SET DATA TYPE ...'
Signed-off-by: gaattc <gaattc000@gmail.com>
2025-07-03 15:38:46 +08:00
8 changed files with 445 additions and 6 deletions

View File

@ -33,12 +33,14 @@ public class SQLAlterTableAlterColumn extends SQLObjectImpl implements SQLAlterT
private SQLName after;
private SQLDataType dataType;
private boolean toFirst;
private SQLExpr using;
@Override
protected void accept0(SQLASTVisitor visitor) {
if (visitor.visit(this)) {
acceptChild(visitor, column);
acceptChild(visitor, setDefault);
acceptChild(visitor, using);
}
visitor.endVisit(this);
}
@ -124,4 +126,16 @@ public class SQLAlterTableAlterColumn extends SQLObjectImpl implements SQLAlterT
}
this.dataType = x;
}
public SQLExpr getUsing() {
return using;
}
public void setUsing(SQLExpr using) {
if (using != null) {
using.setParent(this);
}
this.using = using;
}
}

View File

@ -5,11 +5,18 @@ import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLAssignItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLWithSubqueryClause;
import com.alibaba.druid.sql.dialect.clickhouse.ast.CKSelectQueryBlock;
import com.alibaba.druid.sql.parser.*;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLSelectListCache;
import com.alibaba.druid.sql.parser.SQLSelectParser;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import java.util.List;
public class CKSelectParser
extends SQLSelectParser {
public CKSelectParser(Lexer lexer) {
@ -85,12 +92,29 @@ public class CKSelectParser
@Override
public void parseFrom(SQLSelectQueryBlock queryBlock) {
super.parseFrom(queryBlock);
if (lexer.token() == Token.FINAL) {
lexer.nextToken();
((CKSelectQueryBlock) queryBlock).setFinal(true);
List<String> comments = null;
if (lexer.hasComment() && lexer.isKeepComments()) {
comments = lexer.readAndResetComments();
}
if (lexer.nextIf(Token.FROM)) {
SQLTableSource from = parseTableSource();
if (comments != null) {
from.addBeforeComment(comments);
}
queryBlock.setFrom(from);
} else {
((CKSelectQueryBlock) queryBlock).setFinal(false);
if (comments != null) {
queryBlock.addAfterComment(comments);
}
}
if (lexer.nextIf(Token.FINAL)) {
if (queryBlock instanceof CKSelectQueryBlock) {
((CKSelectQueryBlock) queryBlock).setFinal(true);
}
}
}

View File

@ -8,7 +8,9 @@ import com.alibaba.druid.sql.dialect.clickhouse.ast.CKCreateTableStatement;
import com.alibaba.druid.sql.dialect.clickhouse.ast.CKSelectQueryBlock;
import com.alibaba.druid.sql.dialect.clickhouse.ast.ClickhouseColumnCodec;
import com.alibaba.druid.sql.dialect.clickhouse.ast.ClickhouseColumnTTL;
import com.alibaba.druid.sql.parser.CharTypes;
import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor;
import com.alibaba.druid.sql.visitor.VisitorFeature;
import com.alibaba.druid.util.StringUtils;
import java.util.List;
@ -230,6 +232,55 @@ public class CKOutputVisitor extends SQLASTOutputVisitor implements CKASTVisitor
return false;
}
@Override
public void printComment(String comment) {
if (comment == null) {
return;
}
if (isEnabled(VisitorFeature.OutputSkipMultilineComment) && comment.startsWith("/*")) {
return;
}
if (isEnabled(VisitorFeature.OutputSkipSingleLineComment)
&& (comment.startsWith("-") || comment.startsWith("#"))) {
return;
}
if (comment.startsWith("--")
&& comment.length() > 2
&& comment.charAt(2) != ' '
&& comment.charAt(2) != '-') {
print0("-- ");
print0(comment.substring(2));
} else if (comment.startsWith("#")
&& comment.length() > 1
&& comment.charAt(1) != ' '
&& comment.charAt(1) != '#') {
print0("# ");
print0(comment.substring(1));
} else if (comment.startsWith("/*")) {
println();
print0(comment);
} else if (comment.startsWith("--")) {
print0(comment);
}
char first = '\0';
for (int i = 0; i < comment.length(); i++) {
char c = comment.charAt(i);
if (CharTypes.isWhitespace(c)) {
continue;
}
first = c;
break;
}
if (first == '-' || first == '#') {
endLineComment = true;
}
}
@Override
protected void printAfterFetch(SQLSelectQueryBlock queryBlock) {
if (queryBlock instanceof CKSelectQueryBlock) {
@ -274,6 +325,19 @@ public class CKOutputVisitor extends SQLASTOutputVisitor implements CKASTVisitor
@Override
protected void printFrom(SQLSelectQueryBlock x) {
SQLTableSource from = x.getFrom();
if (from == null) {
return;
}
List<String> beforeComments = from.getBeforeCommentsDirect();
if (beforeComments != null) {
for (String comment : beforeComments) {
println();
print0(comment);
}
}
super.printFrom(x);
if (x instanceof CKSelectQueryBlock && ((CKSelectQueryBlock) x).isFinal()) {
print0(ucase ? " FINAL" : " final");

View File

@ -3,10 +3,15 @@ package com.alibaba.druid.sql.dialect.hologres.visitor;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLPartitionBy;
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.dialect.clickhouse.ast.CKSelectQueryBlock;
import com.alibaba.druid.sql.dialect.postgresql.visitor.PGOutputVisitor;
import com.alibaba.druid.sql.parser.CharTypes;
import com.alibaba.druid.sql.visitor.VisitorFeature;
import java.util.List;
public class HologresOutputVisitor extends PGOutputVisitor {
public HologresOutputVisitor(StringBuilder appender, boolean parameterized) {
super(appender, DbType.hologres, parameterized);
@ -30,6 +35,27 @@ public class HologresOutputVisitor extends PGOutputVisitor {
partitionBy.accept(this);
}
@Override
protected void printFrom(SQLSelectQueryBlock x) {
SQLTableSource from = x.getFrom();
if (from == null) {
return;
}
List<String> beforeComments = from.getBeforeCommentsDirect();
if (beforeComments != null) {
for (String comment : beforeComments) {
println();
print0(comment);
}
}
super.printFrom(x);
if (x instanceof CKSelectQueryBlock && ((CKSelectQueryBlock) x).isFinal()) {
print0(ucase ? " FINAL" : " final");
}
}
@Override
public void printComment(String comment) {
if (comment == null) {

View File

@ -787,6 +787,18 @@ public class PGSQLStatementParser extends SQLStatementParser {
lexer.nextToken();
accept(Token.NULL);
alterColumn.setSetNotNull(true);
} else if (lexer.identifierEquals("DATA")) {
// alter column ... set data type ...
lexer.nextToken();
accept(Token.TYPE);
SQLDataType dataType = this.exprParser.parseDataType();
alterColumn.setDataType(dataType);
// using ...
if (lexer.token() == USING) {
lexer.nextToken();
SQLExpr usingExpr = this.exprParser.expr();
alterColumn.setUsing(usingExpr);
}
} else {
accept(Token.DEFAULT);
SQLExpr defaultValue = this.exprParser.expr();

View File

@ -2995,6 +2995,15 @@ public class PGOutputVisitor extends SQLASTOutputVisitor implements PGASTVisitor
return false;
}
public boolean visit(SQLAlterTableAlterColumn x) {
super.visit(x);
if (x.getUsing() != null) {
print0(ucase ? " USING " : " using ");
x.getUsing().accept(this);
}
return false;
}
@Override
protected void printTableOptionsPrefix(SQLCreateTableStatement x) {
println();

View File

@ -0,0 +1,257 @@
package com.alibaba.druid.sql.dialect.postgresql.parser;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLCastExpr;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableAlterColumn;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableStatement;
import com.alibaba.druid.sql.dialect.postgresql.visitor.PGSchemaStatVisitor;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.stat.TableStat;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* @author gaattc
* @since 1.2.24
*/
public class PGSQLStatementParserTest {
/**
* ALTER COLUMN ... SET DATA TYPE ...
*/
@Test
public void testAlterColumnSetDataType() {
String sql = "alter table if exists products alter column price set data type decimal(12,2)";
PGSQLStatementParser parser = new PGSQLStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
assertEquals(1, statementList.size());
SQLAlterTableStatement stmt = (SQLAlterTableStatement) statementList.get(0);
assertEquals(SQLAlterTableStatement.class, stmt.getClass());
assertEquals("products", stmt.getTableName());
assertTrue(stmt.isIfExists());
SQLAlterTableAlterColumn alterColumnItem = (SQLAlterTableAlterColumn) stmt.getItems().get(0);
assertNotNull(alterColumnItem);
assertEquals("price", alterColumnItem.getColumn().getName().getSimpleName());
assertNotNull(alterColumnItem.getDataType());
assertEquals("decimal", alterColumnItem.getDataType().getName().toLowerCase());
assertEquals(2, alterColumnItem.getDataType().getArguments().size());
assertEquals("12", alterColumnItem.getDataType().getArguments().get(0).toString());
assertEquals("2", alterColumnItem.getDataType().getArguments().get(1).toString());
String outputSql = stmt.toString();
assertEquals("ALTER TABLE IF EXISTS products\n\tALTER COLUMN price SET DATA TYPE decimal(12, 2)", outputSql);
}
/**
* ALTER COLUMN ... SET DEFAULT ...
*/
@Test
public void testAlterColumnSetDefault() {
String sql = "ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77";
PGSQLStatementParser parser = new PGSQLStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLAlterTableStatement stmt = (SQLAlterTableStatement) statementList.get(0);
SQLAlterTableAlterColumn alterColumnItem = (SQLAlterTableAlterColumn) stmt.getItems().get(0);
assertEquals("price", alterColumnItem.getColumn().getName().getSimpleName());
assertNotNull(alterColumnItem.getSetDefault());
assertEquals("7.77", alterColumnItem.getSetDefault().toString());
assertNull(alterColumnItem.getDataType());
}
/**
* ALTER COLUMN ... SET NOT NULL
*/
@Test
public void testAlterColumnSetNotNull() {
String sql = "ALTER TABLE products ALTER COLUMN price SET NOT NULL";
PGSQLStatementParser parser = new PGSQLStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLAlterTableStatement stmt = (SQLAlterTableStatement) statementList.get(0);
SQLAlterTableAlterColumn alterColumnItem = (SQLAlterTableAlterColumn) stmt.getItems().get(0);
assertEquals("price", alterColumnItem.getColumn().getName().getSimpleName());
assertTrue(alterColumnItem.isSetNotNull());
assertNull(alterColumnItem.getDataType());
}
/**
* DROP
*/
@Test
public void testAlterColumnDropDefault() {
String sql = "ALTER TABLE products ALTER COLUMN price DROP DEFAULT";
PGSQLStatementParser parser = new PGSQLStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLAlterTableStatement stmt = (SQLAlterTableStatement) statementList.get(0);
SQLAlterTableAlterColumn alterColumnItem = (SQLAlterTableAlterColumn) stmt.getItems().get(0);
assertEquals("price", alterColumnItem.getColumn().getName().getSimpleName());
assertTrue(alterColumnItem.isDropDefault());
assertNull(alterColumnItem.getDataType());
}
/**
* 测试核心功能ALTER COLUMN ... SET DATA TYPE ... USING ...
*/
@Test
public void testAlterColumnSetDataTypeWithUsing() {
String sql = "ALTER TABLE my_table ALTER COLUMN my_column SET DATA TYPE integer USING my_column::integer";
SQLAlterTableStatement stmt = parseSingleAlterStatement(sql);
// 验证表名
assertEquals("my_table", stmt.getTableName());
SQLAlterTableAlterColumn item = (SQLAlterTableAlterColumn) stmt.getItems().get(0);
// 验证列名
assertEquals("my_column", item.getColumn().getName().getSimpleName());
// 验证新数据类型
assertEquals("integer", item.getDataType().getName().toLowerCase());
// 验证 USING 子句
assertNotNull("USING clause should be parsed", item.getUsing());
assertTrue("USING expression should be a CAST expression", item.getUsing() instanceof SQLCastExpr);
assertEquals("my_column::integer", SQLUtils.toPGString(item.getUsing()));
}
/**
* 边界场景测试测试带有 schema 的复杂类型名
*/
@Test
public void testAlterColumnWithSchemaQualifiedType() {
String sql = "ALTER TABLE public.users ALTER COLUMN user_status SET DATA TYPE custom_schema.user_enum USING user_status::text::custom_schema.user_enum";
SQLAlterTableStatement stmt = parseSingleAlterStatement(sql);
// 验证带 schema 的表名
assertEquals("public.users", stmt.getTableSource().toString());
SQLAlterTableAlterColumn item = (SQLAlterTableAlterColumn) stmt.getItems().get(0);
// 验证数据类型
assertEquals("custom_schema.user_enum", item.getDataType().toString());
// 验证 USING 子句
assertNotNull(item.getUsing());
assertEquals("custom_schema.user_enum(user_status::text)", SQLUtils.toPGString(item.getUsing()));
}
/**
* 测试 Visitor 还原 PG 语法的完整性
* 确保解析后的AST能够被 Visitor 正确地还原为原始 SQL 或等价的 PG SQL
*/
@Test
public void testVisitorOutputCompleteness() {
String originalSql = "ALTER TABLE my_table ALTER COLUMN my_column SET DATA TYPE integer USING my_column::integer";
SQLStatement stmt = parseSingleAlterStatement(originalSql);
// 使用 SQLUtils (其内部使用 Visitor) 来格式化 SQL
String formattedSql = SQLUtils.toSQLString(stmt, DbType.postgresql, new SQLUtils.FormatOption(true, true));
String expectedFormattedSql =
"ALTER TABLE my_table\n" +
"\tALTER COLUMN my_column SET DATA TYPE integer USING my_column::integer";
assertEquals(expectedFormattedSql, formattedSql);
}
/**
* 测试 Visitor 统计功能的正确性
*/
@Test
public void testSchemaStatVisitorWithUsing() {
String sql = "ALTER TABLE my_table ALTER COLUMN my_column SET DATA TYPE integer USING old_column::integer";
// 使用 SQLUtils.parseStatements 来简化解析
SQLStatement stmt = SQLUtils.parseStatements(sql, DbType.postgresql).get(0);
PGSchemaStatVisitor visitor = new PGSchemaStatVisitor();
stmt.accept(visitor);
// 验证表被识别
assertTrue("The table 'my_table' should be present in the stats.",
visitor.containsTable("my_table"));
// 验证 ALTER COLUMN <column> 被识别
TableStat.Column myColumnStat = visitor.getColumn("my_table", "my_column");
assertNotNull("The altered column 'my_column' should be identified by the visitor.", myColumnStat);
// 验证 USING <column> 被识别
TableStat.Column oldColumnStat = visitor.getColumn("my_table", "old_column");
assertNotNull("The column 'old_column' from the USING clause should be identified by the visitor.", oldColumnStat);
}
/**
* 异常场景测试测试非法的 'SET DATA' 语法
*/
@Test(expected = ParserException.class)
public void testInvalidSyntax_SetDataWithoutType() {
String sql = "ALTER TABLE my_table ALTER COLUMN my_column SET DATA my_column";
// 应该在解析 'my_column' 时报错因为它期望 'TYPE'
parseSingleAlterStatement(sql);
}
/**
* 异常场景测试测试 'SET DATA TYPE' 后缺少类型定义
*/
@Test(expected = ParserException.class)
public void testInvalidSyntax_SetDataTypeWithoutDefinition() {
String sql = "ALTER TABLE my_table ALTER COLUMN my_column SET DATA TYPE";
// 解析到末尾发现缺少数据类型应该报错
parseSingleAlterStatement(sql);
}
/**
* 异常场景测试测试 'USING' 关键字后缺少表达式
*/
@Test(expected = ParserException.class)
public void testInvalidSyntax_UsingWithoutExpression() {
String sql = "ALTER TABLE my_table ALTER COLUMN my_column SET DATA TYPE integer USING";
// 解析到末尾发现缺少 USING 表达式应该报错
parseSingleAlterStatement(sql);
}
/**
* 辅助方法用于解析单条 ALTER 语句并返回其 AST
* @param sql SQL 字符串
* @return SQLAlterTableStatement 对象
*/
private SQLAlterTableStatement parseSingleAlterStatement(String sql) {
PGSQLStatementParser parser = new PGSQLStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
if (statementList.size() != 1) {
Assert.fail("Expected a single statement, but got " + statementList.size());
}
SQLStatement stmt = statementList.get(0);
if (!(stmt instanceof SQLAlterTableStatement)) {
Assert.fail("Expected SQLAlterTableStatement, but got " + stmt.getClass().getName());
}
return (SQLAlterTableStatement) stmt;
}
}

View File

@ -0,0 +1,33 @@
/*
this is comment 1
this is comment 2
*/
select 1
--------------------
/*
this is comment 1
this is comment 2
*/
SELECT 1
------------------------------------------------------------------------------------------------------------------------
/* this is comment 1 */
select 1
--------------------
/* this is comment 1 */
SELECT 1
------------------------------------------------------------------------------------------------------------------------
select a /* xxxx
xxx
xxxx*/ from test
--------------------
SELECT a
/* xxxx
xxx
xxxx*/
FROM test
------------------------------------------------------------------------------------------------------------------------
select a /* xxxxxxxx*/ from test
--------------------
SELECT a
/* xxxxxxxx*/
FROM test