Appearance
第二部分:JSqlParser 核心功能详解 (Intermediate)
模块四:解析 DDL (Data Definition Language) 语句
数据定义语言 (DDL) 用于创建、修改和删除数据库对象,如表、索引、视图等。JSqlParser 提供了对常用 DDL 语句的解析支持。
1. 解析 CREATE TABLE 语句 (CreateTable)
CREATE TABLE 语句用于创建新的数据库表。com.github.jsqlparser.statement.create.table.CreateTable 类代表了解析后的 CREATE TABLE 语句。
一个 CreateTable 对象主要包含以下部分:
- 表名 (Table):
- getTable(): 返回一个 Table 对象,表示要创建的表的名称。
- 列定义 (List<ColumnDefinition>):
- getColumnDefinitions(): 返回一个 List<ColumnDefinition>。每个 ColumnDefinition 对象描述了表中的一列。
- getColumnName(): 获取列名 (字符串)。
- getColDataType(): 返回一个 ColDataType 对象,描述列的数据类型 (如 VARCHAR, INT, DATE 等)。
- getDataType(): 获取数据类型名称 (字符串,如 "VARCHAR", "INT")。
- getArgumentsStringList(): 获取数据类型的参数 (如 VARCHAR(255) 中的 "255",DECIMAL(10,2) 中的 "10", "2")。
- getColumnSpecs(): 返回一个 List<String>,包含列的约束和选项的字符串表示 (如 NOT NULL, UNIQUE, PRIMARY KEY, DEFAULT 'value', AUTO_INCREMENT 等)。解析这些字符串可能需要额外的逻辑,因为它们是直接从 SQL 中提取的。
- getColumnDefinitions(): 返回一个 List<ColumnDefinition>。每个 ColumnDefinition 对象描述了表中的一列。
- 表级别约束 (List<Index>):
- getIndexes(): 返回一个 List<Index>。在 JSqlParser 中,表级别的约束(如 PRIMARY KEY (col1, col2), UNIQUE (col_a, col_b), FOREIGN KEY (...) REFERENCES ..., CHECK (...))通常被解析为 Index 对象 (尽管名为 "Index",但它也用于表示这些约束)。
- getType(): 获取约束类型 (字符串,如 "PRIMARY KEY", "UNIQUE KEY", "FOREIGN KEY", "CHECK")。
- getName(): 获取约束的名称 (如果 SQL 中定义了)。
- getColumns(): 返回 List<Index.ColumnParams> (在旧版本中可能是 List<String> columnsNames),表示约束涉及的列。
- getColumnsNames(): (已废弃,但旧代码中可能见到) 返回列名列表。推荐使用 getColumns() 并从中提取列名。
- 对于 FOREIGN KEY:
- getReferencedTable(): (JSqlParser 4.8+) 获取外键引用的表名。
- getReferencedColumnNames(): (JSqlParser 4.8+) 获取外键引用的列名。
- getFkSourceColumns() 和 getFkTargetColumns() (旧版本)
- 对于 CHECK 约束,其表达式可以通过 getColDataType().getArgumentsStringList() 或特定于 Index 的方法(取决于 JSqlParser 版本和具体实现)来间接获取,或者直接在 Index 对象的某些属性中找到(较新版本对 CHECK 约束有更专门的处理)。
- getIndexes(): 返回一个 List<Index>。在 JSqlParser 中,表级别的约束(如 PRIMARY KEY (col1, col2), UNIQUE (col_a, col_b), FOREIGN KEY (...) REFERENCES ..., CHECK (...))通常被解析为 Index 对象 (尽管名为 "Index",但它也用于表示这些约束)。
- 表选项 (List<String>):
- getTableOptionsStrings(): 返回一个 List<String>,包含表的选项,如 ENGINE=InnoDB, DEFAULT CHARSET=utf8mb4 (MySQL 特定)。这些也是字符串形式,需要进一步解析。
- CREATE TABLE ... AS SELECT ... (CTAS):
- getSelect(): 如果是 CREATE TABLE ... AS SELECT ... 语句,此方法返回一个 Select 对象,代表其后的 SELECT 查询。
- isSelect(): 判断是否为 CTAS 语句。
- 其他属性:
- isUnlogged(): 是否为 UNLOGGED TABLE (PostgreSQL)。
- isIfNotExists(): 是否包含 IF NOT EXISTS 子句。
- isTemporary(): 是否为 TEMPORARY TABLE。
示例代码:
import com.github.jsqlparser.parser.CCJSqlParserUtil;
import com.github.jsqlparser.statement.Statement;
import com.github.jsqlparser.statement.create.table.CreateTable;
import com.github.jsqlparser.statement.create.table.ColumnDefinition;
import com.github.jsqlparser.statement.create.table.Index;
import com.github.jsqlparser.schema.Table;
import com.github.jsqlparser.JSQLParserException;
import java.util.List;
public class ParseCreateTableDemo {
public static void main(String\[\] args) {
String sqlCreateTable \= "CREATE TABLE IF NOT EXISTS employees (" \+
"id INT PRIMARY KEY AUTO\_INCREMENT, " \+
"name VARCHAR(100) NOT NULL, " \+
"email VARCHAR(100) UNIQUE, " \+
"department\_id INT, " \+
"hire\_date DATE DEFAULT (CURRENT\_DATE), " \+
"salary DECIMAL(10, 2), " \+
"CONSTRAINT fk\_department FOREIGN KEY (department\_id) REFERENCES departments(id) ON DELETE SET NULL, " \+
"CHECK (salary \> 0)" \+
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
String sqlCTAS \= "CREATE TABLE archived\_employees AS SELECT \* FROM employees WHERE status \= 'ARCHIVED'";
try {
System.out.println("--- Parsing CREATE TABLE \---");
parseCreateTable(sqlCreateTable);
System.out.println("\\n--- Parsing CREATE TABLE AS SELECT \---");
parseCreateTable(sqlCTAS);
} catch (JSQLParserException e) {
e.printStackTrace();
}
}
public static void parseCreateTable(String sql) throws JSQLParserException {
Statement statement \= CCJSqlParserUtil.parse(sql);
if (statement instanceof CreateTable) {
CreateTable createTableStatement \= (CreateTable) statement;
Table table \= createTableStatement.getTable();
System.out.println("Table to Create: " \+ table.getFullyQualifiedName());
System.out.println("IF NOT EXISTS: " \+ createTableStatement.isIfNotExists());
if (createTableStatement.isSelect()) {
System.out.println("Created from SELECT statement: " \+ createTableStatement.getSelect());
// You can further analyze the Select object as shown in Module 2
} else {
List\<ColumnDefinition\> columnDefinitions \= createTableStatement.getColumnDefinitions();
if (columnDefinitions \!= null) {
System.out.println("Column Definitions:");
for (ColumnDefinition colDef : columnDefinitions) {
System.out.println(" \- Name: " \+ colDef.getColumnName());
System.out.println(" Data Type: " \+ colDef.getColDataType().getDataType());
if (colDef.getColDataType().getArgumentsStringList() \!= null) {
System.out.println(" Arguments: " \+ colDef.getColDataType().getArgumentsStringList());
}
if (colDef.getColumnSpecs() \!= null && \!colDef.getColumnSpecs().isEmpty()) {
System.out.println(" Column Specs (Constraints/Options): " \+ colDef.getColumnSpecs());
}
}
}
List\<Index\> indexes \= createTableStatement.getIndexes(); // Table-level constraints
if (indexes \!= null) {
System.out.println("Table-Level Constraints (Indexes):");
for (Index index : indexes) {
System.out.println(" \- Type: " \+ index.getType());
if (index.getName() \!= null) {
System.out.println(" Name: " \+ index.getName());
}
System.out.println(" Columns: " \+ index.getColumns()); // List\<Index.ColumnParams\>
if ("FOREIGN KEY".equalsIgnoreCase(index.getType())) {
// For JSqlParser 4.8+
if (index.getReferencedTable() \!= null) {
System.out.println(" References Table: " \+ index.getReferencedTable().getFullyQualifiedName());
System.out.println(" References Columns: " \+ index.getReferencedColumnNames());
}
// You might find other FK related info in index.getFkSourceColumns(), getFkTargetColumns() for older versions
// or more generic properties for ON DELETE / ON UPDATE actions.
}
// For CHECK constraints, the expression might be part of the type or a dedicated property.
// e.g. for CHECK (salary \> 0), 'CHECK (salary \> 0)' might be the type or a specific field.
}
}
}
List\<String\> tableOptions \= createTableStatement.getTableOptionsStrings();
if (tableOptions \!= null && \!tableOptions.isEmpty()) {
System.out.println("Table Options: " \+ tableOptions);
}
} else {
System.out.println("Not a CREATE TABLE statement: " \+ sql);
}
}
}2. 解析 ALTER TABLE 语句 (Alter)
ALTER TABLE 语句用于修改已存在表的结构,例如添加、删除或修改列,添加或删除约束等。com.github.jsqlparser.statement.alter.Alter 类代表了解析后的 ALTER TABLE 语句。
ALTER TABLE 的语法非常多样,JSqlParser 通过 AlterExpression 来表示具体的修改操作。
一个 Alter 对象主要包含:
- 目标表 (Table):
- getTable(): 返回被修改的 Table 对象。
- 修改操作 (List<AlterExpression>):
- getAlterExpressions(): 返回一个 List<AlterExpression>。每个 AlterExpression 代表一个具体的 ALTER 操作。
- AlterExpression 是一个标记接口,其具体实现类代表了不同的 ALTER 操作,例如:
- AddColumnDefinition: ADD [COLUMN] column_definition
- getColumnDefinition(): 返回 ColumnDefinition。
- ModifyColumn: MODIFY [COLUMN] column_definition (或 ALTER COLUMN ... TYPE ... 在某些方言中)
- getColumnName() / getOldColumnName()
- getColDataType() / getNewColDataType()
- getColumnDefinitions() (JSqlParser 4.7+ can hold multiple for some syntaxes)
- DropColumn: DROP [COLUMN] column_name
- getColumnName()
- AddConstraint: ADD CONSTRAINT constraint_definition
- getConstraintName()
- getConstraint(): 返回一个 Constraint 对象 (通常是 Index 的实例,如 PrimaryKey, ForeignKey, UniqueConstraint, CheckConstraint)。
- DropConstraint: DROP CONSTRAINT constraint_name
- getConstraintName()
- RenameTable: RENAME TO new_table_name
- getNewTableName()
- RenameColumn: RENAME COLUMN old_name TO new_name (特定方言)
- getOldColumnName(), getNewColumnName()
- AddIndex: ADD INDEX index_definition
- getIndex(): 返回 Index 对象。
- DropIndex: DROP INDEX index_name
- getIndexName()
- 等等。AlterOperation 枚举类中定义了许多操作类型字符串,可以与 alterExpression.getOperation().toString() 进行比较。
- AddColumnDefinition: ADD [COLUMN] column_definition
示例代码:
import com.github.jsqlparser.parser.CCJSqlParserUtil;
import com.github.jsqlparser.statement.Statement;
import com.github.jsqlparser.statement.alter.Alter;
import com.github.jsqlparser.statement.alter.AlterExpression;
import com.github.jsqlparser.statement.alter.AlterExpression.ColumnDataType; // For ALTER COLUMN TYPE
import com.github.jsqlparser.statement.alter.AlterExpression.ColumnDropNotNull; // For ALTER COLUMN DROP NOT NULL
import com.github.jsqlparser.statement.create.table.ColumnDefinition;
import com.github.jsqlparser.statement.create.table.Index; // For constraints
import com.github.jsqlparser.schema.Table;
import com.github.jsqlparser.JSQLParserException;
import java.util.List;
public class ParseAlterTableDemo {
public static void main(String\[\] args) {
String sqlAlterAddCol \= "ALTER TABLE products ADD COLUMN stock\_count INT DEFAULT 0";
String sqlAlterModifyCol \= "ALTER TABLE products MODIFY COLUMN product\_name VARCHAR(255) NOT NULL";
String sqlAlterDropCol \= "ALTER TABLE products DROP COLUMN old\_feature";
String sqlAlterAddConstraint \= "ALTER TABLE employees ADD CONSTRAINT uq\_emp\_badge UNIQUE (badge\_number)";
String sqlAlterRenameTable \= "ALTER TABLE old\_customers RENAME TO customers\_archive";
try {
System.out.println("--- Parsing ALTER TABLE ADD COLUMN \---");
parseAlterTable(sqlAlterAddCol);
System.out.println("\\n--- Parsing ALTER TABLE MODIFY COLUMN \---");
parseAlterTable(sqlAlterModifyCol);
System.out.println("\\n--- Parsing ALTER TABLE DROP COLUMN \---");
parseAlterTable(sqlAlterDropCol);
System.out.println("\\n--- Parsing ALTER TABLE ADD CONSTRAINT \---");
parseAlterTable(sqlAlterAddConstraint);
System.out.println("\\n--- Parsing ALTER TABLE RENAME TABLE \---");
parseAlterTable(sqlAlterRenameTable);
} catch (JSQLParserException e) {
e.printStackTrace();
}
}
public static void parseAlterTable(String sql) throws JSQLParserException {
Statement statement \= CCJSqlParserUtil.parse(sql);
if (statement instanceof Alter) {
Alter alterStatement \= (Alter) statement;
Table table \= alterStatement.getTable();
System.out.println("Altering Table: " \+ table.getFullyQualifiedName());
List\<AlterExpression\> alterExpressions \= alterStatement.getAlterExpressions();
if (alterExpressions \!= null) {
for (AlterExpression altExpr : alterExpressions) {
System.out.println(" Operation: " \+ altExpr.getOperation()); // Generic operation string
// Specific AlterExpression types (JSqlParser 4.7+ has more specific classes)
// Older versions might rely more on getOperation() string and getParameters()
if (altExpr.getColDataTypeList() \!= null && \!altExpr.getColDataTypeList().isEmpty()) {
// This is a common way to represent ADD COLUMN or MODIFY COLUMN
for (AlterExpression.ColumnDataType cd : altExpr.getColDataTypeList()) {
System.out.println(" Column: " \+ cd.getColumnName());
System.out.println(" New Data Type: " \+ cd.getColDataType());
if (cd.getColumnSpecs() \!= null && \!cd.getColumnSpecs().isEmpty()) {
System.out.println(" Column Specs: " \+ cd.getColumnSpecs());
}
}
} else if (altExpr.getFkColumns() \!= null && \!altExpr.getFkColumns().isEmpty()) {
// Could be ADD FOREIGN KEY
System.out.println(" Foreign Key Columns: " \+ altExpr.getFkColumns());
// Further details for FK would be in other properties or a specific Constraint object
} else if (altExpr.getPkColumns() \!= null && \!altExpr.getPkColumns().isEmpty()) {
// Could be ADD PRIMARY KEY
System.out.println(" Primary Key Columns: " \+ altExpr.getPkColumns());
} else if (altExpr.getUkColumns() \!= null && \!altExpr.getUkColumns().isEmpty()) {
// Could be ADD UNIQUE KEY
System.out.println(" Unique Key Columns: " \+ altExpr.getUkColumns());
if (altExpr.getUkName() \!= null) {
System.out.println(" Unique Key Name: " \+ altExpr.getUkName());
}
} else if (altExpr.getConstraintName() \!= null && altExpr.getIndex() \!= null) {
// ADD CONSTRAINT (often parsed as an Index)
System.out.println(" Constraint Name: " \+ altExpr.getConstraintName());
System.out.println(" Constraint Type (via Index): " \+ altExpr.getIndex().getType());
System.out.println(" Constraint Columns: " \+ altExpr.getIndex().getColumns());
} else if ("DROP".equalsIgnoreCase(altExpr.getOperation().name()) && altExpr.get columnName() \!= null) {
// DROP COLUMN (simplified check, real check needs more context)
System.out.println(" Drop Column: " \+ altExpr.getColumnName());
} else if (altExpr.getNewTableName() \!= null) {
System.out.println(" Rename Table To: " \+ altExpr.getNewTableName().getFullyQualifiedName());
}
// Add more specific checks based on the type of AlterExpression or its properties
// For example, JSqlParser 4.7+ introduces more specific classes like
// AlterExpression.AddColumn(), AlterExpression.DropColumn(), etc.
// which makes type checking more direct.
// If using older versions, you might rely on altExpr.getOperation().toString()
// and checking various optional parameters like getColumnName(), getDataType(), etc.
// Example for a more specific type (if available in your JSqlParser version)
/\*
if (altExpr instanceof AlterExpression.AddColumn) {
AlterExpression.AddColumn addColExpr \= (AlterExpression.AddColumn) altExpr;
for (ColumnDefinition colDef : addColExpr.getColumnDefinitions()) {
System.out.println(" ADD Column Name: " \+ colDef.getColumnName());
System.out.println(" ADD Column Type: " \+ colDef.getColDataType());
}
} else if (altExpr instanceof AlterExpression.DropColumn) {
AlterExpression.DropColumn dropColExpr \= (AlterExpression.DropColumn) altExpr;
System.out.println(" DROP Column Name: " \+ dropColExpr.getColumnName());
}
\*/
}
}
} else {
System.out.println("Not an ALTER TABLE statement: " \+ sql);
}
}
}注意:ALTER TABLE 的解析可能因 JSqlParser 版本而异,较新版本提供了更细致的 AlterExpression 子类。上面的示例代码尝试提供一种通用的检查方式,并提及了新版本中可能存在的更具体的类。
3. 解析 DROP TABLE 语句 (Drop)
DROP TABLE 语句用于删除已存在的表。com.github.jsqlparser.statement.drop.Drop 类代表了解析后的 DROP 语句 (它也用于 DROP INDEX, DROP VIEW 等)。
一个 Drop 对象主要包含:
- 对象名称 (NameReferencingIdentifier):
- getName(): 返回一个 NameReferencingIdentifier 接口的实例,通常是 Table 对象,表示要删除的表的名称。
- 对象类型 (String):
- getType(): 返回一个字符串,指示要删除的对象类型,例如 "TABLE", "INDEX", "VIEW"。
- IF EXISTS:
- isIfExists(): 判断是否包含 IF EXISTS 子句。
- CASCADE / RESTRICT (特定方言):
- getParameters(): 返回一个 List<String>,可能包含 "CASCADE" 或 "RESTRICT"。
示例代码:
import com.github.jsqlparser.parser.CCJSqlParserUtil;
import com.github.jsqlparser.statement.Statement;
import com.github.jsqlparser.statement.drop.Drop;
import com.github.jsqlparser.schema.Table; // To cast getName() result
import com.github.jsqlparser.JSQLParserException;
public class ParseDropTableDemo {
public static void main(String\[\] args) {
String sqlDropTable \= "DROP TABLE IF EXISTS old\_logs CASCADE";
String sqlDropIndex \= "DROP INDEX idx\_user\_email ON users";
try {
System.out.println("--- Parsing DROP TABLE \---");
parseDrop(sqlDropTable);
System.out.println("\\n--- Parsing DROP INDEX \---");
parseDrop(sqlDropIndex);
} catch (JSQLParserException e) {
e.printStackTrace();
}
}
public static void parseDrop(String sql) throws JSQLParserException {
Statement statement \= CCJSqlParserUtil.parse(sql);
if (statement instanceof Drop) {
Drop dropStatement \= (Drop) statement;
System.out.println("Object Type to Drop: " \+ dropStatement.getType());
// getName() returns NameReferencingIdentifier, which could be Table, IndexName, etc.
// For DROP TABLE, it's usually a Table.
if (dropStatement.getName() instanceof Table) {
Table table \= (Table) dropStatement.getName();
System.out.println("Object Name: " \+ table.getFullyQualifiedName());
} else {
System.out.println("Object Name: " \+ dropStatement.getName().getFullyQualifiedName()); // Generic access
}
System.out.println("IF EXISTS: " \+ dropStatement.isIfExists());
if (dropStatement.getParameters() \!= null && \!dropStatement.getParameters().isEmpty()) {
System.out.println("Parameters: " \+ dropStatement.getParameters()); // e.g., \[CASCADE\]
}
} else {
System.out.println("Not a DROP statement: " \+ sql);
}
}
}4. 其他 DDL 语句简介
JSqlParser 还支持解析其他一些 DDL 语句,其结构和访问方式与上述类似:
- CREATE INDEX (CreateIndex):
- getTable(): 获取索引所属的表。
- getIndex(): 获取 Index 对象,包含索引名、类型 (如 UNIQUE)、列等信息。
- DROP INDEX (Drop):
- getName(): 获取索引名 (通常是 Index 对象或其名称的直接表示)。
- getTable(): (某些 DROP INDEX 语法包含表名,如 DROP INDEX index_name ON table_name)
- getType(): 会是 "INDEX"。
- TRUNCATE TABLE (Truncate):
- getTable(): 获取要清空的表。
- CREATE VIEW (CreateView):
- getView(): 获取视图名 (Table 对象)。
- getSelect(): 获取定义视图的 Select 对象。
- getColumns(): 如果视图定义了列名。
- ALTER VIEW (AlterView):
- getView(): 视图名。
- getSelect(): 新的 Select 定义。
- DROP VIEW (Drop):
- getName(): 视图名。
- getType(): 会是 "VIEW"。
解析这些语句时,通常也是获取对应的 Statement 子类,然后通过其提供的方法访问各个组成部分。
动手实验 4:
- 解析 CREATE TABLE 语句:
- 编写 Java 代码解析以下 SQL 语句:
CREATE TABLE user\_preferences ( user\_id INT NOT NULL, preference\_key VARCHAR(50) NOT NULL, preference\_value TEXT, last\_modified TIMESTAMP DEFAULT CURRENT\_TIMESTAMP, PRIMARY KEY (user\_id, preference\_key), FOREIGN KEY (user\_id) REFERENCES users(id) ON DELETE CASCADE );- 提取表名。
- 列出所有列的名称、数据类型及其参数。
- 识别并打印列级约束 (如 NOT NULL, DEFAULT)。
- 识别并打印表级约束 (主键和外键),包括约束名称(如果定义)、类型、涉及的列以及外键的引用信息。
- 解析 ALTER TABLE 语句:
编写 Java 代码解析以下 SQL 语句:
ALTER TABLE orders
ADD COLUMN shipping_address_id INT NULL AFTER customer_id,
ADD CONSTRAINT fk_shipping_address FOREIGN KEY (shipping_address_id) REFERENCES addresses(id),
MODIFY COLUMN order_status VARCHAR(20) DEFAULT 'PENDING',
DROP COLUMN notes;提取目标表名。
对于每一个 ALTER 操作:
- 识别操作类型 (ADD COLUMN, ADD CONSTRAINT, MODIFY COLUMN, DROP COLUMN)。
- 提取相关的详细信息(如列定义、约束定义、被删除的列名)。
模块四小结:
在本模块中,我们学习了如何使用 JSqlParser 解析常见的 DDL 语句:
- CREATE TABLE: 提取表名、列定义(名称、数据类型、列约束)、表级约束(主键、外键、唯一键、检查约束)以及表选项。了解了 CTAS 的解析方式。
- ALTER TABLE: 提取目标表,并通过 AlterExpression 识别和解析各种修改操作,如添加/修改/删除列、添加/删除约束。
- DROP TABLE: 提取要删除的表名,以及 IF EXISTS 和 CASCADE/RESTRICT 等选项。
- 简要介绍了其他 DDL 语句如 CREATE/DROP INDEX, TRUNCATE TABLE, CREATE/DROP VIEW 的解析入口。
DDL 语句的解析对于理解数据库模式、进行模式迁移或自动化数据库管理任务非常有用。