1 package org.apache.torque.engine.database.transform;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
160 next();
161 if (!token.getStr().equals("("))
162 {
163 err("( expected");
164 }
165 next();
166
167 Table tbl = new Table (tableName);
168
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
194
195
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();
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();
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
396
397 if (columnType.toUpperCase().equals("ENUM"))
398 {
399 inEnum = true;
400 next();
401 while (!token.getStr().equals(")"))
402 {
403
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
423 next();
424 }
425 next();
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();
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 }