| /* |
| * 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 |
| * 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 org.apache.empire.db; |
| |
| // java |
| import java.lang.reflect.Field; |
| import java.sql.Connection; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.apache.empire.commons.ObjectUtils; |
| import org.apache.empire.commons.Options; |
| import org.apache.empire.commons.StringUtils; |
| import org.apache.empire.data.DataType; |
| import org.apache.empire.db.DBIndex.DBIndexType; |
| import org.apache.empire.db.DBRelation.DBCascadeAction; |
| import org.apache.empire.db.exceptions.NoPrimaryKeyException; |
| import org.apache.empire.db.exceptions.RecordDeleteFailedException; |
| import org.apache.empire.db.exceptions.RecordUpdateFailedException; |
| import org.apache.empire.exceptions.InvalidArgumentException; |
| import org.apache.empire.exceptions.ItemExistsException; |
| import org.apache.empire.exceptions.ObjectNotValidException; |
| import org.apache.empire.exceptions.UnexpectedReturnValueException; |
| import org.apache.empire.exceptions.UnspecifiedErrorException; |
| |
| |
| /** |
| * This class represent one table of the database. |
| * It contains methods to get, add, update and delete records from the database. |
| * |
| */ |
| public class DBTable extends DBRowSet implements Cloneable |
| { |
| // *Deprecated* private static final long serialVersionUID = 1L; |
| |
| // Integer size definitions |
| public static final int DEFAULT = 0; |
| public static final int SMALLINT = 2; |
| public static final int MEDIUMINT = 4; |
| public static final int BIGINT = 8; |
| |
| private static AtomicInteger tableCount = new AtomicInteger(0); |
| |
| private final String name; |
| private String alias; |
| private DBIndex primaryKey = null; |
| private final List<DBIndex> indexes = new ArrayList<DBIndex>(); |
| private Boolean quoteName = null; |
| private DBCascadeAction cascadeDeleteAction = DBCascadeAction.NONE; |
| |
| /** |
| * Construct a new DBTable object set the specified parameters |
| * to this object and add this object to the current database. |
| * |
| * @param name the table name |
| * @param db the valid database object |
| */ |
| public DBTable(String name, DBDatabase db, String alias) |
| { |
| super(db); |
| // generate alias |
| if (alias==null) |
| alias = "t" + String.valueOf(tableCount.incrementAndGet()); |
| // init |
| this.name = name; |
| this.alias = alias; |
| // Add Table to Database |
| if (db != null) |
| db.addTable(this); |
| } |
| |
| /** |
| * Construct a new DBTable object set the specified parameters |
| * to this object and add this object to the current database. |
| * |
| * @param name the table name |
| * @param db the valid database object |
| */ |
| public DBTable(String name, DBDatabase db) |
| { |
| this(name, db, null); |
| } |
| |
| /** |
| * Returns the table name of this object. |
| * |
| * @return the table name of this object |
| */ |
| @Override |
| public String getName() |
| { |
| return name; |
| } |
| |
| /** |
| * Returns the table alias name of this object. |
| * |
| * @return the table alias name of this object |
| */ |
| @Override |
| public String getAlias() |
| { |
| return alias; |
| } |
| |
| /** |
| * Returns whether or not the table supports record updates. Default is true. |
| * @return true if the table allows record updates |
| */ |
| @Override |
| public boolean isUpdateable() |
| { |
| return true; |
| } |
| |
| /** |
| * Returns an array of all primary key columns. |
| * |
| * @return an array of all primary key columns |
| */ |
| @Override |
| public DBColumn[] getKeyColumns() |
| { |
| return ((primaryKey != null) ? primaryKey.getColumns() : null); |
| } |
| |
| /** |
| * Clones this table and assigns a new table alias. |
| * This second instance of the same table can be used for self-joins. |
| * <pre> |
| * This method requires that all declared column fields are NOT declared final. |
| * i.e. instead of: |
| * |
| * public final DBTableColumn MYCOL; |
| * |
| * columns must be declared: |
| * |
| * public DBTableColumn MYCOL; |
| * |
| * A runtime exception for the CloneNotSupported will be thrown if references cannot be adjusted. |
| * |
| * Alternatively a second table instance may be created manually like this: |
| * |
| * public final MyTable MYTABLE1 = new MyTable(); |
| * public final MyTable MYTABLE2 = new MyTable(); |
| * |
| * ... |
| * cmd.join(MYTABLE1.ID, MYTABLE2.PARENTID); // self-join |
| * ... |
| * </pre> |
| * @return a table clone with new table alias |
| */ |
| @Override |
| public Object clone() throws CloneNotSupportedException |
| { |
| DBTable clone = (DBTable) super.clone(); |
| initClonedFields(clone); |
| // set new alias |
| clone.alias = "t" + String.valueOf(tableCount.incrementAndGet()); |
| // done |
| log.info("clone: Table " + name + " cloned! Alias old=" + alias + " new=" + clone.alias); |
| return clone; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <T extends DBTable> T clone(String newAlias) |
| { |
| try { |
| DBTable clone = (DBTable) super.clone(); |
| initClonedFields(clone); |
| // set new alias |
| if (StringUtils.isEmpty(newAlias)) |
| clone.alias = "t" + String.valueOf(tableCount.incrementAndGet()); |
| else |
| clone.alias = newAlias; |
| // done |
| log.info("clone: Table " + name + " cloned! Alias old=" + alias + " new=" + clone.alias); |
| return (T)clone; |
| } catch (CloneNotSupportedException e) { |
| // unable to clone table |
| log.error("Unable to clone table " + getName()); |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected <T extends DBTable> void initClonedFields(T clone) throws CloneNotSupportedException |
| { |
| // clone all columns |
| Class<?> colClass = columns.get(0).getClass(); |
| Class<?> colBase = colClass.getSuperclass(); |
| clone.columns = new ArrayList<DBColumn>(); |
| Field[] fields = getClass().getFields(); |
| for (int i = 0; i < columns.size(); i++) |
| { |
| DBTableColumn srcCol = (DBTableColumn) columns.get(i); |
| DBTableColumn newCol = new DBTableColumn(clone, srcCol); |
| // Replace all references for oldCol to newCol |
| for (int j = 0; j < fields.length; j++) |
| { // Find a class of Type DBColumn or DBTableColumn |
| Field f = fields[j]; |
| Class<?> type = f.getType(); |
| if (type == colClass || type == colBase) |
| { try |
| { // Check if the field points to the old Value |
| if (f.get(clone) == srcCol) |
| { // Check accessible |
| if (f.isAccessible()==false) |
| { // not accessible |
| f.setAccessible(true); |
| try { |
| f.set(clone, newCol); |
| } finally { |
| f.setAccessible(false); |
| } |
| } |
| else |
| { // already accessible |
| f.set(clone, newCol); |
| } |
| } |
| } catch (Exception e) { |
| // IllegalAccessException or IllegalArgumentException |
| String fieldName = fields[j].getName(); |
| log.error("Failed to modify declared table field: " + fieldName + ". Reason is: " + e.toString()); |
| // throw CloneNotSupportedException |
| CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to replace field reference for field " + fieldName); |
| cnse.initCause(e); |
| throw cnse; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds a column to this table's column list. |
| * @param column a column object |
| */ |
| protected void addColumn(DBTableColumn column) |
| { // find column by name |
| if (column==null || column.getRowSet()!=this) |
| throw new InvalidArgumentException("column", column); |
| if (getColumn(column.getName())!=null) |
| throw new ItemExistsException(column.getName()); |
| // add now |
| columns.add(column); |
| } |
| |
| /** |
| * Creates a new Column object and appends it to the column list |
| * @param columnName the column name |
| * @param type the type of the column e.g. integer, text, date |
| * @param size the column width |
| * @param required true if not null column |
| * @param defValue a Object object |
| * @return the new column object |
| */ |
| protected DBTableColumn crateAndAppendColumn(String columnName, DataType type, double size, boolean required, Object defValue) |
| { |
| // Check exists |
| if (getColumn(columnName)!=null) |
| throw new ItemExistsException(columnName); |
| // Make sure (DataType.INTEGER & DataMode.AutoGenerated) = DataType.AUTOINC |
| boolean autoGenerated = (type==DataType.AUTOINC || type==DataType.UNIQUEID); |
| DBTableColumn column = new DBTableColumn(this, type, columnName, size, required, autoGenerated, defValue); |
| // auto-set primary key |
| if (column.getDataType()==DataType.AUTOINC) |
| { // Automatically set primary key |
| if (this.primaryKey==null) |
| this.setPrimaryKey(column); |
| else |
| throw new UnspecifiedErrorException("Table "+getName()+" already has a Primary-Key! No column of type AUTOINC can be added."); |
| } |
| /* |
| // auto-set timestamp column |
| if (column.getDataType()==DataType.TIMESTAMP) |
| { // Automatically set timestamp column |
| if (timestampColumn==null) |
| this.setTimestampColumn(column); |
| else |
| log.warn("Table {} already has a Timestamp column. DataType of column {} should be DATETIME.", getName(), column.getName()); |
| } |
| */ |
| addColumn(column); |
| return column; |
| } |
| |
| /** |
| * Creates a new DBTableColumn object and adds it to the column collection. |
| * Instead of the data mode enum, a boolean flag is used to indicate whether the column is required or optional. |
| * |
| * @param columnName the column name |
| * @param type the type of the column e.g. integer, text, date |
| * @param size the column width |
| * @param required true if not null column |
| * @param defValue a Object object |
| * @return the new column object |
| */ |
| public final DBTableColumn addColumn(String columnName, DataType type, double size, boolean required, Object defValue) |
| { |
| if (defValue instanceof Class<?>) |
| { |
| log.warn("Column {}: a class object of type \"{}\" has been passed as default value. Please check!", columnName, ((Class<?>)defValue).getName()); |
| } |
| return this.crateAndAppendColumn(columnName, type, size, required, defValue); |
| } |
| |
| /** |
| * Creates a new table column and adds it to the table's column list |
| * |
| * @param columnName the column name |
| * @param type the type of the column e.g. integer, text, date |
| * @param size the column width |
| * @param required true if not null column |
| * @return the new column object |
| */ |
| public final DBTableColumn addColumn(String columnName, DataType type, double size, boolean required) |
| { |
| return this.crateAndAppendColumn(columnName, type, size, required, null); |
| } |
| |
| /** |
| * Creates a new table column with options and adds it to the table's column list |
| * This overload should be used for column containing enum values which have no default value. |
| * |
| * @param columnName the column name |
| * @param type the type of the column e.g. integer, text, date |
| * @param size the column width |
| * @param required true if not null column |
| * @param options this list of options |
| * @return the new column object |
| */ |
| public final DBTableColumn addColumn(String columnName, DataType type, double size, boolean required, Options options) |
| { |
| DBTableColumn col = this.crateAndAppendColumn(columnName, type, size, required, null); |
| col.setOptions(options); |
| return col; |
| } |
| |
| /** |
| * Creates a new table column with options and adds it to the table's column list |
| * This overload should be used for column containing enum values which have a default value. |
| * |
| * @param columnName the column name |
| * @param type the type of the column e.g. integer, text, date |
| * @param size the column width |
| * @param required true if not null column |
| * @param options this list of options |
| * @param defValue the default value |
| * @return the new column object |
| */ |
| public final DBTableColumn addColumn(String columnName, DataType type, double size, boolean required, Options options, Object defValue) |
| { |
| // defValue must be part of options |
| if (defValue!=null && !options.contains(defValue)) |
| throw new InvalidArgumentException("devValue", defValue); |
| // add |
| DBTableColumn col = this.crateAndAppendColumn(columnName, type, size, required, defValue); |
| col.setOptions(options); |
| return col; |
| } |
| |
| /** |
| * Creates a new table column with Enum-Options and adds it to the table's column list |
| * This overload should be used for column containing enum values which have no default value. |
| * |
| * @param columnName the column name |
| * @param type the type of the column e.g. integer, text, date |
| * @param size the column width |
| * @param required true if not null column |
| * @param enumType the class of the enum type |
| * @return the new column object |
| */ |
| public final DBTableColumn addColumn(String columnName, DataType type, double size, boolean required, Class<?> enumType) |
| { |
| if (!enumType.isEnum()) |
| { // Class must be an enum type |
| throw new InvalidArgumentException("enumType", enumType); |
| } |
| DBTableColumn col = this.crateAndAppendColumn(columnName, type, size, required, null); |
| col.setEnumOptions(enumType); |
| return col; |
| } |
| |
| /** |
| * Creates a new table column with Enum-Options and adds it to the table's column list |
| * This overload should be used for column containing enum values which have a default value. |
| * |
| * @param columnName the column name |
| * @param type the type of the column e.g. integer, text, date |
| * @param size the column width |
| * @param required true if not null column |
| * @param enumType defValue the default value |
| * @return the new column object |
| */ |
| public final DBTableColumn addColumn(String columnName, DataType type, double size, boolean required, Enum<?> enumValue) |
| { |
| Object defValue = ObjectUtils.getEnumValue(enumValue, type.isNumeric()); |
| DBTableColumn col = this.crateAndAppendColumn(columnName, type, size, required, defValue); |
| col.setEnumOptions(enumValue.getClass()); |
| return col; |
| } |
| |
| /** |
| * Adds an Identity column to the table which also serves as the PrimaryKey |
| * An Identity Column is always an auto-generated Integer(Long) value |
| * @param name the name of the identity column |
| * @param seqName (optional) name of the sequence if supported by DBMS |
| * @return the Identity column |
| */ |
| public DBTableColumn addIdentity(String name, String seqName) |
| { |
| return addColumn("ID", DataType.AUTOINC, 0, true, seqName); |
| } |
| |
| /** |
| * Adds a new ForgeinKey table column the column list |
| * The foreign table must have a single column foreign key |
| * @param name the name of the new column |
| * @param target the table on which to reference |
| * @param required true if the value is required |
| * @param options (optional) a set of allowed values for this column |
| * @return the new column |
| */ |
| public DBTableColumn addForeignKey(String name, DBTable target, boolean required, Options options, DBCascadeAction cascadeAction) |
| { |
| // Check target: If null then Table has not been defined yet! |
| if (target==null) |
| throw new ObjectNotValidException(target); |
| // Check key |
| DBColumn[] keyCols = target.getKeyColumns(); |
| if (keyCols==null || keyCols.length!=1) |
| throw new InvalidArgumentException("target", target); |
| // add column |
| DBTableColumn keyCol = (DBTableColumn)keyCols[0]; |
| DataType keyDataType = keyCol.getDataType(); |
| if (keyDataType==DataType.AUTOINC) |
| keyDataType =DataType.INTEGER; |
| DBTableColumn referenceColumn = addColumn(name, keyDataType, keyCol.getSize(), required, options); |
| // Adapter foreign key |
| String fkName = getName() + "_" + name.replace("_ID", "_FK"); |
| DBRelation relation = db.addRelation(fkName, referenceColumn.referenceOn(keyCol)); |
| if (cascadeAction!=null) |
| relation.setOnDeleteAction(cascadeAction); |
| return referenceColumn; |
| } |
| |
| /** |
| * Adds a new ForgeinKey table column the column list |
| * The foreign table must have a single column foreign key |
| * @param name the name of the new column |
| * @param target the table on which to reference |
| * @param required true if the value is required |
| * @param cascade whether or not to cascade deletes for this relation |
| * @return the new column |
| */ |
| public final DBTableColumn addForeignKey(String name, DBTable target, boolean required, boolean cascade) |
| { |
| return addForeignKey(name, target, required, null, (cascade ? DBCascadeAction.CASCADE : DBCascadeAction.NONE)); |
| } |
| |
| /** |
| * Adds a new ForgeinKey table column the column list |
| * The foreign table must have a single column foreign key |
| * @param name the name of the new column |
| * @param target the table on which to reference |
| * @param required true if the value is required |
| * @return the new column |
| */ |
| public final DBTableColumn addForeignKey(String name, DBTable target, boolean required) |
| { |
| return addForeignKey(name, target, required, false); |
| } |
| |
| /** |
| * Adds a Timestamp column to the current table |
| * There can only be one timestamp column per table |
| * @param name the name of the new column |
| * @return the new column |
| */ |
| public DBTableColumn addTimestamp(String name) |
| { |
| DBTableColumn tsColumn = addColumn(name, DataType.TIMESTAMP, 0, true); |
| this.setTimestampColumn(tsColumn); |
| return tsColumn; |
| } |
| |
| /** |
| * Returns the primary key. |
| * |
| * @return the the DBIndex object -> primary key |
| */ |
| public DBIndex getPrimaryKey() |
| { |
| return primaryKey; |
| } |
| |
| /** |
| * Returns the list of indexes (except the primary key). |
| * |
| * @return a list of DBIndex objects |
| */ |
| public List<DBIndex> getIndexes() |
| { |
| return Collections.unmodifiableList(this.indexes); |
| } |
| |
| /** |
| * Sets the primary key. |
| * |
| * @param columns a array with one or more DBColumn objects |
| */ |
| public void setPrimaryKey(DBColumn... columns) |
| { |
| checkParamNull("columns", columns); |
| // All columns must belong to this table |
| for (int i=0; i<columns.length; i++) |
| if (columns[i].getRowSet()!=this) |
| throw new InvalidArgumentException("columns["+String.valueOf(i)+"]", columns[i].getFullName()); |
| // Check if already exists |
| if (primaryKey!=null) |
| { // compare columns |
| if (primaryKey.compareColumns(columns)) |
| return; // already set |
| // warn |
| log.warn("A PrimaryKey for the Table {} has already been set. Replacing with new one.", getName()); |
| // new key |
| removeIndex(primaryKey); |
| } |
| // Set primary Key now |
| if (columns.length>0) |
| { // create primary key |
| primaryKey = new DBIndex(name + "_PK", DBIndexType.PRIMARY_KEY, columns); |
| addIndex(primaryKey); |
| // log |
| log.debug("PrimaryKey {} of length {} has been set for table {}", primaryKey.getName(), columns.length, getName()); |
| } |
| else |
| { // No primary Key |
| primaryKey = null; |
| } |
| } |
| |
| /** |
| * Adds an index. |
| * |
| * @param index the index to add |
| */ |
| public DBIndex addIndex(DBIndex index) |
| { |
| if (index==null) |
| throw new InvalidArgumentException("index", null); |
| // Check index name |
| String name = index.getName(); |
| for (DBIndex i : indexes) |
| { |
| if (i==index || name.equalsIgnoreCase(i.getName())) |
| { |
| throw new ItemExistsException(name); |
| } |
| } |
| // add Index now |
| indexes.add(index); |
| index.setTable(this); |
| return index; |
| } |
| |
| /** |
| * Adds an index. |
| * |
| * @param name the index name |
| * @param unique is this a unique index |
| * @param columns the columns indexed by this index |
| * |
| * @return the Index object |
| */ |
| public final DBIndex addIndex(String name, DBIndexType type, DBColumn... columns) |
| { |
| if (name==null || columns==null || columns.length==0) |
| throw new InvalidArgumentException("name|columns", null); |
| if (type==DBIndexType.PRIMARY_KEY && this.primaryKey!=null) |
| throw new InvalidArgumentException("type", DBIndexType.PRIMARY_KEY.name()); |
| // add Index now |
| DBIndex index = new DBIndex(name, type, columns); |
| addIndex(index); |
| return index; |
| } |
| |
| /** |
| * Adds an index. |
| * Overload for convenience |
| */ |
| public final DBIndex addIndex(String name, boolean unique, DBColumn... columns) |
| { |
| return addIndex(name, (unique) ? DBIndexType.UNIQUE : DBIndexType.STANDARD, columns); |
| } |
| |
| /** |
| * removes an index. |
| * |
| * @param index the index to remove |
| */ |
| public void removeIndex(DBIndex index) |
| { |
| if (index.getTable()!=this || !indexes.contains(index)) |
| throw new InvalidArgumentException("index", index); |
| // table |
| indexes.remove(index); |
| index.setTable(null); |
| } |
| |
| /** |
| * Adds a timestamp column to the table used for optimistic locking. |
| * |
| * @param columnName the column name |
| * |
| * @return the timestamp table column object |
| */ |
| public DBTableColumn addTimestampColumn(String columnName) |
| { |
| DBTableColumn col = addColumn(columnName, DataType.TIMESTAMP, 0, true, DBDatabase.SYSDATE); |
| if (this.timestampColumn!=col) |
| setTimestampColumn(col); // make sure, this is the timestamp column, even if another one exists |
| return col; |
| } |
| |
| /** |
| * Adds the table's name to the supplied sql command buffer. |
| * |
| * @param buf the SQL-Command |
| * @param context the current SQL-Command context |
| */ |
| @Override |
| public void addSQL(StringBuilder buf, long context) |
| { |
| // Append Name |
| if ((context & CTX_NAME|CTX_FULLNAME)!=0) |
| { // append Qualified Name |
| db.appendQualifiedName(buf, name, quoteName); |
| } |
| // Append Alias |
| if ((context & CTX_ALIAS)!=0 && alias!=null) |
| { // append alias |
| buf.append(getRenameTablePhrase()); |
| buf.append(getAlias()); |
| } |
| } |
| |
| /** |
| * Gets all table fields and the fields properties. |
| * Set this to the specified DBRecord object. |
| * |
| * @param record the DBRecord object. contains all fields and the field properties |
| * @param conn a valid connection to the database. |
| */ |
| @Override |
| public void createRecord(DBRecordBase record, Object[] initalKey, boolean deferredInit) |
| { |
| FieldInitMode fieldInitMode = (deferredInit ? FieldInitMode.SET_DEFAULTS_DEFERRED : FieldInitMode.SET_DEFAULTS); |
| super.initRecord(record, initalKey, fieldInitMode, true); |
| } |
| |
| /** |
| * Checks weather a unique constraint is violated when inserting or updating a record.<BR> |
| * <P> |
| * @param id the record's primary key |
| * @param conn a valid JDBC connection |
| */ |
| public DBIndex checkUniqueConstraints(DBRecordBase record) |
| { |
| for (DBIndex idx : getIndexes()) |
| { |
| if (idx.getType()==DBIndexType.PRIMARY_KEY) |
| { // Only for new records |
| if (!record.isNew()) |
| continue; // not new |
| } |
| else if (idx.getType().isUnique()) |
| { // check if any of the fields were actually changed |
| if (!record.isNew() && !record.wasAnyModified(idx.getColumns())) |
| continue; // not modified |
| } |
| else |
| { // No unique index |
| continue; |
| } |
| // Check index |
| DBCommand cmd = createRecordCommand(record.getContext()); |
| cmd.select(count()); |
| for (DBColumn c : idx.getColumns()) |
| { |
| Object value = record.get(c); |
| cmd.where(c.is(value)); |
| } |
| DBUtils utils = record.getContext().getUtils(); |
| int count = utils.querySingleInt(cmd); |
| if (count>0) |
| { // Index is violated |
| return idx; |
| } |
| } |
| // no index violation detected |
| return null; |
| } |
| |
| /** |
| * returns the default cascade action for deletes on this table. |
| * This is used as the default for newly created relations on this table and does not affect existing relations. |
| * @return the delete cascade action for new relations (DBRelation.DBCascadeAction.CASCADE_RECORDS) are enabled |
| */ |
| public DBCascadeAction getDefaultCascadeDeleteAction() |
| { |
| return cascadeDeleteAction; |
| } |
| |
| /** |
| * sets the default cascade action for deletes on foreign key relations. |
| * @param cascadeDeleteAction cascade action for deletes (DBRelation.DBCascadeAction.CASCADE_RECORDS) |
| */ |
| public void setDefaultCascadeDeleteAction(DBCascadeAction cascadeDeleteAction) |
| { |
| this.cascadeDeleteAction = cascadeDeleteAction; |
| } |
| |
| /** |
| * Creates a delete SQL-Command by using the DBCommand getDelete method |
| * execute the the SQL-Command with the DBDatabase |
| * executeSQL method. |
| * |
| * @param key an array of the primary key columns |
| * @param conn a valid connection to the database. |
| */ |
| @Override |
| public void deleteRecord(Object[] key, DBContext context) |
| { |
| // Check Primary key |
| if (primaryKey == null ) |
| throw new NoPrimaryKeyException(this); |
| |
| // Check Columns |
| DBColumn[] keyColumns = primaryKey.getColumns(); |
| if (key == null || key.length != keyColumns.length) |
| throw new InvalidArgumentException("key", key); |
| |
| // Delete References |
| deleteAllReferences(key, context); |
| |
| // Build SQL-Statement |
| DBCommand cmd = createRecordCommand(context); |
| // Set key constraints |
| cmd.where(getKeyConstraints(key)); |
| // Perform delete |
| String sqlCmd = cmd.getDelete(this); |
| int affected = context.executeSQL(sqlCmd, cmd.getParamValues()); |
| if (affected < 0) |
| { // Delete Failed |
| throw new UnexpectedReturnValueException(affected, "db.executeSQL()"); |
| } |
| else if (affected == 0) |
| { // Record not found |
| throw new RecordDeleteFailedException(this, key); |
| } |
| else if (affected > 1) |
| { // Multiple Records affected |
| throw new RecordUpdateFailedException(this, key); |
| } |
| } |
| |
| /** |
| * Returns a list of all foreign key relations for this table |
| * @return the list of foreign key relations |
| */ |
| public List<DBRelation> getForeignKeyRelations() |
| { |
| List<DBRelation> relations = new ArrayList<DBRelation>(); |
| for (DBRelation r : getDatabase().getRelations()) |
| { // check relation |
| if (this.equals(r.getForeignKeyTable())) |
| relations.add(r); |
| } |
| return Collections.unmodifiableList(relations); |
| } |
| |
| /** |
| * validates a column value |
| * @return the validated (possibly converted) value |
| */ |
| protected Object validateValue(DBTableColumn column, Object value) |
| { |
| return db.validateValue(column, value); |
| } |
| |
| /** |
| * initializes the Record Default Values |
| * @param record the record |
| * @param conn (optional) to allow the dbms handle autogenerated fields |
| */ |
| @Override |
| protected void initRecordDefaultValues(DBRecordBase record, FieldInitMode fieldInitMode) |
| { |
| // check field init mode |
| if (fieldInitMode==FieldInitMode.NONE) |
| throw new InvalidArgumentException("fieldInitMode", fieldInitMode); |
| // Use connection if not deferred |
| Connection conn = (fieldInitMode==FieldInitMode.SET_DEFAULTS_DEFERRED ? null : record.getContext().getConnection()); |
| /* |
| * Connection Auto-Detect (disabled!) |
| * Use derived class to add connection if necessary |
| * |
| DBColumn pkColumn = (primaryKey!=null ? primaryKey.getColumn(0) : null); |
| if (conn==null && pkColumn!=null && pkColumn.isAutoGenerated() && fields[getColumnIndex(pkColumn)]==null) |
| { // Init AutoGenerated Key |
| conn = rec.getContext().getConnection(); |
| } |
| */ |
| Object[] fields = record.getFields(); |
| // Set Default values |
| // ATTENTION: Do not set to ObjectUtils.NO_VALUE |
| for (int i = 0; i < fields.length; i++) |
| { // already set ? |
| if (fields[i]!=null) |
| continue; |
| // check default |
| DBColumn column = columns.get(i); |
| // getDefaultValue |
| Object value = ((DBTableColumn)column).getRecordDefaultValue(conn); |
| if (ObjectUtils.isEmpty(value)) |
| continue; |
| // Initial value |
| record.modifyValue(i, value, false); |
| } |
| } |
| |
| } |