Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `CHECKSUM` · `OPTIMIZE` · `REPAIR TABLE` |
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
| **MySQL data loading** | `LOAD DATA` · `LOAD XML` · `SELECT … INTO OUTFILE` with the same `FIELDS` and `LINES` clauses |
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
| **Tablespaces** | MySQL and PostgreSQL `CREATE TABLESPACE` · `ALTER TABLESPACE` |
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -1153,10 +1153,12 @@ public enum Feature {
export,

/**
* PostgreSQL and DuckDB "COPY" and BigQuery "LOAD DATA" statements are allowed
* PostgreSQL and DuckDB "COPY", BigQuery and MySQL "LOAD DATA" and MySQL "LOAD XML" statements
* are allowed
*
* @see net.sf.jsqlparser.statement.CopyStatement
* @see net.sf.jsqlparser.statement.load.LoadDataStatement
* @see net.sf.jsqlparser.statement.load.MySqlLoadDataStatement
*/
copy, loadData,

Expand Down
196 changes: 196 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/MySqlFileFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement;

import java.io.Serializable;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;

/**
* The {@code {FIELDS | COLUMNS}} and {@code LINES} clauses that describe the text file of a MySQL
* {@code LOAD DATA} or {@code SELECT ... INTO OUTFILE}:
*
* <pre>
* [{FIELDS | COLUMNS} [TERMINATED BY 'string'] [[OPTIONALLY] ENCLOSED BY 'char'] [ESCAPED BY 'char']]
* [LINES [STARTING BY 'string'] [TERMINATED BY 'string']]
* </pre>
*
* Each clause has at least one subclause. MySQL accepts the subclauses of a clause in any order and
* more than once; like the server, the model keeps the last value of each and prints them in the
* order above. {@code OPTIONALLY} stays set once a subclause has written it.
*
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/load-data.html">LOAD DATA</a>
*/
public class MySqlFileFormat extends ASTNodeAccessImpl implements Serializable {

/** The spelling of the fields clause. */
public enum FieldsKeyword {
FIELDS, COLUMNS
}

private FieldsKeyword fieldsKeyword;
private StringValue fieldsTerminatedBy;
private boolean fieldsOptionallyEnclosed;
private StringValue fieldsEnclosedBy;
private StringValue fieldsEscapedBy;
private StringValue linesStartingBy;
private StringValue linesTerminatedBy;

/** The spelling of the fields clause; null prints FIELDS when a subclause is set. */
public FieldsKeyword getFieldsKeyword() {
return fieldsKeyword;
}

public void setFieldsKeyword(FieldsKeyword fieldsKeyword) {
this.fieldsKeyword = fieldsKeyword;
}

public MySqlFileFormat withFieldsKeyword(FieldsKeyword fieldsKeyword) {
setFieldsKeyword(fieldsKeyword);
return this;
}

public StringValue getFieldsTerminatedBy() {
return fieldsTerminatedBy;
}

public void setFieldsTerminatedBy(StringValue fieldsTerminatedBy) {
this.fieldsTerminatedBy = fieldsTerminatedBy;
}

public MySqlFileFormat withFieldsTerminatedBy(StringValue fieldsTerminatedBy) {
setFieldsTerminatedBy(fieldsTerminatedBy);
return this;
}

public boolean isFieldsOptionallyEnclosed() {
return fieldsOptionallyEnclosed;
}

public void setFieldsOptionallyEnclosed(boolean fieldsOptionallyEnclosed) {
this.fieldsOptionallyEnclosed = fieldsOptionallyEnclosed;
}

public MySqlFileFormat withFieldsOptionallyEnclosed(boolean fieldsOptionallyEnclosed) {
setFieldsOptionallyEnclosed(fieldsOptionallyEnclosed);
return this;
}

public StringValue getFieldsEnclosedBy() {
return fieldsEnclosedBy;
}

public void setFieldsEnclosedBy(StringValue fieldsEnclosedBy) {
this.fieldsEnclosedBy = fieldsEnclosedBy;
}

public MySqlFileFormat withFieldsEnclosedBy(StringValue fieldsEnclosedBy) {
setFieldsEnclosedBy(fieldsEnclosedBy);
return this;
}

public StringValue getFieldsEscapedBy() {
return fieldsEscapedBy;
}

public void setFieldsEscapedBy(StringValue fieldsEscapedBy) {
this.fieldsEscapedBy = fieldsEscapedBy;
}

public MySqlFileFormat withFieldsEscapedBy(StringValue fieldsEscapedBy) {
setFieldsEscapedBy(fieldsEscapedBy);
return this;
}

public StringValue getLinesStartingBy() {
return linesStartingBy;
}

public void setLinesStartingBy(StringValue linesStartingBy) {
this.linesStartingBy = linesStartingBy;
}

public MySqlFileFormat withLinesStartingBy(StringValue linesStartingBy) {
setLinesStartingBy(linesStartingBy);
return this;
}

public StringValue getLinesTerminatedBy() {
return linesTerminatedBy;
}

public void setLinesTerminatedBy(StringValue linesTerminatedBy) {
this.linesTerminatedBy = linesTerminatedBy;
}

public MySqlFileFormat withLinesTerminatedBy(StringValue linesTerminatedBy) {
setLinesTerminatedBy(linesTerminatedBy);
return this;
}

/** Whether the format prints a fields clause: a spelling or a subclause is set. */
public boolean hasFieldsClause() {
return fieldsKeyword != null || fieldsTerminatedBy != null || fieldsEnclosedBy != null
|| fieldsEscapedBy != null;
}

/** Whether the format prints a lines clause: a subclause is set. */
public boolean hasLinesClause() {
return linesStartingBy != null || linesTerminatedBy != null;
}

/** Whether the format prints nothing. */
public boolean isEmpty() {
return !hasFieldsClause() && !hasLinesClause();
}

/** Appends the fields clause and then the lines clause, separated by a space. */
public StringBuilder appendTo(StringBuilder builder) {
if (hasFieldsClause()) {
appendFieldsClause(builder);
if (hasLinesClause()) {
builder.append(' ');
}
}
if (hasLinesClause()) {
appendLinesClause(builder);
}
return builder;
}

private void appendFieldsClause(StringBuilder builder) {
builder.append(fieldsKeyword != null ? fieldsKeyword : FieldsKeyword.FIELDS);
if (fieldsTerminatedBy != null) {
builder.append(" TERMINATED BY ").append(fieldsTerminatedBy);
}
if (fieldsEnclosedBy != null) {
builder.append(fieldsOptionallyEnclosed ? " OPTIONALLY ENCLOSED BY " : " ENCLOSED BY ")
.append(fieldsEnclosedBy);
}
if (fieldsEscapedBy != null) {
builder.append(" ESCAPED BY ").append(fieldsEscapedBy);
}
}

private void appendLinesClause(StringBuilder builder) {
builder.append("LINES");
if (linesStartingBy != null) {
builder.append(" STARTING BY ").append(linesStartingBy);
}
if (linesTerminatedBy != null) {
builder.append(" TERMINATED BY ").append(linesTerminatedBy);
}
}

@Override
public String toString() {
return appendTo(new StringBuilder()).toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.insert.InsertBulk;
import net.sf.jsqlparser.statement.load.LoadDataStatement;
import net.sf.jsqlparser.statement.load.MySqlLoadDataStatement;
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
import net.sf.jsqlparser.statement.lock.LockStatement;
import net.sf.jsqlparser.statement.lock.UnlockStatement;
Expand Down Expand Up @@ -680,6 +681,13 @@ public <S> Void visit(LoadDataStatement loadDataStatement, S context) {
return null;
}

@Override
public <S> Void visit(MySqlLoadDataStatement loadData, S context) {
analysis.claimTopLevel();
analysis.certain(StmtFeature.MODIFIES_DATA);
return super.visit(loadData, context);
}

@Override
public <S> Void visit(PurgeStatement purgeStatement, S context) {
analysis.claimTopLevel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
import net.sf.jsqlparser.statement.export.ExportDataStatement;
import net.sf.jsqlparser.statement.load.LoadDataStatement;
import net.sf.jsqlparser.statement.load.MySqlLoadDataStatement;

public interface StatementVisitor<T> {

Expand Down Expand Up @@ -660,6 +661,14 @@ default void visit(LoadDataStatement loadDataStatement) {
this.visit(loadDataStatement, null);
}

default <S> T visit(MySqlLoadDataStatement loadData, S context) {
return null;
}

default void visit(MySqlLoadDataStatement loadData) {
this.visit(loadData, null);
}

<S> T visit(PurgeStatement purgeStatement, S context);

default void visit(PurgeStatement purgeStatement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
import net.sf.jsqlparser.statement.export.ExportDataStatement;
import net.sf.jsqlparser.statement.load.LoadDataStatement;
import net.sf.jsqlparser.statement.load.MySqlLoadDataStatement;

@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"})
public class StatementVisitorAdapter<T> implements StatementVisitor<T> {
Expand Down Expand Up @@ -850,6 +851,14 @@ public <S> T visit(LoadDataStatement loadDataStatement, S context) {
return null;
}

@Override
public <S> T visit(MySqlLoadDataStatement loadData, S context) {
fromItemVisitor.visitFromItem(loadData.getTable(), context);
expressionVisitor.visitExpressions(loadData.getColumns(), context);
expressionVisitor.visitUpdateSets(loadData.getUpdateSets(), context);
return null;
}

@Override
public <S> T visit(PurgeStatement purgeStatement, S context) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.statement.load;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.schema.Table;
Expand All @@ -18,6 +19,7 @@
import net.sf.jsqlparser.statement.select.PlainSelect;

import java.util.List;
import java.util.function.Consumer;

/**
* BigQuery's {@code LOAD DATA {INTO | OVERWRITE} table [(column_definitions)] [OPTIONS
Expand Down Expand Up @@ -128,14 +130,19 @@ public LoadDataStatement withConnectionName(String connectionName) {
}

public StringBuilder appendTo(StringBuilder builder) {
return appendTo(builder, builder::append);
}

/** Renders the options and file options through the caller's expression visitor. */
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionRenderer) {
builder.append("LOAD DATA ").append(overwrite ? "OVERWRITE " : "INTO ").append(table);
if (columnDefinitions != null && !columnDefinitions.isEmpty()) {
builder.append(" ").append(PlainSelect.getStringList(columnDefinitions, true, true));
}
if (options != null) {
builder.append(" OPTIONS (").append(options).append(")");
appendOptions(builder.append(" OPTIONS ("), options, expressionRenderer).append(")");
}
builder.append(" FROM FILES (").append(fileOptions).append(")");
appendOptions(builder.append(" FROM FILES ("), fileOptions, expressionRenderer).append(")");
if (withPartitionColumns) {
builder.append(" WITH PARTITION COLUMNS");
}
Expand All @@ -145,6 +152,19 @@ public StringBuilder appendTo(StringBuilder builder) {
return builder;
}

private static StringBuilder appendOptions(StringBuilder builder, ExpressionList<?> options,
Consumer<Expression> expressionRenderer) {
if (options != null) {
for (int i = 0; i < options.size(); i++) {
if (i > 0) {
builder.append(", ");
}
expressionRenderer.accept(options.get(i));
}
}
return builder;
}

@Override
public String toString() {
return appendTo(new StringBuilder()).toString();
Expand Down
Loading
Loading