/*
// 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.io.*;
import java.lang.reflect.*;
import java.util.*;
import javax.jmi.model.*;
import javax.jmi.reflect.*;
import net.sf.farrago.cwm.core.*;
import net.sf.farrago.cwm.relational.*;
import net.sf.farrago.fem.med.*;
import net.sf.farrago.fem.sql2003.*;
import org.eigenbase.jmi.*;
import org.eigenbase.sql.*;
import org.eigenbase.sql.type.*;
import org.eigenbase.sql.util.SqlBuilder;
import org.eigenbase.util.*;
/**
* Base class for DDL generators which use the visitor pattern to generate DDL
* given a catalog object.
*
* <p>Escape rules:
*
* <ol>
* <li>In a SET SCHEMA command, apostrophes (') and quotes (") enclose the
* schema name, like this: '"Foo"'. In this context, apostrophes and quotes must
* be escaped.</li>
* <li>CREATE and DROP commands use quotes (") to enclose the object name. Only
* quotes are escaped.</li>
* </ol>
*
* @author Jason Ouellette
* @version $Id$
*/
public abstract class DdlGenerator
implements ReflectiveVisitor
{
//~ Static fields/initializers ---------------------------------------------
protected static final String VALUE_NULL = "NULL";
protected static final String NL = System.getProperty("line.separator");
protected static final String SEP = ";" + NL + NL;
private static final List<Class> ADDITIONAL_PARAMETER_TYPES =
Collections.singletonList((Class) GeneratedDdlStmt.class);
//~ Instance fields --------------------------------------------------------
protected final SqlDialect sqlDialect;
private boolean schemaQualified;
protected boolean dropCascade;
protected String previousSetSchema;
private final ReflectiveVisitDispatcher<DdlGenerator, CwmModelElement>
visitDispatcher =
ReflectUtil.createDispatcher(
DdlGenerator.class,
CwmModelElement.class);
//~ Methods ----------------------------------------------------------------
/**
* Creates a DdlGenerator.
*
* @param sqlDialect SQL dialect
*/
protected DdlGenerator(SqlDialect sqlDialect)
{
this.sqlDialect = sqlDialect;
}
protected abstract JmiModelView getModelView();
/**
* Sets whether object names should be qualified with a schema name, if they
* have one. Default is false.
*
* @param schemaQualified whether to qualify object names with schema name
*/
public void setSchemaQualified(boolean schemaQualified)
{
this.schemaQualified = schemaQualified;
}
/**
* Sets whether DROP statements should include a CASCADE directive at the
* end. Default is false, so you must explicitly request CASCADE.
*
* @param dropCascade whether to append CASCADE to DROP statements
*/
public void setDropCascade(boolean dropCascade)
{
this.dropCascade = dropCascade;
}
/**
* Appends a 'SET SCHEMA' command to <code>stmt</code> if <code>
* schemaName</code> is not null. If <code>evenIfUnchanged</code> is true,
* does so even if the schema is the same as the previous call to this
* method.
*
* @param stmt Statement to append to
* @param schemaName Name of schema
* @param evenIfUnchanged Whether to generate again for same schema name
*
* @return whether SET SCHEMA command was generated
*/
public boolean generateSetSchema(
GeneratedDdlStmt stmt,
String schemaName,
boolean evenIfUnchanged)
{
if ((schemaName != null)
&& (evenIfUnchanged
|| (previousSetSchema == null)
|| !previousSetSchema.equals(schemaName)))
{
SqlBuilder sb = createSqlBuilder();
sb.append("SET SCHEMA ");
sb.literal(sb.getDialect().quoteIdentifier(schemaName));
stmt.addStmt(sb.getSql());
previousSetSchema = schemaName;
return true;
} else {
return false;
}
}
protected SqlBuilder createSqlBuilder()
{
return new SqlBuilder(sqlDialect);
}
public void generateCreate(CwmModelElement e, GeneratedDdlStmt stmt)
{
generate("create", e, stmt);
}
public void generateDrop(CwmModelElement e, GeneratedDdlStmt stmt)
{
generate("drop", e, stmt);
}
private void generate(
String method,
CwmModelElement e,
GeneratedDdlStmt stmt)
{
Method m =
visitDispatcher.lookupVisitMethod(
this.getClass(),
e.getClass(),
method,
ADDITIONAL_PARAMETER_TYPES);
if (m != null) {
try {
m.invoke(
this,
e,
stmt);
} catch (InvocationTargetException e1) {
throw Util.newInternal(e1, "while exporting '" + e + "'");
} catch (IllegalAccessException e1) {
throw Util.newInternal(e1, "while exporting '" + e + "'");
} catch (RuntimeException e1) {
throw Util.newInternal(e1, "while exporting '" + e + "'");
}
}
}
protected static SqlTypeName getSqlTypeName(CwmClassifier classifier)
{
//REVIEW: make this work for UDTs
if (classifier == null) {
return SqlTypeName.ANY;
} else {
String typeName = classifier.getName();
SqlTypeName stn = SqlTypeName.get(typeName);
if (stn == null) {
return SqlTypeName.ANY;
}
return stn;
}
}
protected static boolean hasPrimaryKeyConstraint(FemStoredColumn col)
{
boolean result = false;
if (col != null) {
Collection<FemKeyComponent> keyComponent = col.getKeyComponent();
if (keyComponent != null) {
for (FemKeyComponent kc : keyComponent) {
if (kc.getKeyConstraint()
instanceof FemPrimaryKeyConstraint)
{
result = true;
break;
}
}
}
}
return result;
}
/**
* Converts a set of elements to a string using this generator.
*
* <p>If <code>sort</code> is specified, sorts list first so that dependent
* elements are created after their dependencies.
*
* @param exportList List of elements to export
* @param sort Whether to sort list in dependency order
*
* @return DDL script
*/
public String getExportText(List<CwmModelElement> exportList, boolean sort)
{
StringBuilder outBuf = new StringBuilder();
GeneratedDdlStmt stmt = new GeneratedDdlStmt();
if (sort) {
final JmiModelView modelView = getModelView();
final JmiModelGraph modelGraph = modelView.getModelGraph();
boolean debug = false;
if (debug) {
PrintWriter pw = new PrintWriter(System.out);
JmiObjUtil.dumpGraph(modelView, pw);
pw.flush();
}
// Mapping rules as per farrago/examples/dmv/schemaDependencies.xml
JmiDependencyMappedTransform transform =
new JmiDependencyMappedTransform(
modelView,
false);
transform.setTieBreaker(new MyComparator());
transform.setAllByAggregation(
AggregationKindEnum.COMPOSITE,
JmiAssocMapping.HIERARCHY);
transform.setAllByAggregation(
AggregationKindEnum.NONE,
JmiAssocMapping.COPY);
transform.setByRefAssoc(
lookupAssoc("DependencyClient", modelGraph),
JmiAssocMapping.CONTRACTION);
transform.setByRefAssoc(
lookupAssoc("DependencySupplier", modelGraph),
JmiAssocMapping.COPY);
// ignore ownership of views: their dependencies are more important
transform.setByRefAssocRefined(
lookupAssoc("ElementOwnership", modelGraph),
JmiAssocMapping.REMOVAL,
null,
lookupClass("LocalView", modelGraph));
// create schemas before the objects contained in them
transform.setByRefAssocRefined(
lookupAssoc("ElementOwnership", modelGraph),
JmiAssocMapping.COPY,
lookupClass("LocalSchema", modelGraph),
null);
// create method implementations after the operations which specify
// their interface
transform.setByRefAssoc(
lookupAssoc("OperationMethod", modelGraph),
JmiAssocMapping.COPY);
JmiDependencyGraph dependencyGraph =
new JmiDependencyGraph(
(Collection) exportList,
transform);
if (debug) {
PrintWriter pw = new PrintWriter(System.out);
JmiObjUtil.dumpGraph(dependencyGraph, pw, new NamerImpl());
pw.flush();
}
exportList = new ArrayList<CwmModelElement>();
JmiDependencyIterator vertexIter =
new JmiDependencyIterator(dependencyGraph);
while (vertexIter.hasNext()) {
JmiDependencyVertex vertex = vertexIter.next();
exportList.addAll((Collection) vertex.getElementSet());
}
}
for (CwmModelElement elem : exportList) {
// proceed if a catalog object has an ddlgen error
try {
stmt.clear();
generateCreate(elem, stmt);
if (!stmt.isTopLevel()) {
continue;
}
final String ddl = stmt.toString();
assert (ddl != null) && !ddl.equals("")
: "Do not know how to generate DDL for " + elem.getClass();
outBuf.append(ddl);
outBuf.append(SEP);
} catch (RuntimeException e) {
throw Util.newInternal(
e,
"Error while exporting '" + elem + "'");
}
}
return outBuf.toString();
}
/**
* Looks up a named class in the model, fails if not found.
*
* @param className Association name
* @param modelGraph Model graph
*
* @return Class, never null
*/
private RefClass lookupClass(
String className,
JmiModelGraph modelGraph)
{
JmiClassVertex classVertex =
modelGraph.getVertexForClassName(className);
if (classVertex == null) {
throw new IllegalArgumentException("unknown class " + className);
}
return classVertex.getRefClass();
}
/**
* Looks up a named association in the model, fails if not found.
*
* @param assocName Association name
* @param modelGraph Model graph
*
* @return Association, never null
*/
private RefAssociation lookupAssoc(
String assocName,
JmiModelGraph modelGraph)
{
JmiAssocEdge edge = modelGraph.getEdgeForAssocName(assocName);
if (edge == null) {
throw new IllegalArgumentException(
"unknown association " + assocName);
}
return edge.getRefAssoc();
}
/**
* Returns whether an object type supports <code>CREATE OR REPLACE</code>
* operation.
*
* @param typeName Name of object type, e.g. "CLUSTERED INDEX"
*
* @return whether type supports REPLACE
*/
protected abstract boolean typeSupportsReplace(String typeName);
/**
* Gathers a list of elements in a schema, optionally including elements
* which don't belong to any schema.
*
* @param list List to populate
* @param schemaName Name of schema
* @param includeNonSchemaElements Whether to include elements which do not
* belong to a schema
* @param catalog Catalog
*/
public abstract void gatherElements(
List<CwmModelElement> list,
String schemaName,
boolean includeNonSchemaElements,
CwmCatalog catalog);
/**
* Outputs the name of an object, optionally qualified by a schema name.
*
* @param sb SqlBuilder to write to
* @param schema Schema object belongs to, or null if object does not belong
* to a schema
* @param objectName Name of object
*/
protected void name(
SqlBuilder sb,
CwmNamespace schema,
String objectName)
{
if (schemaQualified && (schema != null)) {
sb.identifier(schema.getName(), objectName);
} else {
sb.identifier(objectName);
}
}
//~ Inner Classes ----------------------------------------------------------
/**
* Implementation of {@link org.eigenbase.jmi.JmiObjUtil.Namer} which
* generates names for objects based on their position in the CWM
* catalog-schema-object hierarchy.
*
* <p>For example, a table's name might be "CATALOG.SALES.EMP (LocalTable)".
*/
private static class NamerImpl
implements JmiObjUtil.Namer
{
public String getName(RefObject o)
{
StringBuilder buf = new StringBuilder();
if (o instanceof CwmModelElement) {
CwmModelElement modelElement = (CwmModelElement) o;
yy(modelElement, buf);
} else {
buf.append(o.toString());
}
buf.append('(');
buf.append(JmiObjUtil.getTypeName(o));
buf.append(')');
return buf.toString();
}
private void yy(CwmModelElement modelElement, StringBuilder buf)
{
CwmNamespace namespace = modelElement.getNamespace();
if (namespace != null) {
yy(namespace, buf);
buf.append('.');
}
buf.append(modelElement.getName());
}
}
/**
* Comparator for schema elements to ensure that export file occurs in an
* intuitive order.
*/
private static class MyComparator
implements Comparator<RefBaseObject>
{
// Priority order of classes.
private final Class [] classes =
{
// data wrappers first
FemDataWrapper.class,
// data server before schema
FemDataServer.class,
// next functions and procedures
CwmProcedure.class,
// schema after non-schema objects
CwmSchema.class,
// index before a view on the same table
FemLocalIndex.class,
CwmTable.class,
};
public int compare(RefBaseObject o1, RefBaseObject o2)
{
// First compare classes. An object sorts earlier if
// its class is higher in the pecking order.
int c = compareClass(o1, o2);
if (c != 0) {
return c;
}
// Next, for objects of the same type, sort by package
// and name. B.C sorts before B.D but after A.D.
if ((o1 instanceof CwmModelElement)
&& (o2 instanceof CwmModelElement))
{
return compareModelElements(
(CwmModelElement) o1,
(CwmModelElement) o2);
}
// Lastly compare by MofId.
return o1.refMofId().compareTo(o2.refMofId());
}
/**
* Compares objects by their class. An object sorts earlier if its class
* is higher in the pecking order.
*/
private int compareClass(Object o1, Object o2)
{
if (o1.getClass() != o2.getClass()) {
int i1 = findClass(o1);
int i2 = findClass(o2);
if (i1 != i2) {
return i1 - i2;
}
}
return o1.getClass().getName().compareTo(
o2.getClass().getName());
}
/**
* Returns the ordinal of an object's class in the pecking order, or
* {@link Integer#MAX_VALUE} if not found.
*/
private int findClass(Object o)
{
for (int i = 0; i < classes.length; i++) {
if (classes[i].isInstance(o)) {
return i;
}
}
return Integer.MAX_VALUE;
}
/**
* Compares two model elements of the same type by their position in the
* hierarchy.
*/
private int compareModelElements(
CwmModelElement me1,
CwmModelElement me2)
{
CwmNamespace ns1 = me1.getNamespace();
CwmNamespace ns2 = me2.getNamespace();
if (ns1 == ns2) {
return me1.getName().compareTo(me2.getName());
} else if (ns1 == null) {
return -1;
} else if (ns2 == null) {
return 1;
} else {
return compareModelElements(ns1, ns2);
}
}
}
}
// End DdlGenerator.java