001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.sequencer.ddl.dialect.oracle;
017
018import static org.modeshape.sequencer.ddl.StandardDdlLexicon.CREATE_VIEW_QUERY_EXPRESSION;
019import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DROP_BEHAVIOR;
020import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DROP_OPTION;
021import static org.modeshape.sequencer.ddl.StandardDdlLexicon.NEW_NAME;
022import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_ADD_COLUMN_DEFINITION;
023import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_ALTER_TABLE_STATEMENT;
024import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_ALTER_COLUMN_DEFINITION;
025import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_COLUMN_DEFINITION;
026import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_COLUMN_REFERENCE;
027import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_CREATE_VIEW_STATEMENT;
028import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_DROP_COLUMN_DEFINITION;
029import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_DROP_TABLE_CONSTRAINT_DEFINITION;
030import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_DROP_TABLE_STATEMENT;
031import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_GRANT_STATEMENT;
032import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_STATEMENT_OPTION;
033import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_TABLE_REFERENCE;
034import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_UNKNOWN_STATEMENT;
035import static org.modeshape.sequencer.ddl.StandardDdlLexicon.VALUE;
036import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.AUTHID_VALUE;
037import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.BITMAP_INDEX;
038import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.IN_OUT_NO_COPY;
039import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_CLUSTER_STATEMENT;
040import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_DATABASE_STATEMENT;
041import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_DIMENSION_STATEMENT;
042import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_DISKGROUP_STATEMENT;
043import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_FUNCTION_STATEMENT;
044import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_INDEXTYPE_STATEMENT;
045import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_INDEX_STATEMENT;
046import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_JAVA_STATEMENT;
047import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_MATERIALIZED_STATEMENT;
048import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_OPERATOR_STATEMENT;
049import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_OUTLINE_STATEMENT;
050import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_PACKAGE_STATEMENT;
051import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_PROCEDURE_STATEMENT;
052import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_PROFILE_STATEMENT;
053import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_RESOURCE_STATEMENT;
054import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_ROLE_STATEMENT;
055import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_ROLLBACK_STATEMENT;
056import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_SEQUENCE_STATEMENT;
057import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_SESSION_STATEMENT;
058import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_SYSTEM_STATEMENT;
059import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_TABLESPACE_STATEMENT;
060import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_TRIGGER_STATEMENT;
061import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_TYPE_STATEMENT;
062import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_USER_STATEMENT;
063import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ALTER_VIEW_STATEMENT;
064import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ANALYZE_STATEMENT;
065import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ASSOCIATE_STATISTICS_STATEMENT;
066import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_AUDIT_STATEMENT;
067import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_BACKSLASH_TERMINATOR;
068import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_COMMENT_ON_STATEMENT;
069import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_COMMIT_STATEMENT;
070import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_BITMAP_JOIN_INDEX_STATEMENT;
071import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_CLUSTER_INDEX_STATEMENT;
072import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_CLUSTER_STATEMENT;
073import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_CONTEXT_STATEMENT;
074import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_CONTROLFILE_STATEMENT;
075import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_DATABASE_STATEMENT;
076import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_DIMENSION_STATEMENT;
077import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_DIRECTORY_STATEMENT;
078import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_DISKGROUP_STATEMENT;
079import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_FUNCTION_STATEMENT;
080import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_INDEXTYPE_STATEMENT;
081import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_JAVA_STATEMENT;
082import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_LIBRARY_STATEMENT;
083import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_MATERIALIZED_VIEW_LOG_STATEMENT;
084import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_MATERIALIZED_VIEW_STATEMENT;
085import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_OPERATOR_STATEMENT;
086import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_OUTLINE_STATEMENT;
087import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_PACKAGE_STATEMENT;
088import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_PFILE_STATEMENT;
089import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_PROCEDURE_STATEMENT;
090import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_PROFILE_STATEMENT;
091import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_ROLE_STATEMENT;
092import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_ROLLBACK_STATEMENT;
093import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_SEQUENCE_STATEMENT;
094import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_SPFILE_STATEMENT;
095import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_SYNONYM_STATEMENT;
096import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_TABLESPACE_STATEMENT;
097import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_TABLE_INDEX_STATEMENT;
098import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_TRIGGER_STATEMENT;
099import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_TYPE_STATEMENT;
100import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_CREATE_USER_STATEMENT;
101import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DISASSOCIATE_STATISTICS_STATEMENT;
102import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_CLUSTER_STATEMENT;
103import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_CONTEXT_STATEMENT;
104import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_DATABASE_STATEMENT;
105import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_DIMENSION_STATEMENT;
106import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_DIRECTORY_STATEMENT;
107import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_DISKGROUP_STATEMENT;
108import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_FUNCTION_STATEMENT;
109import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_INDEXTYPE_STATEMENT;
110import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_INDEX_STATEMENT;
111import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_JAVA_STATEMENT;
112import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_LIBRARY_STATEMENT;
113import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_MATERIALIZED_STATEMENT;
114import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_OPERATOR_STATEMENT;
115import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_OUTLINE_STATEMENT;
116import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_PACKAGE_STATEMENT;
117import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_PROCEDURE_STATEMENT;
118import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_PROFILE_STATEMENT;
119import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_ROLE_STATEMENT;
120import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_ROLLBACK_STATEMENT;
121import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_SEQUENCE_STATEMENT;
122import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_SYNONYM_STATEMENT;
123import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_TABLESPACE_STATEMENT;
124import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_TRIGGER_STATEMENT;
125import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_TYPE_STATEMENT;
126import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_DROP_USER_STATEMENT;
127import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_EXPLAIN_PLAN_STATEMENT;
128import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_FLASHBACK_STATEMENT;
129import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_FUNCTION_PARAMETER;
130import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_LOCK_TABLE_STATEMENT;
131import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_MERGE_STATEMENT;
132import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_NOAUDIT_STATEMENT;
133import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_PURGE_STATEMENT;
134import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_RENAME_COLUMN;
135import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_RENAME_CONSTRAINT;
136import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_RENAME_STATEMENT;
137import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_REVOKE_STATEMENT;
138import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_ROLLBACK_STATEMENT;
139import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_SAVEPOINT_STATEMENT;
140import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_SET_CONSTRAINTS_STATEMENT;
141import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_SET_CONSTRAINT_STATEMENT;
142import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_SET_ROLE_STATEMENT;
143import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_SET_TRANSACTION_STATEMENT;
144import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.TYPE_TRUNCATE_STATEMENT;
145import static org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlLexicon.UNIQUE_INDEX;
146import java.util.ArrayList;
147import java.util.List;
148import java.util.Locale;
149import org.modeshape.common.text.ParsingException;
150import org.modeshape.common.text.Position;
151import org.modeshape.common.text.TokenStream;
152import org.modeshape.common.util.CheckArg;
153import org.modeshape.sequencer.ddl.DdlParserProblem;
154import org.modeshape.sequencer.ddl.DdlSequencerI18n;
155import org.modeshape.sequencer.ddl.DdlTokenStream;
156import org.modeshape.sequencer.ddl.DdlTokenStream.DdlTokenizer;
157import org.modeshape.sequencer.ddl.StandardDdlLexicon;
158import org.modeshape.sequencer.ddl.StandardDdlParser;
159import org.modeshape.sequencer.ddl.datatype.DataType;
160import org.modeshape.sequencer.ddl.datatype.DataTypeParser;
161import org.modeshape.sequencer.ddl.node.AstNode;
162
163/**
164 * Oracle-specific DDL Parser. Includes custom data types as well as custom DDL statements.
165 */
166public class OracleDdlParser extends StandardDdlParser
167    implements OracleDdlConstants, OracleDdlConstants.OracleStatementStartPhrases {
168
169    /**
170     * The Oracle parser identifier.
171     */
172    public static final String ID = "ORACLE";
173
174    static List<String[]> oracleDataTypeStrings = new ArrayList<String[]>();
175
176    public OracleDdlParser() {
177        super();
178
179        setDatatypeParser(new OracleDataTypeParser());
180        initialize();
181    }
182
183    /**
184     * {@inheritDoc}
185     * 
186     * @see org.modeshape.sequencer.ddl.StandardDdlParser#areNextTokensCreateTableOptions(org.modeshape.sequencer.ddl.DdlTokenStream)
187     */
188    @Override
189    protected boolean areNextTokensCreateTableOptions( final DdlTokenStream tokens ) throws ParsingException {
190        // current token can't be a terminator or the next n-tokens can't be a statement
191        return (tokens.hasNext() && !isTerminator(tokens) && (tokens.computeNextStatementStartKeywordCount() == 0));
192    }
193
194    /**
195     * {@inheritDoc}
196     * 
197     * @see org.modeshape.sequencer.ddl.StandardDdlParser#parseNextCreateTableOption(org.modeshape.sequencer.ddl.DdlTokenStream,
198     *      org.modeshape.sequencer.ddl.node.AstNode)
199     */
200    @Override
201    protected void parseNextCreateTableOption( final DdlTokenStream tokens,
202                                               final AstNode tableNode ) throws ParsingException {
203        final String tableProperty = tokens.consume();
204        boolean processed = false;
205
206        // if token is a number add it to previous option
207        if (tableProperty.matches("\\b\\d+\\b")) {
208            final List<AstNode> options = tableNode.getChildren(TYPE_STATEMENT_OPTION);
209
210            if (!options.isEmpty()) {
211                final AstNode option = options.get(options.size() - 1);
212                final String currValue = (String)option.getProperty(VALUE);
213                option.setProperty(VALUE, currValue + SPACE + tableProperty);
214                processed = true;
215            }
216        }
217
218        if (!processed) {
219            final AstNode tableOption = nodeFactory().node("option", tableNode, TYPE_STATEMENT_OPTION);
220            tableOption.setProperty(VALUE, tableProperty);
221        }
222    }
223
224    private void initialize() {
225        setTerminator(DEFAULT_TERMINATOR);
226
227        setDoUseTerminator(true);
228
229        oracleDataTypeStrings.addAll(OracleDataTypes.CUSTOM_DATATYPE_START_PHRASES); //
230    }
231
232    /**
233     * {@inheritDoc}
234     * 
235     * @see org.modeshape.sequencer.ddl.StandardDdlParser#getId()
236     */
237    @Override
238    public String getId() {
239        return ID;
240    }
241
242    /**
243     * {@inheritDoc}
244     * 
245     * @see org.modeshape.sequencer.ddl.StandardDdlParser#getIdentifyingKeywords()
246     */
247    @Override
248    public String[] getIdentifyingKeywords() {
249        return new String[] {getId(), "spool.log"};
250    }
251
252    /**
253     * {@inheritDoc}
254     * 
255     * @see org.modeshape.sequencer.ddl.StandardDdlParser#initializeTokenStream(org.modeshape.sequencer.ddl.DdlTokenStream)
256     */
257    @Override
258    protected void initializeTokenStream( DdlTokenStream tokens ) {
259        super.initializeTokenStream(tokens);
260        tokens.registerKeyWords(CUSTOM_KEYWORDS);
261        tokens.registerKeyWords(OracleDataTypes.CUSTOM_DATATYPE_START_WORDS);
262        tokens.registerStatementStartPhrase(ALTER_PHRASES);
263        tokens.registerStatementStartPhrase(CREATE_PHRASES);
264        tokens.registerStatementStartPhrase(DROP_PHRASES);
265        tokens.registerStatementStartPhrase(MISC_PHRASES);
266        tokens.registerStatementStartPhrase(SET_PHRASES);
267    }
268
269    @Override
270    protected void rewrite( DdlTokenStream tokens,
271                            AstNode rootNode ) {
272        assert tokens != null;
273        assert rootNode != null;
274
275        // We may have a prepare statement that is followed by a missing terminator node
276        // We also may have nodes that have an extra terminator node representing the '/' backslash
277        // These nodes will have type "TYPE_BACKSLASH_TERMINATOR".
278
279        List<AstNode> copyOfNodes = new ArrayList<AstNode>(rootNode.getChildren());
280
281        AstNode complexNode = null;
282
283        for (AstNode child : copyOfNodes) {
284
285            if ((complexNode != null && nodeFactory().hasMixinType(child, TYPE_UNKNOWN_STATEMENT))
286                || (complexNode != null && nodeFactory().hasMixinType(child, TYPE_BACKSLASH_TERMINATOR))) {
287                mergeNodes(tokens, complexNode, child);
288                rootNode.removeChild(child);
289            } else {
290                complexNode = null;
291            }
292
293            if (nodeFactory().hasMixinType(child, TYPE_CREATE_FUNCTION_STATEMENT)
294                || nodeFactory().hasMixinType(child, TYPE_CREATE_TRIGGER_STATEMENT)
295                || nodeFactory().hasMixinType(child, TYPE_CREATE_LIBRARY_STATEMENT)
296                || nodeFactory().hasMixinType(child, TYPE_CREATE_PACKAGE_STATEMENT)
297                || nodeFactory().hasMixinType(child, TYPE_CREATE_PROCEDURE_STATEMENT)) {
298                complexNode = child;
299            }
300        }
301
302        // We also may have nodes that have an extra terminator node representing the '/' backslash
303        // These nodes will have type "TYPE_BACKSLASH_TERMINATOR".
304
305        super.rewrite(tokens, rootNode); // Removes all extra "missing terminator" nodes
306
307        // Now we need to walk the tree again looking for unknown nodes under the root
308        // and attach them to the previous node, assuming the node can contain multiple nested statements.
309        // CREATE FUNCTION is one of those types
310
311        copyOfNodes = new ArrayList<AstNode>(rootNode.getChildren());
312        boolean foundComplexNode = false;
313        complexNode = null;
314        for (AstNode child : copyOfNodes) {
315            if (matchesComplexNode(child)) {
316                foundComplexNode = true;
317                complexNode = child;
318            } else if (foundComplexNode) {
319                if (complexNode != null && nodeFactory().hasMixinType(child, TYPE_UNKNOWN_STATEMENT)) {
320                    mergeNodes(tokens, complexNode, child);
321                    rootNode.removeChild(child);
322                } else {
323                    foundComplexNode = false;
324                    complexNode = null;
325                }
326            }
327        }
328    }
329
330    /**
331     * {@inheritDoc}
332     * 
333     * @see org.modeshape.sequencer.ddl.StandardDdlParser#consumeIdentifier(org.modeshape.sequencer.ddl.DdlTokenStream)
334     */
335    @Override
336    protected String consumeIdentifier( DdlTokenStream tokens ) throws ParsingException {
337        Position startPosition = tokens.nextPosition();
338        String id = super.consumeIdentifier(tokens);
339        if (!tokens.hasNext()) return id;
340        Position nextPosition = tokens.nextPosition();
341
342        while (((nextPosition.getIndexInContent() - startPosition.getIndexInContent() - id.length()) == 0)) {
343            // allowed symbols in an identifier: underscore, dollar sign, pound sign, at sign
344            if (tokens.matches(DdlTokenizer.SYMBOL)) {
345                if (tokens.matches('$') || tokens.matches('#') || tokens.matches('@')) {
346                    id += tokens.consume(); // consume symbol
347                } else {
348                    break; // not a valid ID symbol
349                }
350            } else {
351                id += tokens.consume(); // consume text
352            }
353
354            nextPosition = tokens.nextPosition();
355        }
356
357        return id;
358    }
359
360    private boolean matchesComplexNode( AstNode node ) {
361        assert node != null;
362
363        for (String mixin : COMPLEX_STMT_TYPES) {
364            if (nodeFactory().hasMixinType(node, mixin)) {
365                return true;
366            }
367        }
368
369        return false;
370    }
371
372    /**
373     * {@inheritDoc}
374     * 
375     * @see org.modeshape.sequencer.ddl.StandardDdlParser#handleUnknownToken(org.modeshape.sequencer.ddl.DdlTokenStream,
376     *      java.lang.String)
377     */
378    @Override
379    public AstNode handleUnknownToken( DdlTokenStream tokens,
380                                       String tokenValue ) throws ParsingException {
381        if (tokenValue.equals("/")) {
382            return nodeFactory().node("backslashTerminator", getRootNode(), TYPE_BACKSLASH_TERMINATOR);
383        }
384
385        return null;
386    }
387
388    /**
389     * {@inheritDoc} The CREATE SCHEMA statement can include CREATE TABLE, CREATE VIEW, and GRANT statements.
390     * 
391     * @see org.modeshape.sequencer.ddl.StandardDdlParser#parseCreateSchemaStatement(org.modeshape.sequencer.ddl.DdlTokenStream,
392     *      org.modeshape.sequencer.ddl.node.AstNode)
393     */
394    @Override
395    protected AstNode parseCreateSchemaStatement( DdlTokenStream tokens,
396                                                  AstNode parentNode ) throws ParsingException {
397        assert tokens != null;
398        assert parentNode != null;
399
400        return super.parseCreateSchemaStatement(tokens, parentNode);
401    }
402
403    /**
404     * {@inheritDoc}
405     * 
406     * @see org.modeshape.sequencer.ddl.StandardDdlParser#parseCustomStatement(org.modeshape.sequencer.ddl.DdlTokenStream,
407     *      org.modeshape.sequencer.ddl.node.AstNode)
408     */
409    @Override
410    protected AstNode parseCustomStatement( DdlTokenStream tokens,
411                                            AstNode parentNode ) throws ParsingException {
412        assert tokens != null;
413        assert parentNode != null;
414
415        AstNode result = null;
416
417        if (tokens.matches(STMT_COMMENT_ON)) {
418            result = parseCommentStatement(tokens, parentNode);
419        } else if (tokens.matches(STMT_ANALYZE)) {
420            return parseStatement(tokens, STMT_ANALYZE, parentNode, TYPE_ANALYZE_STATEMENT);
421        } else if (tokens.matches(STMT_ASSOCIATE_STATISTICS)) {
422            return parseStatement(tokens, STMT_ASSOCIATE_STATISTICS, parentNode, TYPE_ASSOCIATE_STATISTICS_STATEMENT);
423        } else if (tokens.matches(STMT_AUDIT)) {
424            return parseStatement(tokens, STMT_AUDIT, parentNode, TYPE_AUDIT_STATEMENT);
425        } else if (tokens.matches(STMT_COMMIT_FORCE) || tokens.matches(STMT_COMMIT_WORK) || tokens.matches(STMT_COMMIT_WRITE)) {
426            return parseStatement(tokens, STMT_COMMIT, parentNode, TYPE_COMMIT_STATEMENT);
427        } else if (tokens.matches(STMT_DISASSOCIATE_STATISTICS)) {
428            return parseStatement(tokens, STMT_DISASSOCIATE_STATISTICS, parentNode, TYPE_DISASSOCIATE_STATISTICS_STATEMENT);
429        } else if (tokens.matches(STMT_EXPLAIN_PLAN)) {
430            return parseStatement(tokens, STMT_EXPLAIN_PLAN, parentNode, TYPE_EXPLAIN_PLAN_STATEMENT);
431        } else if (tokens.matches(STMT_FLASHBACK)) {
432            return parseStatement(tokens, STMT_FLASHBACK, parentNode, TYPE_FLASHBACK_STATEMENT);
433        } else if (tokens.matches(STMT_LOCK_TABLE)) {
434            return parseStatement(tokens, STMT_LOCK_TABLE, parentNode, TYPE_LOCK_TABLE_STATEMENT);
435        } else if (tokens.matches(STMT_MERGE)) {
436            return parseStatement(tokens, STMT_MERGE, parentNode, TYPE_MERGE_STATEMENT);
437        } else if (tokens.matches(STMT_NOAUDIT)) {
438            return parseStatement(tokens, STMT_NOAUDIT, parentNode, TYPE_NOAUDIT_STATEMENT);
439        } else if (tokens.matches(STMT_PURGE)) {
440            return parseStatement(tokens, STMT_PURGE, parentNode, TYPE_PURGE_STATEMENT);
441        } else if (tokens.matches(STMT_RENAME)) {
442            return parseStatement(tokens, STMT_RENAME, parentNode, TYPE_RENAME_STATEMENT);
443        } else if (tokens.matches(STMT_ROLLBACK)) {
444            return parseStatement(tokens, STMT_ROLLBACK, parentNode, TYPE_ROLLBACK_STATEMENT);
445        } else if (tokens.matches(STMT_ROLLBACK_WORK)) {
446            return parseStatement(tokens, STMT_ROLLBACK_WORK, parentNode, TYPE_ROLLBACK_STATEMENT);
447        } else if (tokens.matches(STMT_ROLLBACK_TO_SAVEPOINT)) {
448            return parseStatement(tokens, STMT_ROLLBACK_TO_SAVEPOINT, parentNode, TYPE_ROLLBACK_STATEMENT);
449        } else if (tokens.matches(STMT_SAVEPOINT)) {
450            return parseStatement(tokens, STMT_SAVEPOINT, parentNode, TYPE_SAVEPOINT_STATEMENT);
451        } else if (tokens.matches(STMT_TRUNCATE)) {
452            return parseStatement(tokens, STMT_TRUNCATE, parentNode, TYPE_TRUNCATE_STATEMENT);
453        }
454
455        if (result == null) {
456            result = super.parseCustomStatement(tokens, parentNode);
457        }
458        return result;
459    }
460
461    @Override
462    protected AstNode parseCreateStatement( DdlTokenStream tokens,
463                                            AstNode parentNode ) throws ParsingException {
464        assert tokens != null;
465        assert parentNode != null;
466
467        if (tokens.matches(STMT_CREATE_INDEX) || tokens.matches(STMT_CREATE_UNIQUE_INDEX)
468            || tokens.matches(STMT_CREATE_BITMAP_INDEX)) {
469            return parseCreateIndex(tokens, parentNode);
470        } else if (tokens.matches(STMT_CREATE_CLUSTER)) {
471            return parseCreateClusterStatement(tokens, parentNode);
472        } else if (tokens.matches(STMT_CREATE_CONTEXT)) {
473            return parseStatement(tokens, STMT_CREATE_CONTEXT, parentNode, TYPE_CREATE_CONTEXT_STATEMENT);
474        } else if (tokens.matches(STMT_CREATE_CONTROLFILE)) {
475            return parseStatement(tokens, STMT_CREATE_CONTROLFILE, parentNode, TYPE_CREATE_CONTROLFILE_STATEMENT);
476        } else if (tokens.matches(STMT_CREATE_DATABASE)) {
477            return parseStatement(tokens, STMT_CREATE_DATABASE, parentNode, TYPE_CREATE_DATABASE_STATEMENT);
478        } else if (tokens.matches(STMT_CREATE_PUBLIC_DATABASE)) {
479            return parseStatement(tokens, STMT_CREATE_PUBLIC_DATABASE, parentNode, TYPE_CREATE_DATABASE_STATEMENT);
480        } else if (tokens.matches(STMT_CREATE_DIMENSION)) {
481            return parseCreateDimensionStatement(tokens, parentNode);
482        } else if (tokens.matches(STMT_CREATE_DIRECTORY)) {
483            return parseStatement(tokens, STMT_CREATE_DIRECTORY, parentNode, TYPE_CREATE_DIRECTORY_STATEMENT);
484        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_DIRECTORY)) {
485            return parseStatement(tokens, STMT_CREATE_OR_REPLACE_DIRECTORY, parentNode, TYPE_CREATE_DIRECTORY_STATEMENT);
486        } else if (tokens.matches(STMT_CREATE_DISKGROUP)) {
487            return parseStatement(tokens, STMT_CREATE_DISKGROUP, parentNode, TYPE_CREATE_DISKGROUP_STATEMENT);
488        } else if (tokens.matches(STMT_CREATE_FUNCTION)) {
489            // ============ > PARSE UNTIL '/' for STMT_CREATE_FUNCTION
490            return parseCreateFunctionStatement(tokens, parentNode);
491        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_FUNCTION)) {
492            // ============ > PARSE UNTIL '/' for STMT_CREATE_OR_REPLACE_FUNCTION
493            return parseCreateFunctionStatement(tokens, parentNode);
494        } else if (tokens.matches(STMT_CREATE_INDEXTYPE)) {
495            return parseStatement(tokens, STMT_CREATE_INDEXTYPE, parentNode, TYPE_CREATE_INDEXTYPE_STATEMENT);
496        } else if (tokens.matches(STMT_CREATE_JAVA)) {
497            return parseCreateJavaStatement(tokens, parentNode);
498        } else if (tokens.matches(STMT_CREATE_LIBRARY)) {
499            // ============ > PARSE UNTIL '/' for STMT_CREATE_LIBRARY
500            return parseStatement(tokens, STMT_CREATE_LIBRARY, parentNode, TYPE_CREATE_LIBRARY_STATEMENT);
501        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_LIBRARY)) {
502            // ============ > PARSE UNTIL '/' STMT_CREATE_OR_REPLACE_LIBRARY
503            return parseStatement(tokens, STMT_CREATE_OR_REPLACE_LIBRARY, parentNode, TYPE_CREATE_LIBRARY_STATEMENT);
504        } else if (tokens.matches(STMT_CREATE_MATERIALIZED_VIEW)) {
505            return parseMaterializedViewStatement(tokens, parentNode);
506        } else if (tokens.matches(STMT_CREATE_OPERATOR)) {
507            return parseStatement(tokens, STMT_CREATE_OPERATOR, parentNode, TYPE_CREATE_OPERATOR_STATEMENT);
508        } else if (tokens.matches(STMT_CREATE_OUTLINE)) {
509            return parseStatement(tokens, STMT_CREATE_OUTLINE, parentNode, TYPE_CREATE_OUTLINE_STATEMENT);
510        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_OUTLINE)) {
511            return parseStatement(tokens, STMT_CREATE_OR_REPLACE_OUTLINE, parentNode, TYPE_CREATE_OUTLINE_STATEMENT);
512        } else if (tokens.matches(STMT_CREATE_PACKAGE)) {
513            // ============ > PARSE UNTIL '/' for STMT_CREATE_PACKAGE
514            return parseStatement(tokens, STMT_CREATE_PACKAGE, parentNode, TYPE_CREATE_PACKAGE_STATEMENT);
515        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_PACKAGE)) {
516            // ============ > PARSE UNTIL '/' for STMT_CREATE_OR_REPLACE_PACKAGE
517            return parseStatement(tokens, STMT_CREATE_OR_REPLACE_PACKAGE, parentNode, TYPE_CREATE_PACKAGE_STATEMENT);
518        } else if (tokens.matches(STMT_CREATE_PFILE)) {
519            return parseStatement(tokens, STMT_CREATE_PFILE, parentNode, TYPE_CREATE_PFILE_STATEMENT);
520        } else if (tokens.matches(STMT_CREATE_PROCEDURE)) {
521            // ============ > PARSE UNTIL '/' for STMT_CREATE_PROCEDURE
522            return parseCreateProcedureStatement(tokens, parentNode);
523        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_PROCEDURE)) {
524            // ============ > PARSE UNTIL '/' for STMT_CREATE_PROCEDURE
525            return parseCreateProcedureStatement(tokens, parentNode);
526        } else if (tokens.matches(STMT_CREATE_PROFILE)) {
527            return parseStatement(tokens, STMT_CREATE_PROFILE, parentNode, TYPE_CREATE_PROFILE_STATEMENT);
528        } else if (tokens.matches(STMT_CREATE_ROLE)) {
529            return parseStatement(tokens, STMT_CREATE_ROLE, parentNode, TYPE_CREATE_ROLE_STATEMENT);
530        } else if (tokens.matches(STMT_CREATE_ROLLBACK)) {
531            return parseStatement(tokens, STMT_CREATE_ROLLBACK, parentNode, TYPE_CREATE_ROLLBACK_STATEMENT);
532        } else if (tokens.matches(STMT_CREATE_PUBLIC_ROLLBACK)) {
533            return parseStatement(tokens, STMT_CREATE_PUBLIC_ROLLBACK, parentNode, TYPE_CREATE_ROLLBACK_STATEMENT);
534        } else if (tokens.matches(STMT_CREATE_SEQUENCE)) {
535            return parseStatement(tokens, STMT_CREATE_SEQUENCE, parentNode, TYPE_CREATE_SEQUENCE_STATEMENT);
536        } else if (tokens.matches(STMT_CREATE_SPFILE)) {
537            return parseStatement(tokens, STMT_CREATE_SPFILE, parentNode, TYPE_CREATE_SPFILE_STATEMENT);
538        } else if (tokens.matches(STMT_CREATE_SYNONYM)) {
539            return parseStatement(tokens, STMT_CREATE_SYNONYM, parentNode, TYPE_CREATE_SYNONYM_STATEMENT);
540        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_SYNONYM)) {
541            return parseStatement(tokens, STMT_CREATE_OR_REPLACE_SYNONYM, parentNode, TYPE_CREATE_SYNONYM_STATEMENT);
542        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_PUBLIC_SYNONYM)) {
543            return parseStatement(tokens, STMT_CREATE_OR_REPLACE_PUBLIC_SYNONYM, parentNode, TYPE_CREATE_SYNONYM_STATEMENT);
544        } else if (tokens.matches(STMT_CREATE_PUBLIC_SYNONYM)) {
545            return parseStatement(tokens, STMT_CREATE_PUBLIC_SYNONYM, parentNode, TYPE_CREATE_SYNONYM_STATEMENT);
546        } else if (tokens.matches(STMT_CREATE_TABLESPACE)) {
547            return parseStatement(tokens, STMT_CREATE_TABLESPACE, parentNode, TYPE_CREATE_TABLESPACE_STATEMENT);
548        } else if (tokens.matches(STMT_CREATE_TRIGGER)) {
549            // ============ > PARSE UNTIL '/' for STMT_CREATE_OR_REPLACE_TRIGGER
550            return parseSlashedStatement(tokens, STMT_CREATE_TRIGGER, parentNode, TYPE_CREATE_TRIGGER_STATEMENT);
551        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_TRIGGER)) {
552            // ============ > PARSE UNTIL '/' for STMT_CREATE_OR_REPLACE_TRIGGER
553            return parseSlashedStatement(tokens, STMT_CREATE_OR_REPLACE_TRIGGER, parentNode, TYPE_CREATE_TRIGGER_STATEMENT);
554        } else if (tokens.matches(STMT_CREATE_TYPE)) {
555            return parseStatement(tokens, STMT_CREATE_TYPE, parentNode, TYPE_CREATE_TYPE_STATEMENT);
556        } else if (tokens.matches(STMT_CREATE_OR_REPLACE_TYPE)) {
557            return parseStatement(tokens, STMT_CREATE_OR_REPLACE_TYPE, parentNode, TYPE_CREATE_TYPE_STATEMENT);
558        } else if (tokens.matches(STMT_CREATE_USER)) {
559            return parseStatement(tokens, STMT_CREATE_USER, parentNode, TYPE_CREATE_USER_STATEMENT);
560        }
561
562        return super.parseCreateStatement(tokens, parentNode);
563    }
564
565    private AstNode parseCreateClusterStatement( DdlTokenStream tokens,
566                                                 AstNode parentNode ) throws ParsingException {
567        markStartOfStatement(tokens);
568        tokens.consume(STMT_CREATE_CLUSTER);
569        String name = parseName(tokens);
570
571        AstNode node = nodeFactory().node(name, parentNode, TYPE_CREATE_CLUSTER_STATEMENT);
572
573        parseUntilTerminator(tokens);
574
575        markEndOfStatement(tokens, node);
576
577        return node;
578    }
579
580    private AstNode parseCreateDimensionStatement( DdlTokenStream tokens,
581                                                   AstNode parentNode ) throws ParsingException {
582        markStartOfStatement(tokens);
583        tokens.consume(STMT_CREATE_DIMENSION);
584        String name = parseName(tokens);
585
586        AstNode node = nodeFactory().node(name, parentNode, TYPE_CREATE_DIMENSION_STATEMENT);
587
588        parseUntilTerminator(tokens);
589
590        markEndOfStatement(tokens, node);
591
592        return node;
593    }
594
595    /**
596     * Parses DDL CREATE FUNCTION statement
597     * 
598     * @param tokens the tokenized {@link DdlTokenStream} of the DDL input content; may not be null
599     * @param parentNode the parent {@link AstNode} node; may not be null
600     * @return the parsed CREATE FUNCTION statement node
601     * @throws ParsingException
602     */
603    protected AstNode parseCreateFunctionStatement( DdlTokenStream tokens,
604                                                    AstNode parentNode ) throws ParsingException {
605        assert tokens != null;
606        assert parentNode != null;
607
608        markStartOfStatement(tokens);
609
610        /* ----------------------------------------------------------------------
611            CREATE [ OR REPLACE ] FUNCTION [ schema. ] function_name
612              [ ( parameter_declaration [, parameter_declaration] ) 
613              ]
614              RETURN datatype
615              [ { invoker_rights_clause
616                | DETERMINISTIC
617                | parallel_enable_clause
618                | result_cache_clause
619                }...
620              ]
621              { { AGGREGATE | PIPELINED }
622                USING [ schema. ] implementation_type
623              | [ PIPELINED ] { IS | AS } { [ declare_section ] body | call_spec }
624              } ;
625            
626            parameter_declaration = parameter_name [ IN | { { OUT | { IN OUT }} [ NOCOPY ] } ] datatype [ { := | DEFAULT } expression ]
627        ---------------------------------------------------------------------- */
628
629        boolean isReplace = tokens.canConsume(STMT_CREATE_OR_REPLACE_FUNCTION);
630
631        tokens.canConsume(STMT_CREATE_FUNCTION);
632
633        String name = parseName(tokens);
634
635        AstNode node = nodeFactory().node(name, parentNode, TYPE_CREATE_FUNCTION_STATEMENT);
636
637        if (isReplace) {
638            // TODO: SET isReplace = TRUE to node (possibly a cnd mixin of "replaceable"
639        }
640
641        boolean ok = parseParameters(tokens, node);
642
643        if (ok) {
644            if (tokens.canConsume("RETURN")) {
645                DataType dType = getDatatypeParser().parse(tokens);
646                if (dType != null) {
647                    getDatatypeParser().setPropertiesOnNode(node, dType);
648                }
649            }
650        }
651
652        parseUntilFwdSlash(tokens, false);
653
654        tokens.canConsume("/");
655
656        markEndOfStatement(tokens, node);
657
658        return node;
659    }
660
661    /**
662     * Parses DDL CREATE PROCEDURE statement
663     * 
664     * @param tokens the tokenized {@link DdlTokenStream} of the DDL input content; may not be null
665     * @param parentNode the parent {@link AstNode} node; may not be null
666     * @return the parsed CREATE PROCEDURE statement node
667     * @throws ParsingException
668     */
669    protected AstNode parseCreateProcedureStatement( DdlTokenStream tokens,
670                                                     AstNode parentNode ) throws ParsingException {
671        assert tokens != null;
672        assert parentNode != null;
673
674        markStartOfStatement(tokens);
675        /* ----------------------------------------------------------------------
676            CREATE [ OR REPLACE ] PROCEDURE [ schema. ] procedure_name
677                [ ( parameter_declaration [, parameter_declaration ] ) ]
678                [ AUTHID { CURRENT_USER | DEFINER  ]
679                { IS | AS }
680                { [ declare_section ] body | call_spec | EXTERNAL} ;
681            
682             call_spec = LANGUAGE { Java_declaration | C_declaration }
683            
684             Java_declaration = JAVA NAME string
685            
686             C_declaration = 
687                C [ NAME name ]
688                    LIBRARY lib_name
689                    [ AGENT IN (argument[, argument ]...) ]
690                    [ WITH CONTEXT ]
691                    [ PARAMETERS (parameter[, parameter ]...) ]
692                    
693            parameter_declaration = parameter_name [ IN | { { OUT | { IN OUT }} [ NOCOPY ] } ] datatype [ { := | DEFAULT } expression ]
694        ---------------------------------------------------------------------- */
695
696        boolean isReplace = tokens.canConsume(STMT_CREATE_OR_REPLACE_PROCEDURE);
697
698        tokens.canConsume(STMT_CREATE_PROCEDURE);
699
700        String name = parseName(tokens);
701
702        AstNode node = nodeFactory().node(name, parentNode, TYPE_CREATE_PROCEDURE_STATEMENT);
703
704        if (isReplace) {
705            // TODO: SET isReplace = TRUE to node (possibly a cnd mixin of "replaceable"
706        }
707
708        boolean ok = parseParameters(tokens, node);
709
710        if (ok) {
711            if (tokens.canConsume("AUTHID")) {
712                if (tokens.canConsume("CURRENT_USER")) {
713                    node.setProperty(AUTHID_VALUE, "AUTHID CURRENT_USER");
714                } else {
715                    tokens.consume("DEFINER");
716                    node.setProperty(AUTHID_VALUE, "DEFINER");
717                }
718            }
719        }
720
721        parseUntilFwdSlash(tokens, false);
722
723        tokens.canConsume("/");
724
725        markEndOfStatement(tokens, node);
726
727        return node;
728    }
729
730    private boolean parseParameters( DdlTokenStream tokens,
731                                     AstNode procedureNode ) throws ParsingException {
732        assert tokens != null;
733        assert procedureNode != null;
734
735        // parameter_declaration = parameter_name [ IN | { { OUT | { IN OUT }} [ NOCOPY ] } ] datatype [ { := | DEFAULT }
736        // expression ]
737        // Assume we start with open parenthesis '(', then we parse comma separated list of function parameters
738        // which have the form: [ parameter-Name ] DataType
739        // So, try getting datatype, if datatype == NULL, then parseName() & parse datatype, then repeat as long as next token is
740        // ","
741
742        tokens.consume(L_PAREN); // EXPECTED
743
744        while (!tokens.canConsume(R_PAREN)) {
745
746            String paramName = parseName(tokens);
747            String inOutStr = null;
748            if (tokens.matches("IN")) {
749                if (tokens.canConsume("IN", "OUT")) {
750                    if (tokens.canConsume("NOCOPY")) {
751                        inOutStr = "IN OUT NOCOPY";
752                    } else {
753                        inOutStr = "IN OUT";
754                    }
755                } else {
756                    tokens.consume("IN");
757                    inOutStr = "IN";
758                }
759
760            } else if (tokens.matches("OUT")) {
761                if (tokens.canConsume("OUT", "NOCOPY")) {
762                    inOutStr = "OUT NOCOPY";
763                } else {
764                    tokens.consume("OUT");
765                    inOutStr = "OUT";
766                }
767            }
768
769            DataType datatype = getDatatypeParser().parse(tokens);
770            AstNode paramNode = nodeFactory().node(paramName, procedureNode, TYPE_FUNCTION_PARAMETER);
771            if (datatype != null) {
772                getDatatypeParser().setPropertiesOnNode(paramNode, datatype);
773            }
774
775            if (tokens.matchesAnyOf(":=", "DEFAULT") || !tokens.matchesAnyOf(COMMA, R_PAREN)) {
776                String msg = DdlSequencerI18n.unsupportedProcedureParameterDeclaration.text(procedureNode.getName());
777                DdlParserProblem problem = new DdlParserProblem(Problems.WARNING, getCurrentMarkedPosition(), msg);
778                addProblem(problem, procedureNode);
779                return false;
780            }
781
782            if (inOutStr != null) {
783                paramNode.setProperty(IN_OUT_NO_COPY, inOutStr);
784            }
785
786            tokens.canConsume(COMMA);
787        }
788
789        return true;
790    }
791
792    /**
793     * Parses DDL CREATE MATERIALIZED VIEW statement This could either be a standard view or a VIEW LOG ON statement.
794     * 
795     * @param tokens the tokenized {@link DdlTokenStream} of the DDL input content; may not be null
796     * @param parentNode the parent {@link AstNode} node; may not be null
797     * @return the parsed CREATE MATERIALIZED VIEW statement node
798     * @throws ParsingException
799     */
800    protected AstNode parseMaterializedViewStatement( DdlTokenStream tokens,
801                                                      AstNode parentNode ) throws ParsingException {
802        assert tokens != null;
803        assert parentNode != null;
804
805        markStartOfStatement(tokens);
806
807        /* ----------------------------------------------------------------------
808            CREATE MATERIALIZED VIEW
809              [ schema. ]materialized_view
810              [ column_alias [, column_alias]... ]
811              [ OF [ schema. ]object_type ] .................... (MORE...)
812
813            EXAMPLES:
814            
815                CREATE MATERIALIZED VIEW LOG ON products
816                   WITH ROWID, SEQUENCE (prod_id)
817                   INCLUDING NEW VALUES;
818                
819                CREATE MATERIALIZED VIEW sales_mv
820                   BUILD IMMEDIATE
821                   REFRESH FAST ON COMMIT
822                   AS SELECT t.calendar_year, p.prod_id, 
823                      SUM(s.amount_sold) AS sum_sales
824                      FROM times t, products p, sales s
825                      WHERE t.time_id = s.time_id AND p.prod_id = s.prod_id
826                      GROUP BY t.calendar_year, p.prod_id;
827        ---------------------------------------------------------------------- */
828
829        boolean isLog = tokens.canConsume(STMT_CREATE_MATERIALIZED_VEIW_LOG);
830
831        tokens.canConsume(STMT_CREATE_MATERIALIZED_VIEW);
832
833        String name = parseName(tokens);
834
835        AstNode node = null;
836
837        if (isLog) {
838            node = nodeFactory().node(name, parentNode, TYPE_CREATE_MATERIALIZED_VIEW_LOG_STATEMENT);
839        } else {
840            node = nodeFactory().node(name, parentNode, TYPE_CREATE_MATERIALIZED_VIEW_STATEMENT);
841        }
842
843        parseUntilTerminator(tokens);
844
845        markEndOfStatement(tokens, node);
846
847        return node;
848    }
849
850    @Override
851    protected AstNode parseGrantStatement( DdlTokenStream tokens,
852                                           AstNode parentNode ) throws ParsingException {
853        CheckArg.isNotNull(tokens, "tokens");
854        CheckArg.isNotNull(parentNode, "parentNode");
855
856        // GRANT { grant_system_privileges | grant_object_privileges } ;
857        //
858        // ** grant_system_privileges **
859        //
860        // { system_privilege | role | ALL PRIVILEGES } [, { system_privilege | role | ALL PRIVILEGES } ]...
861        // TO grantee_clause [ WITH ADMIN OPTION ]
862        //
863        // ** grant_object_privileges **
864        //
865        // { object_privilege | ALL [ PRIVILEGES ] } [ (column [, column ]...) ] [, { object_privilege | ALL [ PRIVILEGES ] } [
866        // (column [, column ]...) ] ]...
867        // on_object_clause
868        // TO grantee_clause [ WITH HIERARCHY OPTION ] [ WITH GRANT OPTION ]
869
870        // ** on_object_clause **
871        //
872        // { [ schema. ] object | { DIRECTORY directory_name | JAVA { SOURCE | RESOURCE } [ schema. ] object } }
873        //
874        // ** grantee_clause **
875        //
876        // { user [ IDENTIFIED BY password ] | role | PUBLIC } [, { user [ IDENTIFIED BY password ] | role | PUBLIC } ]...
877
878        AstNode node = null;
879
880        // Original implementation does NOT parse Insert statement, but just returns a generic TypedStatement
881        markStartOfStatement(tokens);
882
883        tokens.consume(GRANT);
884        String name = GRANT;
885
886        tokens.consume(); // First Privilege token
887
888        node = nodeFactory().node(name, parentNode, TYPE_GRANT_STATEMENT);
889
890        while (tokens.hasNext()
891               && !isTerminator(tokens)
892               && (!tokens.matches(DdlTokenizer.STATEMENT_KEY) || (tokens.matches(DdlTokenizer.STATEMENT_KEY) && tokens.matches("GRANT",
893                                                                                                                                "OPTION")))) {
894            tokens.consume();
895        }
896
897        markEndOfStatement(tokens, node);
898
899        return node;
900
901        // if( tokens.matches(GRANT, DdlTokenStream.ANY_VALUE, "TO")) {
902        // markStartOfStatement(tokens);
903        // tokens.consume(GRANT);
904        // String privilege = tokens.consume();
905        // tokens.consume("TO");
906        // tokens.consume(); // TO Value
907        // && problem != null
908        // String value = parseUntilTerminator(tokens);
909        //
910        // AstNode grantNode = nodeFactory().node("GRANT", parentNode, TYPE_GRANT_STATEMENT);
911        // markEndOfStatement(tokens, grantNode);
912        //
913        // return grantNode;
914        // } else if( tokens.matches(GRANT, DdlTokenStream.ANY_VALUE, COMMA)) {
915        // markStartOfStatement(tokens);
916        // tokens.consume(GRANT);
917        // String privilege = tokens.consume();
918        // // Assume Multiple Privileges
919        // while( tokens.canConsume(COMMA)) {
920        // tokens.consume(); // Next privilege
921        // }
922        //
923        // tokens.consume("ON");
924        // tokens.consume(); // TO Value
925        //
926        // tokens.canConsume("WITH", GRANT);
927        //
928        // String value = parseUntilTerminator(tokens);
929        //
930        // AstNode grantNode = nodeFactory().node("GRANT", parentNode, TYPE_GRANT_STATEMENT);
931        // markEndOfStatement(tokens, grantNode);
932        //
933        // return grantNode;
934        // } else if( tokens.matches(GRANT, DdlTokenStream.ANY_VALUE, "ON", DdlTokenStream.ANY_VALUE, "TO",
935        // DdlTokenStream.ANY_VALUE, "WITH", GRANT)) {
936        // markStartOfStatement(tokens);
937        // //GRANT ALL ON bonuses TO hr WITH GRANT OPTION;
938        //
939        // tokens.consume(GRANT);
940        // String privilege = tokens.consume();
941        // tokens.consume("ON");
942        // tokens.consume(); // ON Value
943        // tokens.consume("TO");
944        // tokens.consume();
945        // tokens.consume("WITH", GRANT);
946        // String value = parseUntilTerminator(tokens);
947        //
948        // AstNode grantNode = nodeFactory().node("GRANT", parentNode, TYPE_GRANT_STATEMENT);
949        // markEndOfStatement(tokens, grantNode);
950        //
951        // return grantNode;
952        // }
953        // else if( tokens.matches(GRANT, DdlTokenStream.ANY_VALUE, "ON")) {
954        // tokens.consume(GRANT);
955        // String privilege = tokens.consume();
956        // tokens.consume("ON");
957        // tokens.consume(); // ON Value
958        //
959        // String value = parseUntilTerminator(tokens);
960        // stmt.appendSource(true, value);&& problem != null
961        // stmt.setType("GRANT" + SPACE + privilege + SPACE + "ON");
962        // consumeTerminator(tokens);
963        // } else if( tokens.matches(GRANT, CREATE) ||
964        // tokens.matches(GRANT, ALTER) ||
965        // tokens.matches(GRANT, DROP) ||
966        // tokens.matches(GRANT, "EXECUTE") ||
967        // tokens.matches(GRANT, "MANAGE") ||
968        // tokens.matches(GRANT, "QUERY") ||
969        // tokens.matches(GRANT, "ON", "COMMIT") ||
970        // tokens.matches(GRANT, "ANY") ||
971        // tokens.matches(GRANT, "SELECT") ||
972        // tokens.matches(GRANT, "RESTRICTED") ||
973        // //
974        // ========================================================================================================================
975        // ===
976        // //
977        // ========================================================================================================================
978        // ===
979        // tokens.matches(GRANT, "FLASHBACK") ||
980        // tokens.matches(GRANT, "GLOBAL") ||
981        // tokens.matches(GRANT, "DEBUG") ||
982        // tokens.matches(GRANT, "GLOBAL") ||
983        // tokens.matches(GRANT, "ADVISOR") ||
984        // tokens.matches(GRANT, "ADMINISTER") ||
985        // tokens.matches(GRANT, "BACKUP") ||
986        // tokens.matches(GRANT, "LOCK") ||
987        // tokens.matches(GRANT, "UPDATE") ||
988        // tokens.matches(GRANT, "DELETE") ||
989        // tokens.matches(GRANT, "INSERT") ||
990        // tokens.matches(GRANT, "UNLIMITED") ||
991        // tokens.matches(GRANT, "UNDER") ||
992        // tokens.matches(GRANT, "ANALYZE") ||
993        // tokens.matches(GRANT, "AUDIT") ||
994        // tokens.matches(GRANT, "COMMENT") ||
995        // tokens.matches(GRANT, "EXEMPT") ||
996        // tokens.matches(GRANT, "FORCE") ||
997        // tokens.matches(GRANT, "RESUMABLE") ||
998        // tokens.matches(GRANT, "SYSDBA") ||
999        // tokens.matches(GRANT, "REFERENCES") ||
1000        // tokens.matches(GRANT, "SYSOPER") ||
1001        // tokens.matches(GRANT, "WRITE") ) {
1002        // tokens.consume(GRANT);
1003        //
1004        // String nextTok = tokens.consume() + SPACE + tokens.consume() + SPACE + tokens.consume();
1005        //
1006        // String value = parseUntilTerminator(tokens);
1007        // stmt.setType("GRANT" + SPACE + nextTok);
1008        // consumeTerminator(tokens);
1009        // }
1010        //
1011        //
1012        // return grantNode;
1013
1014        // return super.parseGrantStatement(tokens, parentNode);
1015    }
1016
1017    /**
1018     * {@inheritDoc}
1019     * 
1020     * @see org.modeshape.sequencer.ddl.StandardDdlParser#parseRevokeStatement(org.modeshape.sequencer.ddl.DdlTokenStream,
1021     *      org.modeshape.sequencer.ddl.node.AstNode)
1022     */
1023    @Override
1024    protected AstNode parseRevokeStatement( DdlTokenStream tokens,
1025                                            AstNode parentNode ) throws ParsingException {
1026        return parseStatement(tokens, STMT_REVOKE, parentNode, TYPE_REVOKE_STATEMENT);
1027    }
1028
1029    @Override
1030    protected AstNode parseAlterTableStatement( DdlTokenStream tokens,
1031                                                AstNode parentNode ) throws ParsingException {
1032        assert tokens != null;
1033        assert parentNode != null;
1034
1035        markStartOfStatement(tokens);
1036
1037        tokens.consume("ALTER", "TABLE");
1038        String tableName = parseName(tokens);
1039
1040        // System.out.println(" >> PARSING ALTER STATEMENT >> TABLE Name = " + tableName);
1041
1042        AstNode alterTableNode = nodeFactory().node(tableName, parentNode, TYPE_ALTER_TABLE_STATEMENT);
1043
1044        // Standalone alter commands
1045        if (tokens.canConsume("RENAME")) {
1046            if (tokens.matches("COLUMN", TokenStream.ANY_VALUE, "TO", TokenStream.ANY_VALUE)) {
1047                // Rename of column
1048                tokens.consume("COLUMN");
1049                String oldColumnName = parseName(tokens);
1050                tokens.consume("TO");
1051                String newColumnName = parseName(tokens);
1052
1053                AstNode renameColumn = nodeFactory().node(oldColumnName, alterTableNode, TYPE_RENAME_COLUMN);
1054                renameColumn.setProperty(NEW_NAME, newColumnName);
1055            } else if (tokens.matches("CONSTRAINT", TokenStream.ANY_VALUE, "TO", TokenStream.ANY_VALUE)) {
1056                // Rename of constraint
1057                tokens.consume("CONSTRAINT");
1058                String oldConstraintName = parseName(tokens);
1059                tokens.consume("TO");
1060                String newConstraintName = parseName(tokens);
1061
1062                AstNode renameColumn = nodeFactory().node(oldConstraintName, alterTableNode, TYPE_RENAME_CONSTRAINT);
1063                renameColumn.setProperty(NEW_NAME, newConstraintName);
1064            } else if (tokens.canConsume("TO")) {
1065                // Rename of table
1066                alterTableNode.setProperty(NEW_NAME, parseName(tokens));
1067            } else {
1068                // TODO add problem
1069            }
1070
1071            parseUntilTerminator(tokens);
1072        } else if (tokens.canConsume("DROP")) {
1073
1074            if (tokens.matches(L_PAREN)) {
1075                // Columns enclosed in "()"
1076                parseColumnNameList(tokens, alterTableNode, TYPE_DROP_COLUMN_DEFINITION);
1077            } else if (tokens.canConsume("COLUMN")) {
1078                // Single DROP COLUMN
1079                String columnName = parseName(tokens);
1080
1081                AstNode columnNode = nodeFactory().node(columnName, alterTableNode, TYPE_DROP_COLUMN_DEFINITION);
1082
1083                if (tokens.canConsume(DropBehavior.CASCADE)) {
1084                    columnNode.setProperty(DROP_BEHAVIOR, DropBehavior.CASCADE);
1085                } else if (tokens.canConsume(DropBehavior.RESTRICT)) {
1086                    columnNode.setProperty(DROP_BEHAVIOR, DropBehavior.RESTRICT);
1087                }
1088            } else if (tokens.canConsume("CONSTRAINT")) {
1089                String constraintName = parseName(tokens);
1090
1091                AstNode constraintNode = nodeFactory().node(constraintName, alterTableNode, TYPE_DROP_TABLE_CONSTRAINT_DEFINITION);
1092
1093                if (tokens.canConsume(DropBehavior.CASCADE)) {
1094                    constraintNode.setProperty(DROP_BEHAVIOR, DropBehavior.CASCADE);
1095                } else if (tokens.canConsume(DropBehavior.RESTRICT)) {
1096                    constraintNode.setProperty(DROP_BEHAVIOR, DropBehavior.RESTRICT);
1097                }
1098            }
1099
1100            parseUntilTerminator(tokens);
1101        } else {
1102            // Loop over tokens and find known parts
1103            while (tokens.hasNext() && !tokens.matches(DdlTokenizer.STATEMENT_KEY)
1104                   && ((doUseTerminator() && !isTerminator(tokens)) || !doUseTerminator())) {
1105                // skip to next known part in statement
1106                while (tokens.hasNext() && !tokens.matchesAnyOf(new String[] {"ADD", "MODIFY"})
1107                       && ((doUseTerminator() && !isTerminator(tokens)) || !doUseTerminator())) {
1108                    tokens.consume();
1109                }
1110
1111                if (!tokens.hasNext()) break;
1112
1113                if (tokens.canConsume("ADD")) {
1114
1115                    if (isTableConstraint(tokens)) {
1116                        parseTableConstraint(tokens, alterTableNode, true);
1117                    } else {
1118                        // This segment can also be enclosed in "()" brackets to handle multiple ColumnDefinition ADDs
1119                        if (tokens.matches(L_PAREN, "REF")) {
1120                            // ALTER TABLE staff ADD (REF(dept) WITH ROWID);
1121                            tokens.consume(L_PAREN, "REF", L_PAREN);
1122                            parseName(tokens);
1123                            tokens.consume(R_PAREN, "WITH", "ROWID", R_PAREN);
1124                        } else if (tokens.matches(L_PAREN, "SCOPE")) {
1125                            // ALTER TABLE staff ADD (SCOPE FOR (dept) IS offices);
1126                            tokens.consume(L_PAREN, "SCOPE", "FOR", L_PAREN);
1127                            parseName(tokens);
1128                            tokens.consume(R_PAREN, "IS");
1129                            parseName(tokens);
1130                            tokens.consume(R_PAREN);
1131                        } else if (tokens.matches(L_PAREN)) {
1132                            // Columns enclosed in "()"
1133                            parseColumns(tokens, alterTableNode, TYPE_ADD_COLUMN_DEFINITION);
1134                        } else {
1135                            // Assume single ADD COLUMN
1136                            parseColumnDefinition(tokens, alterTableNode, TYPE_ADD_COLUMN_DEFINITION);
1137                        }
1138                    }
1139
1140                } else if (tokens.canConsume("MODIFY")) {
1141
1142                    if (tokens.matches(L_PAREN)) {
1143                        // Columns enclosed in "()"
1144                        parseColumns(tokens, alterTableNode, TYPE_ALTER_COLUMN_DEFINITION);
1145                    } else {
1146                        // Only support column definition changes if not followed by a reserved word, i.e.:
1147                        // - COLUMN - column substitution
1148                        // - LOB - lob properties modification
1149                        // - NESTED [TABLE] - nested table
1150                        // - DEFAULT - table default attributes
1151                        // - [SUB]PARTITION - [sub]partition attributes
1152                        if (tokens.matchesAnyOf("COLUMN", "LOB", "NESTED", "DEFAULT", "PARTITION", "SUBPARTITION")) continue;
1153
1154                        // Assume single MODIFY
1155                        parseColumnDefinition(tokens, alterTableNode, TYPE_ALTER_COLUMN_DEFINITION);
1156                    }
1157
1158                }
1159            }
1160        }
1161
1162        markEndOfStatement(tokens, alterTableNode);
1163
1164        return alterTableNode;
1165
1166    }
1167
1168    @Override
1169    protected AstNode parseAlterStatement( DdlTokenStream tokens,
1170                                           AstNode parentNode ) throws ParsingException {
1171        assert tokens != null;
1172        assert parentNode != null;
1173
1174        if (tokens.matches(ALTER, TABLE)) {
1175            return parseAlterTableStatement(tokens, parentNode);
1176        } else if (tokens.matches(STMT_ALTER_CLUSTER)) {
1177            return parseStatement(tokens, STMT_ALTER_CLUSTER, parentNode, TYPE_ALTER_CLUSTER_STATEMENT);
1178        } else if (tokens.matches(STMT_ALTER_DATABASE)) {
1179            // could encounter: ALTER DATABASE RENAME FILE 'diskc:log3.log' TO 'diskb:log3.log';
1180            // So need to parse up past the RENAME check
1181            markStartOfStatement(tokens);
1182            tokens.consume(STMT_ALTER_DATABASE);
1183            AstNode result = nodeFactory().node("database", parentNode, TYPE_ALTER_DATABASE_STATEMENT);
1184            tokens.canConsume("RENAME");
1185            parseUntilTerminator(tokens);
1186            markEndOfStatement(tokens, result);
1187            return result;
1188        } else if (tokens.matches(STMT_ALTER_DIMENSION)) {
1189            return parseStatement(tokens, STMT_ALTER_DIMENSION, parentNode, TYPE_ALTER_DIMENSION_STATEMENT);
1190        } else if (tokens.matches(STMT_ALTER_DISKGROUP)) {
1191            return parseStatement(tokens, STMT_ALTER_DISKGROUP, parentNode, TYPE_ALTER_DISKGROUP_STATEMENT);
1192        } else if (tokens.matches(STMT_ALTER_FUNCTION)) {
1193            return parseStatement(tokens, STMT_ALTER_FUNCTION, parentNode, TYPE_ALTER_FUNCTION_STATEMENT);
1194        } else if (tokens.matches(STMT_ALTER_INDEX)) {
1195            // could encounter: ALTER INDEX upper_ix RENAME TO xxxxxx
1196            // So need to parse up past the RENAME check
1197            markStartOfStatement(tokens);
1198            tokens.consume(ALTER, INDEX);
1199            String indexName = parseName(tokens);
1200            AstNode result = nodeFactory().node(indexName, parentNode, TYPE_ALTER_INDEX_STATEMENT);
1201            tokens.canConsume("RENAME");
1202            parseUntilTerminator(tokens);
1203            markEndOfStatement(tokens, result);
1204            return result;
1205        } else if (tokens.matches(STMT_ALTER_INDEXTYPE)) {
1206            return parseStatement(tokens, STMT_ALTER_INDEXTYPE, parentNode, TYPE_ALTER_INDEXTYPE_STATEMENT);
1207        } else if (tokens.matches(STMT_ALTER_JAVA)) {
1208            return parseStatement(tokens, STMT_ALTER_JAVA, parentNode, TYPE_ALTER_JAVA_STATEMENT);
1209        } else if (tokens.matches(STMT_ALTER_MATERIALIZED)) {
1210            return parseStatement(tokens, STMT_ALTER_MATERIALIZED, parentNode, TYPE_ALTER_MATERIALIZED_STATEMENT);
1211        } else if (tokens.matches(STMT_ALTER_OPERATOR)) {
1212            return parseStatement(tokens, STMT_ALTER_OPERATOR, parentNode, TYPE_ALTER_OPERATOR_STATEMENT);
1213        } else if (tokens.matches(STMT_ALTER_OUTLINE)) {
1214            return parseStatement(tokens, STMT_ALTER_OUTLINE, parentNode, TYPE_ALTER_OUTLINE_STATEMENT);
1215        } else if (tokens.matches(STMT_ALTER_PACKAGE)) {
1216            return parseStatement(tokens, STMT_ALTER_PACKAGE, parentNode, TYPE_ALTER_PACKAGE_STATEMENT);
1217        } else if (tokens.matches(STMT_ALTER_PROCEDURE)) {
1218            return parseStatement(tokens, STMT_ALTER_PROCEDURE, parentNode, TYPE_ALTER_PROCEDURE_STATEMENT);
1219        } else if (tokens.matches(STMT_ALTER_PROFILE)) {
1220            return parseStatement(tokens, STMT_ALTER_PROFILE, parentNode, TYPE_ALTER_PROFILE_STATEMENT);
1221        } else if (tokens.matches(STMT_ALTER_RESOURCE)) {
1222            return parseStatement(tokens, STMT_ALTER_RESOURCE, parentNode, TYPE_ALTER_RESOURCE_STATEMENT);
1223        } else if (tokens.matches(STMT_ALTER_ROLE)) {
1224            return parseStatement(tokens, STMT_ALTER_ROLE, parentNode, TYPE_ALTER_ROLE_STATEMENT);
1225        } else if (tokens.matches(STMT_ALTER_ROLLBACK)) {
1226            return parseStatement(tokens, STMT_ALTER_ROLLBACK, parentNode, TYPE_ALTER_ROLLBACK_STATEMENT);
1227        } else if (tokens.matches(STMT_ALTER_SEQUENCE)) {
1228            return parseStatement(tokens, STMT_ALTER_SEQUENCE, parentNode, TYPE_ALTER_SEQUENCE_STATEMENT);
1229        } else if (tokens.matches(STMT_ALTER_SESSION)) {
1230            return parseStatement(tokens, STMT_ALTER_SESSION, parentNode, TYPE_ALTER_SESSION_STATEMENT);
1231        } else if (tokens.matches(STMT_ALTER_SYSTEM)) {
1232            return parseStatement(tokens, STMT_ALTER_SYSTEM, parentNode, TYPE_ALTER_SYSTEM_STATEMENT);
1233        } else if (tokens.matches(STMT_ALTER_TABLESPACE)) {
1234            return parseStatement(tokens, STMT_ALTER_TABLESPACE, parentNode, TYPE_ALTER_TABLESPACE_STATEMENT);
1235        } else if (tokens.matches(STMT_ALTER_TRIGGER)) {
1236            return parseStatement(tokens, STMT_ALTER_TRIGGER, parentNode, TYPE_ALTER_TRIGGER_STATEMENT);
1237        } else if (tokens.matches(STMT_ALTER_TYPE)) {
1238            return parseStatement(tokens, STMT_ALTER_TYPE, parentNode, TYPE_ALTER_TYPE_STATEMENT);
1239        } else if (tokens.matches(STMT_ALTER_USER)) {
1240            // could encounter: ALTER USER app_user1 GRANT .....
1241            // So need to parse up past the GRANT check
1242            markStartOfStatement(tokens);
1243            tokens.consume(STMT_ALTER_USER);
1244            String name = parseName(tokens);
1245            AstNode result = nodeFactory().node(name, parentNode, TYPE_ALTER_USER_STATEMENT);
1246            tokens.canConsume("GRANT");
1247            parseUntilTerminator(tokens);
1248            markEndOfStatement(tokens, result);
1249            return result;
1250        } else if (tokens.matches(STMT_ALTER_VIEW)) {
1251            return parseStatement(tokens, STMT_ALTER_VIEW, parentNode, TYPE_ALTER_VIEW_STATEMENT);
1252        }
1253
1254        return super.parseAlterStatement(tokens, parentNode);
1255    }
1256
1257    /**
1258     * {@inheritDoc}
1259     * 
1260     * @see org.modeshape.sequencer.ddl.StandardDdlParser#parseCreateViewStatement(org.modeshape.sequencer.ddl.DdlTokenStream,
1261     *      org.modeshape.sequencer.ddl.node.AstNode)
1262     */
1263    @Override
1264    protected AstNode parseCreateViewStatement( DdlTokenStream tokens,
1265                                                AstNode parentNode ) throws ParsingException {
1266        assert tokens != null;
1267        assert parentNode != null;
1268
1269        markStartOfStatement(tokens);
1270        // CREATE [OR REPLACE]
1271        // [[NO] FORCE] VIEW [schema.] view
1272        // [ ( { alias [ inline_constraint... ]
1273        // | out_of_line_constraint
1274        // }
1275        // [, { alias [ inline_constraint...]
1276        // | out_of_line_constraint
1277        // }
1278        // ]
1279        // )
1280        // | object_view_clause
1281        // | XMLType_view_clause
1282        // ]
1283        // AS subquery [ subquery_restriction_clause ] ;
1284
1285        // NOTE: the query expression along with the CHECK OPTION clause require no SQL statement terminator.
1286        // So the CHECK OPTION clause will NOT
1287
1288        String stmtType = "CREATE";
1289        tokens.consume("CREATE");
1290        if (tokens.canConsume("OR", "REPLACE")) {
1291            stmtType = stmtType + SPACE + "OR REPLACE";
1292        } else if (tokens.canConsume("NO", "FORCE")) {
1293            stmtType = stmtType + SPACE + "NO FORCE";
1294        } else if (tokens.canConsume("FORCE")) {
1295            stmtType = stmtType + SPACE + "FORCE";
1296        }
1297
1298        tokens.consume("VIEW");
1299        stmtType = stmtType + SPACE + "VIEW";
1300
1301        String name = parseName(tokens);
1302
1303        AstNode createViewNode = nodeFactory().node(name, parentNode, TYPE_CREATE_VIEW_STATEMENT);
1304
1305        // CONSUME COLUMNS
1306        parseColumnNameList(tokens, createViewNode, TYPE_COLUMN_REFERENCE);
1307
1308        // (object_view_clause)
1309        //
1310        // OF [ schema. ] type_name
1311        // { WITH OBJECT IDENTIFIER
1312        // { DEFAULT | ( attribute [, attribute ]... ) }
1313        // | UNDER [ schema. ] superview
1314        // }
1315        // ( { out_of_line_constraint
1316        // | attribute { inline_constraint }...
1317        // } [, { out_of_line_constraint
1318        // | attribute { inline_constraint }...
1319        // }
1320        // ]...
1321        // )
1322
1323        // (XMLType_view_clause)
1324        //
1325        // OF XMLTYPE [ XMLSchema_spec ]
1326        // WITH OBJECT IDENTIFIER
1327        // { DEFAULT | ( expr [, expr ]...) }
1328
1329        // Basically, if next token matches "OF", then parse until token matches "AS"
1330
1331        if (tokens.matches("OF")) {
1332            do {
1333                tokens.consume();
1334            } while (!tokens.matches("AS"));
1335        }
1336
1337        tokens.consume("AS");
1338
1339        String queryExpression = parseUntilTerminator(tokens);
1340
1341        createViewNode.setProperty(CREATE_VIEW_QUERY_EXPRESSION, queryExpression);
1342
1343        markEndOfStatement(tokens, createViewNode);
1344
1345        return createViewNode;
1346    }
1347
1348    /**
1349     * The tokens must start with a left paren, end with a right paren, and have content between. Any parens in the content must
1350     * have matching parens.
1351     * 
1352     * @param tokens the tokens being processed (cannot be <code>null</code> but or empty)
1353     * @return the content (never <code>null</code> or empty.
1354     * @throws ParsingException if there is a problem parsing the query expression
1355     */
1356    private String parseContentBetweenParens( final DdlTokenStream tokens ) throws ParsingException {
1357        tokens.consume(L_PAREN); // don't include first paren in expression
1358
1359        int numLeft = 1;
1360        int numRight = 0;
1361
1362        final StringBuilder text = new StringBuilder();
1363
1364        while (tokens.hasNext()) {
1365            if (tokens.matches(L_PAREN)) {
1366                ++numLeft;
1367            } else if (tokens.matches(R_PAREN)) {
1368                if (numLeft == ++numRight) {
1369                    tokens.consume(R_PAREN); // don't include last paren in expression
1370                    break;
1371                }
1372            }
1373
1374            final String token = tokens.consume();
1375
1376            // don't add space if empty or if this token or previous token is a period
1377            if (!PERIOD.equals(token) && (text.length() != 0) && (PERIOD.charAt(0) != (text.charAt(text.length() - 1)))) {
1378                text.append(SPACE);
1379            }
1380
1381            text.append(token);
1382        }
1383
1384        if ((numLeft != numRight) || (text.length() == 0)) {
1385            throw new ParsingException(tokens.nextPosition());
1386        }
1387
1388        return text.toString();
1389    }
1390
1391    /**
1392     * If the index type is a bitmap-join the columns are from the dimension tables which are defined in the FROM clause. All
1393     * other index types the columns are from the table the index in on.
1394     * <p>
1395     * <code>
1396     * column-expression == left-paren column-name [ASC | DESC] | constant | function [, column-name [ASC | DESC] | constant | function ]* right-paren
1397     * </code>
1398     * 
1399     * @param columnExpressionList the comma separated column expression list (cannot be <code>null</code>)
1400     * @param indexNode the index node whose column expression list is being processed (cannot be <code>null</code>)
1401     */
1402    private void parseIndexColumnExpressionList( final String columnExpressionList,
1403                                                 final AstNode indexNode ) {
1404        final DdlTokenStream tokens = new DdlTokenStream(columnExpressionList, DdlTokenStream.ddlTokenizer(false), false);
1405        tokens.start();
1406
1407        tokens.consume(L_PAREN); // must have opening paren
1408        int numLeft = 1;
1409        int numRight = 0;
1410
1411        // must have content between the parens
1412        if (!tokens.matches(R_PAREN)) {
1413            final List<String> possibleColumns = new ArrayList<String>(); // dimension table columns
1414            final List<String> functions = new ArrayList<String>(); // functions, constants
1415            final StringBuilder text = new StringBuilder();
1416            boolean isFunction = false;
1417
1418            while (tokens.hasNext()) {
1419                if (tokens.canConsume(COMMA)) {
1420                    if (isFunction) {
1421                        functions.add(text.toString());
1422                    } else {
1423                        possibleColumns.add(text.toString());
1424                    }
1425
1426                    text.setLength(0); // clear out
1427                    isFunction = false;
1428                    continue;
1429                }
1430
1431                if (tokens.matches(L_PAREN)) {
1432                    isFunction = true;
1433                    ++numLeft;
1434                } else if (tokens.matches("ASC") || tokens.matches("DESC")) {
1435                    text.append(SPACE);
1436                } else if (tokens.matches(R_PAREN)) {
1437                    if (numLeft == ++numRight) {
1438                        if (isFunction) {
1439                            functions.add(text.toString());
1440                        } else {
1441                            possibleColumns.add(text.toString());
1442                        }
1443
1444                        break;
1445                    }
1446                }
1447
1448                text.append(tokens.consume());
1449            }
1450
1451            if (!possibleColumns.isEmpty()) {
1452                List<AstNode> tableNodes = null;
1453                final boolean tableIndex = indexNode.hasMixin(OracleDdlLexicon.TYPE_CREATE_TABLE_INDEX_STATEMENT);
1454
1455                // find appropriate table nodes
1456                if (tableIndex) {
1457                    // table index so find table node
1458                    final String tableName = (String)indexNode.getProperty(OracleDdlLexicon.TABLE_NAME);
1459                    final AstNode parent = indexNode.getParent();
1460                    final List<AstNode> nodes = parent.childrenWithName(tableName);
1461
1462                    if (!nodes.isEmpty()) {
1463                        if (nodes.size() == 1) {
1464                            tableNodes = nodes;
1465                        } else {
1466                            // this should not be possible but check none the less
1467                            for (final AstNode node : nodes) {
1468                                if (node.hasMixin(StandardDdlLexicon.TYPE_CREATE_TABLE_STATEMENT)) {
1469                                    tableNodes = new ArrayList<AstNode>(1);
1470                                    tableNodes.add(node);
1471                                    break;
1472                                }
1473                            }
1474                        }
1475                    }
1476                } else {
1477                    // must be bitmap-join
1478                    tableNodes = indexNode.getChildren(StandardDdlLexicon.TYPE_TABLE_REFERENCE);
1479                }
1480
1481                if ((tableNodes != null) && !tableNodes.isEmpty()) {
1482                    boolean processed = false;
1483
1484                    for (String possibleColumn : possibleColumns) {
1485                        // first determine any ordering
1486                        final int ascIndex = possibleColumn.toUpperCase().indexOf(" ASC");
1487                        final boolean asc = (ascIndex != -1);
1488                        final int descIndex = possibleColumn.toUpperCase().indexOf(" DESC");
1489                        boolean desc = (descIndex != -1);
1490
1491                        // adjust column name if there is ordering
1492                        if (asc) {
1493                            possibleColumn = possibleColumn.substring(0, ascIndex);
1494                        } else if (desc) {
1495                            possibleColumn = possibleColumn.substring(0, descIndex);
1496                        }
1497
1498                        if (tableIndex) {
1499                            if (tableNodes.isEmpty()) {
1500                                if (asc) {
1501                                    functions.add(possibleColumn + SPACE + "ASC");
1502                                } else if (desc) {
1503                                    functions.add(possibleColumn + SPACE + "DESC");
1504                                } else {
1505                                    functions.add(possibleColumn);
1506                                }
1507                            } else {
1508                                // only one table reference. need to find column.
1509                                final AstNode tableNode = tableNodes.get(0);
1510                                final List<AstNode> columnNodes = tableNode.getChildren(StandardDdlLexicon.TYPE_COLUMN_DEFINITION);
1511
1512                                if (!columnNodes.isEmpty()) {
1513                                    // find column
1514                                    for (final AstNode colNode : columnNodes) {
1515                                        if (colNode.getName().equalsIgnoreCase(possibleColumn)) {
1516                                            final AstNode colRef = nodeFactory().node(possibleColumn,
1517                                                                                      indexNode,
1518                                                                                      TYPE_COLUMN_REFERENCE);
1519
1520                                            if (asc || desc) {
1521                                                colRef.addMixin(OracleDdlLexicon.TYPE_INDEX_ORDERABLE);
1522
1523                                                if (asc) {
1524                                                    colRef.setProperty(OracleDdlLexicon.INDEX_ORDER, "ASC");
1525                                                } else {
1526                                                    colRef.setProperty(OracleDdlLexicon.INDEX_ORDER, "DESC");
1527                                                }
1528                                            }
1529
1530                                            processed = true;
1531                                            break;
1532                                        }
1533                                    }
1534                                }
1535
1536                                if (!processed) {
1537                                    if (asc) {
1538                                        functions.add(possibleColumn + SPACE + "ASC");
1539                                    } else if (desc) {
1540                                        functions.add(possibleColumn + SPACE + "DESC");
1541                                    } else {
1542                                        functions.add(possibleColumn);
1543                                    }
1544
1545                                    processed = true;
1546                                }
1547                            }
1548                        } else {
1549                            // bitmap-join
1550                            for (final AstNode dimensionTableNode : tableNodes) {
1551                                if (possibleColumn.toUpperCase(Locale.ROOT).startsWith(dimensionTableNode.getName().toUpperCase(Locale.ROOT) + PERIOD)) {
1552                                    final AstNode colRef = nodeFactory().node(possibleColumn, indexNode, TYPE_COLUMN_REFERENCE);
1553
1554                                    if (asc || desc) {
1555                                        colRef.addMixin(OracleDdlLexicon.TYPE_INDEX_ORDERABLE);
1556
1557                                        if (asc) {
1558                                            colRef.setProperty(OracleDdlLexicon.INDEX_ORDER, "ASC");
1559                                        } else {
1560                                            colRef.setProperty(OracleDdlLexicon.INDEX_ORDER, "DESC");
1561                                        }
1562                                    }
1563
1564                                    processed = true;
1565                                    break;
1566                                }
1567                            }
1568
1569                            // probably a constant or function
1570                            if (!processed) {
1571                                if (asc) {
1572                                    functions.add(possibleColumn + SPACE + "ASC");
1573                                } else if (desc) {
1574                                    functions.add(possibleColumn + SPACE + "DESC");
1575                                } else {
1576                                    functions.add(possibleColumn);
1577                                }
1578
1579                                processed = true;
1580                            }
1581                        }
1582                    }
1583                }
1584            }
1585
1586            if (!functions.isEmpty()) {
1587                indexNode.setProperty(OracleDdlLexicon.OTHER_INDEX_REFS, functions);
1588            }
1589        }
1590
1591        if (numLeft != numRight) {
1592            throw new ParsingException(tokens.nextPosition());
1593        }
1594
1595        tokens.consume(R_PAREN); // must have closing paren
1596    }
1597
1598    /**
1599     * Parses DDL CREATE INDEX
1600     * <p>
1601     * <code>
1602     * CREATE [ UNIQUE | BITMAP ] INDEX index-name ON { cluster_index_clause | table_index_clause | bitmap_join_index_clause } [index_attributes] [UNUSABLE]
1603     * 
1604     * cluster_index_clause = CLUSTER cluster-name
1605     * table_index_clause = table-name [table-alias] ( { column | constant | function } [ ASC | DESC ] [ , { column | column_expression } [ ASC | DESC ]] * )
1606     * bitmap_join_index_clause = table-name ( column [ASC | DESC] [, column [ASC | DESC]] ) FROM table-name [, table-name] WHERE condition [local-partition-index]
1607     * 
1608     * </code>
1609     * 
1610     * @param tokens the tokenized {@link DdlTokenStream} of the DDL input content; may not be null
1611     * @param parentNode the parent {@link AstNode} node; may not be null
1612     * @return the parsed CREATE INDEX
1613     * @throws ParsingException
1614     */
1615    private AstNode parseCreateIndex( final DdlTokenStream tokens,
1616                                      final AstNode parentNode ) throws ParsingException {
1617        assert tokens != null;
1618        assert parentNode != null;
1619        assert (tokens.matches(STMT_CREATE_INDEX) || tokens.matches(STMT_CREATE_UNIQUE_INDEX) || tokens.matches(STMT_CREATE_BITMAP_INDEX));
1620
1621        markStartOfStatement(tokens);
1622        tokens.consume(CREATE);
1623
1624        final boolean isUnique = tokens.canConsume(UNIQUE);
1625        final boolean isBitmap = tokens.canConsume("BITMAP");
1626
1627        tokens.consume(INDEX);
1628        final String indexName = parseName(tokens);
1629
1630        tokens.consume(ON);
1631
1632        AstNode indexNode = null;
1633
1634        if (tokens.canConsume("CLUSTER")) {
1635            // table-cluster_index_clause
1636            indexNode = nodeFactory().node(indexName, parentNode, TYPE_CREATE_CLUSTER_INDEX_STATEMENT);
1637            indexNode.setProperty(OracleDdlLexicon.INDEX_TYPE, OracleDdlConstants.IndexTypes.CLUSTER);
1638
1639            final String clusterName = parseName(tokens);
1640            indexNode.setProperty(OracleDdlLexicon.CLUSTER_NAME, clusterName);
1641        } else {
1642            final String tableName = parseName(tokens);
1643
1644            if (!tokens.matches('(')) {
1645                // must be a table-index-clause as this has to be table-alias
1646                final String tableAlias = tokens.consume();
1647                indexNode = nodeFactory().node(indexName, parentNode, TYPE_CREATE_TABLE_INDEX_STATEMENT);
1648                indexNode.setProperty(OracleDdlLexicon.INDEX_TYPE, OracleDdlConstants.IndexTypes.TABLE);
1649                indexNode.setProperty(OracleDdlLexicon.TABLE_ALIAS, tableAlias);
1650            }
1651
1652            // parse left-paren content right-paren
1653            final String columnExpressionList = parseContentBetweenParens(tokens);
1654
1655            // must have FROM and WHERE clauses
1656            if (tokens.canConsume("FROM")) {
1657                indexNode = nodeFactory().node(indexName, parentNode, TYPE_CREATE_BITMAP_JOIN_INDEX_STATEMENT);
1658                indexNode.setProperty(OracleDdlLexicon.INDEX_TYPE, OracleDdlConstants.IndexTypes.BITMAP_JOIN);
1659                parseTableReferenceList(tokens, indexNode);
1660
1661                tokens.consume("WHERE");
1662                final String whereClause = parseUntilTerminator(tokens); // this will have index attributes also:-(
1663                indexNode.setProperty(OracleDdlLexicon.WHERE_CLAUSE, whereClause);
1664            } else {
1665                indexNode = nodeFactory().node(indexName, parentNode, TYPE_CREATE_TABLE_INDEX_STATEMENT);
1666                indexNode.setProperty(OracleDdlLexicon.INDEX_TYPE, OracleDdlConstants.IndexTypes.TABLE);
1667            }
1668
1669            indexNode.setProperty(OracleDdlLexicon.TABLE_NAME, tableName);
1670            parseIndexColumnExpressionList('(' + columnExpressionList + ')', indexNode);
1671        }
1672
1673        indexNode.setProperty(UNIQUE_INDEX, isUnique);
1674        indexNode.setProperty(BITMAP_INDEX, isBitmap);
1675
1676        // index attributes are optional as is UNUSABLE
1677        if (tokens.hasNext()) {
1678            boolean unusable = false;
1679            final List<String> indexAttributes = new ArrayList<String>();
1680
1681            while (tokens.hasNext() && !isTerminator(tokens)) {
1682                String token = tokens.consume();
1683
1684                if ("UNUSABLE".equalsIgnoreCase(token)) {
1685                    unusable = true;
1686                    break; // must be last token found before terminator
1687                }
1688
1689                // if number add it to previous
1690                boolean processed = false;
1691
1692                if (token.matches("\\b\\d+\\b")) {
1693                    if (!indexAttributes.isEmpty()) {
1694                        final int index = (indexAttributes.size() - 1);
1695                        final String value = indexAttributes.get(index);
1696                        final String newValue = (value + SPACE + token);
1697                        indexAttributes.set(index, newValue);
1698                        processed = true;
1699                    }
1700                }
1701
1702                if (!processed) {
1703                    indexAttributes.add(token);
1704                }
1705            }
1706
1707            if (!indexAttributes.isEmpty()) {
1708                indexNode.setProperty(OracleDdlLexicon.INDEX_ATTRIBUTES, indexAttributes);
1709            }
1710
1711            indexNode.setProperty(OracleDdlLexicon.UNUSABLE_INDEX, unusable);
1712        }
1713
1714        markEndOfStatement(tokens, indexNode);
1715        return indexNode;
1716    }
1717
1718    private void parseTableReferenceList( final DdlTokenStream tokens,
1719                                          final AstNode parentNode ) {
1720        final List<String> tableRefs = parseNameList(tokens);
1721
1722        if (!tableRefs.isEmpty()) {
1723            for (String tableName : tableRefs) {
1724                nodeFactory().node(tableName, parentNode, TYPE_TABLE_REFERENCE);
1725            }
1726        }
1727    }
1728
1729    private AstNode parseCommentStatement( DdlTokenStream tokens,
1730                                           AstNode parentNode ) throws ParsingException {
1731        assert tokens != null;
1732        assert parentNode != null;
1733
1734        markStartOfStatement(tokens);
1735
1736        // COMMENT ON COLUMN CS_EXT_FILES.FILE_UID IS
1737        // 'UNIQUE INTERNAL IDENTIFIER, NOT EXPOSED'
1738        // %
1739
1740        /*
1741            commentCommand
1742                :    'COMMENT' 'ON' 
1743                  ( 'TABLE' tableName 
1744                  | 'VIEW' tableName
1745                  | 'INDEX' indexName
1746                  | 'COLUMN' columnName 
1747                  | 'PROCEDURE' procedureId
1748                  | 'MATERIALIZED'? 'VIEW' tableName 
1749                  ) 'IS' ('NULL' | (CharLiteral)+) commandEnd? //CharLiteral (',' unicodeCharLiteral)*) commandEnd?
1750                ;
1751         */
1752        tokens.consume("COMMENT"); // consumes 'COMMENT' 'ON'
1753        tokens.consume("ON");
1754        String obj = tokens.consume();
1755        String objName = parseName(tokens);
1756
1757        // System.out.println("  >> FOUND [COMMENT ON] STATEMENT >>  TABLE Name = " + objName);
1758        String commentString = null;
1759
1760        tokens.consume("IS");
1761        if (tokens.matches("NULL")) {
1762            tokens.consume("NULL");
1763            commentString = "NULL";
1764        } else {
1765            commentString = parseUntilTerminator(tokens).trim();
1766        }
1767
1768        AstNode commentNode = nodeFactory().node(objName, parentNode, TYPE_COMMENT_ON_STATEMENT);
1769        commentNode.setProperty(OracleDdlLexicon.COMMENT, commentString);
1770        commentNode.setProperty(OracleDdlLexicon.TARGET_OBJECT_TYPE, obj);
1771
1772        markEndOfStatement(tokens, commentNode);
1773
1774        return commentNode;
1775    }
1776
1777    @Override
1778    protected AstNode parseSetStatement( DdlTokenStream tokens,
1779                                         AstNode parentNode ) throws ParsingException {
1780        assert tokens != null;
1781        assert parentNode != null;
1782
1783        if (tokens.matches(STMT_SET_CONSTRAINT)) {
1784            return parseStatement(tokens, STMT_SET_CONSTRAINT, parentNode, TYPE_SET_CONSTRAINT_STATEMENT);
1785        } else if (tokens.matches(STMT_SET_CONSTRAINTS)) {
1786            return parseStatement(tokens, STMT_SET_CONSTRAINTS, parentNode, TYPE_SET_CONSTRAINTS_STATEMENT);
1787        } else if (tokens.matches(STMT_SET_ROLE)) {
1788            return parseStatement(tokens, STMT_SET_ROLE, parentNode, TYPE_SET_ROLE_STATEMENT);
1789        } else if (tokens.matches(STMT_SET_TRANSACTION)) {
1790            return parseStatement(tokens, STMT_SET_TRANSACTION, parentNode, TYPE_SET_TRANSACTION_STATEMENT);
1791        }
1792
1793        return super.parseSetStatement(tokens, parentNode);
1794    }
1795
1796    @Override
1797    protected AstNode parseDropStatement( DdlTokenStream tokens,
1798                                          AstNode parentNode ) throws ParsingException {
1799        assert tokens != null;
1800        assert parentNode != null;
1801
1802        AstNode dropNode = null;
1803
1804        if (tokens.matches(StatementStartPhrases.STMT_DROP_TABLE)) {
1805            markStartOfStatement(tokens);
1806
1807            // DROP TABLE [ schema. ]table [ CASCADE CONSTRAINTS ] [ PURGE ] ;
1808
1809            tokens.consume(DROP, TABLE);
1810
1811            String name = parseName(tokens);
1812            dropNode = nodeFactory().node(name, parentNode, TYPE_DROP_TABLE_STATEMENT);
1813
1814            if (tokens.canConsume("CASCADE", "CONSTRAINTTS")) {
1815                dropNode.setProperty(DROP_BEHAVIOR, "CASCADE CONSTRAINTS");
1816            }
1817
1818            if (tokens.canConsume("PURGE")) {
1819                AstNode optionNode = nodeFactory().node(DROP_OPTION, dropNode, TYPE_STATEMENT_OPTION);
1820                optionNode.setProperty(StandardDdlLexicon.VALUE, "PURGE");
1821            }
1822
1823            markEndOfStatement(tokens, dropNode);
1824
1825            return dropNode;
1826        } else if (tokens.matches(STMT_DROP_CLUSTER)) {
1827            return parseStatement(tokens, STMT_DROP_CLUSTER, parentNode, TYPE_DROP_CLUSTER_STATEMENT);
1828        } else if (tokens.matches(STMT_DROP_CONTEXT)) {
1829            return parseStatement(tokens, STMT_DROP_CONTEXT, parentNode, TYPE_DROP_CONTEXT_STATEMENT);
1830        } else if (tokens.matches(STMT_DROP_DATABASE)) {
1831            return parseStatement(tokens, STMT_DROP_DATABASE, parentNode, TYPE_DROP_DATABASE_STATEMENT);
1832        } else if (tokens.matches(STMT_DROP_PUBLIC_DATABASE)) {
1833            return parseStatement(tokens, STMT_DROP_PUBLIC_DATABASE, parentNode, TYPE_DROP_DATABASE_STATEMENT);
1834        } else if (tokens.matches(STMT_DROP_DIMENSION)) {
1835            return parseStatement(tokens, STMT_DROP_DIMENSION, parentNode, TYPE_DROP_DIMENSION_STATEMENT);
1836        } else if (tokens.matches(STMT_DROP_DIRECTORY)) {
1837            return parseStatement(tokens, STMT_DROP_DIRECTORY, parentNode, TYPE_DROP_DIRECTORY_STATEMENT);
1838        } else if (tokens.matches(STMT_DROP_DISKGROUP)) {
1839            return parseStatement(tokens, STMT_DROP_DISKGROUP, parentNode, TYPE_DROP_DISKGROUP_STATEMENT);
1840        } else if (tokens.matches(STMT_DROP_FUNCTION)) {
1841            return parseStatement(tokens, STMT_DROP_FUNCTION, parentNode, TYPE_DROP_FUNCTION_STATEMENT);
1842        } else if (tokens.matches(STMT_DROP_INDEX)) {
1843            return parseStatement(tokens, STMT_DROP_INDEX, parentNode, TYPE_DROP_INDEX_STATEMENT);
1844        } else if (tokens.matches(STMT_DROP_INDEXTYPE)) {
1845            return parseStatement(tokens, STMT_DROP_INDEXTYPE, parentNode, TYPE_DROP_INDEXTYPE_STATEMENT);
1846        } else if (tokens.matches(STMT_DROP_JAVA)) {
1847            return parseStatement(tokens, STMT_DROP_JAVA, parentNode, TYPE_DROP_JAVA_STATEMENT);
1848        } else if (tokens.matches(STMT_DROP_LIBRARY)) {
1849            return parseStatement(tokens, STMT_DROP_LIBRARY, parentNode, TYPE_DROP_LIBRARY_STATEMENT);
1850        } else if (tokens.matches(STMT_DROP_MATERIALIZED)) {
1851            return parseStatement(tokens, STMT_DROP_MATERIALIZED, parentNode, TYPE_DROP_MATERIALIZED_STATEMENT);
1852        } else if (tokens.matches(STMT_DROP_OPERATOR)) {
1853            return parseStatement(tokens, STMT_DROP_OPERATOR, parentNode, TYPE_DROP_OPERATOR_STATEMENT);
1854        } else if (tokens.matches(STMT_DROP_OUTLINE)) {
1855            return parseStatement(tokens, STMT_DROP_OUTLINE, parentNode, TYPE_DROP_OUTLINE_STATEMENT);
1856        } else if (tokens.matches(STMT_DROP_PACKAGE)) {
1857            return parseStatement(tokens, STMT_DROP_PACKAGE, parentNode, TYPE_DROP_PACKAGE_STATEMENT);
1858        } else if (tokens.matches(STMT_DROP_PROCEDURE)) {
1859            return parseStatement(tokens, STMT_DROP_PROCEDURE, parentNode, TYPE_DROP_PROCEDURE_STATEMENT);
1860        } else if (tokens.matches(STMT_DROP_PROFILE)) {
1861            return parseStatement(tokens, STMT_DROP_PROFILE, parentNode, TYPE_DROP_PROFILE_STATEMENT);
1862        } else if (tokens.matches(STMT_DROP_ROLE)) {
1863            return parseStatement(tokens, STMT_DROP_ROLE, parentNode, TYPE_DROP_ROLE_STATEMENT);
1864        } else if (tokens.matches(STMT_DROP_ROLLBACK)) {
1865            return parseStatement(tokens, STMT_DROP_ROLLBACK, parentNode, TYPE_DROP_ROLLBACK_STATEMENT);
1866        } else if (tokens.matches(STMT_DROP_SEQUENCE)) {
1867            return parseStatement(tokens, STMT_DROP_SEQUENCE, parentNode, TYPE_DROP_SEQUENCE_STATEMENT);
1868        } else if (tokens.matches(STMT_DROP_SYNONYM)) {
1869            return parseStatement(tokens, STMT_DROP_SYNONYM, parentNode, TYPE_DROP_SYNONYM_STATEMENT);
1870        } else if (tokens.matches(STMT_DROP_PUBLIC_SYNONYM)) {
1871            return parseStatement(tokens, STMT_DROP_PUBLIC_SYNONYM, parentNode, TYPE_DROP_SYNONYM_STATEMENT);
1872        } else if (tokens.matches(STMT_DROP_TABLESPACE)) {
1873            return parseStatement(tokens, STMT_DROP_TABLESPACE, parentNode, TYPE_DROP_TABLESPACE_STATEMENT);
1874        } else if (tokens.matches(STMT_DROP_TRIGGER)) {
1875            return parseStatement(tokens, STMT_DROP_TRIGGER, parentNode, TYPE_DROP_TRIGGER_STATEMENT);
1876        } else if (tokens.matches(STMT_DROP_TYPE)) {
1877            return parseStatement(tokens, STMT_DROP_TYPE, parentNode, TYPE_DROP_TYPE_STATEMENT);
1878        } else if (tokens.matches(STMT_DROP_USER)) {
1879            return parseStatement(tokens, STMT_DROP_USER, parentNode, TYPE_DROP_USER_STATEMENT);
1880        }
1881
1882        return super.parseDropStatement(tokens, parentNode);
1883    }
1884
1885    private AstNode parseCreateJavaStatement( DdlTokenStream tokens,
1886                                              AstNode parentNode ) throws ParsingException {
1887        assert tokens != null;
1888        assert parentNode != null;
1889
1890        markStartOfStatement(tokens);
1891        tokens.consume(STMT_CREATE_JAVA);
1892        AstNode result = nodeFactory().node(getStatementTypeName(STMT_CREATE_JAVA), parentNode, TYPE_CREATE_JAVA_STATEMENT);
1893
1894        // We want to parse until we find a terminator AND all brackets are matched.
1895
1896        // Assume we start with open parenthesis '{', then we can count on walking through ALL tokens until we find the close
1897        // parenthesis '}'. If there are intermediate parenthesis, we can count on them being pairs.
1898
1899        int iParen = 0;
1900
1901        while (tokens.hasNext()) {
1902            if (tokens.matches('{')) {
1903                iParen++;
1904            } else if (tokens.matches('}')) {
1905                iParen--;
1906            }
1907            tokens.consume();
1908
1909            if (isTerminator(tokens) && iParen == 0) {
1910                break;
1911            }
1912        }
1913
1914        markEndOfStatement(tokens, result);
1915
1916        return result;
1917    }
1918
1919    /**
1920     * Utility method to additionally check if MODIFY definition without datatype
1921     * 
1922     * @param tokens a {@link DdlTokenStream} instance; may not be null
1923     * @param columnMixinType a {@link String}; may not be null
1924     * @return {@code true} if the given stream si at the start of the column defintion, {@code false} otherwise.
1925     * @throws ParsingException
1926     */
1927    protected boolean isColumnDefinitionStart( DdlTokenStream tokens,
1928                                               String columnMixinType ) throws ParsingException {
1929        boolean result = isColumnDefinitionStart(tokens);
1930        if (!result && TYPE_ALTER_COLUMN_DEFINITION.equals(columnMixinType)) {
1931            for (String start : INLINE_COLUMN_PROPERTY_START) {
1932                if (tokens.matches(TokenStream.ANY_VALUE, start)) return true;
1933            }
1934        }
1935        return result;
1936    }
1937
1938    /**
1939     * Utility method designed to parse columns within an ALTER TABLE ADD/MODIFY statement.
1940     * 
1941     * @param tokens the tokenized {@link DdlTokenStream} of the DDL input content; may not be null
1942     * @param tableNode
1943     * @param columnMixinType
1944     * @throws ParsingException
1945     */
1946    protected void parseColumns( DdlTokenStream tokens,
1947                                 AstNode tableNode,
1948                                 String columnMixinType ) throws ParsingException {
1949        assert tokens != null;
1950        assert tableNode != null;
1951
1952        // TODO: Oracle has changed some things between versions 9i, and 10/11,
1953        // Basically they've added column properties (i.e. SORT option, ENCRYPT encryption_spec)
1954        // Need to 1) Override parseColumnDefinition shouldParseOracleProceduresAndFunctionsto handle these.
1955
1956        boolean isInParen = tokens.matches(L_PAREN);
1957
1958        String tableElementString = getTableElementsString(tokens, false);
1959
1960        DdlTokenStream localTokens = new DdlTokenStream(tableElementString, DdlTokenStream.ddlTokenizer(false), false);
1961
1962        localTokens.start();
1963
1964        StringBuilder unusedTokensSB = new StringBuilder();
1965
1966        do {
1967            if (isColumnDefinitionStart(localTokens, columnMixinType)) {
1968                parseColumnDefinition(localTokens, tableNode, columnMixinType);
1969            } else {
1970                // THIS IS AN ERROR (WARNING if it is an ALTER TABLE MODIFY part). NOTHING FOUND.
1971                // NEED TO absorb tokens
1972                while (localTokens.hasNext() && !localTokens.matches(COMMA)) {
1973                    unusedTokensSB.append(SPACE).append(localTokens.consume());
1974                }
1975            }
1976        } while (localTokens.canConsume(COMMA));
1977
1978        // columns inside parentheses should not leave unused tokens
1979        if (isInParen) {
1980            while (localTokens.hasNext()) {
1981                unusedTokensSB.append(SPACE).append(localTokens.consume());
1982            }
1983        }
1984
1985        if (unusedTokensSB.length() > 0 && TYPE_ADD_COLUMN_DEFINITION.equals(columnMixinType)) {
1986            String msg = DdlSequencerI18n.unusedTokensParsingColumnDefinition.text(tableNode.getName());
1987            DdlParserProblem problem = new DdlParserProblem(Problems.WARNING, getCurrentMarkedPosition(), msg);
1988            problem.setUnusedSource(unusedTokensSB.toString());
1989            addProblem(problem, tableNode);
1990        }
1991    }
1992
1993    /**
1994     * Overloaded version of method with default mixin type set to {@link StandardDdlLexicon#TYPE_ADD_COLUMN_DEFINITION}
1995     * 
1996     * @param tokens the tokenized {@link DdlTokenStream} of the DDL input content; may not be null
1997     * @param tableNode
1998     * @param isAlterTable
1999     * @throws ParsingException
2000     */
2001    protected void parseColumns( DdlTokenStream tokens,
2002                                 AstNode tableNode,
2003                                 boolean isAlterTable ) throws ParsingException {
2004        parseColumns(tokens, tableNode, (isAlterTable ? TYPE_ALTER_COLUMN_DEFINITION : TYPE_ADD_COLUMN_DEFINITION));
2005    }
2006
2007    /**
2008     * Alternative StandardDdlParser method that can handle column definition without datatype (for MODIFY) and supports setting
2009     * mixin type of a column.
2010     * 
2011     * @param tokens the {@link DdlTokenStream} representing the tokenized DDL content; may not be null
2012     * @param tableNode
2013     * @param columnMixinType
2014     * @throws ParsingException
2015     */
2016    protected void parseColumnDefinition( DdlTokenStream tokens,
2017                                          AstNode tableNode,
2018                                          String columnMixinType ) throws ParsingException {
2019        assert tokens != null;
2020        assert tableNode != null;
2021
2022        boolean isAlterTable = columnMixinType != TYPE_COLUMN_DEFINITION;
2023
2024        tokens.canConsume("COLUMN");
2025        String columnName = parseName(tokens);
2026        AstNode columnNode = nodeFactory().node(columnName, tableNode, columnMixinType);
2027
2028        DataType datatype = getDatatypeParser().parse(tokens);
2029        if (datatype != null) {
2030            getDatatypeParser().setPropertiesOnNode(columnNode, datatype);
2031        }
2032
2033        // Now clauses and constraints can be defined in any order, so we need to keep parsing until we get to a comma or
2034        // if statement is an alter where multiple single column add/modify clauses can be in a single command, break parsing
2035        // also on ADD/MODIFY
2036        StringBuilder unusedTokensSB = new StringBuilder();
2037
2038        while (tokens.hasNext() && !tokens.matches(COMMA) && !(isAlterTable && tokens.matchesAnyOf("ADD", "MODIFY"))) {
2039            boolean parsedDefaultClause = parseDefaultClause(tokens, columnNode);
2040            if (!parsedDefaultClause) {
2041                boolean parsedCollate = parseCollateClause(tokens, columnNode);
2042                boolean parsedConstraint = parseColumnConstraint(tokens, columnNode, isAlterTable);
2043                if (!parsedCollate && !parsedConstraint) {
2044                    // THIS IS AN ERROR. NOTHING FOUND.
2045                    // NEED TO absorb tokens
2046                    unusedTokensSB.append(SPACE).append(tokens.consume());
2047                }
2048            }
2049            tokens.canConsume(DdlTokenizer.COMMENT);
2050        }
2051
2052        if ((unusedTokensSB.length() == 1 && unusedTokensSB.charAt(0) == ';') || (unusedTokensSB.length() > 1)) {
2053            String msg = DdlSequencerI18n.unusedTokensParsingColumnDefinition.text(tableNode.getName());
2054            DdlParserProblem problem = new DdlParserProblem(Problems.WARNING, Position.EMPTY_CONTENT_POSITION, msg);
2055            problem.setUnusedSource(unusedTokensSB.toString());
2056            addProblem(problem, tableNode);
2057        }
2058    }
2059
2060    /**
2061     * Utility method to parse a generic statement given a start phrase and statement mixin type.
2062     * 
2063     * @param tokens the tokenized {@link DdlTokenStream} of the DDL input content; may not be null
2064     * @param stmt_start_phrase the string array statement start phrase
2065     * @param parentNode the parent {@link AstNode} node; may not be null
2066     * @param mixinType the mixin type of the newly created statement node
2067     * @return the new node
2068     */
2069    protected AstNode parseSlashedStatement( DdlTokenStream tokens,
2070                                             String[] stmt_start_phrase,
2071                                             AstNode parentNode,
2072                                             String mixinType ) {
2073        assert tokens != null;
2074        assert stmt_start_phrase != null && stmt_start_phrase.length > 0;
2075        assert parentNode != null;
2076
2077        markStartOfStatement(tokens);
2078        tokens.consume(stmt_start_phrase);
2079        AstNode result = nodeFactory().node(getStatementTypeName(stmt_start_phrase), parentNode, mixinType);
2080
2081        parseUntilFwdSlash(tokens, false);
2082
2083        consumeSlash(tokens);
2084
2085        markEndOfStatement(tokens, result);
2086
2087        return result;
2088    }
2089
2090    /**
2091     * Various Oracle statements (i.e. "CREATE OR REPLACE PACKAGE", etc...) may contain multiple SQL statements that will be
2092     * terminated by the semicolon, ';'. In these cases, the terminator is now the '/' character.
2093     * 
2094     * @param tokens the tokenized {@link DdlTokenStream} of the DDL input content; may not be null
2095     * @param stopAtStatementStart
2096     * @return the parsed string.
2097     * @throws ParsingException
2098     */
2099    private String parseUntilFwdSlash( DdlTokenStream tokens,
2100                                       boolean stopAtStatementStart ) throws ParsingException {
2101        StringBuilder sb = new StringBuilder();
2102        if (stopAtStatementStart) {
2103            while (tokens.hasNext()
2104
2105            && !tokens.matches(DdlTokenizer.STATEMENT_KEY) && !tokens.matches('/')) { // !tokens.matches(DdlTokenizer.STATEMENT_KEY
2106                // )
2107                // &&
2108                sb.append(SPACE).append(tokens.consume());
2109            }
2110        } else {
2111            while (tokens.hasNext() && !isFwdSlashedStatement(tokens) && !tokens.matches('/')) { // !tokens.matches(DdlTokenizer.
2112                // STATEMENT_KEY) &&
2113                sb.append(SPACE).append(tokens.consume());
2114            }
2115        }
2116        return sb.toString();
2117    }
2118
2119    private boolean isFwdSlashedStatement( DdlTokenStream tokens ) throws ParsingException {
2120        for (int i = 0; i < SLASHED_STMT_PHRASES.length; i++) {
2121            if (tokens.matches(SLASHED_STMT_PHRASES[i])) {
2122                return true;
2123            }
2124        }
2125
2126        return false;
2127    }
2128
2129    private void consumeSlash( DdlTokenStream tokens ) throws ParsingException {
2130        tokens.canConsume("/");
2131    }
2132
2133    /**
2134     * {@inheritDoc}
2135     * 
2136     * @see org.modeshape.sequencer.ddl.StandardDdlParser#getValidSchemaChildTypes()
2137     */
2138    @Override
2139    protected String[] getValidSchemaChildTypes() {
2140        return VALID_SCHEMA_CHILD_STMTS;
2141    }
2142
2143    // ===========================================================================================================================
2144    // PARSE OBJECTS
2145    // ===========================================================================================================================
2146
2147    /**
2148     * This class provides custom data type parsing for Oracle-specific data types.
2149     */
2150    class OracleDataTypeParser extends DataTypeParser {
2151
2152        /*
2153         * (non-Javadoc)
2154         * @see org.modeshape.sequencer.ddl.datatype.DataTypeParser#parseCustomType(org.modeshape.common.text.DdlTokenStream)
2155         */
2156        @Override
2157        protected DataType parseCustomType( DdlTokenStream tokens ) throws ParsingException {
2158            DataType dataType = null;
2159            String typeName = null;
2160
2161            if (tokens.matches(OracleDataTypes.DTYPE_BINARY_FLOAT)) {
2162                dataType = new DataType();
2163                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_BINARY_FLOAT);
2164                dataType.setName(typeName);
2165            } else if (tokens.matches(OracleDataTypes.DTYPE_BINARY_DOUBLE)) {
2166                dataType = new DataType();
2167                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_BINARY_DOUBLE);
2168                dataType.setName(typeName);
2169            } else if (tokens.matches(OracleDataTypes.DTYPE_LONG)) {
2170                dataType = new DataType();
2171                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_LONG);
2172                dataType.setName(typeName);
2173            } else if (tokens.matches(OracleDataTypes.DTYPE_LONG_RAW)) {
2174                dataType = new DataType();
2175                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_LONG_RAW);
2176                dataType.setName(typeName);
2177            } else if (tokens.matches(OracleDataTypes.DTYPE_BLOB)) {
2178                dataType = new DataType();
2179                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_BLOB);
2180                dataType.setName(typeName);
2181            } else if (tokens.matches(OracleDataTypes.DTYPE_CLOB)) {
2182                dataType = new DataType();
2183                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_CLOB);
2184                dataType.setName(typeName);
2185            } else if (tokens.matches(OracleDataTypes.DTYPE_NCLOB)) {
2186                dataType = new DataType();
2187                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_NCLOB);
2188                dataType.setName(typeName);
2189            } else if (tokens.matches(OracleDataTypes.DTYPE_BFILE)) {
2190                dataType = new DataType();
2191                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_BFILE);
2192                dataType.setName(typeName);
2193            } else if (tokens.matches(OracleDataTypes.DTYPE_VARCHAR2)) {
2194                dataType = new DataType();
2195                // VARCHAR2(size [BYTE | CHAR])
2196                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_VARCHAR2); // VARCHAR2
2197                consume(tokens, dataType, false, L_PAREN);
2198                long length = parseLong(tokens, dataType);
2199                canConsume(tokens, dataType, true, "BYTE");
2200                canConsume(tokens, dataType, true, "CHAR");
2201                consume(tokens, dataType, false, R_PAREN);
2202                dataType.setName(typeName);
2203                dataType.setLength(length);
2204            } else if (tokens.matches(OracleDataTypes.DTYPE_RAW)) {
2205                dataType = new DataType();
2206                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_RAW);
2207                long length = parseBracketedLong(tokens, dataType);
2208                dataType.setName(typeName);
2209                dataType.setLength(length);
2210            } else if (tokens.matches(OracleDataTypes.DTYPE_NVARCHAR2)) {
2211                dataType = new DataType();
2212                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_NVARCHAR2);
2213                long length = parseBracketedLong(tokens, dataType);
2214                dataType.setName(typeName);
2215                dataType.setLength(length);
2216            } else if (tokens.matches(OracleDataTypes.DTYPE_NUMBER)) {
2217                dataType = new DataType();
2218                typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_NUMBER);
2219                int precision = 0;
2220                int scale = 0;
2221                if (tokens.matches(L_PAREN)) {
2222                    consume(tokens, dataType, false, L_PAREN);
2223                    precision = (int)parseLong(tokens, dataType);
2224                    if (canConsume(tokens, dataType, false, COMMA)) {
2225                        scale = (int)parseLong(tokens, dataType);
2226                    } else {
2227                        scale = getDefaultScale();
2228                    }
2229                    consume(tokens, dataType, false, R_PAREN);
2230                } else {
2231                    precision = getDefaultPrecision();
2232                    scale = getDefaultScale();
2233                }
2234                dataType.setName(typeName);
2235                dataType.setPrecision(precision);
2236                dataType.setScale(scale);
2237            } else if (tokens.matches(OracleDataTypes.DTYPE_INTERVAL_YEAR)) {
2238                // INTERVAL YEAR (year_precision) TO MONTH
2239
2240            } else if (tokens.matches(OracleDataTypes.DTYPE_INTERVAL_DAY)) {
2241                // INTERVAL DAY (day_precision) TO SECOND (fractional_seconds_precision)
2242            }
2243
2244            if (dataType == null) {
2245                dataType = super.parseCustomType(tokens);
2246            }
2247
2248            return dataType;
2249        }
2250
2251        /**
2252         * Because Oracle has an additional option on the CHAR datatype, we need to override the super method, check for CHAR type
2253         * and parse, else call super.parseCharStringType(). {@inheritDoc}
2254         * 
2255         * @see org.modeshape.sequencer.ddl.datatype.DataTypeParser#parseCharStringType(org.modeshape.sequencer.ddl.DdlTokenStream)
2256         */
2257        @Override
2258        protected DataType parseCharStringType( DdlTokenStream tokens ) throws ParsingException {
2259            DataType dataType = null;
2260
2261            if (tokens.matches(OracleDataTypes.DTYPE_CHAR_ORACLE)) { // CHAR (size [BYTE | CHAR]) (i.e. CHAR (10 BYTE) )
2262                dataType = new DataType();
2263                String typeName = consume(tokens, dataType, true, OracleDataTypes.DTYPE_CHAR_ORACLE);
2264                consume(tokens, dataType, false, L_PAREN);
2265                long length = parseLong(tokens, dataType);
2266                canConsume(tokens, dataType, true, "BYTE");
2267                canConsume(tokens, dataType, true, "CHAR");
2268                consume(tokens, dataType, false, R_PAREN);
2269                dataType.setName(typeName);
2270                dataType.setLength(length);
2271            } else {
2272                dataType = super.parseCharStringType(tokens);
2273            }
2274
2275            return dataType;
2276        }
2277
2278        /**
2279         * {@inheritDoc}
2280         * 
2281         * @see org.modeshape.sequencer.ddl.datatype.DataTypeParser#isCustomDataType(org.modeshape.sequencer.ddl.DdlTokenStream)
2282         */
2283        @Override
2284        protected boolean isCustomDataType( DdlTokenStream tokens ) throws ParsingException {
2285            for (String[] stmt : oracleDataTypeStrings) {
2286                if (tokens.matches(stmt)) return true;
2287            }
2288
2289            return false;
2290        }
2291
2292    }
2293
2294    /**
2295     * {@inheritDoc}
2296     * 
2297     * @see org.modeshape.sequencer.ddl.StandardDdlParser#getDataTypeStartWords()
2298     */
2299    @Override
2300    protected List<String> getCustomDataTypeStartWords() {
2301        return OracleDataTypes.CUSTOM_DATATYPE_START_WORDS;
2302    }
2303}