View Javadoc

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