Skip to content
Merged
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 @@ -152,6 +152,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
| **MySQL resource groups** | `CREATE`/`ALTER`/`DROP RESOURCE GROUP` · `SET RESOURCE GROUP` |
| **MySQL loadable functions** | `CREATE [AGGREGATE] FUNCTION … RETURNS … SONAME` |
| **MySQL servers, plugins and components** | `CREATE SERVER` · `ALTER SERVER` with `OPTIONS (… PORT 3306)` · `INSTALL`/`UNINSTALL PLUGIN` · `INSTALL`/`UNINSTALL COMPONENT` |
| **PostgreSQL session statements** | `DISCARD` · `LISTEN` · `UNLISTEN` · `SHOW`/`RESET TIME ZONE`/`TRANSACTION ISOLATION LEVEL`/`SESSION AUTHORIZATION` · `COPY` options from before 9.0 |
| **MySQL server administration** | `FLUSH` · `KILL` · `RESET PERSIST` · `HANDLER … OPEN`/`READ`/`CLOSE` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ public String getKind() {
return kind;
}

/**
* @return the keywords that name the kind in the statement, which may carry a modifier of
* {@link #getKind()}
*/
protected String getKindKeywords() {
return kind;
}

public void setOrReplace(boolean orReplace) {
this.orReplace = orReplace;
}
Expand All @@ -156,7 +164,7 @@ public String formatDeclaration() {

private StringBuilder appendDeclarationTo(StringBuilder builder,
Consumer<TableElement> printer) {
appendTokens(builder, functionDeclarationParts);
appendTokens(builder, getFunctionDeclarationParts());
if (returnType != null) {
builder.append(' ');
returnType.appendTo(builder, printer);
Expand Down Expand Up @@ -188,7 +196,7 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<TableElement> prin
if (definer != null) {
builder.append("DEFINER = ").append(definer).append(' ');
}
builder.append(kind).append(' ');
builder.append(getKindKeywords()).append(' ');
return appendDeclarationTo(builder, printer);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@

import java.util.Collections;
import java.util.List;
import java.util.Objects;

import net.sf.jsqlparser.statement.create.table.TableOption;
import net.sf.jsqlparser.statement.create.table.ConstraintKind;

/**
* Internal subclass for table-level option operations within ALTER TABLE. Handles ENGINE,
* ALGORITHM, LOCK, KEY_BLOCK_SIZE, COMMENT, ENCRYPTION, AUTO_INCREMENT (SET_TABLE_OPTION),
* DISCARD/IMPORT TABLESPACE, DISABLE/ENABLE KEYS.
* DISCARD/IMPORT TABLESPACE, DISABLE/ENABLE KEYS, and MySQL's {@code WITH | WITHOUT VALIDATION}
* modifier (VALIDATION).
*/
public class AlterExpressionTableOption extends AlterExpression {

private TableOption structuredTableOption;
private AlterExpressionPartition.ExchangeValidationMode validationMode;

public TableOption getStructuredTableOption() {
return structuredTableOption;
Expand All @@ -36,6 +39,28 @@ public void setStructuredTableOption(TableOption option) {
super.setUseEqual(false);
}

/**
* @return the mode of MySQL's {@code WITH | WITHOUT VALIDATION} modifier, which applies to the
* whole statement, or {@code null} for any other action. The mode written after
* {@code EXCHANGE PARTITION ... WITH TABLE} belongs to that action instead.
*/
public AlterExpressionPartition.ExchangeValidationMode getValidationMode() {
return getOperation() == AlterOperation.VALIDATION ? validationMode : null;
}

/** Makes this action the {@code WITH | WITHOUT VALIDATION} modifier. */
public void setValidationMode(AlterExpressionPartition.ExchangeValidationMode validationMode) {
this.validationMode = Objects.requireNonNull(validationMode, "validationMode");
structuredTableOption = null;
setOperation(AlterOperation.VALIDATION);
}

public AlterExpressionTableOption withValidationMode(
AlterExpressionPartition.ExchangeValidationMode validationMode) {
setValidationMode(validationMode);
return this;
}

@Override
public List<ColumnIdentityAction> getColumnIdentityActions() {
return Collections.emptyList();
Expand Down Expand Up @@ -164,6 +189,9 @@ protected void appendBody(StringBuilder b) {
return;
}
switch (getOperation()) {
case VALIDATION:
b.append(validationMode).append(" VALIDATION");
break;
case SET_TABLE_OPTION:
b.append(getTableOption());
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.util.Locale;

public enum AlterOperation {
ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, DROP_CHECK, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, ATTACH_PARTITION, DETACH_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY, ALTER_PRIMARY_KEY, ALTER_RELATION, ORDER_BY, FOREIGN_OPTIONS;
ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, DROP_CHECK, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, ATTACH_PARTITION, DETACH_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY, ALTER_PRIMARY_KEY, ALTER_RELATION, ORDER_BY, FOREIGN_OPTIONS, VALIDATION;

public static AlterOperation from(String operation) {
return Enum.valueOf(AlterOperation.class, operation.toUpperCase(Locale.ROOT));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@
package net.sf.jsqlparser.statement.create.function;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import net.sf.jsqlparser.statement.CreateFunctionalStatement;
import net.sf.jsqlparser.statement.RoutineCharacteristic;

/**
* A {@code CREATE PROCEDURE} statement
* A {@code CREATE FUNCTION} statement. A MySQL loadable function
* ({@code CREATE [AGGREGATE] FUNCTION name RETURNS type SONAME 'library'}) is held as a
* {@link LoadableFunction}, which then is the source of the declaration parts.
*/
public class CreateFunction extends CreateFunctionalStatement {

private boolean aggregate;
private LoadableFunction loadableFunction;

public CreateFunction() {
super("FUNCTION");
}
Expand All @@ -31,6 +38,71 @@ public CreateFunction(boolean orReplace, List<String> functionDeclarationParts)
super(orReplace, "FUNCTION", functionDeclarationParts);
}

/**
* @return whether the function is declared with {@code AGGREGATE}, as a MySQL loadable
* aggregate function is
*/
public boolean isAggregate() {
return aggregate;
}

public void setAggregate(boolean aggregate) {
this.aggregate = aggregate;
}

public CreateFunction withAggregate(boolean aggregate) {
setAggregate(aggregate);
return this;
}

/**
* @return the definition of a MySQL loadable function, or {@code null} for any other function
*/
public LoadableFunction getLoadableFunction() {
return loadableFunction;
}

/**
* Makes this statement a MySQL loadable function. The definition replaces the declaration parts
* and the characteristics; {@link #getFunctionDeclarationParts()} is derived from it.
*
* @param loadableFunction the definition, or {@code null} to remove it
*/
public void setLoadableFunction(LoadableFunction loadableFunction) {
if (loadableFunction != null) {
super.setFunctionDeclarationParts(null, Collections.emptyList());
}
this.loadableFunction = loadableFunction;
}

public CreateFunction withLoadableFunction(LoadableFunction loadableFunction) {
setLoadableFunction(loadableFunction);
return this;
}

@Override
protected String getKindKeywords() {
return aggregate ? "AGGREGATE " + getKind() : getKind();
}

/**
* @return the declaration parts; for a loadable function, a snapshot of
* {@link #getLoadableFunction()}
*/
@Override
public List<String> getFunctionDeclarationParts() {
return loadableFunction != null ? loadableFunction.toDeclarationParts()
: super.getFunctionDeclarationParts();
}

/** Replaces the declaration parts, which also removes a loadable function definition. */
@Override
public void setFunctionDeclarationParts(List<String> functionDeclarationParts,
List<RoutineCharacteristic> characteristics) {
loadableFunction = null;
super.setFunctionDeclarationParts(functionDeclarationParts, characteristics);
}

@Override
public CreateFunction withFunctionDeclarationParts(List<String> functionDeclarationParts) {
return (CreateFunction) super.withFunctionDeclarationParts(functionDeclarationParts);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*-
* #%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.create.function;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

import net.sf.jsqlparser.expression.StringValue;

/**
* The definition of a MySQL loadable function:
* {@code [IF NOT EXISTS] name RETURNS {STRING | INTEGER | REAL | DECIMAL} SONAME 'library'}.
*/
public class LoadableFunction implements Serializable {

/** The result type of a loadable function. */
public enum ReturnType {
STRING, INTEGER, REAL, DECIMAL;

/**
* @param keyword a type keyword as MySQL spells it, including the synonyms {@code INT},
* {@code INT4} and {@code DEC}
* @return the result type, or {@code null} if the keyword names none
*/
public static ReturnType from(String keyword) {
switch (keyword.toUpperCase(Locale.ROOT)) {
case "STRING":
return STRING;
case "INT":
case "INT4":
case "INTEGER":
return INTEGER;
case "REAL":
return REAL;
case "DEC":
case "DECIMAL":
return DECIMAL;
default:
return null;
}
}
}

private boolean ifNotExists;
private String name;
private String returnTypeKeyword;
private StringValue library;

public LoadableFunction() {
// filled by the setters
}

public LoadableFunction(String name, ReturnType returnType, String library) {
setName(name);
setReturnType(returnType);
setLibrary(new StringValue(library));
}

public boolean isIfNotExists() {
return ifNotExists;
}

public void setIfNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public ReturnType getReturnType() {
return returnTypeKeyword == null ? null : ReturnType.from(returnTypeKeyword);
}

public void setReturnType(ReturnType returnType) {
returnTypeKeyword = Objects.requireNonNull(returnType, "returnType").name();
}

/**
* @return the result type as written, which may be one of the synonyms {@code INT},
* {@code INT4} and {@code DEC}
*/
public String getReturnTypeKeyword() {
return returnTypeKeyword;
}

/**
* @param keyword a keyword that {@link ReturnType#from(String)} resolves
*/
public void setReturnTypeKeyword(String keyword) {
if (ReturnType.from(keyword) == null) {
throw new IllegalArgumentException(
"A loadable function returns STRING, INTEGER, REAL or DECIMAL, not " + keyword);
}
returnTypeKeyword = keyword;
}

/**
* @return the shared library file named by {@code SONAME}
*/
public StringValue getLibrary() {
return library;
}

public void setLibrary(StringValue library) {
this.library = library;
}

public LoadableFunction withIfNotExists(boolean ifNotExists) {
setIfNotExists(ifNotExists);
return this;
}

/**
* @return the definition as the declaration parts of the CREATE FUNCTION statement
*/
public List<String> toDeclarationParts() {
List<String> parts = new ArrayList<>();
if (ifNotExists) {
parts.add("IF");
parts.add("NOT");
parts.add("EXISTS");
}
parts.add(name);
parts.add("RETURNS");
parts.add(returnTypeKeyword);
parts.add("SONAME");
parts.add(String.valueOf(library));
return parts;
}

@Override
public String toString() {
return String.join(" ", toDeclarationParts());
}
}
7 changes: 6 additions & 1 deletion src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
import net.sf.jsqlparser.statement.create.extension.CreateExtension;
import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
import net.sf.jsqlparser.statement.create.fdw.CreateForeignDataWrapper;
import net.sf.jsqlparser.statement.create.function.CreateFunction;
import net.sf.jsqlparser.statement.create.index.CreateIndex;
import net.sf.jsqlparser.statement.create.macro.CreateMacro;
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
Expand Down Expand Up @@ -2633,7 +2634,11 @@ public void visit(AlterSequence alterSequence) {

@Override
public <S> Void visit(CreateFunctionalStatement createFunctionalStatement, S context) {
throwUnsupported(createFunctionalStatement);
// a MySQL loadable function names a shared library and no table
if (!(createFunctionalStatement instanceof CreateFunction)
|| ((CreateFunction) createFunctionalStatement).getLoadableFunction() == null) {
throwUnsupported(createFunctionalStatement);
}
return null;
}

Expand Down
Loading
Loading