Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ public static AnnouncementModel updateAnnouncement(User user, AnnouncementModel

public static int updateContainer(List<String> discussionSrcIds, Container targetContainer, User user)
{
return ContainerManager.updateContainer(_comm.getTableInfoAnnouncements(), "discussionSrcIdentifier", discussionSrcIds, targetContainer, user, false);
return Table.updateContainer(_comm.getTableInfoAnnouncements(), "discussionSrcIdentifier", discussionSrcIds, targetContainer, user, false);
}


Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/attachments/AttachmentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ static AttachmentService get()

void copyAttachment(AttachmentParent parent, Attachment a, String newName, User auditUser) throws IOException;

void moveAttachments(Container newContainer, List<AttachmentParent> parents, User auditUser) throws IOException;
int moveAttachments(Container newContainer, List<AttachmentParent> parents, User auditUser) throws IOException;

@NotNull
List<AttachmentFile> getAttachmentFiles(AttachmentParent parent, Collection<Attachment> attachments) throws IOException;
Expand Down
10 changes: 2 additions & 8 deletions api/src/org/labkey/api/audit/AbstractAuditTypeProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
import org.labkey.api.data.DbSchemaType;
import org.labkey.api.data.DbScope;
import org.labkey.api.data.MutableColumnInfo;
import org.labkey.api.data.SQLFragment;
import org.labkey.api.data.SqlExecutor;
import org.labkey.api.data.Table;
import org.labkey.api.data.TableInfo;
import org.labkey.api.dataiterator.DataIterator;
import org.labkey.api.dataiterator.ExistingRecordDataIterator;
Expand Down Expand Up @@ -395,11 +394,6 @@ else if (value instanceof Date date)

public int moveEvents(Container targetContainer, String idColumnName, Collection<?> ids)
{
TableInfo auditTable = createStorageTableInfo();
SQLFragment sql = new SQLFragment("UPDATE ").append(auditTable)
.append(" SET container = ").appendValue(targetContainer)
.append(" WHERE ").append(idColumnName);
auditTable.getSchema().getSqlDialect().appendInClauseSql(sql, ids);
return new SqlExecutor(auditTable.getSchema()).execute(sql);
return Table.updateContainer(createStorageTableInfo(), idColumnName, ids, targetContainer, null, false);
}
}
40 changes: 12 additions & 28 deletions api/src/org/labkey/api/data/ContainerManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.labkey.api.audit.provider.ContainerAuditProvider;
import org.labkey.api.cache.Cache;
import org.labkey.api.cache.CacheManager;
import org.labkey.api.collections.CaseInsensitiveHashMap;
import org.labkey.api.collections.CaseInsensitiveHashSet;
import org.labkey.api.collections.ConcurrentHashSet;
import org.labkey.api.collections.IntHashMap;
Expand Down Expand Up @@ -103,7 +104,6 @@
import org.labkey.api.view.ViewContext;
import org.labkey.api.writer.MemoryVirtualFile;
import org.labkey.folder.xml.FolderDocument;
import org.labkey.remoteapi.collections.CaseInsensitiveHashMap;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;

Expand Down Expand Up @@ -138,7 +138,7 @@

/**
* This class manages a hierarchy of collections, backed by a database table called Containers.
* Containers are named using filesystem-like paths e.g. /proteomics/comet/. Each path
* Containers are named using filesystem-like paths e.g., /proteomics/comet/. Each path
* maps to a UID and set of permissions. The current security scheme allows ACLs
* to be specified explicitly on the directory or completely inherited. ACLs are not combined.
* <p/>
Expand All @@ -150,7 +150,7 @@
* a container is deleted, it should never get put back in the cache. We accomplish this by synchronizing on
* the removal from the cache, and the database lookup/cache insertion. While a container is in the middle
* of being deleted, it's OK for other clients to see it because FKs enforce that it's always internally
* consistent, even if some of the data has already been deleted.
* consistent, even if some data has already been deleted.
*/
public class ContainerManager
{
Expand Down Expand Up @@ -2683,8 +2683,7 @@ private static Container getContainerForIdOrPath(String targetContainer)
return c;
}

// targetContainer must be in the same app project at this time
// i.e. child of current project, project of current child, sibling within project
// Current and target containers must be within the same project tree at this time
private static boolean isValidTargetContainer(Container current, Container target)
{
if (current.isRoot() || target.isRoot())
Expand All @@ -2694,31 +2693,16 @@ private static boolean isValidTargetContainer(Container current, Container targe
if (current.equals(target))
return true;

boolean moveFromProjectToChild = current.isProject() && target.getParent().equals(current);
boolean moveFromChildToProject = !current.isProject() && current.getParent().isProject() && current.getParent().equals(target);
boolean moveFromChildToSibling = !current.isProject() && current.getParent().isProject() && current.getParent().equals(target.getParent());
// from project to descendant
if (current.isProject())
return target.isDescendant(current);

return moveFromProjectToChild || moveFromChildToProject || moveFromChildToSibling;
}

public static int updateContainer(TableInfo dataTable, String idField, Collection<?> ids, Container targetContainer, User user, boolean withModified)
{
try (DbScope.Transaction transaction = dataTable.getSchema().getScope().ensureTransaction())
{
SQLFragment dataUpdate = new SQLFragment("UPDATE ").append(dataTable)
.append(" SET container = ").appendValue(targetContainer.getEntityId());
if (withModified)
{
dataUpdate.append(", modified = ").appendValue(new Date());
dataUpdate.append(", modifiedby = ").appendValue(user.getUserId());
}
dataUpdate.append(" WHERE ").append(idField);
dataTable.getSchema().getSqlDialect().appendInClauseSql(dataUpdate, ids);
int numUpdated = new SqlExecutor(dataTable.getSchema()).execute(dataUpdate);
transaction.commit();
// from descendant to project
if (target.isProject())
return current.isDescendant(target);

return numUpdated;
}
// from descendant to descendant
return current.getProject() != null && current.getProject().equals(target.getProject());
}

/**
Expand Down
113 changes: 91 additions & 22 deletions api/src/org/labkey/api/data/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.poi.ss.formula.functions.T;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
Expand All @@ -37,6 +36,7 @@
import org.labkey.api.dataiterator.DataIteratorContext;
import org.labkey.api.dataiterator.Pump;
import org.labkey.api.dataiterator.SimpleTranslator;
import org.labkey.api.dataiterator.SimpleTranslator.SpecialColumn;
import org.labkey.api.dataiterator.TableInsertDataIteratorBuilder;
import org.labkey.api.di.DataIntegrationService;
import org.labkey.api.exceptions.OptimisticConflictException;
Expand Down Expand Up @@ -102,12 +102,12 @@ public class Table
// Makes long parameter lists easier to read
public static final int NO_OFFSET = 0;

public static final String ENTITY_ID_COLUMN_NAME = "EntityId";
public static final String OWNER_COLUMN_NAME = "Owner";
public static final String CREATED_BY_COLUMN_NAME = "CreatedBy";
public static final String CREATED_COLUMN_NAME = "Created";
public static final String MODIFIED_BY_COLUMN_NAME = "ModifiedBy";
public static final String MODIFIED_COLUMN_NAME = "Modified";
private static final String ENTITY_ID_COLUMN_NAME = SpecialColumn.EntityId.name();
private static final String OWNER_COLUMN_NAME = "Owner";
private static final String CREATED_BY_COLUMN_NAME = SpecialColumn.CreatedBy.name();
private static final String CREATED_COLUMN_NAME = SpecialColumn.Created.name();
private static final String MODIFIED_BY_COLUMN_NAME = SpecialColumn.ModifiedBy.name();
private static final String MODIFIED_COLUMN_NAME = SpecialColumn.Modified.name();

/** Columns that are magically populated as part of an insert or update operation */
public static final Set<String> AUTOPOPULATED_COLUMN_NAMES = CaseInsensitiveHashSet.of(
Expand Down Expand Up @@ -819,7 +819,7 @@ else if (propName.endsWith("id"))
*/
public static <K> K insert(@Nullable User user, TableInfo table, K fieldsIn)
{
assert (table.getTableType() != DatabaseTableType.NOT_IN_DB): ("Table " + table.getSchema().getName() + "." + table.getName() + " is not in the physical database.");
assert assertInDb(table);

// _executeTriggers(table, fields);

Expand Down Expand Up @@ -963,7 +963,7 @@ public static <K> K update(@Nullable User user, TableInfo table, K fieldsIn, Obj
/* NOTE this does not enforce that keyColumn is an appropriately unique column! */
public static <K> K update(@Nullable User user, TableInfo table, K fieldsIn, @Nullable ColumnInfo keyColumn, Object pkVals, @Nullable Filter filter, Level level)
{
assert (table.getTableType() != DatabaseTableType.NOT_IN_DB): (table.getName() + " is not in the physical database.");
assert assertInDb(table);
assert null != pkVals;

// _executeTriggers(table, previous, fields);
Expand Down Expand Up @@ -1128,6 +1128,71 @@ else if (pkVals instanceof Map)
return (fieldsIn instanceof Map && !(fieldsIn instanceof BoundMap)) ? (K)fields : fieldsIn;
}

/**
* Updates the container of specified rows in the provided database table. Optionally, the modification timestamp
* and the user who made the modification can also be updated if specified.
*
* @param table The table where the container update should be applied.
* @param idField The name of the identifier field used to locate the rows to update.
* @param ids A collection of identifier values specifying the rows to be updated.
* @param targetContainer The target container to set for the specified rows.
* @param user The user performing the update. If null, modified/modifiedBy details are not updated.
* @param withModified If true, updates the modified timestamp and the user who made the modification.
* @return The number of rows updated in the table.
*/
public static int updateContainer(TableInfo table, String idField, Collection<?> ids, Container targetContainer, @Nullable User user, boolean withModified)
{
assert assertInDb(table);
ColumnInfo idColumn = table.getColumn(idField);
if (idColumn == null)
throw new IllegalArgumentException("Table " + fullTableName(table) + " has no column named '" + idField + "'.");

if (ids == null || ids.isEmpty())
return 0;

SimpleFilter filter = new SimpleFilter();
filter.addInClause(idColumn.getFieldKey(), ids);

return updateContainer(table, targetContainer, filter, user, withModified);
}

public static int updateContainer(TableInfo table, Container targetContainer, @NotNull SimpleFilter filter, @Nullable User user, boolean withModified)
{
assert assertInDb(table);
ColumnInfo containerColumn = table.getColumn(SpecialColumn.Container.name());
if (containerColumn == null)
throw new IllegalArgumentException("Table " + fullTableName(table) + " has no column named '" + SpecialColumn.Container.name() + "'.");

SQLFragment dataUpdate = new SQLFragment("UPDATE ").append(table)
.append(" SET ").appendIdentifier(containerColumn.getSelectIdentifier())
.append(" = ")
.appendValue(targetContainer);

if (withModified)
{
assert user != null : "User must be specified when updating modified/modifiedBy details.";
ColumnInfo colModified = table.getColumn(MODIFIED_COLUMN_NAME);
if (null != colModified)
{
dataUpdate.append(", ").appendIdentifier(colModified.getSelectIdentifier())
.append(" = ")
.appendValue(new java.sql.Timestamp(System.currentTimeMillis()));
}

ColumnInfo colModifiedBy = table.getColumn(MODIFIED_BY_COLUMN_NAME);
if (null != colModifiedBy)
{
dataUpdate.append(", ").appendIdentifier(colModifiedBy.getSelectIdentifier())
.append(" = ")
.appendValue(user.getUserId());
}
}

SQLFragment whereClause = filter.getSQLFragment(table.getSqlDialect(), null, createMetaDataNameMap(table));
dataUpdate.append("\n").append(whereClause);

return new SqlExecutor(table.getSchema()).execute(dataUpdate);
}

public static void delete(TableInfo table, Object rowId)
{
Expand All @@ -1154,25 +1219,24 @@ public static void delete(TableInfo table, Object rowId)

public static int delete(TableInfo table)
{
assert (table.getTableType() != DatabaseTableType.NOT_IN_DB): (table.getName() + " is not in the physical database.");
assert assertInDb(table);
SqlExecutor sqlExecutor = new SqlExecutor(table.getSchema());

return sqlExecutor.execute("DELETE FROM " + table.getSelectName());
}

public static int delete(TableInfo table, Filter filter)
{
assert (table.getTableType() != DatabaseTableType.NOT_IN_DB): (table.getName() + " is not in the physical database.");

assert assertInDb(table);
SQLFragment where = filter.getSQLFragment(table.getSqlDialect(), null, createMetaDataNameMap(table));

SQLFragment deleteSQL = new SQLFragment("DELETE FROM ").append(table).append("\n\t").append(where);

return new SqlExecutor(table.getSchema()).execute(deleteSQL);
}

public static void truncate(TableInfo table)
{
assert (table.getTableType() != DatabaseTableType.NOT_IN_DB): (table.getName() + " is not in the physical database.");
assert assertInDb(table);
SqlExecutor sqlExecutor = new SqlExecutor(table.getSchema());
sqlExecutor.execute(table.getSqlDialect().getTruncateSql(table.getSelectName()));
}
Expand Down Expand Up @@ -1569,7 +1633,7 @@ static public Map<FieldKey, ColumnInfo> createColumnMap(@Nullable TableInfo tabl
* Create a map that can be passed into Filter.getSQLFragment() that create a SQL fragment using getMetaDataName() instead of
* getAlias().
*/
static private Map<FieldKey, ColumnInfo> createMetaDataNameMap(TableInfo table)
private static Map<FieldKey, ColumnInfo> createMetaDataNameMap(TableInfo table)
{
Map<FieldKey, ColumnInfo> ret = new HashMap<>();
for (var column : table.getColumns())
Expand All @@ -1581,7 +1645,6 @@ static private Map<FieldKey, ColumnInfo> createMetaDataNameMap(TableInfo table)
return ret;
}


public static boolean checkAllColumns(TableInfo table, Collection<ColumnInfo> columns, String prefix)
{
return checkAllColumns(table, columns, prefix, false);
Expand All @@ -1591,16 +1654,13 @@ public static boolean checkAllColumns(TableInfo table, Collection<ColumnInfo> co
{
int bad = 0;

// Map<FieldKey, ColumnInfo> mapFK = new HashMap<>(columns.size()*2);
Map<String, ColumnInfo> mapAlias = new HashMap<>(columns.size()*2);
ColumnInfo prev;

for (ColumnInfo column : columns)
{
if (!checkColumn(table, column, prefix))
bad++;
// if (enforceUnique && null != (prev=mapFK.put(column.getFieldKey(), column)) && prev != column)
// bad++;
if (enforceUnique && !(column instanceof AliasedColumn) && null != (prev = mapAlias.put(column.getAlias().getId(), column)) && prev != column)
{
_log.warn(prefix + ": Column " + column + " from table: " + column.getParentTable() + " is mapped to the same alias (" + column.getAlias().getId() + ") as column " + prev + " from table: " + prev.getParentTable());
Expand All @@ -1621,7 +1681,6 @@ public static boolean checkAllColumns(TableInfo table, Collection<ColumnInfo> co
return 0 == bad;
}


public static boolean checkColumn(TableInfo table, ColumnInfo column, String prefix)
{
if (column.getParentTable() != table)
Expand All @@ -1635,8 +1694,7 @@ public static boolean checkColumn(TableInfo table, ColumnInfo column, String pre
}
}


public static ParameterMapStatement deleteStatement(Connection conn, TableInfo tableDelete /*, Set<String> columns */) throws SQLException
public static ParameterMapStatement deleteStatement(Connection conn, TableInfo tableDelete) throws SQLException
{
if (!(tableDelete instanceof UpdateableTableInfo updatable))
throw new IllegalArgumentException();
Expand Down Expand Up @@ -1732,6 +1790,17 @@ public static ParameterMapStatement deleteStatement(Connection conn, TableInfo t
return new ParameterMapStatement(tableDelete.getSchema().getScope(), conn, sqlfDelete, updatable.remapSchemaColumns());
}

private static boolean assertInDb(TableInfo table)
{
if (table.getTableType() == DatabaseTableType.NOT_IN_DB)
throw new AssertionError("Table " + fullTableName(table) + " is not in the physical database.");
return true;
}

private static String fullTableName(TableInfo table)
{
return table.getSchema().getName() + "." + table.getName();
}

public static class TestDataIterator extends AbstractDataIterator
{
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/exp/OntologyManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ public static void updateObjectPropertyOrder(User user, Container container, Str
*/
public static int updateContainer(Container targetContainer, User user, @NotNull String objectLSID)
{
return ContainerManager.updateContainer(getTinfoObject(), "objectURI", List.of(objectLSID), targetContainer, user, false);
return Table.updateContainer(getTinfoObject(), "objectURI", List.of(objectLSID), targetContainer, user, false);
}

/**
Expand Down
4 changes: 3 additions & 1 deletion api/src/org/labkey/api/exp/list/ListDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,11 @@ public static BodySetting getForValue(int value)
int getListId();
void setPreferredListIds(Collection<Integer> preferredListIds); // Attempts to use this list IDs when inserting
Container getContainer();
@Nullable Domain getDomain();

@Nullable Domain getDomain();
@Nullable Domain getDomain(boolean forUpdate);
@NotNull Domain getDomainOrThrow();
@NotNull Domain getDomainOrThrow(boolean forUpdate);

String getName();
String getKeyName();
Expand Down
11 changes: 10 additions & 1 deletion api/src/org/labkey/api/query/QueryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,16 @@ public String getDefaultCommentSummary()
List<DetailedAuditTypeEvent> getQueryUpdateAuditRecords(User user, Container container, long transactionAuditId, @Nullable ContainerFilter containerFilter);
AuditHandler getDefaultAuditHandler();

int moveAuditEvents(Container targetContainer, List<Long> rowPks, String schemaName, String queryName);
/**
* Moves audit events associated with the specific rows, identified by primary key, to the target container.
*
* @param targetContainer The container to which audit events will be moved.
* @param rowPks A collection of primary key values identifying the rows whose audit events should be moved.
* @param schemaName The schema name of the table.
* @param queryName The query (table) name.
* @return The number of audit events moved.
*/
int moveAuditEvents(Container targetContainer, Collection<?> rowPks, String schemaName, String queryName);

/**
* Returns a URL for the audit history for the table.
Expand Down
Loading