1 /* Copyright (c) 2001-2016, The HSQL Development Group
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer.
9  *
10  * Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * Neither the name of the HSQL Development Group nor the names of its
15  * contributors may be used to endorse or promote products derived from this
16  * software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
22  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (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  */
30 
31 
32 package org.hsqldb;
33 
34 import org.hsqldb.error.Error;
35 import org.hsqldb.error.ErrorCode;
36 import org.hsqldb.lib.StringConverter;
37 import org.hsqldb.rights.Grantee;
38 
39 import java.util.concurrent.atomic.AtomicInteger;
40 
41 /**
42  * Provides Name Management for SQL objects. <p>
43  *
44  * This class now includes the HsqlName class introduced in 1.7.1 and improves
45  * auto-naming with multiple databases in the engine.<p>
46  *
47  * Methods check user defined names and issue system generated names
48  * for SQL objects.<p>
49  *
50  * This class does not deal with the type of the SQL object for which it
51  * is used.<p>
52  *
53  * Some names beginning with SYS_ are reserved for system generated names.
54  * These are defined in isReserveName(String name) and created by the
55  * makeAutoName(String type) factory method<p>
56  *
57  * sysNumber is used to generate system-generated names. It is
58  * set to the largest integer encountered in names that use the
59  * SYS_xxxxxxx_INTEGER format. As the DDL is processed before any ALTER
60  * command, any new system generated name will have a larger integer suffix
61  * than all the existing names.
62  *
63  * @author Fred Toussi (fredt@users dot sourceforge.net)
64  * @version 2.3.2
65  * @since 1.7.2
66  */
67 public final class HsqlNameManager {
68 
69     private static final HsqlNameManager staticManager = new HsqlNameManager();
70 
71     static {
72         staticManager.serialNumber.set(Integer.MIN_VALUE);
73     }
74 
75     private static final HsqlName[] autoColumnNames       = new HsqlName[32];
76     private static final String[]   autoNoNameColumnNames = new String[32];
77 
78     static {
79         for (int i = 0; i < autoColumnNames.length; i++) {
80             autoColumnNames[i] = new HsqlName(staticManager, "C" + (i + 1), 0,
81                                               false);
82             autoNoNameColumnNames[i] = String.valueOf(i);
83         }
84     }
85 
86     private AtomicInteger serialNumber = new AtomicInteger(1);    // 0 is reserved in lookups
87     private int      sysNumber = 10000;                           // avoid name clash in older scripts
88     private HsqlName catalogName;
89     private boolean  sqlRegularNames;
90     HsqlName         subqueryTableName;
91 
HsqlNameManager()92     public HsqlNameManager() {
93         sqlRegularNames = true;
94     }
95 
HsqlNameManager(Database database)96     public HsqlNameManager(Database database) {
97 
98         catalogName = new HsqlName(this, SqlInvariants.DEFAULT_CATALOG_NAME,
99                                    SchemaObject.CATALOG, false);
100         sqlRegularNames = database.sqlRegularNames;
101         subqueryTableName = new HsqlName(this, SqlInvariants.SYSTEM_SUBQUERY,
102                                          false, SchemaObject.TABLE);
103         subqueryTableName.schema = SqlInvariants.SYSTEM_SCHEMA_HSQLNAME;
104     }
105 
getCatalogName()106     public HsqlName getCatalogName() {
107         return catalogName;
108     }
109 
setSqlRegularNames(boolean value)110     public void setSqlRegularNames(boolean value) {
111         sqlRegularNames = value;
112     }
113 
newSystemObjectName(String name, int type)114     public static HsqlName newSystemObjectName(String name, int type) {
115         return new HsqlName(staticManager, name, type, false);
116     }
117 
newInfoSchemaColumnName(String name, HsqlName table)118     public static HsqlName newInfoSchemaColumnName(String name,
119             HsqlName table) {
120 
121         HsqlName hsqlName = new HsqlName(staticManager, name, false,
122                                          SchemaObject.COLUMN);
123 
124         hsqlName.schema = SqlInvariants.INFORMATION_SCHEMA_HSQLNAME;
125         hsqlName.parent = table;
126 
127         return hsqlName;
128     }
129 
newInfoSchemaTableName(String name)130     public static HsqlName newInfoSchemaTableName(String name) {
131 
132         HsqlName hsqlName = new HsqlName(staticManager, name,
133                                          SchemaObject.TABLE, false);
134 
135         hsqlName.schema = SqlInvariants.INFORMATION_SCHEMA_HSQLNAME;
136 
137         return hsqlName;
138     }
139 
newInfoSchemaObjectName(String name, boolean isQuoted, int type)140     public static HsqlName newInfoSchemaObjectName(String name,
141             boolean isQuoted, int type) {
142 
143         HsqlName hsqlName = new HsqlName(staticManager, name, type, isQuoted);
144 
145         hsqlName.schema = SqlInvariants.INFORMATION_SCHEMA_HSQLNAME;
146 
147         return hsqlName;
148     }
149 
newHsqlName(HsqlName schema, String name, int type)150     public HsqlName newHsqlName(HsqlName schema, String name, int type) {
151 
152         HsqlName hsqlName = new HsqlName(this, name, type, false);
153 
154         hsqlName.schema = schema;
155 
156         return hsqlName;
157     }
158 
159     //
newHsqlName(String name, boolean isquoted, int type)160     public HsqlName newHsqlName(String name, boolean isquoted, int type) {
161         return new HsqlName(this, name, isquoted, type);
162     }
163 
newHsqlName(HsqlName schema, String name, boolean isquoted, int type)164     public HsqlName newHsqlName(HsqlName schema, String name,
165                                 boolean isquoted, int type) {
166 
167         HsqlName hsqlName = new HsqlName(this, name, isquoted, type);
168 
169         hsqlName.schema = schema;
170 
171         return hsqlName;
172     }
173 
newHsqlName(HsqlName schema, String name, boolean isquoted, int type, HsqlName parent)174     public HsqlName newHsqlName(HsqlName schema, String name,
175                                 boolean isquoted, int type, HsqlName parent) {
176 
177         HsqlName hsqlName = new HsqlName(this, name, isquoted, type);
178 
179         hsqlName.schema = schema;
180         hsqlName.parent = parent;
181 
182         return hsqlName;
183     }
184 
newColumnSchemaHsqlName(HsqlName table, SimpleName name)185     public HsqlName newColumnSchemaHsqlName(HsqlName table, SimpleName name) {
186         return newColumnHsqlName(table, name.name, name.isNameQuoted);
187     }
188 
newColumnHsqlName(HsqlName table, String name, boolean isquoted)189     public HsqlName newColumnHsqlName(HsqlName table, String name,
190                                       boolean isquoted) {
191 
192         HsqlName hsqlName = new HsqlName(this, name, isquoted,
193                                          SchemaObject.COLUMN);
194 
195         hsqlName.schema = table.schema;
196         hsqlName.parent = table;
197 
198         return hsqlName;
199     }
200 
201     /**
202      * Same name string but different objects and serial number
203      */
getSubqueryTableName()204     public HsqlName getSubqueryTableName() {
205         return subqueryTableName;
206     }
207 
208     /**
209      * Auto names are used for autogenerated indexes or anonymous constraints.
210      */
newAutoName(String prefix, HsqlName schema, HsqlName parent, int type)211     public HsqlName newAutoName(String prefix, HsqlName schema,
212                                 HsqlName parent, int type) {
213 
214         HsqlName name = newAutoName(prefix, (String) null, schema, parent,
215                                     type);
216 
217         return name;
218     }
219 
newSpecificRoutineName(HsqlName name)220     public HsqlName newSpecificRoutineName(HsqlName name) {
221 
222         StringBuffer sb = new StringBuffer();
223 
224         sb.append(name.name).append('_').append(++sysNumber);
225 
226         HsqlName hsqlName = new HsqlName(this, sb.toString(),
227                                          SchemaObject.SPECIFIC_ROUTINE,
228                                          name.isNameQuoted);
229 
230         hsqlName.parent = name;
231         hsqlName.schema = name.schema;
232 
233         return hsqlName;
234     }
235 
236     /**
237      * Column index i is 0 based, returns 1 based numbered column.
238      */
getAutoColumnName(int i)239     public static HsqlName getAutoColumnName(int i) {
240 
241         if (i < autoColumnNames.length) {
242             return autoColumnNames[i];
243         }
244 
245         return new HsqlName(staticManager, "C_" + (i + 1),
246                             SchemaObject.COLUMN, false);
247     }
248 
getAutoNoNameColumnString(int i)249     public static String getAutoNoNameColumnString(int i) {
250 
251         if (i < autoColumnNames.length) {
252             return autoNoNameColumnNames[i];
253         }
254 
255         return String.valueOf(i);
256     }
257 
getAutoSavepointNameString(long i, int j)258     public static String getAutoSavepointNameString(long i, int j) {
259 
260         StringBuffer sb = new StringBuffer("S");
261 
262         sb.append(i).append('_').append(j);
263 
264         return sb.toString();
265     }
266 
267     /**
268      * Auto names are used for autogenerated indexes or anonymous constraints.
269      */
newAutoName(String prefix, String namepart, HsqlName schema, HsqlName parent, int type)270     public HsqlName newAutoName(String prefix, String namepart,
271                                 HsqlName schema, HsqlName parent, int type) {
272 
273         StringBuffer sb = new StringBuffer();
274 
275         if (prefix != null) {
276             if (prefix.length() != 0) {
277                 sb.append("SYS_");
278                 sb.append(prefix);
279                 sb.append('_');
280 
281                 if (namepart != null) {
282                     sb.append(namepart);
283                     sb.append('_');
284                 }
285 
286                 sb.append(++sysNumber);
287             }
288         } else {
289             sb.append(namepart);
290         }
291 
292         HsqlName name = new HsqlName(this, sb.toString(), type, false);
293 
294         name.schema = schema;
295         name.parent = parent;
296 
297         return name;
298     }
299 
getSimpleName(String name, boolean isNameQuoted)300     public static SimpleName getSimpleName(String name, boolean isNameQuoted) {
301         return new SimpleName(name, isNameQuoted);
302     }
303 
304     public static class SimpleName {
305 
306         public String  name;
307         public boolean isNameQuoted;
308 
SimpleName()309         private SimpleName() {}
310 
SimpleName(String name, boolean isNameQuoted)311         private SimpleName(String name, boolean isNameQuoted) {
312             this.name         = name;
313             this.isNameQuoted = isNameQuoted;
314         }
315 
hashCode()316         public int hashCode() {
317             return name.hashCode();
318         }
319 
equals(Object other)320         public boolean equals(Object other) {
321 
322             if (other instanceof SimpleName) {
323                 return ((SimpleName) other).isNameQuoted == isNameQuoted
324                        && ((SimpleName) other).name.equals(name);
325             }
326 
327             return false;
328         }
329 
getStatementName()330         public String getStatementName() {
331 
332             return isNameQuoted
333                    ? StringConverter.toQuotedString(name, '"', true)
334                    : name;
335         }
336 
getNameString()337         public String getNameString() {
338             return name;
339         }
340     }
341 
342     public static final class HsqlName extends SimpleName {
343 
344         static HsqlName[] emptyArray = new HsqlName[]{};
345 
346         //
347         HsqlNameManager  manager;
348         public String    statementName;
349         public String    comment;
350         public HsqlName  schema;
351         public HsqlName  parent;
352         public Grantee   owner;
353         public final int type;
354 
355         //
356         private final int hashCode;
357 
HsqlName(HsqlNameManager man, int type)358         private HsqlName(HsqlNameManager man, int type) {
359 
360             manager   = man;
361             this.type = type;
362             hashCode  = manager.serialNumber.getAndIncrement();
363         }
364 
HsqlName(HsqlNameManager man, String name, boolean isquoted, int type)365         private HsqlName(HsqlNameManager man, String name, boolean isquoted,
366                          int type) {
367 
368             this(man, type);
369 
370             rename(name, isquoted);
371         }
372 
373         /** for auto names and system-defined names */
HsqlName(HsqlNameManager man, String name, int type, boolean isQuoted)374         private HsqlName(HsqlNameManager man, String name, int type,
375                          boolean isQuoted) {
376 
377             this(man, type);
378 
379             this.name          = name;
380             this.statementName = name;
381             this.isNameQuoted  = isQuoted;
382 
383             if (isNameQuoted) {
384                 statementName = StringConverter.toQuotedString(name, '"',
385                         true);
386             }
387         }
388 
getStatementName()389         public String getStatementName() {
390             return statementName;
391         }
392 
getSchemaQualifiedStatementName()393         public String getSchemaQualifiedStatementName() {
394 
395             switch (type) {
396 
397                 case SchemaObject.PARAMETER :
398                 case SchemaObject.VARIABLE : {
399                     return statementName;
400                 }
401                 case SchemaObject.COLUMN : {
402                     if (parent == null
403                             || SqlInvariants.SYSTEM_SUBQUERY.equals(
404                                 parent.name)) {
405                         return statementName;
406                     }
407 
408                     StringBuffer sb = new StringBuffer();
409 
410                     if (schema != null) {
411                         sb.append(schema.getStatementName());
412                         sb.append('.');
413                     }
414 
415                     sb.append(parent.getStatementName());
416                     sb.append('.');
417                     sb.append(statementName);
418 
419                     return sb.toString();
420                 }
421             }
422 
423             if (schema == null
424                     || SqlInvariants.SYSTEM_SCHEMA.equals(schema.name)) {
425                 return statementName;
426             }
427 
428             StringBuffer sb = new StringBuffer();
429 
430             sb.append(schema.getStatementName());
431             sb.append('.');
432             sb.append(statementName);
433 
434             return sb.toString();
435         }
436 
rename(HsqlName name)437         public void rename(HsqlName name) {
438             rename(name.name, name.isNameQuoted);
439         }
440 
rename(String name, boolean isquoted)441         public void rename(String name, boolean isquoted) {
442 
443             if (manager.sqlRegularNames && name.length() > 128) {
444                 throw Error.error(ErrorCode.X_42501, name);
445             }
446 
447             // get rid of the excess
448             this.name          = name;
449             this.statementName = this.name;
450             this.isNameQuoted  = isquoted;
451 
452             if (isNameQuoted) {
453                 statementName = StringConverter.toQuotedString(name, '"',
454                         true);
455             }
456 
457             if (name.startsWith("SYS_")) {
458                 int length = name.lastIndexOf('_') + 1;
459 
460                 try {
461                     int temp = Integer.parseInt(name.substring(length));
462 
463                     if (temp > manager.sysNumber) {
464                         manager.sysNumber = temp;
465                     }
466                 } catch (NumberFormatException e) {}
467             }
468         }
469 
rename(String prefix, String name, boolean isquoted)470         void rename(String prefix, String name, boolean isquoted) {
471 
472             StringBuffer sbname = new StringBuffer(prefix);
473 
474             sbname.append('_');
475             sbname.append(name);
476             rename(sbname.toString(), isquoted);
477         }
478 
setSchemaIfNull(HsqlName schema)479         public void setSchemaIfNull(HsqlName schema) {
480 
481             if (this.schema == null) {
482                 this.schema = schema;
483             }
484         }
485 
equals(Object other)486         public boolean equals(Object other) {
487 
488             if (other instanceof HsqlName) {
489                 return hashCode == ((HsqlName) other).hashCode;
490             }
491 
492             return false;
493         }
494 
495         /**
496          * hash code for this object is its unique serial number.
497          */
hashCode()498         public int hashCode() {
499             return hashCode;
500         }
501 
502         /**
503          * "SYS_IDX_" is used for auto-indexes on referring FK columns or
504          * unique constraints.
505          * "SYS_PK_" is for the primary key constraints
506          * "SYS_CT_" is for unique and check constraints
507          * "SYS_REF_" is for FK constraints in referenced tables
508          * "SYS_FK_" is for FK constraints in referencing tables
509          *
510          */
511         static final String[] sysPrefixes = new String[] {
512             "SYS_IDX_", "SYS_PK_", "SYS_REF_", "SYS_CT_", "SYS_FK_",
513         };
514 
sysPrefixLength(String name)515         static int sysPrefixLength(String name) {
516 
517             for (int i = 0; i < sysPrefixes.length; i++) {
518                 if (name.startsWith(sysPrefixes[i])) {
519                     return sysPrefixes[i].length();
520                 }
521             }
522 
523             return 0;
524         }
525 
isReservedName(String name)526         static boolean isReservedName(String name) {
527             return sysPrefixLength(name) > 0;
528         }
529 
isReservedName()530         boolean isReservedName() {
531             return isReservedName(name);
532         }
533 
toString()534         public String toString() {
535 
536             return getClass().getName() + super.hashCode()
537                    + "[this.hashCode()=" + this.hashCode + ", name=" + name
538                    + ", name.hashCode()=" + name.hashCode()
539                    + ", isNameQuoted=" + isNameQuoted + "]";
540         }
541 
compareTo(Object o)542         public int compareTo(Object o) {
543             return hashCode - o.hashCode();
544         }
545 
546         /**
547          * Returns true if the identifier consists of all uppercase letters
548          * digits and underscore, beginning with a letter and is not in the
549          * keyword list.
550          */
isRegularIdentifier(String name)551         static boolean isRegularIdentifier(String name) {
552 
553             for (int i = 0, length = name.length(); i < length; i++) {
554                 int c = name.charAt(i);
555 
556                 if (c >= 'A' && c <= 'Z') {
557                     continue;
558                 } else if (c == '_' && i > 0) {
559                     continue;
560                 } else if (c >= '0' && c <= '9') {
561                     continue;
562                 }
563 
564                 return false;
565             }
566 
567             return !Tokens.isKeyword(name);
568         }
569     }
570 }
571