Source code: org/hsqldb/Database.java
1 /* Copyrights and Licenses
2 *
3 * This product includes Hypersonic SQL.
4 * Originally developed by Thomas Mueller and the Hypersonic SQL Group.
5 *
6 * Copyright (c) 1995-2000 by the Hypersonic SQL Group. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without modification, are permitted
8 * provided that the following conditions are met:
9 * - Redistributions of source code must retain the above copyright notice, this list of conditions
10 * and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright notice, this list of
12 * conditions and the following disclaimer in the documentation and/or other materials
13 * provided with the distribution.
14 * - All advertising materials mentioning features or use of this software must display the
15 * following acknowledgment: "This product includes Hypersonic SQL."
16 * - Products derived from this software may not be called "Hypersonic SQL" nor may
17 * "Hypersonic SQL" appear in their names without prior written permission of the
18 * Hypersonic SQL Group.
19 * - Redistributions of any form whatsoever must retain the following acknowledgment: "This
20 * product includes Hypersonic SQL."
21 * This software is provided "as is" and any expressed or implied warranties, including, but
22 * not limited to, the implied warranties of merchantability and fitness for a particular purpose are
23 * disclaimed. In no event shall the Hypersonic SQL Group or its contributors be liable for any
24 * direct, indirect, incidental, special, exemplary, or consequential damages (including, but
25 * not limited to, procurement of substitute goods or services; loss of use, data, or profits;
26 * or business interruption). However caused any on any theory of liability, whether in contract,
27 * strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this
28 * software, even if advised of the possibility of such damage.
29 * This software consists of voluntary contributions made by many individuals on behalf of the
30 * Hypersonic SQL Group.
31 *
32 *
33 * For work added by the HSQL Development Group:
34 *
35 * Copyright (c) 2001-2002, The HSQL Development Group
36 * All rights reserved.
37 *
38 * Redistribution and use in source and binary forms, with or without
39 * modification, are permitted provided that the following conditions are met:
40 *
41 * Redistributions of source code must retain the above copyright notice, this
42 * list of conditions and the following disclaimer, including earlier
43 * license statements (above) and comply with all above license conditions.
44 *
45 * Redistributions in binary form must reproduce the above copyright notice,
46 * this list of conditions and the following disclaimer in the documentation
47 * and/or other materials provided with the distribution, including earlier
48 * license statements (above) and comply with all above license conditions.
49 *
50 * Neither the name of the HSQL Development Group nor the names of its
51 * contributors may be used to endorse or promote products derived from this
52 * software without specific prior written permission.
53 *
54 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
58 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
59 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
60 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
61 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
62 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
63 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65 */
66
67
68 package org.hsqldb;
69
70 import java.sql.SQLException;
71 import java.sql.Types;
72 import java.util.Enumeration;
73 import java.util.Hashtable;
74 import java.util.Vector;
75
76 /**
77 * Database is the root class for HSQL Database Engine database. <p>
78 *
79 * Although it either directly or indirectly provides all or most of the
80 * services required for DBMS functionality, this class should not be used
81 * directly by an application. Instead, to achieve portability and
82 * generality, the jdbc* classes should be used.
83 *
84 * @version 1.7.0
85 */
86
87 // fredt@users 20020130 - patch 476694 by velichko - transaction savepoints
88 // additions to different parts to support savepoint transactions
89 // fredt@users 20020215 - patch 1.7.0 by fredt - new HsqlProperties class
90 // support use of properties from database.properties file
91 // fredt@users 20020218 - patch 1.7.0 by fredt - DEFAULT keyword
92 // support for default values for table columns
93 // fredt@users 20020305 - patch 1.7.0 - restructuring
94 // some methods move to Table.java, some removed
95 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP) - restructuring
96 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP) - error trapping
97 // boucherb@users 20020130 - patch 1.7.0 - use lookup for speed
98 // idents listed in alpha-order for easy check of stats...
99 // fredt@users 20020420 - patch523880 by leptipre@users - VIEW support
100 // fredt@users 20020430 - patch 549741 by velichko - ALTER TABLE RENAME
101 // fredt@users 20020405 - patch 1.7.0 by fredt - other ALTER TABLE statements
102 // boucherb@users - added javadoc comments
103 // tony_lai@users 20020820 - patch 595099 by tlai@users - use user define PK name
104 // tony_lai@users 20020820 - patch 595073 by tlai@users - duplicated exception msg
105 // tony_lai@users 20020820 - patch 595156 by tlai@users - violation of Integrity constraint name
106 // tony_lai@users 20020820 - patch 1.7.1 - modification to shutdown compact process to save memory usage
107 // boucherb@users 20020828 - patch 1.7.1 - allow reconnect to local db that has shutdown
108 // fredt@users 20020912 - patch 1.7.1 by fredt - drop duplicate name triggers
109 // fredt@users 20020912 - patch 1.7.1 by fredt - log alter statements
110 class Database {
111
112 private String sName;
113 private UserManager aAccess;
114 private Vector tTable;
115 private DatabaseInformation dInfo;
116 Logger logger;
117 boolean bReadOnly;
118 private boolean bShutdown;
119 private Hashtable hAlias;
120 private boolean bIgnoreCase;
121 private boolean bReferentialIntegrity;
122 private Vector cSession;
123 private HsqlDatabaseProperties databaseProperties;
124 private Session sysSession;
125 private static final int CALL = 1;
126 private static final int CHECKPOINT = 2;
127 private static final int COMMIT = 3;
128 private static final int CONNECT = 4;
129 private static final int CREATE = 5;
130 private static final int DELETE = 6;
131 private static final int DISCONNECT = 7;
132 private static final int DROP = 8;
133 private static final int GRANT = 9;
134 private static final int INSERT = 10;
135 private static final int REVOKE = 11;
136 private static final int ROLLBACK = 12;
137 private static final int SAVEPOINT = 13;
138 private static final int SCRIPT = 14;
139 private static final int SELECT = 15;
140 private static final int SET = 16;
141 private static final int SHUTDOWN = 17;
142 private static final int UPDATE = 18;
143 private static final int SEMICOLON = 19;
144 private static final int ALTER = 20;
145 private static final Hashtable hCommands = new Hashtable(37);
146
147 static {
148 hCommands.put("ALTER", new Integer(ALTER));
149 hCommands.put("CALL", new Integer(CALL));
150 hCommands.put("CHECKPOINT", new Integer(CHECKPOINT));
151 hCommands.put("COMMIT", new Integer(COMMIT));
152 hCommands.put("CONNECT", new Integer(CONNECT));
153 hCommands.put("CREATE", new Integer(CREATE));
154 hCommands.put("DELETE", new Integer(DELETE));
155 hCommands.put("DISCONNECT", new Integer(DISCONNECT));
156 hCommands.put("DROP", new Integer(DROP));
157 hCommands.put("GRANT", new Integer(GRANT));
158 hCommands.put("INSERT", new Integer(INSERT));
159 hCommands.put("REVOKE", new Integer(REVOKE));
160 hCommands.put("ROLLBACK", new Integer(ROLLBACK));
161 hCommands.put("SAVEPOINT", new Integer(SAVEPOINT));
162 hCommands.put("SCRIPT", new Integer(SCRIPT));
163 hCommands.put("SELECT", new Integer(SELECT));
164 hCommands.put("SET", new Integer(SET));
165 hCommands.put("SHUTDOWN", new Integer(SHUTDOWN));
166 hCommands.put("UPDATE", new Integer(UPDATE));
167 hCommands.put(";", new Integer(SEMICOLON));
168 }
169
170 /**
171 * Constructs a new Database object that mounts or creates the database
172 * files specified by the supplied name.
173 *
174 * @param name the path to and common name shared by the database files
175 * this Database uses
176 * @exception SQLException if the specified path and common name
177 * combination is illegal or unavailable, or the database files the
178 * name resolves to are in use by another process
179 */
180 Database(String name) throws SQLException {
181
182 if (Trace.TRACE) {
183 Trace.trace();
184 }
185
186 sName = name;
187
188 open();
189 }
190
191 /**
192 * Opens the database. The database can be opened by the constructor,
193 * or reopened by the close(int closemode) method during a
194 * "shutdown compact".
195 * @see close(int closemode)
196 */
197
198 // tony_lai@users 20020820
199 private void open() throws SQLException {
200
201 tTable = new Vector();
202 aAccess = new UserManager();
203 cSession = new Vector();
204 hAlias = new Hashtable();
205 logger = new Logger();
206 bReferentialIntegrity = true;
207
208 Library.register(hAlias);
209
210 dInfo = new DatabaseInformation(this, tTable, aAccess);
211
212 boolean newdatabase = false;
213
214 sysSession = new Session(this, new User(null, null, true, null),
215 true, false, 0);
216
217 registerSession(sysSession);
218
219 databaseProperties = new HsqlDatabaseProperties(sName);
220
221 if (sName.equals(".")) {
222 newdatabase = true;
223
224 databaseProperties.setProperty("sql.strict_fk", true);
225 } else {
226 newdatabase = logger.openLog(this, sysSession, sName);
227 }
228
229 HsqlName.sysNumber = 0;
230
231 Library.setSqlMonth(databaseProperties.isPropertyTrue("sql.month"));
232 Parser.setEnforceSize(
233 databaseProperties.isPropertyTrue("sql.enforce_size"));
234 Column.setCompareInLocal(
235 databaseProperties.isPropertyTrue("sql.compare_in_locale"));
236
237 Record.gcFrequency =
238 databaseProperties.getIntegerProperty("hsqldb.gc_interval", 0);
239
240 if (newdatabase) {
241 execute("CREATE USER SA PASSWORD \"\" ADMIN", sysSession);
242 }
243
244 aAccess.grant("PUBLIC", "CLASS \"java.lang.Math\"", UserManager.ALL);
245 aAccess.grant("PUBLIC", "CLASS \"org.hsqldb.Library\"",
246 UserManager.ALL);
247 }
248
249 /**
250 * Retrieves this Database object's name, as know to this Database
251 * object.
252 *
253 * @return this Database object's name
254 */
255 String getName() {
256 return sName;
257 }
258
259 /**
260 * Retrieves this Database object's properties.
261 *
262 * @return this Database object's properties object
263 */
264 HsqlDatabaseProperties getProperties() {
265 return databaseProperties;
266 }
267
268 /**
269 * isShutdown attribute getter.
270 *
271 * @return the value of this Database object's isShutdown attribute
272 */
273 boolean isShutdown() {
274 return bShutdown;
275 }
276
277 /**
278 * Constructs a new Session that operates within (is connected to) the
279 * context of this Database object. <p>
280 *
281 * If successful, the new Session object initially operates on behalf of
282 * the user specified by the supplied user name.
283 *
284 * @param username the name of the initial user of this session. The user
285 * must already exist in this Database object.
286 * @param password the password of the specified user. This must match
287 * the password, as known to this Database object, of the specified
288 * user
289 * @return a new Session object that initially that initially operates on
290 * behalf of the specified user
291 * @throws SQLException if the specified user does not exist or a bad
292 * password is specified
293 */
294 synchronized Session connect(String username,
295 String password) throws SQLException {
296
297 User user = aAccess.getUser(username.toUpperCase(),
298 password.toUpperCase());
299 int size = cSession.size();
300 int id = size;
301
302 for (int i = 0; i < size; i++) {
303 if (cSession.elementAt(i) == null) {
304 id = i;
305
306 break;
307 }
308 }
309
310 Session session = new Session(this, user, true, bReadOnly, id);
311
312 logger.writeToLog(session,
313 "CONNECT USER " + username + " PASSWORD \""
314 + password + "\"");
315 registerSession(session);
316
317 return session;
318 }
319
320 /**
321 * Binds the specified Session object into this Database object's active
322 * session registry. This method is typically called from {@link
323 * #connect} as the final step, when a successful connection has been
324 * made.
325 *
326 * @param session the Session object to register
327 */
328 void registerSession(Session session) {
329
330 int size = cSession.size();
331 int id = session.getId();
332
333 if (id >= size) {
334 cSession.setSize(id + 1);
335 }
336
337 cSession.setElementAt(session, id);
338 }
339
340 /**
341 * A specialized SQL statement executor, tailored for use by {@link
342 * WebServerConnection}. Calling this method fully connects the specified
343 * user, executes the specifed statement, and then disconects.
344 *
345 * @param user the name of the user for which to execute the specified
346 * statement. The user must already exist in this Database object.
347 * @param password the password of the specified user. This must match
348 * the password, as known to this Database object, of the specified
349 * user
350 * @param statement the SQL statement to execute
351 * @return the result of executing the specified statement, in a form
352 * already suitable for transmitting as part of an HTTP response.
353 */
354 byte[] execute(String user, String password, String statement) {
355
356 Result r = null;
357
358 try {
359 Session session = connect(user, password);
360
361 r = execute(statement, session);
362
363 execute("DISCONNECT", session);
364
365 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
366 } catch (SQLException e) {
367 r = new Result(e.getMessage(), e.getErrorCode());
368 } catch (Exception e) {
369 r = new Result(e.getMessage(), Trace.GENERAL_ERROR);
370 }
371
372 try {
373 return r.getBytes();
374 } catch (Exception e) {
375 return new byte[0];
376 }
377 }
378
379 /**
380 * The main SQL statement executor. <p>
381 *
382 * All requests to execute SQL statements against this Database object
383 * eventually go through this method.
384 *
385 * @param statement the SQL statement to execute
386 * @param session an object representing a connected user and a
387 * collection of session state attributes
388 * @return the result of executing the specified statement, in a form
389 * suitable for either wrapping in a local ResultSet object or for
390 * transmitting to a remote client via the native HSQLDB protocol
391 */
392 synchronized Result execute(String statement, Session session) {
393
394 if (Record.gcFrequency != 0
395 && Record.memoryRecords > Record.gcFrequency) {
396 System.gc();
397 Trace.printSystemOut("gc at " + Record.memoryRecords);
398
399 Record.memoryRecords = 0;
400 }
401
402 if (Trace.TRACE) {
403 Trace.trace(statement);
404 }
405
406 Result rResult = null;
407
408 try {
409 Tokenizer c = new Tokenizer(statement);
410 Parser p = new Parser(this, c, session);
411
412 logger.cleanUp();
413
414 if (Trace.DOASSERT) {
415 Trace.doAssert(!session.isNestedTransaction());
416 }
417
418 Trace.check(session != null, Trace.ACCESS_IS_DENIED);
419 Trace.check(!bShutdown, Trace.DATABASE_IS_SHUTDOWN);
420
421 while (true) {
422 c.setPartMarker();
423
424 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
425 session.setScripting(false);
426
427 String sToken = c.getString();
428
429 if (sToken.length() == 0) {
430 break;
431 }
432
433 // boucherb@users 20020306 - patch 1.7.0 - use lookup for tokens
434 Integer command = (Integer) hCommands.get(sToken);
435
436 if (command == null) {
437 throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
438 }
439
440 int cmd = command.intValue();
441
442 switch (cmd) {
443
444 case SELECT :
445 rResult = p.processSelect();
446 break;
447
448 case INSERT :
449 rResult = p.processInsert();
450 break;
451
452 case UPDATE :
453 rResult = p.processUpdate();
454 break;
455
456 case DELETE :
457 rResult = p.processDelete();
458 break;
459
460 case CALL :
461 rResult = p.processCall();
462 break;
463
464 case SET :
465 rResult = processSet(c, session);
466 break;
467
468 case COMMIT :
469 rResult = processCommit(c, session);
470
471 session.setScripting(true);
472 break;
473
474 case ROLLBACK :
475 rResult = processRollback(c, session);
476
477 session.setScripting(true);
478 break;
479
480 case SAVEPOINT :
481 rResult = processSavepoint(c, session);
482
483 session.setScripting(true);
484 break;
485
486 case CREATE :
487 rResult = processCreate(c, session);
488 break;
489
490 case ALTER :
491 rResult = processAlter(c, session);
492 break;
493
494 case DROP :
495 rResult = processDrop(c, session);
496 break;
497
498 case GRANT :
499 rResult = processGrantOrRevoke(c, session, true);
500 break;
501
502 case REVOKE :
503 rResult = processGrantOrRevoke(c, session, false);
504 break;
505
506 case CONNECT :
507 rResult = processConnect(c, session);
508 break;
509
510 case DISCONNECT :
511 rResult = processDisconnect(session);
512 break;
513
514 case SCRIPT :
515 rResult = processScript(c, session);
516 break;
517
518 case SHUTDOWN :
519 rResult = processShutdown(c, session);
520 break;
521
522 case CHECKPOINT :
523 rResult = processCheckpoint(session);
524 break;
525
526 case SEMICOLON :
527 break;
528 }
529
530 if (session.getScripting()) {
531 logger.writeToLog(session, c.getLastPart());
532 }
533 }
534 } catch (SQLException e) {
535
536 // e.printStackTrace();
537 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
538 // tony_lai@users 20020820 - patch 595073
539 // rResult = new Result(Trace.getMessage(e) + " in statement ["
540 rResult = new Result(e.getMessage() + " in statement ["
541 + statement + "]", e.getErrorCode());
542 } catch (Exception e) {
543 e.printStackTrace();
544
545 String s = Trace.getMessage(Trace.GENERAL_ERROR) + " " + e;
546
547 rResult = new Result(s + " in statement [" + statement + "]",
548 Trace.GENERAL_ERROR);
549 } catch (java.lang.OutOfMemoryError e) {
550 e.printStackTrace();
551
552 rResult = new Result("out of memory", Trace.GENERAL_ERROR);
553 }
554
555 return rResult == null ? new Result()
556 : rResult;
557 }
558
559 /**
560 * Puts this Database object in global read-only mode. That is, after
561 * this call, all existing and future sessions are limited to read-only
562 * transactions. Any following attempts to update the state of the
563 * database will result in throwing a SQLException.
564 */
565 void setReadOnly() {
566 bReadOnly = true;
567 }
568
569 /**
570 * Retrieves a Vector containing references to all registered non-system
571 * tables and views. This includes all tables and views registered with
572 * this Database object via a call to {@link #linkTable linkTable}.
573 *
574 * @return a Vector of all registered non-system tables and views
575 */
576 Vector getTables() {
577 return tTable;
578 }
579
580 /**
581 * Retrieves the UserManager object for this Database.
582 *
583 * @return UserManager object
584 */
585 UserManager getUserManager() {
586 return aAccess;
587 }
588
589 /**
590 * isReferentialIntegrity attribute setter.
591 *
592 * @param ref if true, this Database object enforces referential
593 * integrity, else not
594 */
595 void setReferentialIntegrity(boolean ref) {
596 bReferentialIntegrity = ref;
597 }
598
599 /**
600 * isReferentialIntegrity attribute getter.
601 *
602 * @return indicates whether this Database object is currently enforcing
603 * referential integrity
604 */
605 boolean isReferentialIntegrity() {
606 return bReferentialIntegrity;
607 }
608
609 /**
610 * Retrieves a map from Java method-call name aliases to the
611 * fully-qualified names of the Java methods themsleves.
612 *
613 * @return a map in the form of a Hashtable
614 */
615 Hashtable getAlias() {
616 return hAlias;
617 }
618
619 /**
620 * Retieves a Java method's fully qualified name, given a String that is
621 * supposedly an alias for it. <p>
622 *
623 * This is somewhat of a misnomer, since it is not an alias that is being
624 * retrieved, but rather what the supplied alias maps to. If the
625 * specified alias does not map to any registered Java method
626 * fully-qualified name, then the specified String itself is returned.
627 *
628 * @param s a call name alias that supposedly maps to a registered Java
629 * method
630 * @return a Java method fully-qualified name, or null if no method is
631 * registered with the given alias
632 */
633 String getAlias(String s) {
634
635 String alias = (String) hAlias.get(s);
636
637 return (alias == null) ? s
638 : alias;
639 }
640
641 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
642 // temp tables should be accessed by the owner and not scripted in the log
643
644 /**
645 * Retrieves the specified user defined table or view visible within the
646 * context of the specified Session, or any system table of the given
647 * name. This excludes any temp tables created in different Sessions.
648 *
649 * @param name of the table or view to retrieve
650 * @param session the Session within which to search for user tables
651 * @return the user table or view, or system table
652 * @throws SQLException if there is no such table or view
653 */
654 Table getTable(String name, Session session) throws SQLException {
655
656 Table t = findUserTable(name, session);
657
658 if (t == null) {
659 t = dInfo.getSystemTable(name, session);
660 }
661
662 if (t == null) {
663 throw Trace.error(Trace.TABLE_NOT_FOUND, name);
664 }
665
666 return t;
667 }
668
669 /**
670 * get a user
671 *
672 * @param name
673 * @param session
674 * @return
675 * @throws SQLException
676 */
677 Table getUserTable(String name, Session session) throws SQLException {
678
679 Table t = findUserTable(name, session);
680
681 if (t == null) {
682 throw Trace.error(Trace.TABLE_NOT_FOUND, name);
683 }
684
685 return t;
686 }
687
688 Table getUserTable(String name) throws SQLException {
689
690 Table t = findUserTable(name);
691
692 if (t == null) {
693 throw Trace.error(Trace.TABLE_NOT_FOUND, name);
694 }
695
696 return t;
697 }
698
699 Table findUserTable(String name) {
700
701 for (int i = 0, tsize = tTable.size(); i < tsize; i++) {
702 Table t = (Table) tTable.elementAt(i);
703
704 if (t.equals(name)) {
705 return t;
706 }
707 }
708
709 return null;
710 }
711
712 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
713 Table findUserTable(String name, Session session) {
714
715 for (int i = 0, tsize = tTable.size(); i < tsize; i++) {
716 Table t = (Table) tTable.elementAt(i);
717
718 if (t.equals(name, session)) {
719 return t;
720 }
721 }
722
723 return null;
724 }
725
726 /**
727 * Generates a SQL script containing all or part of the SQL statements
728 * required to recreate the current state of this Database object.
729 *
730 * @param drop if true, include drop statements for each droppable
731 * database object
732 * @param insert if true, include the insert statements required to
733 * populate each of this Database object's memory tables to match
734 * their current state.
735 * @param cached if true, include the insert statement required to
736 * populate each of this Database object's CACHED tables to match
737 * their current state.
738 * @param session the Session in which to generate the requested SQL
739 * script
740 * @return A Result object consisting of one VARCHAR column with a row
741 * for each statement in the generated script
742 * @throws SQLException if the specified Session's currently connected
743 * User does not have the right to call this method or there is some
744 * problem generating the result
745 */
746 Result getScript(boolean drop, boolean insert, boolean cached,
747 Session session) throws SQLException {
748 return DatabaseScript.getScript(this, drop, insert, cached, session);
749 }
750
751 /**
752 * Attempts to register the specified table or view with this Database
753 * object.
754 *
755 * @param t the table of view to register
756 * @throws SQLException if there is a problem
757 */
758 void linkTable(Table t) throws SQLException {
759 tTable.addElement(t);
760 }
761
762 /**
763 * isIgnoreCase attribute getter.
764 *
765 * @return the value of this Database object's isIgnoreCase attribute
766 */
767 boolean isIgnoreCase() {
768 return bIgnoreCase;
769 }
770
771 /**
772 * Responsible for parsing and executing the SCRIPT SQL statement
773 *
774 * @param c the tokenized representation of the statement being processed
775 * @param session
776 * @return
777 * @throws SQLException
778 */
779 private Result processScript(Tokenizer c,
780 Session session) throws SQLException {
781
782 String sToken = c.getString();
783
784 if (c.wasValue()) {
785 sToken = (String) c.getAsValue();
786
787 Log.scriptToFile(this, sToken, true, session);
788
789 return new Result();
790 } else {
791 c.back();
792
793 // fredt@users - patch 1.7.0 - no DROP TABLE statements with SCRIPT command
794 // try to script all but drop, insert; but no positions for cached tables
795 return getScript(false, true, false, session);
796 }
797 }
798
799 /**
800 * Responsible for handling the parse and execution of CREATE SQL
801 * statements.
802 *
803 * @param c the tokenized representation of the statement being processed
804 * @param session
805 * @return
806 * @throws SQLException
807 */
808 private Result processCreate(Tokenizer c,
809 Session session) throws SQLException {
810
811 session.checkReadWrite();
812 session.checkAdmin();
813
814 String sToken = c.getString();
815
816 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
817 boolean isTemp = false;
818
819 if (sToken.equals("TEMP")) {
820 isTemp = true;
821 sToken = c.getString();
822
823 Trace.check(sToken.equals("TABLE") || sToken.equals("MEMORY")
824 || sToken.equals("TEXT"), Trace.UNEXPECTED_TOKEN,
825 sToken);
826 session.setScripting(false);
827 } else {
828 session.checkReadWrite();
829 session.checkAdmin();
830 session.setScripting(true);
831 }
832
833 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
834 if (sToken.equals("TABLE")) {
835 int tableType = isTemp ? Table.TEMP_TABLE
836 : Table.MEMORY_TABLE;
837
838 processCreateTable(c, session, tableType);
839 } else if (sToken.equals("MEMORY")) {
840 c.getThis("TABLE");
841
842 int tableType = isTemp ? Table.TEMP_TABLE
843 : Table.MEMORY_TABLE;
844
845 processCreateTable(c, session, tableType);
846 } else if (sToken.equals("CACHED")) {
847 c.getThis("TABLE");
848 processCreateTable(c, session, Table.CACHED_TABLE);
849 } else if (sToken.equals("TEXT")) {
850 c.getThis("TABLE");
851
852 int tableType = isTemp ? Table.TEMP_TEXT_TABLE
853 : Table.TEXT_TABLE;
854
855 processCreateTable(c, session, tableType);
856 } else if (sToken.equals("VIEW")) {
857 processCreateView(c, session);
858 } else if (sToken.equals("TRIGGER")) {
859 processCreateTrigger(c, session);
860 } else if (sToken.equals("USER")) {
861 String u = c.getStringToken();
862
863 c.getThis("PASSWORD");
864
865 String p = c.getStringToken();
866 boolean admin;
867
868 if (c.getString().equals("ADMIN")) {
869 admin = true;
870 } else {
871 admin = false;
872 }
873
874 aAccess.createUser(u, p, admin);
875 } else if (sToken.equals("ALIAS")) {
876 String name = c.getString();
877
878 sToken = c.getString();
879
880 Trace.check(sToken.equals("FOR"), Trace.UNEXPECTED_TOKEN, sToken);
881
882 sToken = c.getString();
883
884 // fredt@users 20010701 - patch 1.6.1 by fredt - open <1.60 db files
885 // convert org.hsql.Library aliases from versions < 1.60 to org.hsqldb
886 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP) - ABS function
887 if (sToken.startsWith("org.hsql.Library.")) {
888 sToken = "org.hsqldb.Library."
889 + sToken.substring("org.hsql.Library.".length());
890 } else if (sToken.equals("java.lang.Math.abs")) {
891 sToken = "org.hsqldb.Library.abs";
892 }
893
894 hAlias.put(name, sToken);
895 } else {
896 boolean unique = false;
897
898 if (sToken.equals("UNIQUE")) {
899 unique = true;
900 sToken = c.getString();
901 }
902
903 if (!sToken.equals("INDEX")) {
904 throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
905 }
906
907 String name = c.getName();
908 boolean isnamequoted = c.wasQuotedIdentifier();
909
910 c.getThis("ON");
911
912 Table t = getTable(c.getName(), session);
913
914 addIndexOn(c, session, name, isnamequoted, t, unique);
915 }
916
917 return new Result();
918 }
919
920 /**
921 * Process a bracketed column list as used in the declaration of SQL
922 * CONSTRAINTS and return an array containing the indexes of the columns
923 * within the table.
924 *
925 * @param c
926 * @param t table that contains the columns
927 * @return
928 * @throws SQLException if a column is not found or is duplicated
929 */
930 private int[] processColumnList(Tokenizer c,
931 Table t) throws SQLException {
932
933 Vector v = new Vector();
934 Hashtable h = new Hashtable();
935
936 c.getThis("(");
937
938 while (true) {
939 String colname = c.getName();
940
941 v.addElement(colname);
942 h.put(colname, colname);
943
944 String sToken = c.getString();
945
946 if (sToken.equals(")")) {
947 break;
948 }
949
950 if (!sToken.equals(",")) {
951 throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
952 }
953 }
954
955 int s = v.size();
956
957 if (s != h.size()) {
958 throw Trace.error(Trace.COLUMN_ALREADY_EXISTS,
959 "duplicate column in list");
960 }
961
962 int col[] = new int[s];
963
964 for (int i = 0; i < s; i++) {
965 col[i] = t.getColumnNr((String) v.elementAt(i));
966 }
967
968 return col;
969 }
970
971 /**
972 * Indexes defined in DDL scripts are handled by this method. If the
973 * name of an existing index begins with "SYS_", the name is changed to
974 * begin with "USER_". The name should be unique within the database.
975 * For compatibility with old database, non-unique names are modified
976 * and assigned a new name<br>
977 * (fredt@users)
978 *
979 * @param c
980 * @param session
981 * @param name
982 * @param t
983 * @param unique
984 * @param namequoted The feature to be added to the IndexOn attribute
985 * @throws SQLException
986 */
987 private void addIndexOn(Tokenizer c, Session session, String name,
988 boolean namequoted, Table t,
989 boolean unique) throws SQLException {
990
991 HsqlName indexname;
992 int col[] = processColumnList(c, t);
993
994 if (HsqlName.isReservedName(name)) {
995 indexname = HsqlName.makeAutoName("USER", name);
996 } else {
997 indexname = new HsqlName(name, namequoted);
998 }
999
1000// fredt@users - to check further - this is confined only to old scripts
1001// rename duplicate indexes
1002/*
1003 if (findIndex(name) != null && session == sysSession
1004 && databaseProperties.getProperty("hsqldb.compatible_version")
1005 .equals("1.6.0")) {
1006 indexname = HsqlName.makeAutoName("USER", name);
1007 name = indexname.name;
1008 }
1009*/
1010 if (findIndex(name) != null) {
1011 throw Trace.error(Trace.INDEX_ALREADY_EXISTS);
1012 }
1013
1014 session.commit();
1015 session.setScripting(!t.isTemp());
1016
1017 TableWorks tw = new TableWorks(t);
1018
1019 tw.createIndex(col, indexname, unique);
1020 }
1021
1022 /**
1023 * Finds an index with the given name in the whole database.
1024 *
1025 * @param name Description of the Parameter
1026 * @return Description of the Return Value
1027 */
1028 private Index findIndex(String name) {
1029
1030 Table t = findTableForIndex(name);
1031
1032 if (t == null) {
1033 return null;
1034 } else {
1035 return t.getIndex(name);
1036 }
1037 }
1038
1039 /**
1040 * Finds the table that has an index with the given name in the
1041 * whole database.
1042 *
1043 * @param name Description of the Parameter
1044 * @return Description of the Return Value
1045 */
1046 private Table findTableForIndex(String name) {
1047
1048 for (int i = 0, tsize = tTable.size(); i < tsize; i++) {
1049 Table t = (Table) tTable.elementAt(i);
1050
1051 if (t.getIndex(name) != null) {
1052 return t;
1053 }
1054 }
1055
1056 return null;
1057 }
1058
1059 /**
1060 * Retrieves the index of a table or view in the Vector that contains
1061 * these objects for a Database.
1062 *
1063 * @param table the Table object
1064 * @return the index of the specified table or view, or -1 if not found
1065 */
1066 int getTableIndex(Table table) {
1067
1068 for (int i = 0, tsize = tTable.size(); i < tsize; i++) {
1069 Table t = (Table) tTable.elementAt(i);
1070
1071 if (t == table) {
1072 return i;
1073 }
1074 }
1075
1076 return -1;
1077 }
1078
1079 /**
1080 * Responsible for handling the execution of CREATE TRIGGER SQL
1081 * statements. <p>
1082 *
1083 * typical sql is: CREATE TRIGGER tr1 AFTER INSERT ON tab1 CALL "pkg.cls"
1084 *
1085 * @param c the tokenized representation of the statement being processed
1086 * @param session
1087 * @throws SQLException
1088 */
1089 private void processCreateTrigger(Tokenizer c,
1090 Session session) throws SQLException {
1091
1092 Table t;
1093 boolean bForEach = false;
1094 boolean bNowait = false;
1095 int nQueueSize = TriggerDef.getDefaultQueueSize();
1096 String sTrigName = c.getName();
1097 String sWhen = c.getString();
1098 String sOper = c.getString();
1099
1100 c.getThis("ON");
1101
1102 String sTableName = c.getString();
1103
1104 t = getTable(sTableName, session);
1105
1106 if (t.isView()) {
1107 throw Trace.error(Trace.NOT_A_TABLE);
1108 }
1109
1110// fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
1111 session.setScripting(!t.isTemp());
1112
1113 // "FOR EACH ROW" or "CALL"
1114 String tok = c.getString();
1115
1116 if (tok.equals("FOR")) {
1117 tok = c.getString();
1118
1119 if (tok.equals("EACH")) {
1120 tok = c.getString();
1121
1122 if (tok.equals("ROW")) {
1123 bForEach = true;
1124 tok = c.getString(); // should be 'NOWAIT' or 'QUEUE' or 'CALL'
1125 } else {
1126 throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, tok);
1127 }
1128 } else {
1129 throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, tok);
1130 }
1131 }
1132
1133 if (tok.equals("NOWAIT")) {
1134 bNowait = true;
1135 tok = c.getString(); // should be 'CALL' or 'QUEUE'
1136 }
1137
1138 if (tok.equals("QUEUE")) {
1139 nQueueSize = Integer.parseInt(c.getString());
1140 tok = c.getString(); // should be 'CALL'
1141 }
1142
1143 if (!tok.equals("CALL")) {
1144 throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, tok);
1145 }
1146
1147 String sClassName = c.getString(); // double quotes have been stripped
1148 TriggerDef td;
1149 Trigger o;
1150
1151 try {
1152 Class cl = Class.forName(sClassName); // dynamically load class
1153
1154 o = (Trigger) cl.newInstance(); // dynamically instantiate it
1155 td = new TriggerDef(sTrigName, sWhen, sOper, bForEach, t, o,
1156 "\"" + sClassName + "\"", bNowait,
1157 nQueueSize);
1158
1159 if (td.isValid()) {
1160 t.addTrigger(td);
1161 td.start(); // start the trigger thread
1162 } else {
1163 String msg = "Error in parsing trigger command ";
1164
1165 throw Trace.error(Trace.UNEXPECTED_TOKEN, msg);
1166 }
1167 } catch (Exception e) {
1168 String msg = "Exception in loading trigger class "
1169 + e.getMessage();
1170
1171 throw Trace.error(Trace.UNKNOWN_FUNCTION, msg);
1172 }
1173 }
1174
1175 /**
1176 * Responsible for handling the creation of table columns during the
1177 * process of executing CREATE TABLE statements.
1178 *
1179 * @param c the tokenized representation of the statement being processed
1180 * @param t target table
1181 * @return
1182 * @throws SQLException
1183 */
1184 private Column processCreateColumn(Tokenizer c,
1185 Table t) throws SQLException {
1186
1187 boolean identity = false;
1188 boolean primarykey = false;
1189 String sToken = c.getString();
1190 String sColumn = sToken;
1191 boolean isnamequoted = c.wasQuotedIdentifier();
1192 String typestring = c.getString();
1193 int iType = Column.getTypeNr(typestring);
1194
1195 Trace.check(!sColumn.equals(Table.DEFAULT_PK),
1196 Trace.COLUMN_ALREADY_EXISTS, sColumn);
1197
1198 if (typestring.equals("IDENTITY")) {
1199 identity = true;
1200 primarykey = true;
1201 }
1202
1203 if (iType == Types.VARCHAR && bIgnoreCase) {
1204 iType = Column.VARCHAR_IGNORECASE;
1205 }
1206
1207 sToken = c.getString();
1208
1209 if (iType == Types.DOUBLE && sToken.equals("PRECISION")) {
1210 sToken = c.getString();
1211 }
1212
1213// fredt@users 20020130 - patch 491987 by jimbag@users
1214 String sLen = "";
1215
1216 if (sToken.equals("(")) {
1217
1218 // read length
1219 do {
1220 sToken = c.getString();
1221
1222 if (!sToken.equals(")")) {
1223 sLen += sToken;
1224 }
1225 } while (!sToken.equals(")"));
1226
1227 sToken = c.getString();
1228 }
1229
1230 int iLen = 0;
1231 int iScale = 0;
1232
1233 // see if we have a scale specified
1234 int index;
1235
1236 if ((index = sLen.indexOf(",")) != -1) {
1237 String sScale = sLen.substring(index + 1, sLen.length());
1238
1239 sLen = sLen.substring(0, index);
1240
1241 try {
1242 iScale = Integer.parseInt(sScale.trim());
1243 } catch (NumberFormatException ne) {
1244 throw Trace.error(Trace.UNEXPECTED_TOKEN, sLen);
1245 }
1246 }
1247
1248 // convert the length
1249 if (sLen.trim().length() > 0) {
1250 try {
1251 iLen = Integer.parseInt(sLen.trim());
1252 } catch (NumberFormatException ne) {
1253 throw Trace.error(Trace.UNEXPECTED_TOKEN, sLen);
1254 }
1255 }
1256
1257 String defaultvalue = null;
1258
1259 if (sToken.equals("DEFAULT")) {
1260 String s = c.getString();
1261
1262 if (c.wasValue() && iType != Types.BINARY
1263 && iType != Types.OTHER) {
1264 Object sv = c.getAsValue();
1265
1266 if (sv != null) {
1267 defaultvalue = String.valueOf(sv);
1268
1269 try {
1270 Column.convertObject(defaultvalue, iType);
1271 } catch (Exception e) {
1272 throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE,
1273 defaultvalue);
1274 }
1275
1276 String testdefault =
1277 (String) Parser.enforceSize(defaultvalue, iType,
1278 iLen, false);
1279
1280 if (defaultvalue.equals(testdefault) == false) {
1281 throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE,
1282 defaultvalue);
1283 }
1284 }
1285 } else {
1286 throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE, s);
1287 }
1288
1289 sToken = c.getString();
1290 }
1291
1292 boolean nullable = true;
1293
1294 if (sToken.equals("NULL")) {
1295 sToken = c.getString();
1296 } else if (sToken.equals("NOT")) {
1297 c.getThis("NULL");
1298
1299 nullable = false;
1300 sToken = c.getString();
1301 }
1302
1303 if (sToken.equals("IDENTITY")) {
1304 identity = true;
1305 sToken = c.getString();
1306 primarykey = true;
1307 }
1308
1309 if (sToken.equals("PRIMARY")) {
1310 c.getThis("KEY");
1311
1312 primarykey = true;
1313 } else {
1314 c.back();
1315 }
1316
1317 return new Column(new HsqlName(sColumn, isnamequoted), nullable,
1318 iType, iLen, iScale, identity, primarykey,
1319 defaultvalue);
1320 }
1321
1322// fredt@users 20020225 - patch 509002 by fredt
1323// temporary attributes for constraints used in processCreateTable()
1324
1325 /**
1326 * temporary attributes for constraints used in processCreateTable()
1327 */
1328 private class TempConstraint {
1329
1330 HsqlName name;
1331 int[] localCol;
1332 Table expTable;
1333 int[] expCol;
1334 int type;
1335 boolean cascade;
1336
1337 TempConstraint(HsqlName name, int[] localCol, Table expTable,
1338 int[] expCol, int type, boolean cascade) {
1339
1340 this.name = name;
1341 this.type = type;
1342 this.localCol = localCol;
1343 this.expTable = expTable;
1344 this.expCol = expCol;
1345 this.cascade = cascade;
1346 }
1347 }
1348
1349// fredt@users 20020225 - patch 509002 by fredt
1350// process constraints after parsing to include primary keys defined as
1351// constraints
1352// fredt@users 20020225 - patch 489777 by fredt
1353// better error trapping
1354// fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
1355
1356 /**
1357 * Responsible for handling the execution CREATE TABLE SQL statements.
1358 *
1359 * @param c
1360 * @param session
1361 * @param type Description of the Parameter
1362 * @throws SQLException
1363 */
1364 private void processCreateTable(Tokenizer c, Session session,
1365 int type) throws SQLException {
1366
1367 Table t;
1368 String sToken = c.getName();
1369 boolean isnamequoted = c.wasQuotedIdentifier();
1370
1371 if (DatabaseInformation.isSystemTable(sToken)
1372 || findUserTable(sToken, session) != null) {
1373 throw Trace.error(Trace.TABLE_ALREADY_EXISTS, sToken);
1374 }
1375
1376 if (type == Table.TEMP_TEXT_TABLE || type == Table.TEXT_TABLE) {
1377 t = new TextTable(this, new HsqlName(sToken, isnamequoted), type,
1378 session);
1379 } else {
1380 t = new Table(this, new HsqlName(sToken, isnamequoted), type,
1381 session);
1382 }
1383
1384 c.getThis("(");
1385
1386 int[] primarykeycolumn = null;
1387 int column = 0;
1388 boolean constraint = false;
1389
1390 while (true) {
1391 sToken = c.getString();
1392 isnamequoted = c.wasQuotedIdentifier();
1393
1394// fredt@users 20020225 - comment
1395// we can check here for reserved words used with quotes as column names
1396 if (sToken.equals("CONSTRAINT") || sToken.equals("PRIMARY")
1397 || sToken.equals("FOREIGN") || sToken.equals("UNIQUE")) {
1398 c.back();
1399
1400 constraint = true;
1401
1402 break;
1403 }
1404
1405 c.back();
1406
1407 Column newcolumn = processCreateColumn(c, t);
1408