1 package org.apache.torque.engine.database.transform;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2001 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution,
22 * if any, must include the following acknowledgment:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.apache.org/)."
25 * Alternately, this acknowledgment may appear in the software itself,
26 * if and wherever such third-party acknowledgments normally appear.
27 *
28 * 4. The names "Apache" and "Apache Software Foundation" and
29 * "Apache Turbine" must not be used to endorse or promote products
30 * derived from this software without prior written permission. For
31 * written permission, please contact apache@apache.org.
32 *
33 * 5. Products derived from this software may not be called "Apache",
34 * "Apache Turbine", nor may "Apache" appear in their name, without
35 * prior written permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.apache.org/>.
55 */
56
57 import java.io.BufferedReader;
58 import java.io.FileReader;
59 import java.io.IOException;
60 import java.util.ArrayList;
61 import java.util.List;
62 import org.apache.torque.engine.database.model.AppData;
63 import org.apache.torque.engine.database.model.Column;
64 import org.apache.torque.engine.database.model.Database;
65 import org.apache.torque.engine.database.model.ForeignKey;
66 import org.apache.torque.engine.database.model.IDMethod;
67 import org.apache.torque.engine.database.model.Table;
68 import org.apache.torque.engine.sql.ParseException;
69 import org.apache.torque.engine.sql.SQLScanner;
70 import org.apache.torque.engine.sql.Token;
71
72 /***
73 * A Class that converts an sql input file to an AppData
74 * structure. The class makes use of SQL Scanner to get
75 * sql tokens and the parses these to create the AppData
76 * class. SQLToAppData is in effect a simplified sql parser.
77 *
78 * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
79 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
80 * @version $Id: SQLToAppData.java,v 1.2 2003/02/18 08:05:50 mpoeschl Exp $
81 */
82 public class SQLToAppData
83 {
84 private String sqlFile;
85 private List tokens;
86 private Token token;
87 private AppData appData;
88 private Database appDataDB;
89 private int count;
90 private String databaseType;
91 private String basePropsFilePath;
92
93 /***
94 * Create a new class with an input Reader
95 *
96 * @param sqlFile the sql file
97 */
98 public SQLToAppData(String sqlFile)
99 {
100 this.sqlFile = sqlFile;
101 }
102
103 /***
104 * Create a new class with an input Reader. This ctor is not used
105 * but putting here in the event db.props properties are found to
106 * be useful converting sql to xml, the infrastructure will exist
107 *
108 * @param sqlFile the sql file
109 * @param databaseType
110 * @param basePropsFilePath
111 */
112 public SQLToAppData(String sqlFile, String databaseType,
113 String basePropsFilePath)
114 {
115 this.sqlFile = sqlFile;
116 this.databaseType = databaseType;
117 this.basePropsFilePath = basePropsFilePath;
118 }
119
120 /***
121 * Get the current input sql file
122 *
123 * @return the sql file
124 */
125 public String getSqlFile()
126 {
127 return sqlFile;
128 }
129
130 /***
131 * Set the current input sql file
132 *
133 * @param sqlFile the sql file
134 */
135 public void setSqlFile(String sqlFile)
136 {
137 this.sqlFile = sqlFile;
138 }
139
140 /***
141 * Move to the next token. Throws an exception
142 * if there is no more tokens available.
143 *
144 * @throws ParseException
145 */
146 private void next() throws ParseException
147 {
148 if (count < tokens.size())
149 {
150 token = (Token) tokens.get(count++);
151 }
152 else
153 {
154 throw new ParseException("No More Tokens");
155 }
156 }
157
158 /***
159 * Creates an error condition and adds the line and
160 * column number of the current token to the error
161 * message.
162 *
163 * @param name name of the error
164 * @throws ParseException
165 */
166 private void err(String name) throws ParseException
167 {
168 throw new ParseException (name + " at [ line: " + token.getLine()
169 + " col: " + token.getCol() + " ]");
170 }
171
172 /***
173 * Check if there is more tokens available for parsing.
174 *
175 * @return true if there are more tokens available
176 */
177 private boolean hasTokens()
178 {
179 return count < tokens.size();
180 }
181
182 /***
183 * Parses a CREATE TABLE FOO command.
184 *
185 * @throws ParseException
186 */
187 private void create() throws ParseException
188 {
189 next();
190 if (token.getStr().toUpperCase().equals("TABLE"))
191 {
192 create_Table();
193 }
194 }
195
196 /***
197 * Parses a CREATE TABLE sql command
198 *
199 * @throws ParseException error parsing the input file
200 */
201 private void create_Table() throws ParseException
202 {
203 next();
204 String tableName = token.getStr(); // name of the table
205 next();
206 if (!token.getStr().equals("("))
207 {
208 err("( expected");
209 }
210 next();
211
212 Table tbl = new Table (tableName);
213 //tbl.setIdMethod("none");
214 while (!token.getStr().equals(";"))
215 {
216 create_Table_Column(tbl);
217 }
218
219 if (tbl.getPrimaryKey().size() == 1)
220 {
221 tbl.setIdMethod(IDMethod.ID_BROKER);
222 }
223 else
224 {
225 tbl.setIdMethod(IDMethod.NO_ID_METHOD);
226 }
227 appDataDB.addTable (tbl);
228 }
229
230 /***
231 * Parses column information between the braces of a CREATE
232 * TABLE () sql statement.
233 *
234 * @throws ParseException error parsing the input file
235 */
236 private void create_Table_Column(Table tbl) throws ParseException
237 {
238 // The token should be the first item
239 // which is the name of the column or
240 // PRIMARY/FOREIGN/UNIQUE
241 if (token.getStr().equals(","))
242 {
243 next();
244 }
245
246 if (token.getStr().toUpperCase().equals("PRIMARY"))
247 {
248 create_Table_Column_Primary(tbl);
249 }
250 else if (token.getStr().toUpperCase().equals("FOREIGN"))
251 {
252 create_Table_Column_Foreign(tbl);
253 }
254 else if (token.getStr().toUpperCase().equals("UNIQUE"))
255 {
256 create_Table_Column_Unique(tbl);
257 }
258 else
259 {
260 create_Table_Column_Data(tbl);
261 }
262 }
263
264 /***
265 * Parses PRIMARY KEY (FOO,BAR) statement
266 *
267 * @throws ParseException error parsing the input file
268 */
269 private void create_Table_Column_Primary (Table tbl) throws ParseException
270 {
271 next();
272 if (!token.getStr().toUpperCase().equals("KEY"))
273 {
274 err("KEY expected");
275 }
276 next();
277 if (!token.getStr().toUpperCase().equals("("))
278 {
279 err("( expected");
280 }
281 next();
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.setPrimaryKey(true);
290 next();
291 while (token.getStr().equals(","))
292 {
293 next();
294 colName = token.getStr();
295 c = tbl.getColumn(colName);
296 if (c == null)
297 {
298 err("Invalid column name: " + colName);
299 }
300 c.setPrimaryKey(true);
301 next();
302 }
303
304 if (!token.getStr().toUpperCase().equals(")"))
305 {
306 err(") expected");
307 }
308 next(); // skip the )
309 }
310
311 /***
312 * Parses UNIQUE (NAME,FOO,BAR) statement
313 *
314 * @throws ParseException error parsing the input file
315 */
316 private void create_Table_Column_Unique(Table tbl) throws ParseException
317 {
318 next();
319 if (!token.getStr().toUpperCase().equals("("))
320 {
321 err("( expected");
322 }
323 next();
324 while (!token.getStr().equals(")"))
325 {
326 if (!token.getStr().equals(","))
327 {
328 String colName = token.getStr();
329 Column c = tbl.getColumn(colName);
330 if (c == null)
331 {
332 err("Invalid column name: " + colName);
333 }
334 c.setUnique(true);
335 }
336 next();
337 }
338 if (!token.getStr().toUpperCase().equals(")"))
339 {
340 err(") expected got: " + token.getStr());
341 }
342
343 next(); // skip the )
344 }
345
346 /***
347 * Parses FOREIGN KEY (BAR) REFERENCES TABLE (BAR) statement
348 *
349 * @throws ParseException error parsing the input file
350 */
351 private void create_Table_Column_Foreign(Table tbl) throws ParseException
352 {
353 next();
354 if (!token.getStr().toUpperCase().equals("KEY"))
355 {
356 err("KEY expected");
357 }
358 next();
359 if (!token.getStr().toUpperCase().equals("("))
360 {
361 err("( expected");
362 }
363 next();
364
365 ForeignKey fk = new ForeignKey();
366 List localColumns = new ArrayList();
367 tbl.addForeignKey(fk);
368
369 String colName = token.getStr();
370 localColumns.add(colName);
371 next();
372 while (token.getStr().equals(","))
373 {
374 next();
375 colName = token.getStr();
376 localColumns.add(colName);
377 next();
378 }
379 if (!token.getStr().toUpperCase().equals(")"))
380 {
381 err(") expected");
382 }
383
384 next();
385
386 if (!token.getStr().toUpperCase().equals("REFERENCES"))
387 {
388 err("REFERENCES expected");
389 }
390
391 next();
392
393 fk.setForeignTableName(token.getStr());
394
395 next();
396
397 if (token.getStr().toUpperCase().equals("("))
398 {
399 next();
400 int i = 0;
401 fk.addReference((String) localColumns.get(i++), token.getStr());
402 next();
403 while (token.getStr().equals(","))
404 {
405 next();
406 fk.addReference((String) localColumns.get(i++), token.getStr());
407 next();
408 }
409 if (!token.getStr().toUpperCase().equals(")"))
410 {
411 err(") expected");
412 }
413 next();
414 }
415 }
416
417 /***
418 * Parse the data definition of the column statement.
419 *
420 * @throws ParseException error parsing the input file
421 */
422 private void create_Table_Column_Data(Table tbl) throws ParseException
423 {
424 String columnSize = null;
425 String columnPrecision = null;
426 String columnDefault = null;
427 boolean inEnum = false;
428
429 String columnName = token.getStr();
430 next();
431 String columnType = token.getStr();
432
433 if (columnName.equals(")") && columnType.equals(";"))
434 {
435 return;
436 }
437
438 next();
439
440 // special case for MySQL ENUM's which are stupid anyway
441 // and not properly handled by Torque.
442 if (columnType.toUpperCase().equals("ENUM"))
443 {
444 inEnum = true;
445 next(); // skip (
446 while (!token.getStr().equals(")"))
447 {
448 // skip until )
449 next();
450 }
451 while (!token.getStr().equals(","))
452 {
453 if (token.getStr().toUpperCase().equals("DEFAULT"))
454 {
455 next();
456 if (token.getStr().equals("'"))
457 {
458 next();
459 }
460 columnDefault = token.getStr();
461 next();
462 if (token.getStr().equals("'"))
463 {
464 next();
465 }
466 }
467 // skip until ,
468 next();
469 }
470 next(); // skip ,
471 columnType = "VARCHAR";
472 }
473 else if (token.getStr().toUpperCase().equals("("))
474 {
475 next();
476 columnSize = token.getStr();
477 next();
478 if (token.getStr().equals(","))
479 {
480 next();
481 columnPrecision = token.getStr();
482 next();
483 }
484
485 if (!token.getStr().equals(")"))
486 {
487 err(") expected");
488 }
489 next();
490 }
491
492 Column col = new Column(columnName);
493 if (columnPrecision != null)
494 {
495 columnSize = columnSize + columnPrecision;
496 }
497 col.setTypeFromString(columnType, columnSize);
498 tbl.addColumn(col);
499
500 if (inEnum)
501 {
502 col.setNotNull(true);
503 if (columnDefault != null)
504 {
505 col.setDefaultValue(columnDefault);
506 }
507 }
508 else
509 {
510 while (!token.getStr().equals(",") && !token.getStr().equals(")"))
511 {
512 if (token.getStr().toUpperCase().equals("NOT"))
513 {
514 next();
515 if (!token.getStr().toUpperCase().equals("NULL"))
516 {
517 err("NULL expected after NOT");
518 }
519 col.setNotNull(true);
520 next();
521 }
522 else if (token.getStr().toUpperCase().equals("PRIMARY"))
523 {
524 next();
525 if (!token.getStr().toUpperCase().equals("KEY"))
526 {
527 err("KEY expected after PRIMARY");
528 }
529 col.setPrimaryKey(true);
530 next();
531 }
532 else if (token.getStr().toUpperCase().equals("UNIQUE"))
533 {
534 col.setUnique(true);
535 next();
536 }
537 else if (token.getStr().toUpperCase().equals("NULL"))
538 {
539 col.setNotNull(false);
540 next();
541 }
542 else if (token.getStr().toUpperCase().equals("AUTO_INCREMENT"))
543 {
544 col.setAutoIncrement(true);
545 next();
546 }
547 else if (token.getStr().toUpperCase().equals("DEFAULT"))
548 {
549 next();
550 if (token.getStr().equals("'"))
551 {
552 next();
553 }
554 col.setDefaultValue(token.getStr());
555 next();
556 if (token.getStr().equals("'"))
557 {
558 next();
559 }
560 }
561 }
562 next(); // eat the ,
563 }
564 }
565
566 /***
567 * Execute the parser.
568 *
569 * @throws IOException If an I/O error occurs
570 * @throws ParseException error parsing the input file
571 */
572 public AppData execute() throws IOException, ParseException
573 {
574 count = 0;
575 appData = new AppData(databaseType, basePropsFilePath);
576 appDataDB = new Database();
577 appData.addDatabase(appDataDB);
578
579 FileReader fr = new FileReader(sqlFile);
580 BufferedReader br = new BufferedReader(fr);
581 SQLScanner scanner = new SQLScanner(br);
582
583 tokens = scanner.scan();
584
585 br.close();
586
587 while (hasTokens())
588 {
589 if (token == null)
590 {
591 next();
592 }
593
594 if (token.getStr().toUpperCase().equals("CREATE"))
595 {
596 create();
597 }
598 if (hasTokens())
599 {
600 next();
601 }
602 }
603 return appData;
604 }
605
606 /***
607 * Just 4 testing.
608 *
609 * @param args commandline args
610 * @throws Exception an exception
611 */
612 public static void main(String args[]) throws Exception
613 {
614 SQLToAppData s2a = new SQLToAppData(args[0]);
615 AppData ad = s2a.execute();
616 System.out.println(ad);
617 }
618 }
This page was automatically generated by Maven