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
33 changes: 33 additions & 0 deletions src/main/java/net/sf/jsqlparser/expression/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import net.sf.jsqlparser.statement.select.Limit;
import net.sf.jsqlparser.statement.select.OrderByElement;

import net.sf.jsqlparser.statement.create.table.ColDataType;
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
import net.sf.jsqlparser.statement.select.PlainSelect;

Expand Down Expand Up @@ -59,6 +60,12 @@ public class Function extends ASTNodeAccessImpl implements Expression {
*/
private List<KeywordArgument> keywordArguments = null;

/**
* The {@code AS type} cast of the argument, as in MySQL's {@code WEIGHT_STRING(str AS CHAR(3))}
* or {@code WEIGHT_STRING(str AS BINARY(4))}.
*/
private ColDataType castType;

/** Column definitions supplied for a record-returning function inside ROWS FROM. */
public List<ColumnDefinition> getResultColumnDefinitions() {
return resultColumnDefinitions;
Expand All @@ -83,6 +90,31 @@ public Function(String name, Expression... parameters) {
this.parameters = new ExpressionList<>(parameters);
}

/**
* Returns the {@code AS type} cast written after the argument, as in MySQL's
* {@code WEIGHT_STRING(str AS CHAR(3))}, or {@code null}.
*/
public ColDataType getCastType() {
return castType;
}

public void setCastType(ColDataType castType) {
this.castType = castType;
}

public Function withCastType(ColDataType castType) {
setCastType(castType);
return this;
}

/** Appends {@code " AS type"} when the argument has a {@link #getCastType() cast type}. */
public StringBuilder appendCastTypeTo(StringBuilder builder) {
if (castType != null) {
builder.append(" AS ").append(castType);
}
return builder;
}

@Override
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
return expressionVisitor.visit(this, context);
Expand Down Expand Up @@ -385,6 +417,7 @@ public String toString() {
}

b.append(parameters);
appendCastTypeTo(b);

if (havingClause != null) {
havingClause.appendTo(b);
Expand Down
69 changes: 60 additions & 9 deletions src/main/java/net/sf/jsqlparser/expression/MySQLIndexHint.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,35 @@
import java.io.Serializable;
import java.util.List;

/**
* A MySQL index hint after a table reference:
* {@code {USE|IGNORE|FORCE} {INDEX|KEY} [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)}.
* <p>
* A table may carry several hints, see {@code Table.getIndexHints()}. {@code USE INDEX ()} has an
* empty index list.
*/
public class MySQLIndexHint implements Serializable {

private final String action;
private final String indexQualifier;
private final Scope scope;
private final List<String> indexNames;

public MySQLIndexHint(String action, String indexQualifier, List<String> indexNames) {
this(action, indexQualifier, null, indexNames);
}

/**
* @param action {@code USE}, {@code IGNORE} or {@code FORCE}
* @param indexQualifier {@code INDEX} or {@code KEY}
* @param scope the {@code FOR JOIN|ORDER BY|GROUP BY} scope, or {@code null} for every use
* @param indexNames the index names, empty for {@code USE INDEX ()}
*/
public MySQLIndexHint(String action, String indexQualifier, Scope scope,
List<String> indexNames) {
this.action = action;
this.indexQualifier = indexQualifier;
this.scope = scope;
this.indexNames = indexNames;
}

Expand All @@ -32,22 +52,53 @@ public String getIndexQualifier() {
return indexQualifier;
}

/**
* Returns the part of the query the hint applies to, or {@code null} when no {@code FOR} clause
* was given.
*/
public Scope getScope() {
return scope;
}

public List<String> getIndexNames() {
return indexNames;
}

@Override
public String toString() {
// use|ignore|force key|index (index1,...,indexN)
StringBuilder buffer = new StringBuilder();
buffer.append(" ").append(action).append(" ").append(indexQualifier).append(" (");
/** Appends {@code " USE INDEX [FOR scope] (i1,i2)"}, with a leading space. */
public StringBuilder appendTo(StringBuilder builder) {
// use|ignore|force key|index [for join|order by|group by] (index1,...,indexN)
builder.append(" ").append(action).append(" ").append(indexQualifier);
if (scope != null) {
builder.append(" FOR ").append(scope.getKeywords());
}
builder.append(" (");
for (int i = 0; i < indexNames.size(); i++) {
if (i > 0) {
buffer.append(",");
builder.append(",");
}
buffer.append(indexNames.get(i));
builder.append(indexNames.get(i));
}
return builder.append(")");
}

@Override
public String toString() {
return appendTo(new StringBuilder()).toString();
}

/** The {@code FOR} clause of an index hint. */
public enum Scope {
JOIN("JOIN"), ORDER_BY("ORDER BY"), GROUP_BY("GROUP BY");

private final String keywords;

Scope(String keywords) {
this.keywords = keywords;
}

/** Returns the keywords after {@code FOR}, for example {@code ORDER BY}. */
public String getKeywords() {
return keywords;
}
buffer.append(")");
return buffer.toString();
}
}
53 changes: 47 additions & 6 deletions src/main/java/net/sf/jsqlparser/schema/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public class Table extends ASTNodeAccessImpl

private UnPivot unpivot;

private MySQLIndexHint mysqlHints;
private List<MySQLIndexHint> mysqlIndexHints;

private SQLServerHints sqlServerHints;

Expand Down Expand Up @@ -411,17 +411,60 @@ public void setUnPivot(UnPivot unpivot) {
this.unpivot = unpivot;
}

/**
* Returns the first MySQL index hint, or {@code null}. Use {@link #getIndexHints()} for a table
* with several hints.
*/
public MySQLIndexHint getIndexHint() {
return mysqlHints;
return mysqlIndexHints == null || mysqlIndexHints.isEmpty() ? null
: mysqlIndexHints.get(0);
}

public Table withHint(MySQLIndexHint hint) {
setHint(hint);
return this;
}

/**
* Replaces the MySQL index hints with the given hint, or removes them for {@code null}.
*/
public void setHint(MySQLIndexHint hint) {
this.mysqlHints = hint;
this.mysqlIndexHints = hint == null ? null : new ArrayList<>(Collections.singleton(hint));
}

/**
* Returns the MySQL index hints in their order, for example
* {@code USE INDEX (i1) IGNORE INDEX FOR ORDER BY (i2)}, or {@code null} when there are none.
*/
public List<MySQLIndexHint> getIndexHints() {
return mysqlIndexHints;
}

public void setIndexHints(List<MySQLIndexHint> indexHints) {
this.mysqlIndexHints = indexHints;
}

public Table withIndexHints(List<MySQLIndexHint> indexHints) {
setIndexHints(indexHints);
return this;
}

public Table addIndexHint(MySQLIndexHint hint) {
if (mysqlIndexHints == null) {
mysqlIndexHints = new ArrayList<>();
}
mysqlIndexHints.add(hint);
return this;
}

/** Appends the MySQL index hints, each with a leading space, in their order. */
public StringBuilder appendIndexHintsTo(StringBuilder builder) {
if (mysqlIndexHints != null) {
for (MySQLIndexHint hint : mysqlIndexHints) {
hint.appendTo(builder);
}
}
return builder;
}

public SQLServerHints getSqlServerHints() {
Expand Down Expand Up @@ -468,9 +511,7 @@ public StringBuilder appendTo(StringBuilder builder) {
builder.append(" ").append(unpivot);
}

if (mysqlHints != null) {
builder.append(mysqlHints);
}
appendIndexHintsTo(builder);

if (sqlServerHints != null) {
builder.append(sqlServerHints);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
import net.sf.jsqlparser.statement.reindex.Reindex;
import net.sf.jsqlparser.statement.securitylabel.SecurityLabel;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.FromItemVisitor;
import net.sf.jsqlparser.statement.select.FromItemVisitorAdapter;
import net.sf.jsqlparser.statement.select.PivotVisitor;
Expand Down Expand Up @@ -255,8 +256,14 @@ public <S> T visit(Delete delete, S context) {
fromItemVisitor.visitTables(delete.getTables(), context);
selectVisitor.visitOutputClause(delete.getOutputClause(), context);
fromItemVisitor.visitFromItem(delete.getTable(), context);
fromItemVisitor.visitTables(delete.getUsingList(), context);
fromItemVisitor.visitJoins(delete.getJoins(), context);
// USING may name subqueries, so visit the items rather than getUsingList()'s tables
if (delete.getUsingFromItemList() != null) {
for (FromItem usingFromItem : delete.getUsingFromItemList()) {
fromItemVisitor.visitFromItem(usingFromItem, context);
}
}
fromItemVisitor.visitJoins(delete.getUsingJoins(), context);
expressionVisitor.visitExpression(delete.getWhere(), context);

expressionVisitor.visitPreferringClause(delete.getPreferringClause(), context);
Expand Down
52 changes: 42 additions & 10 deletions src/main/java/net/sf/jsqlparser/statement/delete/Delete.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class Delete extends ASTNodeAccessImpl implements Statement {
private OracleHint oracleHint = null;
private List<Table> tables;
private List<FromItem> usingFromItemList;
private List<Join> usingJoins;
private List<Join> joins;
private Expression where;
private PreferringClause preferringClause;
Expand Down Expand Up @@ -281,21 +282,14 @@ public String toString() {
b.append(' ').append(forPortionClause);
}

appendJoinsTo(b, joins);

if (usingFromItemList != null && !usingFromItemList.isEmpty()) {
b.append(" USING ");
b.append(usingFromItemList.stream()
.map(Object::toString)
.collect(joining(", ")));
}

if (joins != null) {
for (Join join : joins) {
if (join.isSimple()) {
b.append(", ").append(join);
} else {
b.append(" ").append(join);
}
}
appendJoinsTo(b, usingJoins);
}

if (where != null) {
Expand Down Expand Up @@ -542,4 +536,42 @@ public List<FromItem> getUsingFromItemList() {
public void setUsingFromItemList(List<FromItem> usingFromItemList) {
this.usingFromItemList = usingFromItemList;
}

/**
* Returns the joins after the USING items, as in MySQL's
* {@code DELETE FROM t1 USING t1 JOIN t2 ON t1.id = t2.id} or PostgreSQL's
* {@code DELETE FROM t USING s JOIN u ON s.id = u.id WHERE ...}, or {@code null}. They are
* rendered only when {@link #getUsingFromItemList()} is not empty. {@link #getJoins()} holds
* the joins of the target table, written before USING.
*/
public List<Join> getUsingJoins() {
return usingJoins;
}

public void setUsingJoins(List<Join> usingJoins) {
this.usingJoins = usingJoins;
}

public Delete withUsingJoins(List<Join> usingJoins) {
setUsingJoins(usingJoins);
return this;
}

public Delete addUsingJoins(Join... usingJoins) {
List<Join> collection = Optional.ofNullable(getUsingJoins()).orElseGet(ArrayList::new);
Collections.addAll(collection, usingJoins);
return this.withUsingJoins(collection);
}

private static void appendJoinsTo(StringBuilder b, List<Join> joins) {
if (joins != null) {
for (Join join : joins) {
if (join.isSimple()) {
b.append(", ").append(join);
} else {
b.append(" ").append(join);
}
}
}
}
}
Loading
Loading