JsonTableFunction.java

/*-
 * #%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.expression;

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

import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.statement.create.table.ColDataType;

public class JsonTableFunction extends Function {
    public enum JsonTablePlanOperator {
        COMMA(", "), INNER(" INNER "), OUTER(" OUTER "), CROSS(" CROSS "), UNION(" UNION ");

        private final String display;

        JsonTablePlanOperator(String display) {
            this.display = display;
        }

        public String getDisplay() {
            return display;
        }
    }

    public enum JsonTableOnErrorType {
        ERROR, EMPTY
    }

    public static class JsonTablePassingClause extends ASTNodeAccessImpl implements Serializable {
        private Expression valueExpression;
        private String parameterName;

        public JsonTablePassingClause() {}

        public JsonTablePassingClause(Expression valueExpression, String parameterName) {
            this.valueExpression = valueExpression;
            this.parameterName = parameterName;
        }

        public Expression getValueExpression() {
            return valueExpression;
        }

        public JsonTablePassingClause setValueExpression(Expression valueExpression) {
            this.valueExpression = valueExpression;
            return this;
        }

        public String getParameterName() {
            return parameterName;
        }

        public JsonTablePassingClause setParameterName(String parameterName) {
            this.parameterName = parameterName;
            return this;
        }

        public void collectExpressions(List<Expression> expressions) {
            if (valueExpression != null) {
                expressions.add(valueExpression);
            }
        }

        @Override
        public String toString() {
            return valueExpression + " AS " + parameterName;
        }
    }

    public static class JsonTableWrapperClause extends ASTNodeAccessImpl implements Serializable {
        private JsonFunction.JsonWrapperType wrapperType;
        private JsonFunction.JsonWrapperMode wrapperMode;
        private boolean array;

        public JsonFunction.JsonWrapperType getWrapperType() {
            return wrapperType;
        }

        public JsonTableWrapperClause setWrapperType(JsonFunction.JsonWrapperType wrapperType) {
            this.wrapperType = wrapperType;
            return this;
        }

        public JsonFunction.JsonWrapperMode getWrapperMode() {
            return wrapperMode;
        }

        public JsonTableWrapperClause setWrapperMode(JsonFunction.JsonWrapperMode wrapperMode) {
            this.wrapperMode = wrapperMode;
            return this;
        }

        public boolean isArray() {
            return array;
        }

        public JsonTableWrapperClause setArray(boolean array) {
            this.array = array;
            return this;
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append(wrapperType);
            if (wrapperMode != null) {
                builder.append(" ").append(wrapperMode);
            }
            if (array) {
                builder.append(" ARRAY");
            }
            builder.append(" WRAPPER");
            return builder.toString();
        }
    }

    public static class JsonTableQuotesClause extends ASTNodeAccessImpl implements Serializable {
        private JsonFunction.JsonQuotesType quotesType;
        private boolean onScalarString;

        public JsonFunction.JsonQuotesType getQuotesType() {
            return quotesType;
        }

        public JsonTableQuotesClause setQuotesType(JsonFunction.JsonQuotesType quotesType) {
            this.quotesType = quotesType;
            return this;
        }

        public boolean isOnScalarString() {
            return onScalarString;
        }

        public JsonTableQuotesClause setOnScalarString(boolean onScalarString) {
            this.onScalarString = onScalarString;
            return this;
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append(quotesType).append(" QUOTES");
            if (onScalarString) {
                builder.append(" ON SCALAR STRING");
            }
            return builder.toString();
        }
    }

    public static class JsonTableOnErrorClause extends ASTNodeAccessImpl implements Serializable {
        private JsonTableOnErrorType type;

        public JsonTableOnErrorType getType() {
            return type;
        }

        public JsonTableOnErrorClause setType(JsonTableOnErrorType type) {
            this.type = type;
            return this;
        }

        @Override
        public String toString() {
            return type + " ON ERROR";
        }
    }

    public static class JsonTablePlanTerm extends ASTNodeAccessImpl implements Serializable {
        private JsonTablePlanExpression nestedPlanExpression;
        private String name;
        private Expression expression;

        public JsonTablePlanExpression getNestedPlanExpression() {
            return nestedPlanExpression;
        }

        public JsonTablePlanTerm setNestedPlanExpression(
                JsonTablePlanExpression nestedPlanExpression) {
            this.nestedPlanExpression = nestedPlanExpression;
            return this;
        }

        public String getName() {
            return name;
        }

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

        public Expression getExpression() {
            return expression;
        }

        public JsonTablePlanTerm setExpression(Expression expression) {
            this.expression = expression;
            return this;
        }

        public void collectExpressions(List<Expression> expressions) {
            if (expression != null) {
                expressions.add(expression);
            }
            if (nestedPlanExpression != null) {
                nestedPlanExpression.collectExpressions(expressions);
            }
        }

        @Override
        public String toString() {
            if (nestedPlanExpression != null) {
                return "(" + nestedPlanExpression + ")";
            }
            if (name != null) {
                return name;
            }
            return expression != null ? expression.toString() : "";
        }
    }

    public static class JsonTablePlanExpression extends ASTNodeAccessImpl implements Serializable {
        private final List<JsonTablePlanTerm> terms = new ArrayList<>();
        private final List<JsonTablePlanOperator> operators = new ArrayList<>();

        public List<JsonTablePlanTerm> getTerms() {
            return terms;
        }

        public JsonTablePlanExpression addTerm(JsonTablePlanTerm term) {
            terms.add(term);
            return this;
        }

        public List<JsonTablePlanOperator> getOperators() {
            return operators;
        }

        public JsonTablePlanExpression addOperator(JsonTablePlanOperator operator) {
            operators.add(operator);
            return this;
        }

        public void collectExpressions(List<Expression> expressions) {
            for (JsonTablePlanTerm term : terms) {
                if (term != null) {
                    term.collectExpressions(expressions);
                }
            }
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            if (!terms.isEmpty()) {
                builder.append(terms.get(0));
            }
            for (int i = 0; i < operators.size() && i + 1 < terms.size(); i++) {
                builder.append(operators.get(i).getDisplay()).append(terms.get(i + 1));
            }
            return builder.toString();
        }
    }

    public static class JsonTablePlanClause extends ASTNodeAccessImpl implements Serializable {
        private boolean defaultPlan;
        private JsonTablePlanExpression planExpression;

        public boolean isDefaultPlan() {
            return defaultPlan;
        }

        public JsonTablePlanClause setDefaultPlan(boolean defaultPlan) {
            this.defaultPlan = defaultPlan;
            return this;
        }

        public JsonTablePlanExpression getPlanExpression() {
            return planExpression;
        }

        public JsonTablePlanClause setPlanExpression(JsonTablePlanExpression planExpression) {
            this.planExpression = planExpression;
            return this;
        }

        public void collectExpressions(List<Expression> expressions) {
            if (planExpression != null) {
                planExpression.collectExpressions(expressions);
            }
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder("PLAN");
            if (defaultPlan) {
                builder.append(" DEFAULT");
            }
            builder.append(" (").append(planExpression).append(")");
            return builder.toString();
        }
    }

    public abstract static class JsonTableColumnDefinition extends ASTNodeAccessImpl
            implements Serializable {
        public abstract void collectExpressions(List<Expression> expressions);
    }

    public static class JsonTableNestedColumnDefinition extends JsonTableColumnDefinition {
        private boolean pathKeyword;
        private Expression pathExpression;
        private String pathName;
        private JsonTableColumnsClause columnsClause;

        public boolean isPathKeyword() {
            return pathKeyword;
        }

        public JsonTableNestedColumnDefinition setPathKeyword(boolean pathKeyword) {
            this.pathKeyword = pathKeyword;
            return this;
        }

        public Expression getPathExpression() {
            return pathExpression;
        }

        public JsonTableNestedColumnDefinition setPathExpression(Expression pathExpression) {
            this.pathExpression = pathExpression;
            return this;
        }

        public String getPathName() {
            return pathName;
        }

        public JsonTableNestedColumnDefinition setPathName(String pathName) {
            this.pathName = pathName;
            return this;
        }

        public JsonTableColumnsClause getColumnsClause() {
            return columnsClause;
        }

        public JsonTableNestedColumnDefinition setColumnsClause(
                JsonTableColumnsClause columnsClause) {
            this.columnsClause = columnsClause;
            return this;
        }

        @Override
        public void collectExpressions(List<Expression> expressions) {
            if (pathExpression != null) {
                expressions.add(pathExpression);
            }
            if (columnsClause != null) {
                columnsClause.collectExpressions(expressions);
            }
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder("NESTED");
            if (pathKeyword) {
                builder.append(" PATH");
            }
            builder.append(" ").append(pathExpression);
            if (pathName != null) {
                builder.append(" AS ").append(pathName);
            }
            builder.append(" ").append(columnsClause);
            return builder.toString();
        }
    }

    public static class JsonTableValueColumnDefinition extends JsonTableColumnDefinition {
        private String columnName;
        private boolean forOrdinality;
        private ColDataType dataType;
        private boolean formatJson;
        private String encoding;
        private Expression pathExpression;
        private JsonTableWrapperClause wrapperClause;
        private JsonTableQuotesClause quotesClause;
        private JsonFunction.JsonOnResponseBehavior onEmptyBehavior;
        private JsonFunction.JsonOnResponseBehavior onErrorBehavior;

        public String getColumnName() {
            return columnName;
        }

        public JsonTableValueColumnDefinition setColumnName(String columnName) {
            this.columnName = columnName;
            return this;
        }

        public boolean isForOrdinality() {
            return forOrdinality;
        }

        public JsonTableValueColumnDefinition setForOrdinality(boolean forOrdinality) {
            this.forOrdinality = forOrdinality;
            return this;
        }

        public ColDataType getDataType() {
            return dataType;
        }

        public JsonTableValueColumnDefinition setDataType(ColDataType dataType) {
            this.dataType = dataType;
            return this;
        }

        public boolean isFormatJson() {
            return formatJson;
        }

        public JsonTableValueColumnDefinition setFormatJson(boolean formatJson) {
            this.formatJson = formatJson;
            return this;
        }

        public String getEncoding() {
            return encoding;
        }

        public JsonTableValueColumnDefinition setEncoding(String encoding) {
            this.encoding = encoding;
            return this;
        }

        public Expression getPathExpression() {
            return pathExpression;
        }

        public JsonTableValueColumnDefinition setPathExpression(Expression pathExpression) {
            this.pathExpression = pathExpression;
            return this;
        }

        public JsonTableWrapperClause getWrapperClause() {
            return wrapperClause;
        }

        public JsonTableValueColumnDefinition setWrapperClause(
                JsonTableWrapperClause wrapperClause) {
            this.wrapperClause = wrapperClause;
            return this;
        }

        public JsonTableQuotesClause getQuotesClause() {
            return quotesClause;
        }

        public JsonTableValueColumnDefinition setQuotesClause(JsonTableQuotesClause quotesClause) {
            this.quotesClause = quotesClause;
            return this;
        }

        public JsonFunction.JsonOnResponseBehavior getOnEmptyBehavior() {
            return onEmptyBehavior;
        }

        public JsonTableValueColumnDefinition setOnEmptyBehavior(
                JsonFunction.JsonOnResponseBehavior onEmptyBehavior) {
            this.onEmptyBehavior = onEmptyBehavior;
            return this;
        }

        public JsonFunction.JsonOnResponseBehavior getOnErrorBehavior() {
            return onErrorBehavior;
        }

        public JsonTableValueColumnDefinition setOnErrorBehavior(
                JsonFunction.JsonOnResponseBehavior onErrorBehavior) {
            this.onErrorBehavior = onErrorBehavior;
            return this;
        }

        @Override
        public void collectExpressions(List<Expression> expressions) {
            if (pathExpression != null) {
                expressions.add(pathExpression);
            }
            if (onEmptyBehavior != null && onEmptyBehavior.getExpression() != null) {
                expressions.add(onEmptyBehavior.getExpression());
            }
            if (onErrorBehavior != null && onErrorBehavior.getExpression() != null) {
                expressions.add(onErrorBehavior.getExpression());
            }
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder(columnName);
            if (forOrdinality) {
                builder.append(" FOR ORDINALITY");
                return builder.toString();
            }

            builder.append(" ").append(dataType);
            if (formatJson) {
                builder.append(" FORMAT JSON");
                if (encoding != null) {
                    builder.append(" ENCODING ").append(encoding);
                }
            }
            if (pathExpression != null) {
                builder.append(" PATH ").append(pathExpression);
            }
            if (wrapperClause != null) {
                builder.append(" ").append(wrapperClause);
            }
            if (quotesClause != null) {
                builder.append(" ").append(quotesClause);
            }
            if (onEmptyBehavior != null) {
                builder.append(" ").append(onEmptyBehavior).append(" ON EMPTY");
            }
            if (onErrorBehavior != null) {
                builder.append(" ").append(onErrorBehavior).append(" ON ERROR");
            }
            return builder.toString();
        }
    }

    public static class JsonTableColumnsClause extends ASTNodeAccessImpl implements Serializable {
        private final List<JsonTableColumnDefinition> columnDefinitions = new ArrayList<>();

        public List<JsonTableColumnDefinition> getColumnDefinitions() {
            return columnDefinitions;
        }

        public JsonTableColumnsClause addColumnDefinition(
                JsonTableColumnDefinition columnDefinition) {
            columnDefinitions.add(columnDefinition);
            return this;
        }

        public void collectExpressions(List<Expression> expressions) {
            for (JsonTableColumnDefinition columnDefinition : columnDefinitions) {
                if (columnDefinition != null) {
                    columnDefinition.collectExpressions(expressions);
                }
            }
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder("COLUMNS (");
            boolean first = true;
            for (JsonTableColumnDefinition columnDefinition : columnDefinitions) {
                if (!first) {
                    builder.append(", ");
                }
                builder.append(columnDefinition);
                first = false;
            }
            builder.append(")");
            return builder.toString();
        }
    }

    private Expression jsonInputExpression;
    private Expression jsonPathExpression;
    private String pathName;
    private final List<JsonTablePassingClause> passingClauses = new ArrayList<>();
    private JsonTableColumnsClause columnsClause;
    private JsonTablePlanClause planClause;
    private JsonTableOnErrorClause onErrorClause;

    public JsonTableFunction() {
        setName("JSON_TABLE");
    }

    public Expression getJsonInputExpression() {
        return jsonInputExpression;
    }

    public JsonTableFunction setJsonInputExpression(Expression jsonInputExpression) {
        this.jsonInputExpression = jsonInputExpression;
        return this;
    }

    public Expression getJsonPathExpression() {
        return jsonPathExpression;
    }

    public JsonTableFunction setJsonPathExpression(Expression jsonPathExpression) {
        this.jsonPathExpression = jsonPathExpression;
        return this;
    }

    public String getPathName() {
        return pathName;
    }

    public JsonTableFunction setPathName(String pathName) {
        this.pathName = pathName;
        return this;
    }

    public List<JsonTablePassingClause> getPassingClauses() {
        return passingClauses;
    }

    public JsonTableFunction addPassingClause(JsonTablePassingClause passingClause) {
        passingClauses.add(Objects.requireNonNull(passingClause, "passingClause"));
        return this;
    }

    public JsonTableColumnsClause getColumnsClause() {
        return columnsClause;
    }

    public JsonTableFunction setColumnsClause(JsonTableColumnsClause columnsClause) {
        this.columnsClause = columnsClause;
        return this;
    }

    public JsonTablePlanClause getPlanClause() {
        return planClause;
    }

    public JsonTableFunction setPlanClause(JsonTablePlanClause planClause) {
        this.planClause = planClause;
        return this;
    }

    public JsonTableOnErrorClause getOnErrorClause() {
        return onErrorClause;
    }

    public JsonTableFunction setOnErrorClause(JsonTableOnErrorClause onErrorClause) {
        this.onErrorClause = onErrorClause;
        return this;
    }

    public List<Expression> getAllExpressions() {
        List<Expression> expressions = new ArrayList<>();
        if (jsonInputExpression != null) {
            expressions.add(jsonInputExpression);
        }
        if (jsonPathExpression != null) {
            expressions.add(jsonPathExpression);
        }
        for (JsonTablePassingClause passingClause : passingClauses) {
            passingClause.collectExpressions(expressions);
        }
        if (columnsClause != null) {
            columnsClause.collectExpressions(expressions);
        }
        if (planClause != null) {
            planClause.collectExpressions(expressions);
        }
        return expressions;
    }

    @Override
    public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
        return expressionVisitor.visit(this, context);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("JSON_TABLE(");
        builder.append(jsonInputExpression).append(", ").append(jsonPathExpression);
        if (pathName != null) {
            builder.append(" AS ").append(pathName);
        }
        if (!passingClauses.isEmpty()) {
            builder.append(" PASSING ");
            boolean first = true;
            for (JsonTablePassingClause passingClause : passingClauses) {
                if (!first) {
                    builder.append(", ");
                }
                builder.append(passingClause);
                first = false;
            }
        }
        builder.append(" ").append(columnsClause);
        if (planClause != null) {
            builder.append(" ").append(planClause);
        }
        if (onErrorClause != null) {
            builder.append(" ").append(onErrorClause);
        }
        builder.append(")");
        return builder.toString();
    }
}