existingRecord)
{
throw new UnsupportedOperationException("Table triggers not yet supported on schema tables");
}
diff --git a/api/src/org/labkey/api/data/TableInfo.java b/api/src/org/labkey/api/data/TableInfo.java
index 1ff06b90cc5..15cde936ec4 100644
--- a/api/src/org/labkey/api/data/TableInfo.java
+++ b/api/src/org/labkey/api/data/TableInfo.java
@@ -508,7 +508,7 @@ enum TriggerMethod
/**
* Executes any trigger scripts for this table.
- *
+ *
* The trigger should be called once before and once after an entire set of rows for each of the
* INSERT, UPDATE, DELETE trigger types. A trigger script may set up data structures to be used
* during validation. In particular, the trigger script might want to do a query to populate a set of
@@ -555,19 +555,20 @@ enum TriggerMethod
* @param c The current Container.
* @param user the current user
* @param type The TriggerType for the event.
+ * @param insertOption The insertOption that invoked this trigger. Will be null when invoked outside a data iterator.
* @param before true if the trigger is before the event, false if after the event.
* @param errors Any errors created by the validation script will be added to the errors collection.
* @param extraContext Optional additional bindings to set in the script's context when evaluating.
* @throws BatchValidationException if the trigger function returns false or the errors map isn't empty.
*/
- void fireBatchTrigger(Container c, User user, TriggerType type, boolean before, BatchValidationException errors, Map extraContext)
+ void fireBatchTrigger(Container c, User user, TriggerType type, @Nullable QueryUpdateService.InsertOption insertOption, boolean before, BatchValidationException errors, Map extraContext)
throws BatchValidationException;
default void fireRowTrigger(Container c, User user, TriggerType type, boolean before, int rowNumber,
- @Nullable Map newRow, @Nullable Map oldRow, Map extraContext)
+ @Nullable Map newRow, @Nullable Map oldRow, Map extraContext)
throws ValidationException
{
- fireRowTrigger(c, user, type, before, rowNumber, newRow, oldRow, extraContext, null);
+ fireRowTrigger(c, user, type, null, before, rowNumber, newRow, oldRow, extraContext, null);
}
/**
@@ -600,6 +601,7 @@ default void fireRowTrigger(Container c, User user, TriggerType type, boolean be
* @param c The current Container.
* @param user the current user
* @param type The TriggerType for the event.
+ * @param insertOption The insertOption that invoked this trigger. Will be null when invoked outside a data iterator.
* @param before true if the trigger is before the event, false if after the event.
* @param newRow The new row for INSERT and UPDATE.
* @param oldRow The previous row for UPDATE and DELETE
@@ -607,9 +609,18 @@ default void fireRowTrigger(Container c, User user, TriggerType type, boolean be
* @param existingRecord Optional existing record for the row, used for merge operation to differentiate new vs existing row
* @throws ValidationException if the trigger function returns false or the errors map isn't empty.
*/
- void fireRowTrigger(Container c, User user, TriggerType type, boolean before, int rowNumber,
- @Nullable Map newRow, @Nullable Map oldRow, Map extraContext, @Nullable Map existingRecord)
- throws ValidationException;
+ void fireRowTrigger(
+ Container c,
+ User user,
+ TriggerType type,
+ @Nullable QueryUpdateService.InsertOption insertOption,
+ boolean before,
+ int rowNumber,
+ @Nullable Map newRow,
+ @Nullable Map oldRow,
+ Map extraContext,
+ @Nullable Map existingRecord
+ ) throws ValidationException;
/**
* Return true if there are trigger scripts associated with this table.
@@ -619,7 +630,18 @@ void fireRowTrigger(Container c, User user, TriggerType type, boolean before, in
/**
* Return true if all trigger scripts support streaming.
*/
- default boolean canStreamTriggers(Container c) { return false; }
+ default boolean canStreamTriggers(Container c)
+ {
+ return false;
+ }
+
+ /**
+ * Returns the full set of columns managed by triggers for this TableInfo.
+ */
+ default @Nullable Set getTriggerManagedColumns(@Nullable Container c, QueryUpdateService.InsertOption insertOption)
+ {
+ return null;
+ }
/**
* Reset the trigger script context by reloading them. Note there could still be caches that need to be reset
@@ -632,9 +654,12 @@ void fireRowTrigger(Container c, User user, TriggerType type, boolean before, in
/**
* Returns true if the underlying database table has triggers.
*/
- default boolean hasDbTriggers() { return false; }
+ default boolean hasDbTriggers()
+ {
+ return false;
+ }
- /* for asserting that tableinfo is not changed unexpectedly */
+ /* for asserting that the TableInfo is not changed unexpectedly */
void setLocked(boolean b);
boolean isLocked();
diff --git a/api/src/org/labkey/api/data/triggers/ScriptTrigger.java b/api/src/org/labkey/api/data/triggers/ScriptTrigger.java
index 3b83669bb7f..9c8083930b0 100644
--- a/api/src/org/labkey/api/data/triggers/ScriptTrigger.java
+++ b/api/src/org/labkey/api/data/triggers/ScriptTrigger.java
@@ -17,12 +17,14 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.labkey.api.collections.Sets;
import org.labkey.api.data.Container;
import org.labkey.api.data.ContainerManager;
import org.labkey.api.data.DbScope;
import org.labkey.api.data.PHI;
import org.labkey.api.data.TableInfo;
import org.labkey.api.query.BatchValidationException;
+import org.labkey.api.query.QueryUpdateService;
import org.labkey.api.query.ValidationException;
import org.labkey.api.script.ScriptReference;
import org.labkey.api.security.User;
@@ -44,25 +46,27 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Supplier;
/**
* Implements a trigger for table operations backed by JavaScript code.
- * User: kevink
- * Date: 12/21/15
*/
public class ScriptTrigger implements Trigger
{
public static final String SERVER_CONTEXT_KEY = "~~ServerContext~~";
- public static final String SERVER_CONTEXT_SCRIPTNAME = "serverContext";
+ public static final String SERVER_CONTEXT_SCRIPT_NAME = "serverContext";
@NotNull protected final Container _container;
@NotNull protected final TableInfo _table;
@NotNull protected final ScriptReference _script;
+ @Nullable protected volatile ManagedColumns _managedColumns = null;
+ protected volatile Boolean _isManagedColumnsEnabled = null;
protected ScriptTrigger(@NotNull Container c, @NotNull TableInfo table, @NotNull ScriptReference script)
{
@@ -112,6 +116,47 @@ public boolean canStream()
return false;
}
+ @Override
+ public @Nullable ManagedColumns getManagedColumns()
+ {
+ if (_managedColumns == null)
+ {
+ var user = _table.getUserSchema() != null ? _table.getUserSchema().getUser() : null;
+ var result = _invokeTableScript(_container, user, Object.class, "managedColumns", null, () -> null);
+ _isManagedColumnsEnabled = !(result instanceof Boolean enabled) || enabled;
+
+ if (result instanceof Map, ?> map)
+ {
+ var insert = managedColumnsFromScriptMap(map, "insert");
+ var update = managedColumnsFromScriptMap(map, "update");
+ var ignored = managedColumnsFromScriptMap(map, "ignored");
+
+ _managedColumns = new ManagedColumns(insert, update, ignored);
+ }
+ else
+ _managedColumns = ManagedColumns.empty();
+ }
+
+ return _managedColumns;
+ }
+
+ @Override
+ public boolean isManagedColumnsEnabled()
+ {
+ // Ensure the flag is initialized
+ if (_isManagedColumnsEnabled == null)
+ getManagedColumns();
+
+ return _isManagedColumnsEnabled;
+ }
+
+ private @NotNull Set managedColumnsFromScriptMap(@NotNull Map, ?> map, String key)
+ {
+ if (map.get(key) instanceof List> columns)
+ return Sets.newCaseInsensitiveHashSet((List) columns);
+ return Collections.emptySet();
+ }
+
/**
* To avoid leaking PHI through log files, avoid including the full row info in the error detail when any of the
* columns in the target table is considered PHI
@@ -137,25 +182,17 @@ public void complete(TableInfo table, Container c, User user, TableInfo.TriggerT
invokeTableScript(table, c, user, "complete", errors, extraContext, () -> null, event.name().toLowerCase());
}
-
@Override
public void beforeInsert(TableInfo table, Container c,
- User user, @Nullable Map newRow,
+ User user, @Nullable QueryUpdateService.InsertOption insertOption, @Nullable Map newRow,
ValidationException errors, Map extraContext)
{
- invokeTableScript(table,
- c,
- user,
- "beforeInsert",
- errors,
- extraContext,
- filterErrorDetailByPhi(table, () -> "New row data: " + newRow),
- newRow);
+ invokeTableScript(table, c, user, "beforeInsert", errors, extraContext, filterErrorDetailByPhi(table, () -> "New row data: " + newRow), newRow);
}
@Override
public void beforeUpdate(TableInfo table, Container c,
- User user, @Nullable Map newRow, @Nullable Map oldRow,
+ User user, @Nullable QueryUpdateService.InsertOption insertOption, @Nullable Map newRow, @Nullable Map oldRow,
ValidationException errors, Map extraContext)
{
invokeTableScript(table, c, user, "beforeUpdate", errors, extraContext, filterErrorDetailByPhi(table, () -> "New row: " + newRow + ". Old row: " + oldRow), newRow, oldRow);
@@ -193,7 +230,6 @@ public void afterDelete(TableInfo table, Container c,
invokeTableScript(table, c, user, "afterDelete", errors, extraContext, filterErrorDetailByPhi(table, () -> "Old row: " + oldRow), oldRow);
}
-
protected void invokeTableScript(TableInfo table, Container c, User user, String methodName, BatchValidationException errors, Map extraContext, Supplier errorDetail, Object... args)
{
Object[] allArgs = Arrays.copyOf(args, args.length+1);
@@ -205,7 +241,6 @@ protected void invokeTableScript(TableInfo table, Container c, User user, String
errors.addRowError(new ValidationException("script error: " + methodName + " trigger closed the connection, possibly due to constraint violation"));
}
-
protected void invokeTableScript(TableInfo table, Container c, User user, String methodName, ValidationException errors, Map extraContext, Supplier errorDetail, Object... args)
{
Object[] allArgs = Arrays.copyOf(args, args.length+1);
@@ -217,7 +252,6 @@ protected void invokeTableScript(TableInfo table, Container c, User user, String
errors.addGlobalError("script error: " + methodName + " trigger closed the connection, possibly due to constraint violation");
}
-
private boolean _hasFn(Container c, User user, String methodName)
{
return _try(c, user, null, (script) -> _script.hasFn(methodName));
@@ -294,7 +328,7 @@ public static class ServerContextModuleScript extends ModuleScript
public ServerContextModuleScript(Script serverContext) throws URISyntaxException
{
- super(serverContext, new URI(BASE_URI + SERVER_CONTEXT_SCRIPTNAME + ".js"), new URI(BASE_URI));
+ super(serverContext, new URI(BASE_URI + SERVER_CONTEXT_SCRIPT_NAME + ".js"), new URI(BASE_URI));
}
public static ServerContextModuleScript create(Script serverContext)
@@ -316,7 +350,7 @@ public static Script getServerContext(Container c, User u)
Context ctx = Context.enter();
try
{
- return ctx.compileString("module.exports = " + jsCode, "serverContext.js", 1, null);
+ return ctx.compileString("module.exports = " + jsCode, SERVER_CONTEXT_SCRIPT_NAME + ".js", 1, null);
}
finally
{
@@ -329,7 +363,6 @@ interface ScriptFn
R apply(ScriptReference scriptReference) throws NoSuchMethodException, ScriptException;
}
-
private boolean isConnectionClosed(DbScope scope)
{
DbScope.Transaction tx = scope.getCurrentTransaction();
@@ -359,7 +392,6 @@ public boolean equals(Object o)
@Override
public int hashCode()
{
-
return Objects.hash(_container, _table, _script);
}
}
diff --git a/api/src/org/labkey/api/data/triggers/Trigger.java b/api/src/org/labkey/api/data/triggers/Trigger.java
index 238d1332b23..8da8e55d065 100644
--- a/api/src/org/labkey/api/data/triggers/Trigger.java
+++ b/api/src/org/labkey/api/data/triggers/Trigger.java
@@ -15,20 +15,26 @@
*/
package org.labkey.api.data.triggers;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.JSONObject;
+import org.labkey.api.collections.Sets;
import org.labkey.api.data.Container;
import org.labkey.api.data.TableInfo;
import org.labkey.api.query.BatchValidationException;
+import org.labkey.api.query.QueryService;
+import org.labkey.api.query.QueryUpdateService;
import org.labkey.api.query.ValidationException;
import org.labkey.api.security.User;
import org.labkey.api.util.UnexpectedException;
import java.lang.reflect.Method;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -38,19 +44,31 @@
public interface Trigger
{
/** The trigger name. */
- default String getName() { return getClass().getSimpleName(); }
+ default String getName()
+ {
+ return getClass().getSimpleName();
+ }
/** Short description of the trigger. */
- default String getDescription() { return null; }
+ default String getDescription()
+ {
+ return null;
+ }
- /** Name of module that defines this trigger. */
- default String getModuleName() { return null; }
+ /** Name of the module that defines this trigger. */
+ default String getModuleName()
+ {
+ return null;
+ }
/**
* For script triggers, this is the path to the trigger script.
* For java triggers, this is the class name.
*/
- default String getSource() { return getClass().getName(); }
+ default String getSource()
+ {
+ return getClass().getName();
+ }
/**
* The set of events that this trigger implements.
@@ -77,10 +95,156 @@ default List getEvents()
}
}
+ record ManagedColumns(@NotNull Set insert, @NotNull Set update, @Nullable Set ignored)
+ {
+ public static ManagedColumns all(@NotNull Set all)
+ {
+ return new ManagedColumns(all, all, null);
+ }
+
+ public static ManagedColumns all(@NotNull String... all)
+ {
+ return all(Sets.newCaseInsensitiveHashSet(all));
+ }
+
+ public static ManagedColumns empty()
+ {
+ return new ManagedColumns(Collections.emptySet(), Collections.emptySet(), null);
+ }
+
+ public static ManagedColumns ignored(@NotNull String... ignored)
+ {
+ return new ManagedColumns(Collections.emptySet(), Collections.emptySet(), Sets.newCaseInsensitiveHashSet(ignored));
+ }
+
+ public @Nullable Set getColumns(TableInfo.TriggerType type)
+ {
+ if (type == TableInfo.TriggerType.INSERT)
+ return insert;
+ if (type == TableInfo.TriggerType.UPDATE)
+ return update;
+ return null;
+ }
+ }
+
/**
- * True if this TriggerScript can be used in a streaming context; triggers will be called without old row values.
+ * Returns the set of column names this trigger will read or write during row processing.
+ *
+ * For each row where a declared column is absent from the input, the trigger must
+ * explicitly set each such column to null or a real value; failure to do so produces
+ * a validation error naming this trigger and the unhandled column.
+ *
+ * Columns that do not exist in the target table's schema (virtual/passthrough columns) may be
+ * declared here and will work correctly — the database writer ignores them.
*/
- default boolean canStream() { return false; }
+ default @Nullable ManagedColumns getManagedColumns()
+ {
+ return null;
+ }
+
+ /**
+ * Returns true if managed columns are enabled for this trigger.
+ */
+ default boolean isManagedColumnsEnabled()
+ {
+ return true;
+ }
+
+ /**
+ * Ensures all columns declared by {@link #getManagedColumns()} are present in {@code newRow}
+ * before INSERT trigger fires.
+ *
+ * For each managed insert column absent from {@code newRow}, this method fills in a value so the
+ * trigger's logic can rely on the column being present:
+ *
+ * For normal inserts, absent columns are initialized to {@code null}.
+ * For MERGE operations ({@code insertOption.mergeRows == true}), absent columns are carried
+ * forward from {@code existingRecord} when a matching row exists, or set to {@code null} for
+ * genuinely new rows ({@code existingRecord.isEmpty()}).
+ *
+ * This method is a no-op when {@code insertOption} is {@code null}, which signals a non-data-iterator
+ * operation where managed-column enforcement does not apply.
+ *
+ * @param newRow the incoming row map to be inserted; modified in place to add missing managed columns
+ * @param existingRecord the existing database row for MERGE operations; an empty map indicates no
+ * matching row yet (new record); must be non-null when {@code insertOption.mergeRows} is true
+ * @param insertOption the insert mode in effect, or {@code null} for non-data-iterator operations
+ */
+ default void setInsertManagedColumns(
+ Map newRow,
+ @Nullable Map existingRecord,
+ @Nullable QueryUpdateService.InsertOption insertOption
+ )
+ {
+ // Trigger managed columns are disabled, do not modify the row
+ if (!QueryService.get().isTriggerManagedColumnsEnabled() || !isManagedColumnsEnabled())
+ return;
+
+ // If this is a merge operation and the existingRecord is not supplied,
+ // then throw an error to avoid overwriting managed values to null.
+ // existingRow == null indicated the existing row was not queried, so throw an error
+ // existingRecord.isEmpty() indicates a new record, so do not throw an error
+ if (insertOption != null && insertOption.mergeRows && existingRecord == null)
+ throw new IllegalArgumentException("An existing record must be supplied for all MERGE triggers");
+
+ setManagedColumns(newRow, existingRecord, TableInfo.TriggerType.INSERT);
+ }
+
+ /**
+ * Ensures all columns declared by {@link #getManagedColumns()} are present in {@code newRow}
+ * before UPDATE trigger fires.
+ *
+ * For each managed update column absent from {@code newRow}, the corresponding value is carried
+ * forward from {@code oldRow}, preserving the existing database value rather than implicitly
+ * nullifying the column.
+ *
+ * This method is a no-op when {@code insertOption} is {@code null}, which signals a non-data-iterator
+ * operation where managed-column enforcement does not apply.
+ *
+ * @param newRow the incoming row map with updated values; modified in place to add missing managed columns
+ * @param oldRow the current database row before the update; provides fallback values for absent managed columns
+ * @param insertOption the insert mode in effect, or {@code null} for non-data-iterator operations
+ */
+ default void setUpdateManagedColumns(
+ Map newRow,
+ @NotNull Map oldRow,
+ @Nullable QueryUpdateService.InsertOption insertOption
+ )
+ {
+ // Trigger managed columns are disabled, do not modify the row
+ if (!QueryService.get().isTriggerManagedColumnsEnabled() || !isManagedColumnsEnabled())
+ return;
+
+ if (oldRow == null)
+ throw new IllegalArgumentException("An existing record must be supplied for all UPDATE triggers");
+
+ setManagedColumns(newRow, oldRow, TableInfo.TriggerType.UPDATE);
+ }
+
+ private void setManagedColumns(Map newRow, Map oldRow, TableInfo.TriggerType type)
+ {
+ if (newRow == null)
+ return;
+
+ var managedCols = getManagedColumns();
+ if (managedCols == null)
+ return;
+
+ var cols = managedCols.getColumns(type);
+ if (cols == null)
+ return;
+
+ for (var col : cols)
+ newRow.putIfAbsent(col, oldRow == null ? null : oldRow.get(col));
+ }
+
+ /**
+ * Returns true if this TriggerScript can be used in a streaming context; triggers will be called without old row values.
+ */
+ default boolean canStream()
+ {
+ return false;
+ }
default void batchTrigger(TableInfo table, Container c, User user, TableInfo.TriggerType event, boolean before, BatchValidationException errors, Map extraContext)
{
@@ -98,7 +262,8 @@ default void complete(TableInfo table, Container c, User user, TableInfo.Trigger
{
}
- default void rowTrigger(TableInfo table, Container c, User user, TableInfo.TriggerType event, boolean before, int rowNumber,
+ default void rowTrigger(TableInfo table, Container c, User user, TableInfo.TriggerType event,
+ @Nullable QueryUpdateService.InsertOption insertOption, boolean before, int rowNumber,
@Nullable Map newRow, @Nullable Map oldRow,
ValidationException errors, Map extraContext,
@Nullable Map existingRecord) throws ValidationException
@@ -108,10 +273,10 @@ default void rowTrigger(TableInfo table, Container c, User user, TableInfo.Trigg
switch (event)
{
case INSERT:
- beforeInsert(table, c, user, newRow, errors, extraContext, existingRecord);
+ beforeInsert(table, c, user, insertOption, newRow, errors, extraContext, existingRecord);
break;
case UPDATE:
- beforeUpdate(table, c, user, newRow, oldRow, errors, extraContext);
+ beforeUpdate(table, c, user, insertOption, newRow, oldRow, errors, extraContext);
break;
case DELETE:
beforeDelete(table, c, user, oldRow, errors, extraContext);
@@ -136,20 +301,20 @@ default void rowTrigger(TableInfo table, Container c, User user, TableInfo.Trigg
}
default void beforeInsert(TableInfo table, Container c,
- User user, @Nullable Map newRow,
+ User user, @Nullable QueryUpdateService.InsertOption insertOption, @Nullable Map newRow,
ValidationException errors, Map extraContext) throws ValidationException
{
}
default void beforeInsert(TableInfo table, Container c,
- User user, @Nullable Map newRow,
+ User user, @Nullable QueryUpdateService.InsertOption insertOption, @Nullable Map newRow,
ValidationException errors, Map extraContext, @Nullable Map existingRecord) throws ValidationException
{
- beforeInsert(table, c, user, newRow, errors, extraContext);
+ beforeInsert(table, c, user, insertOption, newRow, errors, extraContext);
}
default void beforeUpdate(TableInfo table, Container c,
- User user, @Nullable Map newRow, @Nullable Map oldRow,
+ User user, @Nullable QueryUpdateService.InsertOption insertOption, @Nullable Map newRow, @Nullable Map oldRow,
ValidationException errors, Map extraContext) throws ValidationException
{
}
diff --git a/api/src/org/labkey/api/dataiterator/ListofMapsDataIterator.java b/api/src/org/labkey/api/dataiterator/ListofMapsDataIterator.java
index b9b2b92ca40..19a35db7495 100644
--- a/api/src/org/labkey/api/dataiterator/ListofMapsDataIterator.java
+++ b/api/src/org/labkey/api/dataiterator/ListofMapsDataIterator.java
@@ -4,11 +4,13 @@
import java.util.Map;
import java.util.Set;
-/** use MapDataIterator.of() */
+/**
+ * @deprecated Use {@link MapDataIterator#of(List)}
+ */
@Deprecated
public class ListofMapsDataIterator extends AbstractMapDataIterator.ListOfMapsDataIterator
{
- public ListofMapsDataIterator(Set colNames, List> rows)
+ public ListofMapsDataIterator(Set colNames, List> rows)
{
super(new DataIteratorContext(), colNames, rows);
}
@@ -16,7 +18,7 @@ public ListofMapsDataIterator(Set colNames, List> row
@Deprecated
public static class Builder extends DataIteratorBuilder.Wrapper
{
- public Builder(Set colNames, List> rows)
+ public Builder(Set colNames, List> rows)
{
super(new ListofMapsDataIterator(colNames, rows));
}
diff --git a/api/src/org/labkey/api/dataiterator/TriggerDataBuilderHelper.java b/api/src/org/labkey/api/dataiterator/TriggerDataBuilderHelper.java
index 7712726b939..d4b77817679 100644
--- a/api/src/org/labkey/api/dataiterator/TriggerDataBuilderHelper.java
+++ b/api/src/org/labkey/api/dataiterator/TriggerDataBuilderHelper.java
@@ -16,20 +16,24 @@
package org.labkey.api.dataiterator;
import org.labkey.api.data.AbstractTableInfo;
+import org.labkey.api.data.ColumnInfo;
import org.labkey.api.data.Container;
import org.labkey.api.data.TableInfo;
import org.labkey.api.data.triggers.Trigger;
import org.labkey.api.exp.query.ExpTable;
import org.labkey.api.query.BatchValidationException;
+import org.labkey.api.query.QueryService;
import org.labkey.api.query.QueryUpdateService;
import org.labkey.api.query.ValidationException;
import org.labkey.api.security.User;
import java.util.Collection;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
-import static org.labkey.api.admin.FolderImportContext.IS_NEW_FOLDER_IMPORT_KEY;
import static org.labkey.api.util.IntegerUtils.asInteger;
public class TriggerDataBuilderHelper
@@ -56,7 +60,6 @@ public DataIteratorBuilder before(DataIteratorBuilder in)
return new Before(in);
}
-
public DataIteratorBuilder after(DataIteratorBuilder in)
{
return new After(in);
@@ -114,21 +117,27 @@ protected Map getOldRow()
class Before implements DataIteratorBuilder
{
- final DataIteratorBuilder _pre;
+ final DataIteratorBuilder _in;
Before(DataIteratorBuilder in)
{
- _pre = in;
+ _in = in;
}
@Override
public DataIterator getDataIterator(DataIteratorContext context)
{
- DataIterator di = _pre.getDataIterator(context);
+ DataIterator di = _in.getDataIterator(context);
+ if (di == null)
+ return null; // can happen if context has errors
+
if (!_target.hasTriggers(_c))
return di;
di = LoggingDataIterator.wrap(di);
+ // Incorporate columns managed by triggers that may not overlap with the requested column set
+ di = getManagedColumnsDataIterator(di, context);
+
Set existingRecordKeyColumnNames = null;
Set sharedKeys = null;
boolean isMergeOrUpdate = context.getInsertOption().allowUpdate;
@@ -140,17 +149,11 @@ public DataIterator getDataIterator(DataIteratorContext context)
sharedKeys = expTable.getExistingRecordSharedKeyColumnNames();
}
- boolean isNewFolderImport = false;
- if (_extraContext != null && _extraContext.get(IS_NEW_FOLDER_IMPORT_KEY) != null)
- {
- isNewFolderImport = (boolean) _extraContext.get(IS_NEW_FOLDER_IMPORT_KEY);
- }
-
di = LoggingDataIterator.wrap(new CoerceDataIterator(di, context, _target, !context.getInsertOption().updateOnly));
context.setWithLookupRemapping(false);
// Skip existing records
- if (!context.getInsertOption().allowUpdate || existingRecordKeyColumnNames == null || isNewFolderImport)
+ if (!context.getInsertOption().allowUpdate || existingRecordKeyColumnNames == null)
return LoggingDataIterator.wrap(new BeforeIterator(new CachingDataIterator(di), context));
// Merge request but merge is not supported
@@ -160,13 +163,48 @@ public DataIterator getDataIterator(DataIteratorContext context)
di = ExistingRecordDataIterator.createBuilder(di, _target, existingRecordKeyColumnNames, sharedKeys, true).getDataIterator(context);
return LoggingDataIterator.wrap(new BeforeIterator(new CachingDataIterator(di), context));
}
- }
+ private DataIterator getManagedColumnsDataIterator(DataIterator di, DataIteratorContext context)
+ {
+ if (QueryService.get().isTriggerManagedColumnsEnabled())
+ {
+ var triggerColumns = _target.getTriggerManagedColumns(_c, context.getInsertOption());
+ if (triggerColumns != null && !triggerColumns.isEmpty())
+ {
+ Function columnMapper;
+ if (_target instanceof AbstractTableInfo target)
+ columnMapper = colName -> target.getColumn(colName, false);
+ else
+ columnMapper = _target::getColumn;
+
+ var columns = triggerColumns.stream().map(columnMapper).filter(Objects::nonNull).toList();
+ if (!columns.isEmpty())
+ {
+ var translator = new SimpleTranslator(di, context);
+ translator.setDebugName("TriggerDataBuilderHelper.Before.translator");
+ translator.selectAll();
+
+ var columnNameMap = translator.getColumnNameMap();
+
+ for (var column : columns)
+ {
+ if (!columnNameMap.containsKey(column.getName()))
+ translator.addColumn(column, (Supplier) () -> null);
+ }
+
+ di = translator.getDataIterator(context);
+ }
+ }
+ }
+
+ return di;
+ }
+ }
class BeforeIterator extends TriggerDataIterator
{
boolean _firstRow = true;
- Map _currentRow = null;
+ Map _currentRow = null;
BeforeIterator(DataIterator di, DataIteratorContext context)
{
@@ -180,7 +218,6 @@ public boolean isScrollable()
return false;
}
-
@Override
public boolean next() throws BatchValidationException
{
@@ -188,7 +225,7 @@ public boolean next() throws BatchValidationException
TableInfo.TriggerType triggerType = getTriggerType();
if (_firstRow)
{
- _target.fireBatchTrigger(_c, _user, triggerType, true, getErrors(), _extraContext);
+ _target.fireBatchTrigger(_c, _user, triggerType, _context.getInsertOption(), true, getErrors(), _extraContext);
firedInit = true;
_firstRow = false;
}
@@ -199,7 +236,7 @@ public boolean next() throws BatchValidationException
_currentRow = getInput().getMap();
try
{
- _target.fireRowTrigger(_c, _user, triggerType, true, rowNumber, _currentRow, getOldRow(), _extraContext, getExistingRecord());
+ _target.fireRowTrigger(_c, _user, triggerType, _context.getInsertOption(), true, rowNumber, _currentRow, getOldRow(), _extraContext, getExistingRecord());
return true;
}
catch (ValidationException vex)
@@ -212,7 +249,6 @@ public boolean next() throws BatchValidationException
return false;
}
-
@Override
public Object get(int i)
{
@@ -224,27 +260,29 @@ public Object get(int i)
}
}
-
class After implements DataIteratorBuilder
{
- final DataIteratorBuilder _post;
+ final DataIteratorBuilder _in;
After(DataIteratorBuilder in)
{
- _post = in;
+ _in = in;
}
@Override
public DataIterator getDataIterator(DataIteratorContext context)
{
- DataIterator it = _post.getDataIterator(context);
+ DataIterator di = _in.getDataIterator(context);
+ if (di == null)
+ return null; // can happen if context has errors
+
if (!_target.hasTriggers(_c))
- return it;
- return new AfterIterator(LoggingDataIterator.wrap(it), context);
+ return di;
+
+ return new AfterIterator(LoggingDataIterator.wrap(di), context);
}
}
-
class AfterIterator extends TriggerDataIterator
{
AfterIterator(DataIterator di, DataIteratorContext context)
@@ -265,7 +303,7 @@ public boolean next() throws BatchValidationException
Map newRow = getInput().getMap();
try
{
- _target.fireRowTrigger(_c, _user, getTriggerType(), false, rowNumber, newRow, getOldRow(), _extraContext, getExistingRecord());
+ _target.fireRowTrigger(_c, _user, getTriggerType(), _context.getInsertOption(), false, rowNumber, newRow, getOldRow(), _extraContext, getExistingRecord());
}
catch (ValidationException vex)
{
@@ -277,7 +315,7 @@ public boolean next() throws BatchValidationException
finally
{
if (!hasNext && firedInit && !getErrors().hasErrors())
- _target.fireBatchTrigger(_c, _user, getTriggerType(), false, getErrors(), _extraContext);
+ _target.fireBatchTrigger(_c, _user, getTriggerType(), _context.getInsertOption(), false, getErrors(), _extraContext);
}
}
}
diff --git a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java
index 7a768bcb915..c3739fcdbfd 100644
--- a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java
+++ b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java
@@ -77,7 +77,6 @@
import org.labkey.api.files.FileContentService;
import org.labkey.api.gwt.client.AuditBehaviorType;
import org.labkey.api.ontology.OntologyService;
-import org.labkey.api.ontology.Quantity;
import org.labkey.api.pipeline.PipeRoot;
import org.labkey.api.pipeline.PipelineService;
import org.labkey.api.reader.TabLoader;
@@ -605,7 +604,7 @@ protected List> _insertRowsUsingInsertRow(User user, Contain
errors.setExtraContext(extraScriptContext);
if (hasTableScript)
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, true, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, null, true, errors, extraScriptContext);
List> result = new ArrayList<>(rows.size());
List> providedValues = new ArrayList<>(rows.size());
@@ -659,7 +658,7 @@ else if (SqlDialect.isTransactionException(sqlx) && errors.hasErrors())
}
if (hasTableScript)
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, false, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, null, false, errors, extraScriptContext);
addAuditEvent(user, container, QueryService.AuditAction.INSERT, null, result, null, providedValues);
@@ -837,7 +836,7 @@ public List> updateRows(User user, Container container, List
assert(getQueryTable().supportsInsertOption(InsertOption.UPDATE));
errors.setExtraContext(extraScriptContext);
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, true, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, null, true, errors, extraScriptContext);
List> result = new ArrayList<>(rows.size());
List> oldRows = new ArrayList<>(rows.size());
@@ -889,7 +888,7 @@ public List> updateRows(User user, Container container, List
}
// Fire triggers, if any, and also throw if there are any errors
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, false, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, null, false, errors, extraScriptContext);
afterInsertUpdate(null==result?0:result.size(), errors, true);
if (errors.hasErrors())
@@ -961,7 +960,7 @@ public List> deleteRows(User user, Container container, List
BatchValidationException errors = new BatchValidationException();
errors.setExtraContext(extraScriptContext);
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, true, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, null, true, errors, extraScriptContext);
// TODO: Support update/delete without selecting the existing row -- unfortunately, we currently get the existing row to check its container matches the incoming container
boolean streaming = false; //_queryTable.canStreamTriggers(container) && _queryTable.getAuditBehavior() != AuditBehaviorType.NONE;
@@ -1006,7 +1005,7 @@ public List> deleteRows(User user, Container container, List
}
// Fire triggers, if any, and also throw if there are any errors
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, false, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, null, false, errors, extraScriptContext);
addAuditEvent(user, container, QueryService.AuditAction.DELETE, configParameters, result, null, null);
@@ -1028,11 +1027,11 @@ public int truncateRows(User user, Container container, @Nullable Map newRow,
@Nullable Map oldRow,
ValidationException errors,
diff --git a/assay/src/org/labkey/assay/plate/PlateManager.java b/assay/src/org/labkey/assay/plate/PlateManager.java
index 6d32f947f1b..2512cdb4ea9 100644
--- a/assay/src/org/labkey/assay/plate/PlateManager.java
+++ b/assay/src/org/labkey/assay/plate/PlateManager.java
@@ -212,11 +212,11 @@ public class PlateManager implements PlateService, AssayListener, ExperimentList
private final AtomicBoolean _pausePlateIndex = new AtomicBoolean(false);
private static final Object PLATE_INDEX_LOCK = new Object();
- // This flag is applied to the extraScriptContext of query mutating calls (e.g. insertRows, updateRows, etc.)
+ // This flag is applied to the extraScriptContext of query mutating calls (e.g., insertRows, updateRows, etc.)
// when those calls are being made for a plate copy operation.
public static final String PLATE_COPY_FLAG = ".plateCopy";
- // This flag is applied to the extraScriptContext of query mutating calls (e.g. insertRows, updateRows, etc.)
+ // This flag is applied to the extraScriptContext of query mutating calls (e.g., insertRows, updateRows, etc.)
// when those calls are being made for a plate save operation.
public static final String PLATE_SAVE_FLAG = ".plateSave";
diff --git a/assay/src/org/labkey/assay/plate/data/WellTriggerFactory.java b/assay/src/org/labkey/assay/plate/data/WellTriggerFactory.java
index 8a67ffdf7d7..bc9f4a0f7b0 100644
--- a/assay/src/org/labkey/assay/plate/data/WellTriggerFactory.java
+++ b/assay/src/org/labkey/assay/plate/data/WellTriggerFactory.java
@@ -17,6 +17,7 @@
import org.labkey.api.data.triggers.TriggerFactory;
import org.labkey.api.query.BatchValidationException;
import org.labkey.api.query.QueryService;
+import org.labkey.api.query.QueryUpdateService;
import org.labkey.api.query.SimpleValidationError;
import org.labkey.api.query.UserSchema;
import org.labkey.api.query.ValidationException;
@@ -56,6 +57,7 @@ public void beforeUpdate(
TableInfo table,
Container c,
User user,
+ @Nullable QueryUpdateService.InsertOption insertOption,
@Nullable Map newRow,
@Nullable Map oldRow,
ValidationException errors,
@@ -88,6 +90,13 @@ private class EnsureSampleWellTypeTrigger implements Trigger
{
private final Map wellTypeMap = new LRUMap<>(PlateSet.MAX_PLATE_SET_WELLS);
+ @Override
+ public @Nullable ManagedColumns getManagedColumns()
+ {
+ // "Type" is a calculated column, so we do not include it as a managed column
+ return ManagedColumns.ignored(WellTable.Column.Type.name());
+ }
+
private void addTypeSample(
Container c,
User user,
@@ -114,8 +123,8 @@ private void addTypeSample(
newRow.put(WellTable.Column.Type.name(), WellGroup.Type.SAMPLE.name());
}
- // Since "Type" is a calculated column (i.e. not in the database) its value is not included in
- // the original row, thus, we need to query for it dynamically.
+ // Since "Type" is a calculated column (i.e., not in the database), its value is not included in
+ // the original row; thus, we need to query for it dynamically.
private boolean hasWellType(Container c, User user, @Nullable Map oldRow)
{
if (oldRow == null)
@@ -142,9 +151,11 @@ public void beforeInsert(
TableInfo table,
Container c,
User user,
+ @Nullable QueryUpdateService.InsertOption insertOption,
@Nullable Map newRow,
ValidationException errors,
- Map extraContext
+ Map extraContext,
+ @Nullable Map existingRecord
)
{
addTypeSample(c, user, newRow, null, extraContext);
@@ -155,6 +166,7 @@ public void beforeUpdate(
TableInfo table,
Container c,
User user,
+ @Nullable QueryUpdateService.InsertOption insertOption,
@Nullable Map newRow,
@Nullable Map oldRow,
ValidationException errors,
diff --git a/core/src/org/labkey/core/query/UsersTable.java b/core/src/org/labkey/core/query/UsersTable.java
index 81843f9af0e..06928e215fa 100644
--- a/core/src/org/labkey/core/query/UsersTable.java
+++ b/core/src/org/labkey/core/query/UsersTable.java
@@ -453,9 +453,9 @@ public SQLFragment toSQLFragment(Map columnMap,
}
@Override
- public void fireRowTrigger(Container c, User user, TriggerType type, boolean before, int rowNumber, @Nullable Map newRow, @Nullable Map oldRow, Map extraContext, @Nullable Map existingRecord) throws ValidationException
+ public void fireRowTrigger(Container c, User user, TriggerType type, @Nullable QueryUpdateService.InsertOption insertOption, boolean before, int rowNumber, @Nullable Map newRow, @Nullable Map oldRow, Map extraContext, @Nullable Map existingRecord) throws ValidationException
{
- super.fireRowTrigger(c, user, type, before, rowNumber, newRow, oldRow, extraContext, existingRecord);
+ super.fireRowTrigger(c, user, type, insertOption, before, rowNumber, newRow, oldRow, extraContext, existingRecord);
Integer userId = null!=oldRow ? asInteger(oldRow.get("UserId")) : null!=newRow ? asInteger(newRow.get("UserId")) : null;
if (null != userId && !before)
UserManager.fireUserPropertiesChanged(userId);
diff --git a/core/src/org/labkey/core/script/RhinoService.java b/core/src/org/labkey/core/script/RhinoService.java
index 2def34d20ba..c317b9438fe 100644
--- a/core/src/org/labkey/core/script/RhinoService.java
+++ b/core/src/org/labkey/core/script/RhinoService.java
@@ -807,7 +807,7 @@ protected Scriptable getRuntimeScope(ScriptContext ctxt)
}
// Other JS scripts can call require('serverContext') to load this.
- extraModules = Map.of(ScriptTrigger.SERVER_CONTEXT_SCRIPTNAME, scriptContextScript);
+ extraModules = Map.of(ScriptTrigger.SERVER_CONTEXT_SCRIPT_NAME, scriptContextScript);
}
Require require = new Require(cx, getTopLevel(), new WrappingModuleScriptProvider(_moduleScriptProvider, extraModules), null, null, true);
diff --git a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java
index 401357afcc1..376e4a5353a 100644
--- a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java
+++ b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java
@@ -9694,7 +9694,7 @@ public Map moveDataClassObjects(Collection extends ExpData> d
// Since those tables already wire up trigger scripts, we'll use that mechanism here as well for the move event.
BatchValidationException errors = new BatchValidationException();
Map extraContext = Map.of("targetContainer", targetContainer, "classObjects", classObjects, "dataIds", dataIds);
- dataClassTable.fireBatchTrigger(sourceContainer, user, TableInfo.TriggerType.MOVE, false, errors, extraContext);
+ dataClassTable.fireBatchTrigger(sourceContainer, user, TableInfo.TriggerType.MOVE, null, false, errors, extraContext);
if (errors.hasErrors())
throw errors;
diff --git a/experiment/src/org/labkey/experiment/samples/AbstractExpFolderImporter.java b/experiment/src/org/labkey/experiment/samples/AbstractExpFolderImporter.java
index 0b446196ae0..a0058e4c426 100644
--- a/experiment/src/org/labkey/experiment/samples/AbstractExpFolderImporter.java
+++ b/experiment/src/org/labkey/experiment/samples/AbstractExpFolderImporter.java
@@ -61,7 +61,6 @@
import java.util.Set;
import java.util.function.Supplier;
-import static org.labkey.api.admin.FolderImportContext.IS_NEW_FOLDER_IMPORT_KEY;
import static org.labkey.api.dataiterator.SimpleTranslator.getContainerFileRootPath;
import static org.labkey.api.dataiterator.SimpleTranslator.getFileRootSubstitutedFilePath;
import static org.labkey.api.exp.XarContext.XAR_JOB_ID_NAME;
@@ -313,15 +312,8 @@ protected void importTsvData(FolderImportContext ctx, XarContext xarContext, Str
options.put(ExperimentService.QueryOptions.DeferRequiredLineageValidation, true);
context.setConfigParameters(options);
- Map extraContext = null;
- if (ctx.isNewFolderImport())
- {
- extraContext = new HashMap<>();
- extraContext.put(IS_NEW_FOLDER_IMPORT_KEY, true);
- }
-
DataIterator data = new ResolveLsidAndFileLinkDataIterator(loader.getDataIterator(context), xarContext, expObject instanceof ExpDataClass ? "DataClass" : ExpMaterial.DEFAULT_CPAS_TYPE, tinfo);
- int count = qus.loadRows(ctx.getUser(), ctx.getContainer(), data, context, extraContext);
+ int count = qus.loadRows(ctx.getUser(), ctx.getContainer(), data, context, null);
log.info("Imported a total of " + count + " rows into : " + tableName);
if (context.getErrors().hasErrors())
diff --git a/list/src/org/labkey/list/model/ListQueryUpdateService.java b/list/src/org/labkey/list/model/ListQueryUpdateService.java
index b4747339634..a05cda0b6d1 100644
--- a/list/src/org/labkey/list/model/ListQueryUpdateService.java
+++ b/list/src/org/labkey/list/model/ListQueryUpdateService.java
@@ -551,7 +551,7 @@ public Map moveRows(
// Before trigger per batch
Map extraContext = Map.of("targetContainer", targetContainer, "keys", rowPks);
- listTable.fireBatchTrigger(sourceContainer, user, TableInfo.TriggerType.MOVE, true, errors, extraContext);
+ listTable.fireBatchTrigger(sourceContainer, user, TableInfo.TriggerType.MOVE, null, true, errors, extraContext);
if (errors.hasErrors())
throw errors;
@@ -574,7 +574,7 @@ public Map moveRows(
listAuditEventsCreatedCount += addDetailedMoveAuditEvents(user, sourceContainer, targetContainer, batch);
// After trigger per batch
- listTable.fireBatchTrigger(sourceContainer, user, TableInfo.TriggerType.MOVE, false, errors, extraContext);
+ listTable.fireBatchTrigger(sourceContainer, user, TableInfo.TriggerType.MOVE, null, false, errors, extraContext);
if (errors.hasErrors())
throw errors;
}
diff --git a/query/src/org/labkey/query/QueryModule.java b/query/src/org/labkey/query/QueryModule.java
index 888f6ebf63c..99ec32e9506 100644
--- a/query/src/org/labkey/query/QueryModule.java
+++ b/query/src/org/labkey/query/QueryModule.java
@@ -247,6 +247,8 @@ public QuerySchema createSchema(DefaultSchema schema, Module module)
"Allow for lookup fields in product folders to query across all folders within the top-level folder.", false);
OptionalFeatureService.get().addExperimentalFeatureFlag(QueryServiceImpl.EXPERIMENTAL_PRODUCT_PROJECT_DATA_LISTING_SCOPED, "Product folders display folder-specific data",
"Only list folder-specific data within product folders.", false);
+ OptionalFeatureService.get().addExperimentalFeatureFlag(QueryService.EXPERIMENTAL_DISABLE_MANAGED_TRIGGER_COLUMNS, "Disable managed columns in query triggers",
+ "By default LabKey enforces managed columns for triggers and errors when the data does not align. Enabling this feature will result in them only logging warnings.", false);
McpService.get().register(new QueryMcp());
}
diff --git a/query/src/org/labkey/query/QueryServiceImpl.java b/query/src/org/labkey/query/QueryServiceImpl.java
index beca1695766..d7f986f2b20 100644
--- a/query/src/org/labkey/query/QueryServiceImpl.java
+++ b/query/src/org/labkey/query/QueryServiceImpl.java
@@ -33,7 +33,6 @@
import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Test;
-import org.labkey.api.action.ApiUsageException;
import org.labkey.api.assay.AssayService;
import org.labkey.api.audit.AbstractAuditHandler;
import org.labkey.api.audit.AuditHandler;
@@ -3577,6 +3576,12 @@ public boolean isProductFoldersDataListingScopedToProject()
return AppProps.getInstance().isOptionalFeatureEnabled(EXPERIMENTAL_PRODUCT_PROJECT_DATA_LISTING_SCOPED);
}
+ @Override
+ public boolean isTriggerManagedColumnsEnabled()
+ {
+ return !AppProps.getInstance().isOptionalFeatureEnabled(EXPERIMENTAL_DISABLE_MANAGED_TRIGGER_COLUMNS);
+ }
+
public static class TestCase extends Assert
{
@Test
diff --git a/specimen/src/org/labkey/specimen/query/SpecimenUpdateService.java b/specimen/src/org/labkey/specimen/query/SpecimenUpdateService.java
index e9d88406623..9368188076c 100644
--- a/specimen/src/org/labkey/specimen/query/SpecimenUpdateService.java
+++ b/specimen/src/org/labkey/specimen/query/SpecimenUpdateService.java
@@ -70,7 +70,6 @@ public SpecimenUpdateService(TableInfo queryTable)
super(queryTable);
}
-
@Override
public int importRows(User user, Container container, DataIteratorBuilder rows, BatchValidationException errors, Map configParameters, @Nullable Map extraScriptContext)
{
@@ -94,7 +93,7 @@ public List> deleteRows(User user, Container container, List
BatchValidationException errors = new BatchValidationException();
errors.setExtraContext(extraScriptContext);
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, true, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, null, true, errors, extraScriptContext);
Set rowIds = new HashSet<>(keys.size());
for (Map key : keys)
@@ -139,7 +138,7 @@ public List> deleteRows(User user, Container container, List
throw new IllegalStateException(e.getMessage());
}
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, false, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, null, false, errors, extraScriptContext);
addAuditEvent(user, container, QueryService.AuditAction.DELETE, configParameters, null, null, null);
@@ -203,7 +202,7 @@ public List> insertRows(User user, Container container, List
try
{
if (hasTableScript)
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, true, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, null, true, errors, extraScriptContext);
}
catch (BatchValidationException e)
{
@@ -263,7 +262,7 @@ public List> insertRows(User user, Container container, List
try
{
if (hasTableScript)
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, false, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, null, false, errors, extraScriptContext);
}
catch (BatchValidationException e)
{
@@ -332,7 +331,7 @@ public List> updateRows(User user, Container container, List
throw new IllegalArgumentException("rows and oldKeys are required to be the same length, but were " + rows.size() + " and " + oldKeys + " in length, respectively");
errors.setExtraContext(extraScriptContext);
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, true, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, null, true, errors, extraScriptContext);
Set rowIds = new HashSet<>(rows.size());
Map> uniqueRows = new LongHashMap<>(rows.size());
@@ -406,7 +405,7 @@ public List> updateRows(User user, Container container, List
throw new IllegalStateException(e.getMessage());
}
- getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, false, errors, extraScriptContext);
+ getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, null, false, errors, extraScriptContext);
addAuditEvent(user, container, QueryService.AuditAction.UPDATE, configParameters, rows, null, null);