Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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