/*
// Licensed to DynamoBI Corporation (DynamoBI) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. DynamoBI licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
*/
package net.sf.farrago.ddl.gen;
import java.util.*;
import javax.jmi.reflect.*;
import net.sf.farrago.catalog.*;
import net.sf.farrago.cwm.behavioral.*;
import net.sf.farrago.cwm.core.*;
import net.sf.farrago.cwm.keysindexes.*;
import net.sf.farrago.cwm.relational.*;
import net.sf.farrago.cwm.relational.enumerations.*;
import net.sf.farrago.fem.med.*;
import net.sf.farrago.fem.security.*;
import net.sf.farrago.fem.sql2003.*;
import org.eigenbase.jmi.*;
import org.eigenbase.sql.SqlDialect;
import org.eigenbase.sql.type.*;
import org.eigenbase.sql.util.SqlBuilder;
import org.eigenbase.util.*;
/**
* Generates DDL statements from catalog objects.
*
* @author Jason Ouellette
* @version $Id$
*/
public class FarragoDdlGenerator
extends DdlGenerator
{
//~ Static fields/initializers ---------------------------------------------
private static final List<String> NON_REPLACEABLE_TYPE_NAMES =
Arrays.asList(
"INDEX",
"CLUSTERED INDEX");
//~ Instance fields --------------------------------------------------------
protected final JmiModelView modelView;
protected boolean uglyViews;
//~ Constructors -----------------------------------------------------------
/**
* Creates a DDL generator.
*
* <p>The <code>modelView</code> parameter can be null if you are generating
* DDL for a single object. A model view is required if calling {@link
* DdlGenerator#gatherElements} or {@link #getExportText(java.util.List,
* boolean)} with <code>sort=true</code>.
*
* @param sqlDialect SQL dialect
* @param modelView Model graph
*/
public FarragoDdlGenerator(
SqlDialect sqlDialect,
JmiModelView modelView)
{
super(sqlDialect);
this.modelView = modelView;
}
//~ Methods ----------------------------------------------------------------
/**
* Sets whether we output the user-defined SQL for VIEWS, which is
* prettier but may be incorrect, or if we output the correct (but ugly)
* system-expanded rewrite of the view definition. Default is false.
*
* @param uglyViews whether to make VIEW SQL ugly
*/
public void setUglyViews(boolean uglyViews)
{
this.uglyViews = uglyViews;
}
private RefClass findRefClass(Class<? extends RefObject> clazz)
{
JmiClassVertex vertex =
modelView.getModelGraph().getVertexForJavaInterface(clazz);
return vertex.getRefClass();
}
public <T extends RefObject> Collection<T> allOfClass(Class<T> clazz)
{
RefClass refClass = findRefClass(clazz);
return (Collection<T>) refClass.refAllOfClass();
}
public <T extends RefObject> Collection<T> allOfType(Class<T> clazz)
{
RefClass refClass = findRefClass(clazz);
return (Collection<T>) refClass.refAllOfType();
}
public String getExportText(List<CwmModelElement> exportList, boolean sort)
{
if (sort) {
assert modelView != null;
}
return super.getExportText(exportList, sort);
}
protected boolean typeSupportsReplace(String typeName)
{
return !NON_REPLACEABLE_TYPE_NAMES.contains(typeName);
}
protected JmiModelView getModelView()
{
return modelView;
}
/**
* Finds a list of elements and their dependencies for a given
* schema and catalog.
*
* @param list - List to which found elements are added.
* @param schemaName - Schema to limit list, null to add all schemas.
* @param includeNonSchemaElements - Will include every other type
* of direct-child object in the catalog.
* @param catalog - Catalog object to search.
*/
public void gatherElements(
List<CwmModelElement> list,
String schemaName,
boolean includeNonSchemaElements,
CwmCatalog catalog)
{
assert modelView != null;
for (CwmModelElement element : catalog.getOwnedElement()) {
if (element instanceof CwmSchema) {
CwmSchema schema = (CwmSchema) element;
// Note: ignore schema name if null.
if (schemaName == null || schema.getName().equals(schemaName)) {
list.add(schema);
for (CwmModelElement element2 : schema.getOwnedElement()) {
list.add(element2);
}
}
} else if (includeNonSchemaElements) {
list.add(element);
}
}
// Include all dependencies. We don't want to generate DDL for them,
// but they are necessary to ensure that views are created in the
// right order, among other things.
// NOTE: We include dependencies of all objects in all schemas. This
// will not produce incorrect results, but may be a performance issue.
for (CwmDependency dependency : allOfClass(CwmDependency.class)) {
list.add(dependency);
}
// Likewise operations.
list.addAll(allOfClass(CwmOperation.class));
if (includeNonSchemaElements) {
// Note these are independent of catalog.
list.addAll(allOfType(FemDataServer.class));
list.addAll(allOfType(FemDataWrapper.class));
list.addAll(allOfType(FemUser.class));
// TODO: ks 25-Nov-2010: extend create for Role to include admin;
// requires that we become aware of GRANT elements since the
// admin isn't directly stored with the FemRole.
list.addAll(allOfType(FemRole.class));
list.addAll(allOfType(FemLabel.class));
}
}
protected void createHeader(
SqlBuilder sb,
String typeName,
GeneratedDdlStmt stmt)
{
createHeader(
sb,
typeName,
stmt.isReplace(),
stmt.getNewName());
}
protected void createHeader(
SqlBuilder sb,
String typeName,
boolean replace,
String newName)
{
sb.append("CREATE ");
if (replace && typeSupportsReplace(typeName)) {
sb.append("OR REPLACE ");
}
if (newName != null) {
sb.append("RENAME TO ");
sb.identifier(newName);
sb.append(" ");
}
sb.append(typeName);
sb.append(" ");
}
public void create(
FemLocalView view,
GeneratedDdlStmt stmt)
{
// Assume that the view was created in the context of its schema. If
// that was not the case, we would have no way to detect it. We'd need
// implement a closure mechanism to deal with SET SCHEMA and SET PATH.
if (!uglyViews
&& generateSetSchema(stmt, view.getNamespace().getName(), false))
{
stmt.addStmt(";" + NL);
}
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "VIEW", stmt);
name(sb, view.getNamespace(), view.getName());
addDescription(sb, view);
sb.append(" AS");
sb.append(NL);
stmt.addStmt(sb.getSqlAndClear());
if (!uglyViews) {
sb.append(view.getOriginalDefinition());
} else {
sb.append(view.getQueryExpression().getBody());
}
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
FemLocalSchema schema,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "SCHEMA", stmt);
name(sb, null, schema.getName());
addDescription(sb, schema);
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
FemUser user,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "USER", stmt);
name(sb, null, user.getName());
stmt.addStmt(sb.getSqlAndClear());
sb.append(NL);
if (user.getEncryptedPassword() == null) {
sb.append("AUTHORIZATION ");
// Auths are not stored by the parser.
sb.literal("Unused");
} else {
sb.append("IDENTIFIED BY ");
sb.literal("********");
}
stmt.addStmt(sb.getSqlAndClear());
CwmNamespace ns = user.getDefaultNamespace();
if (ns != null) {
sb.append(NL);
sb.append("DEFAULT ");
if (ns instanceof FemLocalSchema) {
sb.append("SCHEMA ");
} else if (ns instanceof FemLocalCatalog) {
sb.append("CATALOG ");
}
name(sb, ns.getNamespace(), ns.getName());
}
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
FemRole role,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "ROLE", stmt);
name(sb, null, role.getName());
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
FemJar jar,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "JAR", stmt);
name(sb, jar.getNamespace(), jar.getName());
addDescription(sb, jar);
stmt.addStmt(sb.getSqlAndClear());
sb.append(NL);
sb.append("LIBRARY ");
sb.literal(jar.getUrl());
sb.append(NL);
sb.append("OPTIONS(");
sb.append(jar.getDeploymentState());
sb.append(")");
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
FemLocalTable table,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "TABLE", false, null);
name(sb, table.getNamespace(), table.getName());
stmt.addStmt(sb.getSqlAndClear());
addColumns(sb, table);
addOptions(
sb,
table.getStorageOptions());
addDescription(sb, table);
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
FemForeignTable table,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "FOREIGN TABLE", false, null);
name(sb, table.getNamespace(), table.getName());
stmt.addStmt(sb.getSqlAndClear());
addColumns(sb, table);
sb.append(NL);
sb.append("SERVER ");
FemDataServer server = table.getServer();
if (server != null) {
name(sb, null, server.getName());
}
addOptions(
sb,
((FemElementWithStorageOptions) table).getStorageOptions());
addDescription(sb, table);
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
FemDataWrapper wrapper,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "FOREIGN DATA WRAPPER", stmt);
name(sb, null, wrapper.getName());
stmt.addStmt(sb.getSqlAndClear());
// "LIBRARY" clause is optional
if (wrapper.getLibraryFile() != null) {
sb.append(" LIBRARY ");
sb.literal(wrapper.getLibraryFile());
}
sb.append(NL);
sb.append("LANGUAGE ");
sb.append(wrapper.getLanguage());
addOptions(
sb,
wrapper.getStorageOptions());
addDescription(sb, wrapper);
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
CwmOperation operation,
GeneratedDdlStmt stmt)
{
// We don't generate any DDL for an operation. Operations need to be
// in the export set so that method implementations occur after type
// declarations (which also declare operations).
stmt.setTopLevel(false);
}
public void create(
FemRoutine routine,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
final ProcedureType routineType = routine.getType();
final CwmClassifier owner = routine.getSpecification().getOwner();
boolean method =
(routine.getSpecification() != null)
&& (owner
instanceof FemUserDefinedType);
if (method) {
createHeader(sb, "SPECIFIC METHOD", stmt);
} else if (routineType.equals(ProcedureTypeEnum.FUNCTION)) {
createHeader(sb, "FUNCTION", stmt);
} else if (routineType.equals(ProcedureTypeEnum.PROCEDURE)) {
createHeader(sb, "PROCEDURE", stmt);
}
name(sb, routine.getNamespace(), routine.getInvocationName());
stmt.addStmt(sb.getSqlAndClear());
if (method) {
sb.append(NL);
sb.append("FOR ");
name(sb, owner.getNamespace(), owner.getName());
} else {
methodBody(routine, sb);
}
if (routine.getExternalName() == null) {
sb.append(NL);
sb.append(routine.getBody().getBody());
}
stmt.addStmt(sb.getSqlAndClear());
}
/**
* Generates a parameters, keyword list and body of a method.
*
* @param routine Routine
* @param sb String buffer to write to
*/
private void methodBody(
FemRoutine routine,
SqlBuilder sb)
{
final ProcedureType routineType = routine.getType();
boolean method =
(routine.getSpecification() != null)
&& (routine.getSpecification().getOwner()
instanceof FemUserDefinedType);
sb.append("(");
sb.append(NL);
List<CwmParameter> returns = new ArrayList<CwmParameter>();
int paramCount = 0;
for (CwmParameter parameter : routine.getParameter()) {
if (parameter.getKind().equals(
ParameterDirectionKindEnum.PDK_RETURN))
{
assert !routineType.equals(ProcedureTypeEnum.PROCEDURE);
returns.add(parameter);
continue;
}
if (paramCount++ > 0) {
sb.append(",");
sb.append(NL);
}
add((FemRoutineParameter) parameter, routineType, sb);
}
sb.append(")");
sb.append(NL);
final List<CwmColumn> columns =
Util.filter(routine.getFeature(), CwmColumn.class);
if (!columns.isEmpty()) {
// UDXs claim to have a boring return type of INTEGER (see comment
// "jvs 8-Jan-2006: should be MULTISET of ROW(x, y, z)" in
// CommonDdlParser.jj) but the details of the returned columns are
// in the features.
sb.append("RETURNS TABLE");
generateColumnsAndKeys(sb, columns, false, false, null);
//addColumns(sb, columns);
sb.append(NL);
} else {
for (CwmParameter aReturn : returns) {
add((FemRoutineParameter) aReturn, routineType, sb);
sb.append(NL);
}
}
if (method) {
sb.append("SELF AS RESULT");
sb.append(NL);
}
sb.append("SPECIFIC ");
name(sb, routine.getNamespace(), routine.getName());
sb.append(NL);
sb.append("LANGUAGE ");
sb.append(routine.getLanguage());
final RoutineDataAccess da = routine.getDataAccess();
if (da.equals(RoutineDataAccessEnum.RDA_MODIFIES_SQL_DATA)) {
sb.append(NL).append("MODIFIES SQL DATA");
} else if (da.equals(RoutineDataAccessEnum.RDA_CONTAINS_SQL)) {
sb.append(NL).append("CONTAINS SQL");
} else if (da.equals(RoutineDataAccessEnum.RDA_NO_SQL)) {
sb.append(NL).append("NO SQL");
} else if (da.equals(RoutineDataAccessEnum.RDA_READS_SQL_DATA)) {
sb.append(NL).append("READS SQL DATA");
}
sb.append(NL);
sb.append(maybeNot(routine.isDeterministic(), "DETERMINISTIC"));
// Elide STATIC DISPATCH for functions and procedures, where it is
// the only option.
if (routine.isStaticDispatch() && method) {
sb.append(NL);
sb.append("STATIC DISPATCH");
}
if (routine.getExternalName() != null) {
sb.append(NL);
sb.append("EXTERNAL NAME ");
sb.literal(routine.getExternalName());
}
}
/**
* Generates DDL for a routine parameter.
*
* @param parameter Parameter
* @param routineType Type of routine parameter belongs to
* @param sb StringBuilder to append to
*/
private void add(
FemRoutineParameter parameter,
ProcedureType routineType,
SqlBuilder sb)
{
final ParameterDirectionKind kind = parameter.getKind();
boolean qualifyType = false;
if (kind.equals(ParameterDirectionKindEnum.PDK_IN)) {
if (routineType.equals(ProcedureTypeEnum.PROCEDURE)) {
sb.append(" IN ");
} else {
sb.append(" ");
}
sb.identifier(parameter.getName());
} else if (kind.equals(ParameterDirectionKindEnum.PDK_INOUT)) {
sb.append(" INOUT ");
sb.identifier(parameter.getName());
} else if (kind.equals(ParameterDirectionKindEnum.PDK_OUT)) {
sb.append(" OUT ");
sb.identifier(parameter.getName());
} else if (kind.equals(ParameterDirectionKindEnum.PDK_RETURN)) {
qualifyType = true;
sb.append("RETURNS");
}
// REVIEW: functions don't have OUT or INOUT params
// REVIEW: procedures don't have RETURNS
sb.append(" ");
appendType(
sb,
parameter.getType(),
parameter.getPrecision(),
parameter.getScale(),
parameter.getLength(),
null,
parameter.getDefaultValue(),
qualifyType);
}
private void appendType(
SqlBuilder sb,
CwmClassifier type,
Integer precision,
Integer scale,
Integer length,
NullableType nullable,
CwmExpression defaultValue,
boolean qualifyType)
{
if ((type instanceof FemSqlobjectType) && qualifyType) {
// Workaround FRG-297; remove qualifyType parameter when fixed
sb.append(type.getNamespace().getName());
sb.append('.');
}
formatTypeInfo(sb, type, precision, scale, length);
if (defaultValue != null) {
String val = defaultValue.getBody();
if ((val != null) && !val.equals(VALUE_NULL)) {
sb.append(" DEFAULT ");
// we expect the setter of body to be responsible
// for forming a valid SQL expression given the
// datatype
sb.append(val);
}
}
if (nullable != null) {
if (NullableTypeEnum.COLUMN_NO_NULLS.toString().equals(
nullable.toString()))
{
sb.append(" NOT NULL");
}
}
}
/**
* Format the core elements of a column's type (type name, precision, scale,
* length) into SQL format.
*
* @param sb SQL builder
* @param col CwmColumn object we want type info for
*/
public static void formatTypeInfo(
SqlBuilder sb,
CwmColumn col)
{
formatTypeInfo(
sb,
col.getType(),
col.getPrecision(),
col.getScale(),
col.getLength());
}
/**
* Format the core elements of a column's type (type name, precision, scale,
* length) into SQL format.
*
* <p>Note that this was refactored out of {@link #appendType} to allow
* separate access.
*
* @param sb StringBuilder to hold the formatted type information
* @param type CwmClassifier object representing the column type
* @param precision Integer specifying the column's precision
* @param scale Integer specifying the column's scale
* @param length Integer specifying the column's length
*/
public static void formatTypeInfo(
SqlBuilder sb,
CwmClassifier type,
Integer precision,
Integer scale,
Integer length)
{
sb.append(type.getName());
SqlTypeName stn = getSqlTypeName(type);
if ((precision != null) && stn.allowsPrec()) {
sb.append("(").append(precision);
if ((scale != null) && stn.allowsScale()) {
sb.append(",").append(scale);
}
sb.append(")");
} else {
if (length != null) {
sb.append("(").append(length).append(")");
}
}
}
/**
* Generates either "s" or "NOT s", depending on <code>b</code>.
*
* @param b Condition
* @param s Flag string
*
* @return string containing s or not s
*/
protected static String maybeNot(boolean b, String s)
{
return b ? s : ("NOT " + s);
}
public void create(
FemSqlobjectType type,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "TYPE", stmt);
name(sb, type.getNamespace(), type.getName());
sb.append(" AS");
addColumns(sb, type);
sb.append(NL);
sb.append(maybeNot(type.isFinal(), "FINAL"));
sb.append(NL);
sb.append(maybeNot(!type.isAbstract(), "INSTANTIABLE"));
addDescription(sb, type);
addOperations(sb, Util.filter(type.getFeature(), CwmOperation.class));
stmt.addStmt(sb.getSqlAndClear());
}
private void addOperations(
SqlBuilder sb,
List<CwmOperation> operations)
{
for (CwmOperation operation : operations) {
sb.append(NL);
sb.append("CONSTRUCTOR METHOD ");
sb.identifier(operation.getName());
sb.append(" ");
// REVIEW: I think there is precisely one method per operation
for (CwmMethod method : operation.getMethod()) {
methodBody((FemRoutine) method, sb);
}
}
}
public void create(
FemSqldistinguishedType type,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "TYPE", stmt);
name(sb, type.getNamespace(), type.getName());
sb.append(" AS ");
appendType(
sb,
type.getType(),
type.getPrecision(),
type.getScale(),
type.getLength(),
null,
null,
true);
sb.append(NL);
sb.append(maybeNot(type.isFinal(), "FINAL"));
sb.append(NL);
sb.append(maybeNot(!type.isAbstract(), "INSTANTIABLE"));
addDescription(sb, type);
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
FemDataServer server,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "SERVER", stmt);
name(sb, null, server.getName());
stmt.addStmt(sb.getSqlAndClear());
// "TYPE" clause is optional
final String type = server.getType();
if ((type != null) && !type.equals("UNKNOWN")) {
sb.append(" TYPE ");
sb.literal(type);
}
// "VERSION" clause is optional
final String version = server.getVersion();
if ((version != null) && !version.equals("UNKNOWN")) {
sb.append(" VERSION ");
sb.literal(version);
}
sb.append(NL);
sb.append("FOREIGN DATA WRAPPER ");
name(sb, null, server.getWrapper().getName());
addOptions(
sb,
server.getStorageOptions());
addDescription(sb, server);
stmt.addStmt(sb.getSqlAndClear());
}
public void create(
CwmDependency dependency,
GeneratedDdlStmt stmt)
{
// We don't generate any DDL for a dependency. Dependencies need to be
// in the export set to ensure that objects occur in the right order.
stmt.setTopLevel(false);
}
public void drop(
CwmSchema schema,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
sb.append("DROP SCHEMA ");
name(sb, null, schema.getName());
if (dropCascade) {
sb.append(" CASCADE");
}
stmt.addStmt(sb.getSqlAndClear());
}
public void drop(
FemRole role,
GeneratedDdlStmt stmt)
{
drop(role, "ROLE", stmt);
}
public void drop(
FemUser user,
GeneratedDdlStmt stmt)
{
drop(user, "USER", stmt);
}
public void drop(
FemJar jar,
GeneratedDdlStmt stmt)
{
drop(jar, "JAR", stmt);
}
public void drop(
CwmView view,
GeneratedDdlStmt stmt)
{
drop(view, "VIEW", stmt);
}
public void drop(
FemDataServer server,
GeneratedDdlStmt stmt)
{
drop(server, "SERVER", stmt);
}
public void drop(
CwmTable table,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
sb.append("DROP TABLE ");
name(sb, table.getNamespace(), table.getName());
if (dropCascade) {
sb.append(" CASCADE");
}
stmt.addStmt(sb.getSqlAndClear());
}
public void drop(
FemDataWrapper plugin,
GeneratedDdlStmt stmt)
{
drop(plugin, "FOREIGN DATA WRAPPER", stmt);
}
public void create(
FemLocalIndex index,
GeneratedDdlStmt stmt)
{
if (index.isClustered()
|| index.getName().startsWith("SYS$CONSTRAINT_INDEX$")
|| index.getName().startsWith("SYS$DELETION_INDEX"))
{
stmt.setTopLevel(false);
}
SqlBuilder sb = createSqlBuilder();
createHeader(
sb,
index.isClustered() ? "CLUSTERED INDEX" : "INDEX",
stmt);
name(sb, null, index.getName());
sb.append(" ON ");
final CwmClass spanned = index.getSpannedClass();
name(sb, spanned.getNamespace(), spanned.getName());
sb.append(" (");
int k = -1;
for (CwmIndexedFeature feature : index.getIndexedFeature()) {
if (++k > 0) {
sb.append(", ");
}
sb.identifier(feature.getName());
}
sb.append(")");
addDescription(sb, index);
stmt.addStmt(sb.getSqlAndClear());
}
public void drop(
FemLocalIndex index,
GeneratedDdlStmt stmt)
{
drop(index, "INDEX", stmt);
}
public void create(
FemLabel label,
GeneratedDdlStmt stmt)
{
SqlBuilder sb = createSqlBuilder();
createHeader(sb, "LABEL", stmt);
name(sb, null, label.getName());
if (label.getParentLabel() != null) {
sb.append(" FROM LABEL ");
name(sb, null, label.getParentLabel().getName());
}
addDescription(sb, label);
stmt.addStmt(sb.getSqlAndClear());
}
public void drop(
FemLabel label,
GeneratedDdlStmt stmt)
{
drop(label, "LABEL", stmt);
}
protected void addColumns(
SqlBuilder sb,
CwmClassifier table)
{
addColumns(sb, table, false, false);
}
protected void addColumns(
SqlBuilder sb,
CwmClassifier table,
boolean skipDefaults,
boolean skipNullable)
{
generateColumnsAndKeysForTable(
sb, table, skipDefaults, skipNullable, null);
}
/**
* Generates the column and key definitions of a table as a SQL string
* (enclosed in parentheses unless there are no columns).
*
* @param sb receives generated string
* @param table table to iterate down for columns and keys
* @param skipDefaults whether to omit default value definitions
* @param skipNullable whether to omit NOT NULL constraint definitions
* @param imposedPrimaryKey if not null, use as PRIMARY KEY
*/
public void generateColumnsAndKeysForTable(
SqlBuilder sb,
CwmClassifier table,
boolean skipDefaults,
boolean skipNullable,
List<String> imposedPrimaryKey)
{
List<CwmColumn> columns =
Util.filter(table.getFeature(), CwmColumn.class);
List<String> pk = imposedPrimaryKey;
pk = generateColumnsAndKeysForCols(
sb, columns, skipDefaults, skipNullable, pk);
if (columns.size() > 0) {
// add unique constraints
List<FemUniqueKeyConstraint> uniques =
FarragoCatalogUtil.getUniqueKeyConstraints(table);
// sort alphabetically
Collections.sort(
uniques,
new Comparator<Object>() {
public int compare(Object obj1, Object obj2) {
return
((FemUniqueKeyConstraint)obj1).getName().compareTo(
((FemUniqueKeyConstraint)obj2).getName());
}
});
boolean firstConst = true;
for (FemUniqueKeyConstraint constraint : uniques) {
if (firstConst) {
if (pk != null && pk.size() > 0) {
sb.append(",");
sb.append(NL);
}
firstConst = false;
} else {
sb.append(",");
sb.append(NL);
}
sb.append(" CONSTRAINT ");
sb.identifier(constraint.getName());
sb.append(" UNIQUE(");
List<CwmColumn> cols = Util.filter(
constraint.getFeature(), CwmColumn.class);
boolean firstCol = true;
for (CwmColumn col : cols) {
if (!firstCol) {
sb.append(",");
} else {
firstCol = false;
}
sb.identifier(col.getName());
}
sb.append(")");
}
}
sb.append(NL);
sb.append(")");
}
// NOTE: original method preserved for backwards compatibility
public void generateColumnsAndKeys(
SqlBuilder sb,
List<CwmColumn> columns,
boolean skipDefaults,
boolean skipNullable,
List<String> imposedPrimaryKey)
{
List<String> pk = imposedPrimaryKey;
pk = generateColumnsAndKeysForCols(
sb, columns, skipDefaults, skipNullable, pk);
if (pk != null && pk.size() > 0) {
sb.append(NL);
}
sb.append(NL);
sb.append(")");
}
private List<String> generateColumnsAndKeysForCols(
SqlBuilder sb,
List<CwmColumn> columns,
boolean skipDefaults,
boolean skipNullable,
List<String> imposedPrimaryKey)
{
boolean isLast = false;
List<String> pk = imposedPrimaryKey;
if (columns.size() > 0) {
sb.append(" (");
sb.append(NL);
int n = 0;
for (CwmColumn column : columns) {
if (n++ > 0) {
sb.append(",");
sb.append(NL);
}
if (imposedPrimaryKey == null) {
if (column instanceof FemStoredColumn) {
if (hasPrimaryKeyConstraint((FemStoredColumn) column)) {
if (pk == null) {
pk = new ArrayList<String>();
}
pk.add(column.getName());
}
}
}
sb.append(" ").identifier(column.getName());
if (column.getType().getName().equals("CURSOR")) {
sb.append(".*");
continue;
}
sb.append(" ");
CwmExpression e;
if (skipDefaults) {
e = null;
} else {
e = column.getInitialValue();
}
final NullableType isNullable;
if (skipNullable) {
isNullable = null;
} else {
isNullable = column.getIsNullable();
}
appendType(
sb, column.getType(), column.getPrecision(),
column.getScale(), column.getLength(), isNullable, e, true);
// is this a stored column?
if (column instanceof FemElementWithStorageOptions) {
addOptions(
sb,
((FemElementWithStorageOptions) column)
.getStorageOptions(),
2);
}
}
if (pk != null) {
if (n++ > 0) {
sb.append(",");
sb.append(NL);
}
addPrimaryKeyConstraint(sb, pk);
}
}
return pk;
}
protected void addOptions(
SqlBuilder sb,
Collection<FemStorageOption> options)
{
addOptions(sb, options, 1);
}
protected void addOptions(
SqlBuilder sb,
Collection<FemStorageOption> options,
int indent)
{
if ((options == null) || (options.size() == 0)) {
return;
}
List<FemStorageOption> sortedOptions =
new ArrayList<FemStorageOption>(options);
Collections.sort(sortedOptions, new FemStorageOptionNameComparator());
if (indent == 1) {
sb.append(NL);
}
sb.append("OPTIONS (");
sb.append(NL);
int k = 0;
for (FemStorageOption option : sortedOptions) {
indent(sb, indent * 2);
sb.identifier(option.getName());
sb.append(" ");
sb.literal(option.getValue());
if (++k < sortedOptions.size()) {
sb.append(",");
}
sb.append(NL);
}
sb.append(")");
}
private static void indent(SqlBuilder sb, int indent)
{
for (int j = 0; j < indent; j++) {
sb.append(' ');
}
}
private void addPrimaryKeyConstraint(
SqlBuilder sb,
List<String> keyColumns)
{
if (keyColumns != null) {
sb.append(" PRIMARY KEY (");
boolean isFirst = true;
for (String keyColumn : keyColumns) {
if (!isFirst) {
sb.append(",");
} else {
isFirst = false;
}
sb.identifier(keyColumn);
}
sb.append(")");
}
}
protected void addDescription(
SqlBuilder sb,
FemAnnotatedElement element)
{
String desc = element.getDescription();
if ((desc != null) && (desc.length() > 0)) {
sb.append(NL);
sb.append("DESCRIPTION ");
sb.literal(desc);
}
}
protected void drop(
CwmModelElement e,
String elementType,
GeneratedDdlStmt stmt)
{
if (e == null) {
return;
}
SqlBuilder sb = createSqlBuilder();
sb.append("DROP ").append(elementType).append(" ");
name(sb, e.getNamespace(), e.getName());
if (dropCascade) {
sb.append(" CASCADE");
}
stmt.addStmt(sb.getSqlAndClear());
}
//~ Inner Classes ----------------------------------------------------------
protected static class FemStorageOptionNameComparator
implements Comparator<FemStorageOption>
{
public int compare(FemStorageOption o1, FemStorageOption o2)
{
int c = o1.getName().compareTo(o2.getName());
if (c != 0) {
return c;
}
return o1.getValue().compareTo(o2.getValue());
}
}
}
// End FarragoDdlGenerator.java