View Javadoc

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