blob: 516849650f12163a89780a291470ddf060443a96 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// 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 com.cloud.utils.db;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.AttributeOverride;
import javax.persistence.CollectionTable;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SecondaryTable;
import javax.persistence.TableGenerator;
import org.apache.commons.lang.ArrayUtils;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.db.Attribute.Flag;
public class SqlGenerator {
Class<?> _clazz;
ArrayList<Attribute> _attributes;
ArrayList<Field> _embeddeds;
ArrayList<Class<?>> _tables;
LinkedHashMap<String, List<Attribute>> _ids;
HashMap<String, TableGenerator> _generators;
ArrayList<Attribute> _ecAttrs;
Field[] _mappedSuperclassFields;
public SqlGenerator(Class<?> clazz) {
_clazz = clazz;
_tables = new ArrayList<Class<?>>();
_attributes = new ArrayList<Attribute>();
_ecAttrs = new ArrayList<Attribute>();
_embeddeds = new ArrayList<Field>();
_ids = new LinkedHashMap<String, List<Attribute>>();
_generators = new HashMap<String, TableGenerator>();
buildAttributes(clazz, DbUtil.getTableName(clazz), DbUtil.getAttributeOverrides(clazz), false, false);
assert (_tables.size() > 0) : "Did you forget to put @Entity on " + clazz.getName();
handleDaoAttributes(clazz);
findEcAttributes();
}
protected boolean checkMethods(Class<?> clazz, Map<String, Attribute> attrs) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("get")) {
String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
assert !attrs.containsKey(fieldName) : "Mismatch in " + clazz.getSimpleName() + " for " + name;
} else if (name.startsWith("is")) {
String fieldName = Character.toLowerCase(name.charAt(2)) + name.substring(3);
assert !attrs.containsKey(fieldName) : "Mismatch in " + clazz.getSimpleName() + " for " + name;
} else if (name.startsWith("set")) {
String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
assert !attrs.containsKey(fieldName) : "Mismatch in " + clazz.getSimpleName() + " for " + name;
}
}
return true;
}
protected void buildAttributes(Class<?> clazz, String tableName, AttributeOverride[] overrides, boolean embedded, boolean isId) {
if (!embedded && clazz.getAnnotation(Entity.class) == null) {
// A class designated with the MappedSuperclass annotation can be mapped in the same way as an entity
// except that the mappings will apply only to its subclasses since no table exists for the mapped superclass itself
if (clazz.getAnnotation(MappedSuperclass.class) != null){
Field[] declaredFields = clazz.getDeclaredFields();
_mappedSuperclassFields = (Field[]) ArrayUtils.addAll(_mappedSuperclassFields, declaredFields);
}
return;
}
Class<?> parent = clazz.getSuperclass();
if (parent != null) {
buildAttributes(parent, DbUtil.getTableName(parent), DbUtil.getAttributeOverrides(parent), false, false);
}
if (!embedded) {
_tables.add(clazz);
_ids.put(tableName, new ArrayList<Attribute>());
}
Field[] fields = clazz.getDeclaredFields();
fields = (Field[]) ArrayUtils.addAll(fields, _mappedSuperclassFields);
_mappedSuperclassFields = null;
for (Field field : fields) {
field.setAccessible(true);
TableGenerator tg = field.getAnnotation(TableGenerator.class);
if (tg != null) {
_generators.put(field.getName(), tg);
}
if (!DbUtil.isPersistable(field)) {
continue;
}
if (field.getAnnotation(Embedded.class) != null) {
_embeddeds.add(field);
Class<?> embeddedClass = field.getType();
assert (embeddedClass.getAnnotation(Embeddable.class) != null) : "Class is not annotated with Embeddable: " + embeddedClass.getName();
buildAttributes(embeddedClass, tableName, DbUtil.getAttributeOverrides(field), true, false);
continue;
}
if (field.getAnnotation(EmbeddedId.class) != null) {
_embeddeds.add(field);
Class<?> embeddedClass = field.getType();
assert (embeddedClass.getAnnotation(Embeddable.class) != null) : "Class is not annotated with Embeddable: " + embeddedClass.getName();
buildAttributes(embeddedClass, tableName, DbUtil.getAttributeOverrides(field), true, true);
continue;
}
Attribute attr = new Attribute(clazz, overrides, field, tableName, embedded, isId);
if (attr.getColumnName().equals(GenericDao.REMOVED_COLUMN)) {
attr.setTrue(Attribute.Flag.DaoGenerated);
attr.setFalse(Attribute.Flag.Insertable);
attr.setFalse(Attribute.Flag.Updatable);
attr.setTrue(Attribute.Flag.TimeStamp);
attr.setFalse(Attribute.Flag.Time);
attr.setFalse(Attribute.Flag.Date);
attr.setTrue(Attribute.Flag.Nullable);
attr.setTrue(Attribute.Flag.Removed);
}
if (attr.isId()) {
List<Attribute> attrs = _ids.get(tableName);
attrs.add(attr);
}
_attributes.add(attr);
}
}
protected void findEcAttributes() {
for (Attribute attr : _attributes) {
if (attr.field == null) {
continue;
}
ElementCollection ec = attr.field.getAnnotation(ElementCollection.class);
if (ec != null) {
Attribute idAttr = _ids.get(attr.table).get(0);
assert supportsElementCollection(attr.field) : "Doesn't support ElementCollection for " + attr.field.getName();
attr.attache = new EcInfo(attr, idAttr);
_ecAttrs.add(attr);
}
}
}
protected boolean supportsElementCollection(Field field) {
ElementCollection otm = field.getAnnotation(ElementCollection.class);
if (otm.fetch() == FetchType.LAZY) {
assert (false) : "Doesn't support laz fetch: " + field.getName();
return false;
}
CollectionTable ct = field.getAnnotation(CollectionTable.class);
if (ct == null) {
assert (false) : "No collection table sepcified for " + field.getName();
return false;
}
return true;
}
public Map<String, TableGenerator> getTableGenerators() {
return _generators;
}
protected void handleDaoAttributes(Class<?> clazz) {
Attribute attr;
Class<?> current = clazz;
while (current != null && current.getAnnotation(Entity.class) != null) {
DiscriminatorColumn column = current.getAnnotation(DiscriminatorColumn.class);
if (column != null) {
String columnName = column.name();
attr = findAttribute(columnName);
if (attr != null) {
attr.setTrue(Attribute.Flag.DaoGenerated);
attr.setTrue(Attribute.Flag.Insertable);
attr.setTrue(Attribute.Flag.Updatable);
attr.setFalse(Attribute.Flag.Nullable);
attr.setTrue(Attribute.Flag.DC);
} else {
attr = new Attribute(DbUtil.getTableName(current), column.name());
attr.setFalse(Flag.Selectable);
attr.setTrue(Flag.Insertable);
attr.setTrue(Flag.DaoGenerated);
attr.setTrue(Flag.DC);
_attributes.add(attr);
}
if (column.discriminatorType() == DiscriminatorType.CHAR) {
attr.setTrue(Attribute.Flag.CharDT);
} else if (column.discriminatorType() == DiscriminatorType.STRING) {
attr.setTrue(Attribute.Flag.StringDT);
} else if (column.discriminatorType() == DiscriminatorType.INTEGER) {
attr.setTrue(Attribute.Flag.IntegerDT);
}
}
PrimaryKeyJoinColumn[] pkjcs = DbUtil.getPrimaryKeyJoinColumns(current);
if (pkjcs != null) {
for (PrimaryKeyJoinColumn pkjc : pkjcs) {
String tableName = DbUtil.getTableName(current);
attr = findAttribute(pkjc.name());
if (attr == null || !tableName.equals(attr.table)) {
Attribute id = new Attribute(DbUtil.getTableName(current), pkjc.name());
if (pkjc.referencedColumnName().length() > 0) {
attr = findAttribute(pkjc.referencedColumnName());
assert (attr != null) : "Couldn't find referenced column name " + pkjc.referencedColumnName();
}
id.field = attr.field;
id.setTrue(Flag.Id);
id.setTrue(Flag.Insertable);
id.setFalse(Flag.Updatable);
id.setFalse(Flag.Nullable);
id.setFalse(Flag.Selectable);
_attributes.add(id);
List<Attribute> attrs = _ids.get(id.table);
attrs.add(id);
}
}
}
current = current.getSuperclass();
}
attr = findAttribute(GenericDao.CREATED_COLUMN);
if (attr != null && attr.field.getType() == Date.class) {
attr.setTrue(Attribute.Flag.DaoGenerated);
attr.setTrue(Attribute.Flag.Insertable);
attr.setFalse(Attribute.Flag.Updatable);
attr.setFalse(Attribute.Flag.Date);
attr.setFalse(Attribute.Flag.Time);
attr.setTrue(Attribute.Flag.TimeStamp);
attr.setFalse(Attribute.Flag.Nullable);
attr.setTrue(Attribute.Flag.Created);
}
attr = findAttribute(GenericDao.XID_COLUMN);
if (attr != null && attr.field.getType() == String.class) {
attr.setTrue(Attribute.Flag.DaoGenerated);
attr.setTrue(Attribute.Flag.Insertable);
attr.setFalse(Attribute.Flag.Updatable);
attr.setFalse(Attribute.Flag.TimeStamp);
attr.setFalse(Attribute.Flag.Time);
attr.setFalse(Attribute.Flag.Date);
attr.setFalse(Attribute.Flag.Nullable);
attr.setFalse(Attribute.Flag.Removed);
}
}
public List<Attribute> getElementCollectionAttributes() {
return _ecAttrs;
}
public Attribute findAttribute(String name) {
for (Attribute attr : _attributes) {
if (attr.columnName.equalsIgnoreCase(name)) {
if (attr.columnName.equalsIgnoreCase(GenericDao.REMOVED_COLUMN) && attr.isUpdatable()) {
return null;
}
return attr;
}
}
return null;
}
public static StringBuilder buildUpdateSql(String tableName, List<Attribute> attrs) {
StringBuilder sql = new StringBuilder("UPDATE ");
sql.append(tableName).append(" SET ");
for (Attribute attr : attrs) {
sql.append(attr.columnName).append(" = ?, ");
}
sql.delete(sql.length() - 2, sql.length());
sql.append(" WHERE ");
return sql;
}
public List<Pair<StringBuilder, Attribute[]>> buildUpdateSqls() {
ArrayList<Pair<StringBuilder, Attribute[]>> sqls = new ArrayList<Pair<StringBuilder, Attribute[]>>(_tables.size());
for (Class<?> table : _tables) {
ArrayList<Attribute> attrs = new ArrayList<Attribute>();
String tableName = DbUtil.getTableName(table);
for (Attribute attr : _attributes) {
if (attr.isUpdatable() && tableName.equals(attr.table)) {
attrs.add(attr);
}
}
if (attrs.size() != 0) {
Pair<StringBuilder, Attribute[]> pair =
new Pair<StringBuilder, Attribute[]>(buildUpdateSql(tableName, attrs), attrs.toArray(new Attribute[attrs.size()]));
sqls.add(pair);
}
}
return sqls;
}
public static StringBuilder buildMysqlUpdateSql(String joins, Collection<Ternary<Attribute, Boolean, Object>> setters) {
if (setters.size() == 0) {
return null;
}
StringBuilder sql = new StringBuilder("UPDATE ");
sql.append(joins);
sql.append(" SET ");
for (Ternary<Attribute, Boolean, Object> setter : setters) {
Attribute attr = setter.first();
sql.append(attr.table).append(".").append(attr.columnName).append("=");
if (setter.second() != null) {
sql.append(attr.table).append(".").append(attr.columnName).append(setter.second() ? "+" : "-");
}
sql.append("?, ");
}
sql.delete(sql.length() - 2, sql.length());
sql.append(" WHERE ");
return sql;
}
public List<Pair<String, Attribute[]>> buildInsertSqls() {
LinkedHashMap<String, ArrayList<Attribute>> map = new LinkedHashMap<String, ArrayList<Attribute>>();
for (Class<?> table : _tables) {
map.put(DbUtil.getTableName(table), new ArrayList<Attribute>());
}
for (Attribute attr : _attributes) {
if (attr.isInsertable()) {
ArrayList<Attribute> attrs = map.get(attr.table);
assert (attrs != null) : "Null set of attributes for " + attr.table;
attrs.add(attr);
}
}
List<Pair<String, Attribute[]>> sqls = new ArrayList<Pair<String, Attribute[]>>(map.size());
for (Map.Entry<String, ArrayList<Attribute>> entry : map.entrySet()) {
ArrayList<Attribute> attrs = entry.getValue();
StringBuilder sql = buildInsertSql(entry.getKey(), attrs);
Pair<String, Attribute[]> pair = new Pair<String, Attribute[]>(sql.toString(), attrs.toArray(new Attribute[attrs.size()]));
sqls.add(pair);
}
return sqls;
}
protected StringBuilder buildInsertSql(String table, ArrayList<Attribute> attrs) {
StringBuilder sql = new StringBuilder("INSERT INTO ");
sql.append(table).append(" (");
for (Attribute attr : attrs) {
sql.append(table).append(".").append(attr.columnName).append(", ");
}
if (attrs.size() > 0) {
sql.delete(sql.length() - 2, sql.length());
}
sql.append(") VALUES (");
for (int i = 0; i < attrs.size(); i++) {
sql.append("?, ");
}
if (attrs.size() > 0) {
sql.delete(sql.length() - 2, sql.length());
}
sql.append(")");
return sql;
}
protected List<Pair<String, Attribute[]>> buildDeleteSqls() {
LinkedHashMap<String, ArrayList<Attribute>> map = new LinkedHashMap<String, ArrayList<Attribute>>();
for (Class<?> table : _tables) {
map.put(DbUtil.getTableName(table), new ArrayList<Attribute>());
}
for (Attribute attr : _attributes) {
if (attr.isId()) {
ArrayList<Attribute> attrs = map.get(attr.table);
assert (attrs != null) : "Null set of attributes for " + attr.table;
attrs.add(attr);
}
}
List<Pair<String, Attribute[]>> sqls = new ArrayList<Pair<String, Attribute[]>>(map.size());
for (Map.Entry<String, ArrayList<Attribute>> entry : map.entrySet()) {
ArrayList<Attribute> attrs = entry.getValue();
String sql = buildDeleteSql(entry.getKey(), attrs);
Pair<String, Attribute[]> pair = new Pair<String, Attribute[]>(sql, attrs.toArray(new Attribute[attrs.size()]));
sqls.add(pair);
}
Collections.reverse(sqls);
return sqls;
}
protected String buildDeleteSql(String table, ArrayList<Attribute> attrs) {
StringBuilder sql = new StringBuilder("DELETE FROM ");
sql.append(table).append(" WHERE ");
for (Attribute attr : attrs) {
sql.append(table).append(".").append(attr.columnName).append("= ? AND ");
}
sql.delete(sql.length() - 5, sql.length());
return sql.toString();
}
public Pair<String, Attribute[]> buildRemoveSql() {
Attribute attribute = findAttribute(GenericDao.REMOVED_COLUMN);
if (attribute == null) {
return null;
}
StringBuilder sql = new StringBuilder("UPDATE ");
sql.append(attribute.table).append(" SET ");
sql.append(attribute.columnName).append(" = ? WHERE ");
List<Attribute> ids = _ids.get(attribute.table);
// if ids == null, that means the removed column was added as a JOIN
// value to another table. We ignore it here.
if (ids == null) {
return null;
}
if (ids.size() == 0) {
return null;
}
for (Attribute id : ids) {
sql.append(id.table).append(".").append(id.columnName).append(" = ? AND ");
}
sql.delete(sql.length() - 5, sql.length());
Attribute[] attrs = ids.toArray(new Attribute[ids.size() + 1]);
attrs[attrs.length - 1] = attribute;
return new Pair<String, Attribute[]>(sql.toString(), attrs);
}
public Map<String, Attribute[]> getIdAttributes() {
LinkedHashMap<String, Attribute[]> ids = new LinkedHashMap<String, Attribute[]>(_ids.size());
for (Map.Entry<String, List<Attribute>> entry : _ids.entrySet()) {
ids.put(entry.getKey(), entry.getValue().toArray(new Attribute[entry.getValue().size()]));
}
return ids;
}
/**
* @return a map of tables and maps of field names to attributes.
*/
public Map<String, Attribute> getAllAttributes() {
Map<String, Attribute> attrs = new LinkedHashMap<String, Attribute>(_attributes.size());
for (Attribute attr : _attributes) {
if (attr.field != null) {
attrs.put(attr.field.getName(), attr);
}
}
return attrs;
}
public Map<Pair<String, String>, Attribute> getAllColumns() {
Map<Pair<String, String>, Attribute> attrs = new LinkedHashMap<Pair<String, String>, Attribute>(_attributes.size());
for (Attribute attr : _attributes) {
if (attr.columnName != null) {
attrs.put(new Pair<String, String>(attr.table, attr.columnName), attr);
}
}
return attrs;
}
protected static void addPrimaryKeyJoinColumns(StringBuilder sql, String fromTable, String toTable, String joinType, PrimaryKeyJoinColumn[] pkjcs) {
if ("right".equalsIgnoreCase(joinType)) {
sql.append(" RIGHT JOIN ").append(toTable).append(" ON ");
} else if ("left".equalsIgnoreCase(joinType)) {
sql.append(" LEFT JOIN ").append(toTable).append(" ON ");
} else {
sql.append(" INNER JOIN ").append(toTable).append(" ON ");
}
for (PrimaryKeyJoinColumn pkjc : pkjcs) {
sql.append(fromTable).append(".").append(pkjc.name());
String refColumn = DbUtil.getReferenceColumn(pkjc);
sql.append("=").append(toTable).append(".").append(refColumn).append(" ");
}
}
public Pair<String, Attribute> getRemovedAttribute() {
Attribute removed = findAttribute(GenericDao.REMOVED_COLUMN);
if (removed == null) {
return null;
}
if (removed.field.getType() != Date.class) {
return null;
}
StringBuilder sql = new StringBuilder();
sql.append(removed.table).append(".").append(removed.columnName).append(" IS NULL ");
return new Pair<String, Attribute>(sql.toString(), removed);
}
protected static void buildJoins(StringBuilder innerJoin, Class<?> clazz) {
String tableName = DbUtil.getTableName(clazz);
SecondaryTable[] sts = DbUtil.getSecondaryTables(clazz);
ArrayList<String> secondaryTables = new ArrayList<String>();
for (SecondaryTable st : sts) {
JoinType jt = clazz.getAnnotation(JoinType.class);
String join = null;
if (jt != null) {
join = jt.type();
}
addPrimaryKeyJoinColumns(innerJoin, tableName, st.name(), join, st.pkJoinColumns());
secondaryTables.add(st.name());
}
Class<?> parent = clazz.getSuperclass();
if (parent.getAnnotation(Entity.class) != null) {
String table = DbUtil.getTableName(parent);
PrimaryKeyJoinColumn[] pkjcs = DbUtil.getPrimaryKeyJoinColumns(clazz);
assert (pkjcs != null) : "No Join columns specified for the super class";
addPrimaryKeyJoinColumns(innerJoin, tableName, table, null, pkjcs);
}
}
public String buildTableReferences() {
StringBuilder sql = new StringBuilder();
sql.append(DbUtil.getTableName(_tables.get(_tables.size() - 1)));
for (Class<?> table : _tables) {
buildJoins(sql, table);
}
return sql.toString();
}
public Pair<StringBuilder, Attribute[]> buildSelectSql(boolean enableQueryCache) {
StringBuilder sql = new StringBuilder("SELECT ");
sql.append(enableQueryCache ? "SQL_CACHE " : "");
ArrayList<Attribute> attrs = new ArrayList<Attribute>();
for (Attribute attr : _attributes) {
if (attr.isSelectable()) {
attrs.add(attr);
sql.append(attr.table).append(".").append(attr.columnName).append(", ");
}
}
if (attrs.size() > 0) {
sql.delete(sql.length() - 2, sql.length());
}
sql.append(" FROM ").append(buildTableReferences());
sql.append(" WHERE ");
sql.append(buildDiscriminatorClause().first());
return new Pair<StringBuilder, Attribute[]>(sql, attrs.toArray(new Attribute[attrs.size()]));
}
public Pair<StringBuilder, Attribute[]> buildSelectSql(Attribute[] attrs) {
StringBuilder sql = new StringBuilder("SELECT ");
for (Attribute attr : attrs) {
sql.append(attr.table).append(".").append(attr.columnName).append(", ");
}
if (attrs.length > 0) {
sql.delete(sql.length() - 2, sql.length());
}
sql.append(" FROM ").append(buildTableReferences());
sql.append(" WHERE ");
sql.append(buildDiscriminatorClause().first());
return new Pair<StringBuilder, Attribute[]>(sql, attrs);
}
/**
* buildDiscriminatorClause builds the join clause when there are multiple tables.
*
* @return
*/
public Pair<StringBuilder, Map<String, Object>> buildDiscriminatorClause() {
StringBuilder sql = new StringBuilder();
Map<String, Object> values = new HashMap<String, Object>();
for (Class<?> table : _tables) {
DiscriminatorValue dv = table.getAnnotation(DiscriminatorValue.class);
if (dv != null) {
Class<?> parent = table.getSuperclass();
String tableName = DbUtil.getTableName(parent);
DiscriminatorColumn dc = parent.getAnnotation(DiscriminatorColumn.class);
assert (dc != null) : "Parent does not have discrminator column: " + parent.getName();
sql.append(tableName);
sql.append(".");
sql.append(dc.name()).append("=");
Object value = null;
if (dc.discriminatorType() == DiscriminatorType.INTEGER) {
sql.append(dv.value());
value = Integer.parseInt(dv.value());
} else if (dc.discriminatorType() == DiscriminatorType.CHAR) {
sql.append(dv.value());
value = dv.value().charAt(0);
} else if (dc.discriminatorType() == DiscriminatorType.STRING) {
String v = dv.value();
v = v.substring(0, v.length() < dc.length() ? v.length() : dc.length());
sql.append("'").append(v).append("'");
value = v;
}
values.put(dc.name(), value);
sql.append(" AND ");
}
}
return new Pair<StringBuilder, Map<String, Object>>(sql, values);
}
public Field[] getEmbeddedFields() {
return _embeddeds.toArray(new Field[_embeddeds.size()]);
}
public String buildCountSql() {
StringBuilder sql = new StringBuilder();
return sql.append("SELECT COUNT(*) FROM ").append(buildTableReferences()).append(" WHERE ").append(buildDiscriminatorClause().first()).toString();
}
public String buildDistinctIdSql() {
StringBuilder sql = new StringBuilder();
return sql.append("SELECT DISTINCT id FROM ").append(buildTableReferences()).append(" WHERE ").append(buildDiscriminatorClause().first()).toString();
}
public String buildDistinctSql(String[] distinctColumnNames) {
StringBuilder sbColumn = new StringBuilder();
if (distinctColumnNames != null && distinctColumnNames.length > 0) {
for (String columnName : distinctColumnNames) {
sbColumn.append(columnName).append(", ");
}
sbColumn.delete(sbColumn.length() - 2, sbColumn.length());
} else {
sbColumn.append("*");
}
StringBuilder sql = new StringBuilder();
return sql.append("SELECT DISTINCT " + sbColumn.toString() + " FROM ").append(buildTableReferences()).append(" WHERE ").append(buildDiscriminatorClause().first()).toString();
}
}