Skip to content

第二部分: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 中提取的。
  • 表级别约束 (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 约束有更专门的处理)。
  • 表选项 (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() 进行比较。

示例代码:

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:

  1. 解析 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)。
    • 识别并打印表级约束 (主键和外键),包括约束名称(如果定义)、类型、涉及的列以及外键的引用信息。
  2. 解析 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 语句的解析对于理解数据库模式、进行模式迁移或自动化数据库管理任务非常有用。