View Javadoc

1   package org.apache.torque.engine.database.transform;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedReader;
23  import java.io.FileReader;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  
29  import org.apache.torque.engine.database.model.Column;
30  import org.apache.torque.engine.database.model.Database;
31  import org.apache.torque.engine.database.model.ForeignKey;
32  import org.apache.torque.engine.database.model.IDMethod;
33  import org.apache.torque.engine.database.model.Table;
34  import org.apache.torque.engine.sql.ParseException;
35  import org.apache.torque.engine.sql.SQLScanner;
36  import org.apache.torque.engine.sql.Token;
37  
38  /**
39   * A Class that converts an sql input file to a Database structure.
40   * The class makes use of SQL Scanner to get
41   * sql tokens and the parses these to create the Database
42   * class. SQLToAppData is in effect a simplified sql parser.
43   *
44   * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
45   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
46   * @version $Id: SQLToAppData.java,v 1.1 2007-10-21 07:57:26 abyrne Exp $
47   */
48  public class SQLToAppData
49  {
50      private String sqlFile;
51      private List tokens;
52      private Token token;
53      private Database appDataDB;
54      private int count;
55      private String databaseType;
56  
57      /**
58       * Create a new class with an input Reader
59       *
60       * @param sqlFile the sql file
61       */
62      public SQLToAppData(String sqlFile)
63      {
64          this.sqlFile = sqlFile;
65      }
66  
67      /**
68       * Create a new class with an input Reader.  This ctor is not used
69       * but putting here in the event db.props properties are found to
70       * be useful converting sql to xml, the infrastructure will exist
71       *
72       * @param sqlFile the sql file
73       * @param databaseType
74       */
75      public SQLToAppData(String sqlFile, String databaseType)
76      {
77          this.sqlFile = sqlFile;
78          this.databaseType = databaseType;
79      }
80  
81      /**
82       * Get the current input sql file
83       *
84       * @return the sql file
85       */
86      public String getSqlFile()
87      {
88          return sqlFile;
89      }
90  
91      /**
92       * Set the current input sql file
93       *
94       * @param sqlFile the sql file
95       */
96      public void setSqlFile(String sqlFile)
97      {
98          this.sqlFile = sqlFile;
99      }
100 
101     /**
102      * Move to the next token.
103      *
104      * @throws ParseException if there is no more tokens available.
105      */
106     private void next() throws ParseException
107     {
108         if (count < tokens.size())
109         {
110             token = (Token) tokens.get(count++);
111         }
112         else
113         {
114             throw new ParseException("No More Tokens");
115         }
116     }
117 
118     /**
119      * Creates an error condition and adds the line and
120      * column number of the current token to the error
121      * message.
122      *
123      * @param name name of the error
124      * @throws ParseException
125      */
126     private void err(String name) throws ParseException
127     {
128         throw new ParseException (name + " at [ line: " + token.getLine()
129                 + " col: " + token.getCol() + " ]");
130     }
131 
132     /**
133      * Check if there is more tokens available for parsing.
134      *
135      * @return true if there are more tokens available
136      */
137     private boolean hasTokens()
138     {
139         return count < tokens.size();
140     }
141 
142     /**
143      * Parses a CREATE TABLE FOO command.
144      *
145      * @throws ParseException
146      */
147     private void create() throws ParseException
148     {
149         next();
150         if (token.getStr().toUpperCase().equals("TABLE"))
151         {
152             create_Table();
153         }
154     }
155 
156     /**
157      * Parses a CREATE TABLE sql command
158      *
159      * @throws ParseException error parsing the input file
160      */
161     private void create_Table() throws ParseException
162     {
163         next();
164         String tableName = token.getStr(); // name of the table
165         next();
166         if (!token.getStr().equals("("))
167         {
168             err("( expected");
169         }
170         next();
171 
172         Table tbl = new Table (tableName);
173         //tbl.setIdMethod("none");
174         while (!token.getStr().equals(";"))
175         {
176             create_Table_Column(tbl);
177         }
178 
179         if (tbl.getPrimaryKey().size() == 1)
180         {
181             tbl.setIdMethod(IDMethod.ID_BROKER);
182         }
183         else
184         {
185             tbl.setIdMethod(IDMethod.NO_ID_METHOD);
186         }
187         appDataDB.addTable (tbl);
188     }
189 
190     /**
191      * Parses column information between the braces of a CREATE
192      * TABLE () sql statement.
193      *
194      * @throws ParseException error parsing the input file
195      */
196     private void create_Table_Column(Table tbl) throws ParseException
197     {
198         // The token should be the first item
199         // which is the name of the column or
200         // PRIMARY/FOREIGN/UNIQUE
201         if (token.getStr().equals(","))
202         {
203             next();
204         }
205 
206         if (token.getStr().toUpperCase().equals("PRIMARY"))
207         {
208             create_Table_Column_Primary(tbl);
209         }
210         else if (token.getStr().toUpperCase().equals("FOREIGN"))
211         {
212             create_Table_Column_Foreign(tbl);
213         }
214         else if (token.getStr().toUpperCase().equals("UNIQUE"))
215         {
216             create_Table_Column_Unique(tbl);
217         }
218         else
219         {
220             create_Table_Column_Data(tbl);
221         }
222     }
223 
224     /**
225      * Parses PRIMARY KEY (FOO,BAR) statement
226      *
227      * @throws ParseException error parsing the input file
228      */
229     private void create_Table_Column_Primary (Table tbl) throws ParseException
230     {
231         next();
232         if (!token.getStr().toUpperCase().equals("KEY"))
233         {
234             err("KEY expected");
235         }
236         next();
237         if (!token.getStr().toUpperCase().equals("("))
238         {
239             err("( expected");
240         }
241         next();
242 
243         String colName = token.getStr();
244         Column c = tbl.getColumn(colName);
245         if (c == null)
246         {
247             err("Invalid column name: " + colName);
248         }
249         c.setPrimaryKey(true);
250         next();
251         while (token.getStr().equals(","))
252         {
253             next();
254             colName = token.getStr();
255             c = tbl.getColumn(colName);
256             if (c == null)
257             {
258                 err("Invalid column name: " + colName);
259             }
260             c.setPrimaryKey(true);
261             next();
262         }
263 
264         if (!token.getStr().toUpperCase().equals(")"))
265         {
266             err(") expected");
267         }
268         next(); // skip the )
269     }
270 
271     /**
272      * Parses UNIQUE (NAME,FOO,BAR) statement
273      *
274      * @throws ParseException error parsing the input file
275      */
276     private void create_Table_Column_Unique(Table tbl) throws ParseException
277     {
278         next();
279         if (!token.getStr().toUpperCase().equals("("))
280         {
281             err("( expected");
282         }
283         next();
284         while (!token.getStr().equals(")"))
285         {
286             if (!token.getStr().equals(","))
287             {
288                 String colName = token.getStr();
289                 Column c = tbl.getColumn(colName);
290                 if (c == null)
291                 {
292                     err("Invalid column name: " + colName);
293                 }
294                 c.setUnique(true);
295             }
296             next();
297         }
298         if (!token.getStr().toUpperCase().equals(")"))
299         {
300             err(") expected got: " + token.getStr());
301         }
302 
303         next(); // skip the )
304     }
305 
306     /**
307      * Parses FOREIGN KEY (BAR) REFERENCES TABLE (BAR) statement
308      *
309      * @throws ParseException error parsing the input file
310      */
311     private void create_Table_Column_Foreign(Table tbl) throws ParseException
312     {
313         next();
314         if (!token.getStr().toUpperCase().equals("KEY"))
315         {
316             err("KEY expected");
317         }
318         next();
319         if (!token.getStr().toUpperCase().equals("("))
320         {
321             err("( expected");
322         }
323         next();
324 
325         ForeignKey fk = new ForeignKey();
326         List localColumns = new ArrayList();
327         tbl.addForeignKey(fk);
328 
329         String colName = token.getStr();
330         localColumns.add(colName);
331         next();
332         while (token.getStr().equals(","))
333         {
334             next();
335             colName = token.getStr();
336             localColumns.add(colName);
337             next();
338         }
339         if (!token.getStr().toUpperCase().equals(")"))
340         {
341             err(") expected");
342         }
343 
344         next();
345 
346         if (!token.getStr().toUpperCase().equals("REFERENCES"))
347         {
348             err("REFERENCES expected");
349         }
350 
351         next();
352 
353         fk.setForeignTableName(token.getStr());
354 
355         next();
356 
357         if (token.getStr().toUpperCase().equals("("))
358         {
359             next();
360             int i = 0;
361             fk.addReference((String) localColumns.get(i++), token.getStr());
362             next();
363             while (token.getStr().equals(","))
364             {
365                 next();
366                 fk.addReference((String) localColumns.get(i++), token.getStr());
367                 next();
368             }
369             if (!token.getStr().toUpperCase().equals(")"))
370             {
371                 err(") expected");
372             }
373             next();
374         }
375     }
376 
377     /**
378      * Parse the data definition of the column statement.
379      *
380      * @throws ParseException error parsing the input file
381      */
382     private void create_Table_Column_Data(Table tbl) throws ParseException
383     {
384         String columnSize = null;
385         String columnPrecision = null;
386         String columnDefault = null;
387         boolean inEnum = false;
388 
389         String columnName = token.getStr();
390         next();
391         String columnType = token.getStr();
392 
393         if (columnName.equals(")") && columnType.equals(";"))
394         {
395             return;
396         }
397 
398         next();
399 
400         // special case for MySQL ENUM's which are stupid anyway
401         // and not properly handled by Torque.
402         if (columnType.toUpperCase().equals("ENUM"))
403         {
404             inEnum = true;
405             next(); // skip (
406             while (!token.getStr().equals(")"))
407             {
408                 // skip until )
409                 next();
410             }
411             while (!token.getStr().equals(","))
412             {
413                 if (token.getStr().toUpperCase().equals("DEFAULT"))
414                 {
415                     next();
416                     if (token.getStr().equals("'"))
417                     {
418                         next();
419                     }
420                     columnDefault = token.getStr();
421                     next();
422                     if (token.getStr().equals("'"))
423                     {
424                         next();
425                     }
426                 }
427                 // skip until ,
428                 next();
429             }
430             next(); // skip ,
431             columnType = "VARCHAR";
432         }
433         else if (token.getStr().toUpperCase().equals("("))
434         {
435             next();
436             columnSize = token.getStr();
437             next();
438             if (token.getStr().equals(","))
439             {
440                 next();
441                 columnPrecision = token.getStr();
442                 next();
443             }
444 
445             if (!token.getStr().equals(")"))
446             {
447                 err(") expected");
448             }
449             next();
450         }
451 
452         Column col = new Column(columnName);
453         if (columnPrecision != null)
454         {
455             columnSize = columnSize + columnPrecision;
456         }
457         col.setTypeFromString(columnType, columnSize);
458         tbl.addColumn(col);
459 
460         if (inEnum)
461         {
462             col.setNotNull(true);
463             if (columnDefault != null)
464             {
465                 col.setDefaultValue(columnDefault);
466             }
467         }
468         else
469         {
470             while (!token.getStr().equals(",") && !token.getStr().equals(")"))
471             {
472                 if (token.getStr().toUpperCase().equals("NOT"))
473                 {
474                     next();
475                     if (!token.getStr().toUpperCase().equals("NULL"))
476                     {
477                         err("NULL expected after NOT");
478                     }
479                     col.setNotNull(true);
480                     next();
481                 }
482                 else if (token.getStr().toUpperCase().equals("PRIMARY"))
483                 {
484                     next();
485                     if (!token.getStr().toUpperCase().equals("KEY"))
486                     {
487                         err("KEY expected after PRIMARY");
488                     }
489                     col.setPrimaryKey(true);
490                     next();
491                 }
492                 else if (token.getStr().toUpperCase().equals("UNIQUE"))
493                 {
494                     col.setUnique(true);
495                     next();
496                 }
497                 else if (token.getStr().toUpperCase().equals("NULL"))
498                 {
499                     col.setNotNull(false);
500                     next();
501                 }
502                 else if (token.getStr().toUpperCase().equals("AUTO_INCREMENT"))
503                 {
504                     col.setAutoIncrement(true);
505                     next();
506                 }
507                 else if (token.getStr().toUpperCase().equals("DEFAULT"))
508                 {
509                     next();
510                     if (token.getStr().equals("'"))
511                     {
512                         next();
513                     }
514                     col.setDefaultValue(token.getStr());
515                     next();
516                     if (token.getStr().equals("'"))
517                     {
518                         next();
519                     }
520                 }
521                 else
522                 {
523                     StringBuffer line = new StringBuffer();
524                     for (Iterator tokenIt = tokens.iterator();
525                        tokenIt.hasNext();)
526                     {
527                         line.append(tokenIt.next());
528                         if (tokenIt.hasNext())
529                         {
530                             line.append(" ");
531                         }
532                     }
533                     throw new ParseException("Error parsing line "
534                             + line + " : Unknown token Nr. "
535                             + count
536                             + " : "
537                             + token);
538                 }
539             }
540             next(); // eat the ,
541         }
542     }
543 
544     /**
545      * Execute the parser.
546      *
547      * @throws IOException If an I/O error occurs
548      * @throws ParseException error parsing the input file
549      */
550     public Database execute() throws IOException, ParseException
551     {
552         count = 0;
553         appDataDB = new Database(databaseType);
554 
555         FileReader fr = new FileReader(sqlFile);
556         BufferedReader br = new BufferedReader(fr);
557         SQLScanner scanner = new SQLScanner(br);
558 
559         tokens = scanner.scan();
560 
561         br.close();
562 
563         while (hasTokens())
564         {
565             if (token == null)
566             {
567                 next();
568             }
569 
570             if (token.getStr().toUpperCase().equals("CREATE"))
571             {
572                 create();
573             }
574             if (hasTokens())
575             {
576                 next();
577             }
578         }
579         return appDataDB;
580     }
581 }