org.teiid.modeshape.sequencer.ddl.CreateProcedureParser Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.modeshape.sequencer.ddl;
import static org.teiid.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_ARRAY_DIMENSIONS;
import static org.teiid.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_LENGTH;
import static org.teiid.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_NAME;
import static org.teiid.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_PRECISION;
import static org.teiid.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_SCALE;
import java.util.HashMap;
import java.util.Map;
import org.modeshape.common.text.ParsingException;
import org.modeshape.common.text.Position;
import org.modeshape.common.util.StringUtil;
import org.teiid.modeshape.sequencer.ddl.TeiidDdlConstants.DdlStatement;
import org.teiid.modeshape.sequencer.ddl.TeiidDdlConstants.SchemaElementType;
import org.teiid.modeshape.sequencer.ddl.TeiidDdlConstants.TeiidNonReservedWord;
import org.teiid.modeshape.sequencer.ddl.TeiidDdlConstants.TeiidReservedWord;
import org.teiid.modeshape.sequencer.ddl.datatype.DataType;
import org.teiid.modeshape.sequencer.ddl.node.AstNode;
/**
* A parser for the Teiid DDL statement.
*
*
* CREATE ( VIRTUAL | FOREIGN )? ( PROCEDURE | FUNCTION ) ( ( ( )* )? ( RETURNS ( ( ( TABLE )? ( )* ) | ) )? ( )? ( AS )? )
*
*/
final class CreateProcedureParser extends StatementParser {
CreateProcedureParser( final TeiidDdlParser teiidDdlParser ) {
super(teiidDdlParser);
}
/**
* {@inheritDoc}
*
* @see org.teiid.modeshape.sequencer.ddl.StatementParser#matches(org.teiid.modeshape.sequencer.ddl.DdlTokenStream)
*/
@Override
boolean matches( final DdlTokenStream tokens ) {
return tokens.matches(DdlStatement.CREATE_VIRTUAL_FUNCTION.tokens())
|| tokens.matches(DdlStatement.CREATE_VIRTUAL_PROCEDURE.tokens())
|| tokens.matches(DdlStatement.CREATE_FOREIGN_FUNCTION.tokens())
|| tokens.matches(DdlStatement.CREATE_FOREIGN_PROCEDURE.tokens())
|| tokens.matches(DdlStatement.CREATE_FUNCTION.tokens()) || tokens.matches(DdlStatement.CREATE_PROCEDURE.tokens());
}
/**
* {@inheritDoc}
*
* @see org.teiid.modeshape.sequencer.ddl.StatementParser#parse(org.teiid.modeshape.sequencer.ddl.DdlTokenStream,
* org.teiid.modeshape.sequencer.ddl.node.AstNode)
*/
@Override
AstNode parse( final DdlTokenStream tokens,
final AstNode parentNode ) throws ParsingException {
boolean procedure = true;
DdlStatement stmt = null;
SchemaElementType schemaElementType = null;
if (tokens.canConsume(DdlStatement.CREATE_VIRTUAL_FUNCTION.tokens())) {
stmt = DdlStatement.CREATE_VIRTUAL_FUNCTION;
schemaElementType = SchemaElementType.VIRTUAL;
procedure = false;
} else if (tokens.canConsume(DdlStatement.CREATE_VIRTUAL_PROCEDURE.tokens())) {
stmt = DdlStatement.CREATE_VIRTUAL_PROCEDURE;
schemaElementType = SchemaElementType.VIRTUAL;
} else if (tokens.canConsume(DdlStatement.CREATE_FOREIGN_FUNCTION.tokens())) {
stmt = DdlStatement.CREATE_FOREIGN_FUNCTION;
schemaElementType = SchemaElementType.FOREIGN;
procedure = false;
} else if (tokens.canConsume(DdlStatement.CREATE_FOREIGN_PROCEDURE.tokens())) {
stmt = DdlStatement.CREATE_FOREIGN_PROCEDURE;
schemaElementType = SchemaElementType.FOREIGN;
} else if (tokens.canConsume(DdlStatement.CREATE_FUNCTION.tokens())) {
stmt = DdlStatement.CREATE_FUNCTION;
schemaElementType = SchemaElementType.FOREIGN;
procedure = false;
} else if (tokens.canConsume(DdlStatement.CREATE_PROCEDURE.tokens())) {
stmt = DdlStatement.CREATE_PROCEDURE;
schemaElementType = SchemaElementType.FOREIGN;
} else {
throw new TeiidDdlParsingException(tokens, "Unparsable create procedure statement");
}
assert (stmt != null) : "Create procedure statement is null";
assert (schemaElementType != null) : "Create procedure schema element type is null";
// parse identifier
final String id = parseIdentifier(tokens);
final AstNode procedureNode = getNodeFactory().node(id,
parentNode,
(procedure ? TeiidDdlLexicon.CreateProcedure.PROCEDURE_STATEMENT : TeiidDdlLexicon.CreateProcedure.FUNCTION_STATEMENT));
procedureNode.setProperty(TeiidDdlLexicon.SchemaElement.TYPE, schemaElementType.toDdl());
// must have parens after identifier and may have one or more parameters
parseProcedureParameters(tokens, procedureNode);
// may have a returns clause
parseReturnsClause(tokens, procedureNode);
// may have an option clause
parseOptionsClause(tokens, procedureNode);
// may have AS clause
parseAsClause(tokens, procedureNode);
return procedureNode;
}
boolean parseAsClause( final DdlTokenStream tokens,
final AstNode procedureNode ) {
if (tokens.canConsume(TeiidReservedWord.AS.toDdl())) {
final String statement = parseStatement(tokens, 0, "", tokens.nextPosition(), "");
if (StringUtil.isBlank(statement)) {
throw new TeiidDdlParsingException(tokens, "Unparsable AS clause (no statement found)");
}
procedureNode.setProperty(TeiidDdlLexicon.CreateProcedure.STATEMENT, statement);
return true;
}
return false;
}
/**
*
* ( IN | OUT | INOUT | VARIADIC )? ( NOT NULL )? ( RESULT )? ( DEFAULT )? ( )?
*
*
* @param tokens the tokens being processed (cannot be null
or empty)
* @param procedureNode the create procedure node owning this parameter (cannot be null
)
*/
void parseProcedureParameter( final DdlTokenStream tokens,
final AstNode procedureNode ) {
String paramType = TeiidReservedWord.IN.toDdl();
if (tokens.matches(TeiidReservedWord.IN.toDdl()) || tokens.matches(TeiidReservedWord.OUT.toDdl())
|| tokens.matches(TeiidReservedWord.INOUT.toDdl()) || tokens.matches(TeiidNonReservedWord.VARIADIC.toDdl())) {
paramType = tokens.consume();
}
final String id = parseIdentifier(tokens);
final AstNode parameterNode = getNodeFactory().node(id, procedureNode, TeiidDdlLexicon.CreateProcedure.PARAMETER);
parameterNode.setProperty(TeiidDdlLexicon.CreateProcedure.PARAMETER_TYPE, paramType);
// parse data type
final DataType dataType = getDataTypeParser().parse(tokens);
getDataTypeParser().setPropertiesOnNode(parameterNode, dataType);
// parse any optional clauses
boolean foundNotNull = false;
boolean foundResult = false;
boolean foundDefault = false;
boolean foundOptions = false;
boolean keepParsing = true;
while (keepParsing && (!foundNotNull || !foundResult || !foundDefault || foundOptions)) {
if (tokens.canConsume(NOT_NULL)) {
foundNotNull = true;
} else if (tokens.canConsume(TeiidNonReservedWord.RESULT.toDdl())) {
foundResult = true;
} else if (parseDefaultClause(tokens, parameterNode)) {
foundDefault = true;
} else if (parseOptionsClause(tokens, parameterNode)) {
foundOptions = true;
} else {
keepParsing = false;
}
}
parameterNode.setProperty(StandardDdlLexicon.NULLABLE, (foundNotNull ? "NOT NULL" : "NULL"));
parameterNode.setProperty(TeiidDdlLexicon.CreateProcedure.PARAMETER_RESULT_FLAG, foundResult);
}
/**
*
* ( ( )* )?
*
*
* @param tokens the tokens being processed (cannot be null
or empty)
* @param procedureNode the create procedure node owning these parameters (cannot be null
)
*/
void parseProcedureParameters( final DdlTokenStream tokens,
final AstNode procedureNode ) {
if (tokens.canConsume(L_PAREN)) {
// parse parameters if any exist
if (!tokens.matches(R_PAREN)) {
parseProcedureParameter(tokens, procedureNode);
while (tokens.canConsume(COMMA)) {
parseProcedureParameter(tokens, procedureNode);
}
}
// must have ending paren
if (!tokens.canConsume(R_PAREN)) {
throw new TeiidDdlParsingException(tokens, "Unparsable procedure parameters (right paren not found)");
}
} else {
throw new TeiidDdlParsingException(tokens, "Unparsable procedure parameters (left paren not found)");
}
}
/**
*
*
*
* ( NOT NULL )? ( )?
*
*
* @param tokens the tokens being processed (cannot be null
or empty)
* @param resultSetNode the result set node owning this result column (cannot be null
)
*/
void parseProcedureResultColumn( final DdlTokenStream tokens,
final AstNode resultSetNode ) {
final String id = parseIdentifier(tokens);
final DataType dataType = getDataTypeParser().parse(tokens);
final boolean notNull = tokens.canConsume(NOT_NULL);
final AstNode resultColumnNode = getNodeFactory().node(id, resultSetNode, TeiidDdlLexicon.CreateProcedure.RESULT_COLUMN);
resultColumnNode.setProperty(StandardDdlLexicon.NULLABLE, (notNull ? "NOT NULL" : "NULL"));
getDataTypeParser().setPropertiesOnNode(resultColumnNode, dataType);
// may have an options clause
parseOptionsClause(tokens, resultColumnNode);
}
/**
*
* ( TABLE )? ( )* )
*
*
* @param tokens the tokens being processed (cannot be null
or empty)
* @param procedureNode the create procedure node owning these result columns (cannot be null
)
* @return the node of the tabular result set or null if tabular result set was not found
* @throws ParsingException if there is a problem parsing the procedure result columns
*/
AstNode parseProcedureResultColumns( final DdlTokenStream tokens,
final AstNode procedureNode ) throws ParsingException {
if (tokens.matches(TABLE, L_PAREN) || tokens.matches(L_PAREN)) {
boolean table = tokens.canConsume(TABLE);
if (tokens.canConsume(L_PAREN)) {
// create result columns node
final AstNode resultSetNode = getNodeFactory().node(TeiidDdlLexicon.CreateProcedure.RESULT_SET,
procedureNode,
TeiidDdlLexicon.CreateProcedure.RESULT_COLUMNS);
resultSetNode.setProperty(TeiidDdlLexicon.CreateProcedure.TABLE_FLAG, table);
parseProcedureResultColumn(tokens, resultSetNode); // must have at least one
while (tokens.canConsume(COMMA)) {
parseProcedureResultColumn(tokens, resultSetNode);
}
// must have ending paren
if (!tokens.canConsume(R_PAREN)) {
throw new TeiidDdlParsingException(tokens, "Unparsable procedure result columns (right paren not found)");
}
return resultSetNode;
}
throw new TeiidDdlParsingException(tokens, "Unparsable procedure result columns (left paren not found)");
}
return null; // no tabular result set found
}
/**
* The RETURNS clause is optional for the create procedure statement.
*
*
* ( RETURNS ( <options clause> )? ( ( ( TABLE )? <lparen> <procedure result column> ( <comma> <procedure result column> )* <rparen> ) | <data type> ) )?
*
*
* @param tokens the tokens being processed (cannot be null
)
* @param procedureNode the procedure node of of the returns clause (cannot be null
)
* @return true
if the returns clause was successfully parsed
*/
boolean parseReturnsClause( final DdlTokenStream tokens,
final AstNode procedureNode ) {
if (tokens.canConsume(TeiidReservedWord.RETURNS.toDdl())) {
// may have options associated with the result set
final Map options = new HashMap<>();
final boolean optionsExist = parseOptionsClause(tokens, options);
// result set must have either one or more result columns or be a data type
AstNode resultNode = parseProcedureResultColumns(tokens, procedureNode);
// if null must be a data type result set
if (resultNode == null) {
final DataType dataType = getDataTypeParser().parse(tokens);
// create data type result set node
resultNode = getNodeFactory().node(TeiidDdlLexicon.CreateProcedure.RESULT_SET,
procedureNode,
TeiidDdlLexicon.CreateProcedure.RESULT_DATA_TYPE);
resultNode.setProperty(DATATYPE_NAME, dataType.getName());
if (dataType.getLength() != DataType.DEFAULT_LENGTH) {
resultNode.setProperty(DATATYPE_LENGTH, dataType.getLength());
}
if (dataType.getPrecision() != DataType.DEFAULT_PRECISION) {
resultNode.setProperty(DATATYPE_PRECISION, dataType.getPrecision());
}
if (dataType.getScale() != DataType.DEFAULT_SCALE) {
resultNode.setProperty(DATATYPE_SCALE, dataType.getScale());
}
if (dataType.getArrayDimensions() != DataType.DEFAULT_ARRAY_DIMENSIONS) {
resultNode.setProperty(DATATYPE_ARRAY_DIMENSIONS, dataType.getArrayDimensions());
}
}
assert (resultNode != null);
// now that we have a result set node we can add in the options as children
if (optionsExist) {
createOptionNodes(options, resultNode);
}
return true;
}
return false;
}
private String parseStatement( final DdlTokenStream tokens,
int numBegins,
final String statement,
Position prevPosition,
String prevValue ) throws ParsingException {
final StringBuilder text = new StringBuilder(statement);
while (tokens.hasNext()) {
final Position currPosition = tokens.nextPosition();
final String value = tokens.consume();
if (TeiidReservedWord.BEGIN.toDdl().equals(value)) {
text.append(getWhitespace(currPosition, prevPosition, prevValue));
text.append(TeiidReservedWord.BEGIN.toDdl());
return parseStatement(tokens, ++numBegins, text.toString(), currPosition, value);
}
if (TeiidReservedWord.END.toDdl().equals(value)) {
text.append(getWhitespace(currPosition, prevPosition, prevValue));
text.append(TeiidReservedWord.END.toDdl());
return parseStatement(tokens, --numBegins, text.toString(), currPosition, value);
}
if (SEMICOLON.equals(value)) {
if (numBegins > 0) {
text.append(getWhitespace(currPosition, prevPosition, prevValue));
text.append(SEMICOLON);
return parseStatement(tokens, numBegins, text.toString(), currPosition, value);
}
text.append(SEMICOLON);
break;
}
text.append(getWhitespace(currPosition, prevPosition, prevValue));
text.append(value);
prevValue = value;
prevPosition = currPosition;
}
return text.toString();
}
@Override
protected void postProcess( AstNode rootNode ) {
}
}