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 @@ -146,6 +146,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
| **PostgreSQL ownership** | `DROP OWNED` · `REASSIGN OWNED` |
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `OPTIMIZE` · `REPAIR TABLE` |
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
| **Routine changes** | `ALTER FUNCTION` · `ALTER PROCEDURE` · `ALTER ROUTINE` · PostgreSQL `ALTER TRIGGER` |
| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` |
| **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` |

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/RoutineBodyBoundary.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ private enum Block {
private boolean handlerAction;
private boolean statementStart = true;

/**
* Tracks a compound statement without a routine header, such as a MySQL trigger body. Its
* tokens belong to the body from the start, as after a routine's parameter list.
*/
static RoutineBodyBoundary forCompoundStatement() {
RoutineBodyBoundary boundary = new RoutineBodyBoundary();
boundary.hasParameters = true;
return boundary;
}

boolean endsAt(Token token) throws ParseException {
if (token.kind == EOF) {
if (!blocks.isEmpty()) {
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,18 @@ public enum Feature {
functionalStatement,

alterFunction, alterProcedure, createOrAlterRoutine,
/**
* PostgreSQL "ALTER ROUTINE" statement is allowed
*
* @see net.sf.jsqlparser.statement.alter.AlterRoutine
*/
alterRoutine,
/**
* PostgreSQL "ALTER TRIGGER" statement is allowed
*
* @see net.sf.jsqlparser.statement.alter.AlterTrigger
*/
alterTrigger,
/**
* SQL block starting with "BEGIN" and ends with "END" statement is allowed
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

/**
* A PostgreSQL {@code SET} or {@code RESET} of a configuration parameter, shared by statements that
* store parameter defaults, such as {@code ALTER ROLE} and {@code ALTER DATABASE}:
* store parameter defaults: {@code ALTER ROLE}, {@code ALTER DATABASE} and the routine
* characteristics of {@code ALTER FUNCTION}, {@code ALTER PROCEDURE} and {@code ALTER ROUTINE}:
*
* <pre>
* SET parameter { TO | = } { value [, ...] | DEFAULT }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public enum Operation {
}

private Operation operation = Operation.CREATE;
private MySqlDefiner definer;
private FunctionReturnType returnType;
private List<String> routineBodyParts;

Expand All @@ -42,6 +43,19 @@ public void setOperation(Operation operation) {
this.operation = operation;
}

/**
* The MySQL {@code DEFINER} account, rendered after {@code CREATE [OR REPLACE]}.
*
* @return the definer, or {@code null}
*/
public MySqlDefiner getDefiner() {
return definer;
}

public void setDefiner(MySqlDefiner definer) {
this.definer = definer;
}

public FunctionReturnType getReturnType() {
return returnType;
}
Expand Down Expand Up @@ -139,6 +153,9 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<TableElement> prin
if (orReplace && operation == Operation.CREATE) {
builder.append("OR REPLACE ");
}
if (definer != null) {
builder.append("DEFINER = ").append(definer).append(' ');
}
builder.append(kind).append(' ');
return appendDeclarationTo(builder, printer);
}
Expand Down
263 changes: 263 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/RoutineCharacteristic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
/*-
* #%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 java.util.function.Consumer;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;

/**
* A characteristic set by {@code ALTER FUNCTION}, {@code ALTER PROCEDURE} or {@code ALTER ROUTINE}.
* PostgreSQL and MySQL share the model, and each {@link Kind} renders its own SQL:
*
* <ul>
* <li>PostgreSQL: the volatility, the null input behavior, {@code [NOT] LEAKPROOF},
* {@code [EXTERNAL] SECURITY}, {@code PARALLEL}, {@code COST}, {@code ROWS}, {@code SUPPORT} and
* the {@code SET} or {@code RESET} of a configuration parameter;</li>
* <li>MySQL: {@code COMMENT}, {@code LANGUAGE SQL}, the SQL data access and
* {@code SQL SECURITY}.</li>
* </ul>
*
* The security kinds are shared; {@link #getSecurityClause()} keeps the spelling. Values keep their
* SQL text, such as {@code 100}. The {@code COMMENT} literal is a {@link StringValue}.
*/
public class RoutineCharacteristic implements Serializable {

public enum Kind {
/** PostgreSQL {@code IMMUTABLE}. */
IMMUTABLE("IMMUTABLE"),
/** PostgreSQL {@code STABLE}. */
STABLE("STABLE"),
/** PostgreSQL {@code VOLATILE}. */
VOLATILE("VOLATILE"),
/** PostgreSQL {@code CALLED ON NULL INPUT}. */
CALLED_ON_NULL_INPUT("CALLED ON NULL INPUT"),
/** PostgreSQL {@code RETURNS NULL ON NULL INPUT}. */
RETURNS_NULL_ON_NULL_INPUT("RETURNS NULL ON NULL INPUT"),
/** PostgreSQL {@code STRICT}. */
STRICT("STRICT"),
/** PostgreSQL {@code LEAKPROOF}. */
LEAKPROOF("LEAKPROOF"),
/** PostgreSQL {@code NOT LEAKPROOF}. */
NOT_LEAKPROOF("NOT LEAKPROOF"),
/** {@code DEFINER}, preceded by the {@link SecurityClause}. */
SECURITY_DEFINER("DEFINER"),
/** {@code INVOKER}, preceded by the {@link SecurityClause}. */
SECURITY_INVOKER("INVOKER"),
/** PostgreSQL {@code PARALLEL UNSAFE}. */
PARALLEL_UNSAFE("PARALLEL UNSAFE"),
/** PostgreSQL {@code PARALLEL RESTRICTED}. */
PARALLEL_RESTRICTED("PARALLEL RESTRICTED"),
/** PostgreSQL {@code PARALLEL SAFE}. */
PARALLEL_SAFE("PARALLEL SAFE"),
/** PostgreSQL {@code COST} followed by the numeric {@link #getValue()}. */
COST("COST"),
/** PostgreSQL {@code ROWS} followed by the numeric {@link #getValue()}. */
ROWS("ROWS"),
/** PostgreSQL {@code SUPPORT} followed by the function name in {@link #getValue()}. */
SUPPORT("SUPPORT"),
/** PostgreSQL {@code SET} or {@code RESET}, see {@link #getParameterChange()}. */
CONFIGURATION_PARAMETER(null),
/** MySQL {@code COMMENT} followed by the string literal of {@link #getComment()}. */
COMMENT("COMMENT"),
/** MySQL {@code LANGUAGE SQL}. */
LANGUAGE_SQL("LANGUAGE SQL"),
/** MySQL {@code CONTAINS SQL}. */
CONTAINS_SQL("CONTAINS SQL"),
/** MySQL {@code NO SQL}. */
NO_SQL("NO SQL"),
/** MySQL {@code READS SQL DATA}. */
READS_SQL_DATA("READS SQL DATA"),
/** MySQL {@code MODIFIES SQL DATA}. */
MODIFIES_SQL_DATA("MODIFIES SQL DATA");

private final String keywords;

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

private boolean hasValue() {
return this == COST || this == ROWS || this == SUPPORT || this == COMMENT;
}
}

/** The words before {@code DEFINER} or {@code INVOKER}. */
public enum SecurityClause {
/** PostgreSQL {@code SECURITY}. */
SECURITY("SECURITY"),
/** PostgreSQL {@code EXTERNAL SECURITY}; {@code EXTERNAL} has no effect. */
EXTERNAL_SECURITY("EXTERNAL SECURITY"),
/** MySQL {@code SQL SECURITY}. */
SQL_SECURITY("SQL SECURITY");

private final String keywords;

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

private Kind kind;
private String value;
private StringValue comment;
private SecurityClause securityClause;
private ConfigurationParameterChange parameterChange;

public RoutineCharacteristic(Kind kind) {
this.kind = kind;
}

public RoutineCharacteristic(Kind kind, String value) {
this.kind = kind;
setValue(value);
}

public RoutineCharacteristic(ConfigurationParameterChange parameterChange) {
this.kind = Kind.CONFIGURATION_PARAMETER;
this.parameterChange = parameterChange;
}

/**
* Creates a MySQL {@code COMMENT} characteristic.
*
* @param comment the string literal
* @return the characteristic
*/
public static RoutineCharacteristic comment(StringValue comment) {
return new RoutineCharacteristic(Kind.COMMENT).withComment(comment);
}

public Kind getKind() {
return kind;
}

public void setKind(Kind kind) {
this.kind = kind;
}

/**
* The SQL text after the keyword of {@link Kind#COST}, {@link Kind#ROWS}, {@link Kind#SUPPORT}
* and {@link Kind#COMMENT}. For {@code COMMENT} it is derived from {@link #getComment()}, such
* as {@code 'it\'s'}.
*
* @return the value as written, or {@code null} for other kinds
*/
public String getValue() {
if (kind == Kind.COMMENT) {
return comment == null ? null : comment.toString();
}
return value;
}

/**
* Sets the SQL text of the value. For {@link Kind#COMMENT} it replaces {@link #getComment()}
* with a literal read without dialect escaping; use {@link #setComment(StringValue)} to keep
* MySQL backslash escapes.
*
* @param value the SQL text
*/
public void setValue(String value) {
if (kind == Kind.COMMENT) {
comment = value == null ? null : new StringValue(value);
} else {
this.value = value;
}
}

/**
* The string literal of {@link Kind#COMMENT}. {@link StringValue#getNotExcapedValue()} returns
* the text that MySQL stores.
*
* @return the literal, or {@code null} for other kinds
*/
public StringValue getComment() {
return comment;
}

public void setComment(StringValue comment) {
this.comment = comment;
}

public RoutineCharacteristic withComment(StringValue comment) {
setComment(comment);
return this;
}

/**
* The spelling of {@link Kind#SECURITY_DEFINER} and {@link Kind#SECURITY_INVOKER}; {@code null}
* renders {@link SecurityClause#SECURITY}.
*
* @return the clause, or {@code null}
*/
public SecurityClause getSecurityClause() {
return securityClause;
}

public void setSecurityClause(SecurityClause securityClause) {
this.securityClause = securityClause;
}

/**
* The change of {@link Kind#CONFIGURATION_PARAMETER}.
*
* @return the change, or {@code null} for other kinds
*/
public ConfigurationParameterChange getParameterChange() {
return parameterChange;
}

public void setParameterChange(ConfigurationParameterChange parameterChange) {
this.parameterChange = parameterChange;
}

public RoutineCharacteristic withValue(String value) {
setValue(value);
return this;
}

public RoutineCharacteristic withSecurityClause(SecurityClause securityClause) {
setSecurityClause(securityClause);
return this;
}

/**
* Appends this characteristic to a builder.
*
* @param builder the target
* @param valueRenderer appends each configuration parameter value
* @return the builder
*/
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> valueRenderer) {
switch (kind) {
case CONFIGURATION_PARAMETER:
if (parameterChange == null) {
throw new IllegalStateException("Expected a configuration parameter change");
}
return parameterChange.appendTo(builder, valueRenderer);
case SECURITY_DEFINER:
case SECURITY_INVOKER:
return builder
.append((securityClause == null ? SecurityClause.SECURITY
: securityClause).keywords)
.append(' ').append(kind.keywords);
default:
builder.append(kind.keywords);
return kind.hasValue() ? builder.append(' ').append(getValue()) : builder;
}
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
return appendTo(builder, builder::append).toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.alter.Alter;
import net.sf.jsqlparser.statement.alter.AlterCollation;
import net.sf.jsqlparser.statement.alter.AlterRoutine;
import net.sf.jsqlparser.statement.alter.AlterTrigger;
import net.sf.jsqlparser.statement.alter.AlterDomain;
import net.sf.jsqlparser.statement.alter.AlterExtension;
import net.sf.jsqlparser.statement.alter.AlterForeignDataWrapper;
Expand Down Expand Up @@ -1187,6 +1189,16 @@ public <S> Void visit(AlterCollation statement, S context) {
return schemaOnly();
}

@Override
public <S> Void visit(AlterRoutine statement, S context) {
return schemaOnly();
}

@Override
public <S> Void visit(AlterTrigger statement, S context) {
return schemaOnly();
}

@Override
public <S> Void visit(AlterDatabase statement, S context) {
return schemaOnly();
Expand Down
Loading
Loading