/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the Hypersonic SQL Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* on behalf of the Hypersonic SQL Group.
*
*
* For work added by the HSQL Development Group:
*
* Copyright (c) 2001-2008, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb;
import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.lib.HashMap;
import org.hsqldb.lib.HashMappedList;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.IntValueHashMap;
import org.hsqldb.lib.Iterator;
import org.hsqldb.lib.StringConverter;
/**
* Script generation.
*
* The core functionality of this class was inherited from Hypersonic and
* extensively rewritten and extended in successive versions of HSQLDB.<p>
*
* @author Thomas Mueller (Hypersonic SQL Group)
* @author fredt@users
* @version 1.8.0
* @since 1.7.0
*/
public class DatabaseScript {
/**
* Returns the DDL and all other statements for the database excluding
* INSERT and SET <tablename> READONLY statements.
* cachedData == true indicates that SET <tablename> INDEX statements should
* also be included.
*
* This class should not have any dependencies on metadata reporting.
*/
public static Result getScript(Database database, boolean indexRoots) {
Iterator it;
Result r = Result.newSingleColumnResult("COMMAND", Types.VARCHAR);
r.metaData.tableNames[0] = "SYSTEM_SCRIPT";
// collation for database
if (database.collation.name != null) {
String name =
StringConverter.toQuotedString(database.collation.name, '"',
true);
addRow(r, "SET DATABASE COLLATION " + name);
}
// Role definitions
it = database.getGranteeManager().getRoleNames().iterator();
String role;
while (it.hasNext()) {
role = (String) it.next();
// ADMIN_ROLE_NAME is not persisted
if (!GranteeManager.DBA_ADMIN_ROLE_NAME.equals(role)) {
addRow(r, "CREATE ROLE " + role);
}
}
// aliases
HashMap h = database.getAliasMap();
HashMap builtin = Library.getAliasMap();
it = h.keySet().iterator();
while (it.hasNext()) {
String alias = (String) it.next();
String java = (String) h.get(alias);
String biJava = (String) builtin.get(alias);
if (biJava != null && biJava.equals(java)) {
continue;
}
StringBuffer buffer = new StringBuffer(64);
buffer.append(Token.T_CREATE).append(' ').append(
Token.T_ALIAS).append(' ');
buffer.append(alias);
buffer.append(" FOR \"");
buffer.append(java);
buffer.append('"');
addRow(r, buffer.toString());
}
addSchemaStatements(database, r, indexRoots);
// rights for classes, tables and views
addRightsStatements(database, r);
if (database.logger.hasLog()) {
int delay = database.logger.getWriteDelay();
boolean millis = delay < 1000;
if (millis) {
if (delay != 0 && delay < 20) {
delay = 20;
}
} else {
delay /= 1000;
}
String statement = "SET WRITE_DELAY " + delay + (millis ? " MILLIS"
: "");
addRow(r, statement);
}
return r;
}
static void addSchemaStatements(Database database, Result r,
boolean indexRoots) {
Iterator schemas = database.schemaManager.userSchemaNameIterator();
while (schemas.hasNext()) {
String schemaKey = (String) schemas.next();
HsqlName schema =
database.schemaManager.toSchemaHsqlName(schemaKey);
HashMappedList tTable =
database.schemaManager.getTables(schema.name);
HsqlArrayList forwardFK = new HsqlArrayList();
// schema creation
{
String ddl = getSchemaCreateDDL(database, schema);
addRow(r, ddl);
}
// sequences
/*
CREATE SEQUENCE <name>
[AS {INTEGER | BIGINT}]
[START WITH <value>]
[INCREMENT BY <value>]
*/
Iterator it = database.schemaManager.sequenceIterator(schema.name);
while (it.hasNext()) {
NumberSequence seq = (NumberSequence) it.next();
StringBuffer a = new StringBuffer(128);
a.append(Token.T_CREATE).append(' ');
a.append(Token.T_SEQUENCE).append(' ');
a.append(seq.getName().statementName).append(' ');
a.append(Token.T_AS).append(' ');
a.append(Types.getTypeString(seq.getType())).append(' ');
a.append(Token.T_START).append(' ');
a.append(Token.T_WITH).append(' ');
a.append(seq.peek()).append(' ');
if (seq.getIncrement() != 1) {
a.append(Token.T_INCREMENT).append(' ');
a.append(Token.T_BY).append(' ');
a.append(seq.getIncrement()).append(' ');
}
addRow(r, a.toString());
}
// tables
for (int i = 0, tSize = tTable.size(); i < tSize; i++) {
Table t = (Table) tTable.get(i);
if (t.isView()) {
continue;
}
StringBuffer a = new StringBuffer(128);
getTableDDL(database, t, i, forwardFK, false, a);
addRow(r, a.toString());
// indexes for table
for (int j = 1; j < t.getIndexCount(); j++) {
Index index = t.getIndex(j);
if (HsqlName.isReservedName(index.getName().name)) {
// the following are autocreated with the table
// indexes for primary keys
// indexes for unique constraints
// own table indexes for foreign keys
continue;
}
a = new StringBuffer(64);
a.append(Token.T_CREATE).append(' ');
if (index.isUnique()) {
a.append(Token.T_UNIQUE).append(' ');
}
a.append(Token.T_INDEX).append(' ');
a.append(index.getName().statementName);
a.append(' ').append(Token.T_ON).append(' ');
a.append(t.getName().statementName);
int[] col = index.getColumns();
int len = index.getVisibleColumns();
getColumnList(t, col, len, a);
addRow(r, a.toString());
}
// readonly for TEXT tables only
if (t.isText() && t.isConnected() && t.isDataReadOnly()) {
a = new StringBuffer(64);
a.append(Token.T_SET).append(' ').append(
Token.T_TABLE).append(' ');
a.append(t.getName().statementName);
a.append(' ').append(Token.T_READONLY).append(' ').append(
Token.T_TRUE);
addRow(r, a.toString());
}
// data source
String dataSource = getDataSource(t);
if (dataSource != null) {
addRow(r, dataSource);
}
// header
String header = getDataSourceHeader(t);
if (!indexRoots && header != null) {
addRow(r, header);
}
// triggers
int numTrigs = TriggerDef.NUM_TRIGS;
for (int tv = 0; tv < numTrigs; tv++) {
HsqlArrayList trigVec = t.triggerLists[tv];
if (trigVec == null) {
continue;
}
int trCount = trigVec.size();
for (int k = 0; k < trCount; k++) {
a = ((TriggerDef) trigVec.get(k)).getDDL();
addRow(r, a.toString());
}
}
}
// forward referencing foreign keys
for (int i = 0, tSize = forwardFK.size(); i < tSize; i++) {
Constraint c = (Constraint) forwardFK.get(i);
StringBuffer a = new StringBuffer(128);
a.append(Token.T_ALTER).append(' ').append(
Token.T_TABLE).append(' ');
a.append(c.getRef().getName().statementName);
a.append(' ').append(Token.T_ADD).append(' ');
getFKStatement(c, a);
addRow(r, a.toString());
}
// SET <tablename> INDEX statements
Session sysSession = database.sessionManager.getSysSession();
for (int i = 0, tSize = tTable.size(); i < tSize; i++) {
Table t = (Table) tTable.get(i);
if (indexRoots && t.isIndexCached()
&& !t.isEmpty(sysSession)) {
addRow(r, getIndexRootsDDL((Table) tTable.get(i)));
}
}
// RESTART WITH <value> statements
for (int i = 0, tSize = tTable.size(); i < tSize; i++) {
Table t = (Table) tTable.get(i);
if (!t.isTemp()) {
String ddl = getIdentityUpdateDDL(t);
addRow(r, ddl);
}
}
// views
for (int i = 0, tSize = tTable.size(); i < tSize; i++) {
Table t = (Table) tTable.get(i);
if (t.isView()) {
View v = (View) tTable.get(i);
StringBuffer a = new StringBuffer(128);
a.append(Token.T_CREATE).append(' ').append(
Token.T_VIEW).append(' ');
a.append(v.getName().statementName).append(' ').append(
'(');
int count = v.getColumnCount();
for (int j = 0; j < count; j++) {
a.append(v.getColumn(j).columnName.statementName);
if (j < count - 1) {
a.append(',');
}
}
a.append(')').append(' ').append(Token.T_AS).append(' ');
a.append(v.getStatement());
addRow(r, a.toString());
}
}
}
}
static String getIdentityUpdateDDL(Table t) {
if (t.identityColumn == -1) {
return "";
} else {
String tablename = t.getName().statementName;
String colname =
t.getColumn(t.identityColumn).columnName.statementName;
long idval = t.identitySequence.peek();
StringBuffer a = new StringBuffer(128);
a.append(Token.T_ALTER).append(' ').append(Token.T_TABLE).append(
' ').append(tablename).append(' ').append(
Token.T_ALTER).append(' ').append(Token.T_COLUMN).append(
' ').append(colname).append(' ').append(
Token.T_RESTART).append(' ').append(Token.T_WITH).append(
' ').append(idval);
return a.toString();
}
}
static String getIndexRootsDDL(Table t) {
StringBuffer a = new StringBuffer(128);
a.append(Token.T_SET).append(' ').append(Token.T_TABLE).append(' ');
a.append(t.getName().statementName);
a.append(' ').append(Token.T_INDEX).append('\'');
a.append(t.getIndexRoots());
a.append('\'');
return a.toString();
}
static String getSchemaCreateDDL(Database database, HsqlName schemaName) {
StringBuffer ab = new StringBuffer(128);
ab.append(Token.T_CREATE).append(' ');
ab.append(Token.T_SCHEMA).append(' ');
ab.append(schemaName.statementName).append(' ');
ab.append(Token.T_AUTHORIZATION).append(' ');
ab.append(GranteeManager.DBA_ADMIN_ROLE_NAME);
return ab.toString();
}
static void getTableDDL(Database database, Table t, int i,
HsqlArrayList forwardFK, boolean useSchema,
StringBuffer a) {
a.append(Token.T_CREATE).append(' ');
if (t.isTemp) {
a.append(Token.T_GLOBAL).append(' ');
a.append(Token.T_TEMPORARY).append(' ');
}
if (t.isText()) {
a.append(Token.T_TEXT).append(' ');
} else if (t.isCached()) {
a.append(Token.T_CACHED).append(' ');
} else {
a.append(Token.T_MEMORY).append(' ');
}
a.append(Token.T_TABLE).append(' ');
if (useSchema) {
a.append(t.getName().schema.statementName).append('.');
}
a.append(t.getName().statementName);
a.append('(');
int columns = t.getColumnCount();
int[] pk = t.getPrimaryKey();
HsqlName pkName = null;
Constraint pkConst = t.getPrimaryConstraint();
if (pkConst != null && !pkConst.getName().isReservedName()) {
pkName = pkConst.getName();
}
for (int j = 0; j < columns; j++) {
Column column = t.getColumn(j);
String colname = column.columnName.statementName;
a.append(colname);
a.append(' ');
String sType = Types.getTypeString(column.getType());
a.append(sType);
// append the size and scale if > 0
boolean hasSize = false;
if (column.getType() == Types.TIMESTAMP) {
if (column.getSize() != 6) {
hasSize = true;
}
} else {
hasSize = column.getSize() > 0;
}
if (hasSize) {
a.append('(');
a.append(column.getSize());
if (column.getScale() > 0) {
a.append(',');
a.append(column.getScale());
}
a.append(')');
}
String defaultString = column.getDefaultDDL();
if (defaultString != null) {
a.append(' ').append(Token.T_DEFAULT).append(' ');
a.append(defaultString);
}
if (j == t.getIdentityColumn()) {
a.append(" GENERATED BY DEFAULT AS IDENTITY(START WITH ");
a.append(column.identityStart);
if (column.identityIncrement != 1) {
a.append(Token.T_COMMA).append(Token.T_INCREMENT).append(
' ').append(Token.T_BY).append(' ');
a.append(column.identityIncrement);
}
a.append(")");
}
if (!column.isNullable()) {
a.append(' ').append(Token.T_NOT).append(' ').append(
Token.T_NULL);
}
if ((pk.length == 1) && (j == pk[0]) && pkName == null) {
a.append(' ').append(Token.T_PRIMARY).append(' ').append(
Token.T_KEY);
}
if (j < columns - 1) {
a.append(',');
}
}
if (pk.length > 1 || (pk.length == 1 && pkName != null)) {
a.append(',');
if (pkName != null) {
a.append(Token.T_CONSTRAINT).append(' ');
a.append(pkName.statementName).append(' ');
}
a.append(Token.T_PRIMARY).append(' ').append(Token.T_KEY);
getColumnList(t, pk, pk.length, a);
}
Constraint[] v = t.getConstraints();
for (int j = 0, vSize = v.length; j < vSize; j++) {
Constraint c = v[j];
switch (c.getType()) {
case Constraint.UNIQUE :
a.append(',').append(Token.T_CONSTRAINT).append(' ');
a.append(c.getName().statementName);
a.append(' ').append(Token.T_UNIQUE);
int[] col = c.getMainColumns();
getColumnList(c.getMain(), col, col.length, a);
break;
case Constraint.FOREIGN_KEY :
// forward referencing FK
Table maintable = c.getMain();
int maintableindex =
database.schemaManager.getTableIndex(maintable);
if (maintableindex > i) {
forwardFK.add(c);
} else {
a.append(',');
getFKStatement(c, a);
}
break;
case Constraint.CHECK :
try {
a.append(',').append(Token.T_CONSTRAINT).append(' ');
a.append(c.getName().statementName);
a.append(' ').append(Token.T_CHECK).append('(');
a.append(c.core.check.getDDL());
a.append(')');
} catch (HsqlException e) {
// should not throw as it is already tested OK
}
break;
}
}
a.append(')');
if (t.onCommitPreserve) {
a.append(' ').append(Token.T_ON).append(' ');
a.append(Token.T_COMMIT).append(' ').append(Token.T_PRESERVE);
a.append(' ').append(Token.T_ROWS);
}
}
/**
* Generates the SET TABLE <tablename> SOURCE <string> statement for a
* text table;
*/
static String getDataSource(Table t) {
String dataSource = t.getDataSource();
if (dataSource == null) {
return null;
}
boolean isDesc = t.isDescDataSource();
StringBuffer a = new StringBuffer(128);
a.append(Token.T_SET).append(' ').append(Token.T_TABLE).append(' ');
a.append(t.getName().statementName);
a.append(' ').append(Token.T_SOURCE).append(' ').append('"');
a.append(dataSource);
a.append('"');
if (isDesc) {
a.append(' ').append(Token.T_DESC);
}
return a.toString();
}
/**
* Generates the SET TABLE <tablename> SOURCE HEADER <string> statement for a
* text table;
*/
static String getDataSourceHeader(Table t) {
String header = t.getHeader();
if (header == null) {
return null;
}
StringBuffer a = new StringBuffer(128);
a.append(Token.T_SET).append(' ').append(Token.T_TABLE).append(' ');
a.append(t.getName().statementName);
a.append(' ').append(Token.T_SOURCE).append(' ');
a.append(Token.T_HEADER).append(' ');
a.append(header);
return a.toString();
}
/**
* Generates the column definitions for a table.
*/
private static void getColumnList(Table t, int[] col, int len,
StringBuffer a) {
a.append('(');
for (int i = 0; i < len; i++) {
a.append(t.getColumn(col[i]).columnName.statementName);
if (i < len - 1) {
a.append(',');
}
}
a.append(')');
}
/**
* Generates the foreign key declaration for a given Constraint object.
*/
private static void getFKStatement(Constraint c, StringBuffer a) {
a.append(Token.T_CONSTRAINT).append(' ');
a.append(c.getName().statementName);
a.append(' ').append(Token.T_FOREIGN).append(' ').append(Token.T_KEY);
int[] col = c.getRefColumns();
getColumnList(c.getRef(), col, col.length, a);
a.append(' ').append(Token.T_REFERENCES).append(' ');
a.append(c.getMain().getName().statementName);
col = c.getMainColumns();
getColumnList(c.getMain(), col, col.length, a);
if (c.getDeleteAction() != Constraint.NO_ACTION) {
a.append(' ').append(Token.T_ON).append(' ').append(
Token.T_DELETE).append(' ');
a.append(getFKAction(c.getDeleteAction()));
}
if (c.getUpdateAction() != Constraint.NO_ACTION) {
a.append(' ').append(Token.T_ON).append(' ').append(
Token.T_UPDATE).append(' ');
a.append(getFKAction(c.getUpdateAction()));
}
}
/**
* Returns the foreign key action rule.
*/
private static String getFKAction(int action) {
switch (action) {
case Constraint.CASCADE :
return Token.T_CASCADE;
case Constraint.SET_DEFAULT :
return Token.T_SET + ' ' + Token.T_DEFAULT;
case Constraint.SET_NULL :
return Token.T_SET + ' ' + Token.T_NULL;
default :
return Token.T_NO + ' ' + Token.T_ACTION;
}
}
/**
* Adds a script line to the result.
*/
private static void addRow(Result r, String sql) {
if (sql == null || sql.length() == 0) {
return;
}
String[] s = new String[1];
s[0] = sql;
r.add(s);
}
/**
* Generates the GRANT statements for grantees.
*
* When views is true, generates rights for views only. Otherwise generates
* rights for tables and classes.
*
* Does not generate script for:
*
* grant on builtin classes to public
* grant select on system tables
*
*/
private static void addRightsStatements(Database dDatabase, Result r) {
StringBuffer a;
HashMappedList userlist = dDatabase.getUserManager().getUsers();
Iterator users = userlist.values().iterator();
GranteeManager gm = dDatabase.getGranteeManager();
Iterator grantees = gm.getGrantees().iterator();
for (; users.hasNext(); ) {
User u = (User) users.next();
String name = u.getName();
// PUBLIC user is not persisted. (However, his
// grants/revokes are). _SYSTEM user not in user list.
if (!name.equals(Token.T_PUBLIC)) {
addRow(r, u.getCreateUserDDL());
}
}
// grantees has ALL Users and Roles, incl. hidden and reserved ones.
// Therefore, we filter out the non-persisting ones.
for (; grantees.hasNext(); ) {
Grantee g = (Grantee) grantees.next();
String name = g.getName();
// _SYSTEM user, DBA Role grants/revokes not persisted
if (name.equals("_SYSTEM") || name.equals("DBA")) {
continue;
}
String roleString = g.allRolesString();
if (roleString != null) {
addRow(r, "GRANT " + roleString + " TO " + name);
}
IntValueHashMap rightsmap = g.getRights();
if (rightsmap == null) {
continue;
}
Iterator dbobjects = rightsmap.keySet().iterator();
while (dbobjects.hasNext()) {
Object nameobject = dbobjects.next();
int right = rightsmap.get(nameobject, 0);
a = new StringBuffer(64);
a.append(Token.T_GRANT).append(' ');
a.append(GranteeManager.getRightsList(right));
a.append(' ').append(Token.T_ON).append(' ');
if (nameobject instanceof String) {
if (nameobject.equals("java.lang.Math")
|| nameobject.equals("org.hsqldb.Library")) {
continue;
}
a.append("CLASS \"");
a.append((String) nameobject);
a.append('\"');
} else {
HsqlName hsqlname = (HsqlName) nameobject;
// assumes all non String objects are table names
Table table = dDatabase.schemaManager.findUserTable(null,
hsqlname.name, hsqlname.schema.name);
// either table != null or is system table
if (table != null) {
a.append(hsqlname.schema.statementName).append(
'.').append(hsqlname.statementName);
} else {
continue;
}
}
a.append(' ').append(Token.T_TO).append(' ');
a.append(g.getName());
addRow(r, a.toString());
}
}
}
}