row : getRows())
- {
- //copy the row map into a case-insensitive hash map
- ciRows.add(new CaseInsensitiveHashMap<>(row));
- }
- }
+ return getProperty("queryName");
+ }
- //reset the rows array
- getParsedData().put("rows", ciRows);
+ /**
+ * Returns the 'command' response property.
+ * @return The command executed, or null if this property
+ * was not present in the response.
+ */
+ public String getCommand()
+ {
+ return getProperty("command");
}
}
diff --git a/src/org/labkey/remoteapi/query/SaveRowsCommand.java b/src/org/labkey/remoteapi/query/SaveRowsCommand.java
index e6287765..61edb98e 100644
--- a/src/org/labkey/remoteapi/query/SaveRowsCommand.java
+++ b/src/org/labkey/remoteapi/query/SaveRowsCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2016 LabKey Corporation
+ * Copyright (c) 2008-2025 LabKey Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,272 +15,408 @@
*/
package org.labkey.remoteapi.query;
-import org.json.JSONArray;
import org.json.JSONObject;
import org.labkey.remoteapi.PostCommand;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import java.util.Map;
/**
- * Base class for commands that make changes to rows exposed from a given
- * query in a given schema. Clients should use {@link UpdateRowsCommand},
- * {@link InsertRowsCommand} or {@link DeleteRowsCommand} and not this class directly.
+ * Command for executing multiple data modification operations (insert, update, delete) in a single request
+ * to a LabKey Server. This command allows batching multiple operations together, optionally in a transaction.
*
- * All three of these subclasses post similar JSON to the server, so this class
- * does all the common work. The client must supply three things: the schemaName,
- * the queryName and an array of 'rows' (i.e. Maps). The rows are added via
- * the {@link #addRow(Map)} or {@link #setRows(List)} methods.
+ * All data exposed from a LabKey Server is organized into schemas containing queries. Each command in a batch
+ * specifies the schema name (e.g., 'lists' or 'study') and query name (e.g., 'People' or 'Samples') to operate on.
*
- * All data exposed from the LabKey Server is organized into a set of queries
- * contained in a set of schemas. A schema is simply a group of queries, identified
- * by a name (e.g., 'lists' or 'study'). A query is particular table or view within
- * that schema (e.g., 'People' or 'Peptides'). Currently, clients may update rows in
- * base tables only, and not in joined views. Therefore the query name must be the
- * name of a table in the schema.
+ * The command supports several features:
+ *
+ * - Multiple operations (insert, update, delete) in a single request
+ * - Optional transaction support to ensure all-or-nothing execution
+ * - Validation-only mode to check operations without making changes
+ * - Audit trail support with configurable detail levels
+ * - Custom audit comments for tracking changes
+ *
*
- * To view the schemas and queries exposed in a given folder, add a Query web part
- * to your portal page and choose the option "Show the list of tables in this schema"
- * in the part configuration page. Alternatively, if it is exposed, click on the Query
- * tab across the top of the main part of the page.
- *
- * Examples:
+ * Example usage:
*
- * // May need to add CONTEXT_PATH for dev instances
- * Connection cn = new Connection("http://localhost:8080", user, password);
- *
- * //Insert Rows Command
- * InsertRowsCommand cmd = new InsertRowsCommand("lists", "People");
+ * ApiKeyCredentialsProvider credentials = new ApiKeyCredentialsProvider("xxx");
+ * Connection conn = new Connection("http://localhost:8080", credentials);
+ * SaveRowsApiCommand saveCmd = new SaveRowsApiCommand();
*
- * Map<String, Object> row = new HashMap<String, Object>();
- * row.put("FirstName", "Insert");
- * row.put("LastName", "Test");
+ * // Add new gene annotations
+ * saveCmd.addCommand(new Command(CommandType.Insert, "genome", "GeneAnnotations",
+ * List.of(
+ * Map.of("name", "p53 binding site", "geneName", "TP53", "start", 1000, "end", 1020),
+ * Map.of("name", "TATA box", "geneName", "BRCA1", "start", 2500, "end", 2506)
+ * )));
*
- * cmd.addRow(row); //can add multiple rows to insert many at once
- * SaveRowsResponse resp = cmd.execute(cn, "PROJECT_NAME");
+ * // Update annotation positions
+ * Command updateCmd = new Command(CommandType.Update, "genome", "GeneAnnotations",
+ * List.of(Map.of(
+ * "name", "Promoter region",
+ * "geneName", "EGFR",
+ * "start", 5000,
+ * "end", 5500
+ * )));
+ * updateCmd.setAuditBehavior(BaseRowsCommand.AuditBehavior.DETAILED);
+ * updateCmd.setAuditUserComment("Updated promoter region coordinates based on new assembly");
+ * saveCmd.addCommands(updateCmd);
*
- * //get the newly-assigned primary key value from the first return row
- * int newKey = resp.getRows().get(0).get("Key");
+ * // Delete obsolete annotation
+ * saveCmd.addCommand(new Command(CommandType.Delete, "genome", "GeneAnnotations",
+ * List.of(Map.of("name", "Putative enhancer", "geneName", "MYC"))));
*
- * //Update Rows Command
- * UpdateRowsCommand cmdUpd = new UpdateRowsCommand("lists", "People");
- * row = new HashMap<String, Object>();
- * row.put("Key", newKey);
- * row.put("LastName", "Test UPDATED");
- * cmdUpd.addRow(row);
- * resp = cmdUpd.execute(cn, "PROJECT_NAME");
- *
- * //Delete Rows Command
- * DeleteRowsCommand cmdDel = new DeleteRowsCommand("lists", "People");
- * row = new HashMap<String, Object>();
- * row.put("Key", newKey);
- * cmdDel.addRow(row);
- * resp = cmdDel.execute(cn, "PROJECT_NAME");
+ * // Execute all commands in a transaction
+ * SaveRowsApiResponse response = saveCmd.execute(conn, "GenomeProject");
*
*/
-public abstract class SaveRowsCommand extends PostCommand
+public class SaveRowsCommand extends PostCommand
{
- public enum AuditBehavior
- {
- NONE,
- SUMMARY,
- DETAILED
- }
-
- private String _schemaName;
- private String _queryName;
+ private final List _commands = new ArrayList<>();
private Map _extraContext;
- private List