UpsertData
→ numberInserts a new row or updates an existing one in a Data Extension. The most commonly used write operation when you don't know whether the row exists.
Syntax
Platform.Function.UpsertData(deName, whereFieldNames, whereFieldValues, fieldNames, fieldValues)
Parameters
UpsertData takes exactly these five array-based arguments:
| Name | Type | Required | Description |
|---|---|---|---|
deName |
string | Yes | Data Extension Name (the external key / CustomerKey is not accepted — runtime-verified) |
whereFieldNames |
string[] | Yes | Nonempty array of column names used to find existing rows; multiple columns use positional AND logic |
whereFieldValues |
array | Yes | Nonempty array of values positionally aligned to whereFieldNames |
fieldNames |
string[] | Yes | Array of column names to insert or update |
fieldValues |
array | Yes | Array of values aligned to fieldNames |
Arrays are required. Scalar strings are rejected even for a single filter or field. The name/value arrays must be nonempty and have matching lengths.
Show test script
<script runat="server">
/*
* Chapter: Parameters
* Proves:
* 1. Exactly five arguments are accepted; every other arity throws.
* 2. All four filter/field name/value arguments require nonempty, positionally
* aligned arrays — scalar strings throw (the official reference allows them).
* 3. Multiple filter columns use AND logic and multiple field values align by position.
* 4. The DE resolves by Name only; CustomerKey and unknown identifiers throw.
* 5. Text, Number, Decimal, Boolean and Date columns accept native values.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function addField(de, name, type, len, scale, isKey, isRequired) {
var field = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(field, "Name", name);
Platform.Function.SetObjectProperty(field, "FieldType", type);
if (len) { Platform.Function.SetObjectProperty(field, "MaxLength", len); }
if (scale) { Platform.Function.SetObjectProperty(field, "Scale", scale); }
Platform.Function.SetObjectProperty(field, "IsPrimaryKey", isKey ? "true" : "false");
Platform.Function.SetObjectProperty(field, "IsRequired", isRequired ? "true" : "false");
Platform.Function.AddObjectArrayItem(de, "Fields", field);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "Id", "Text", "50", null, true, true);
addField(de, "Grp", "Text", "50", null, false, false);
addField(de, "Txt", "Text", "100", null, false, false);
addField(de, "Num", "Number", null, null, false, false);
addField(de, "Dec", "Decimal", "18", "2", false, false);
addField(de, "Flag", "Boolean", null, null, false, false);
addField(de, "Dt", "Date", null, null, false, false);
var status = [0, 0];
return String(Platform.Function.InvokeCreate(de, status, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var status = [0, 0];
return String(Platform.Function.InvokeDelete(de, status, null));
}
function countRows(name, field, value) {
var rows = Platform.Function.LookupRows(name, field, value);
return rows === null ? 0 : rows.length;
}
var deName = "ssjsg_up_par_2334_name", deKey = "ssjsg_up_par_2334_key";
assert("setup: throwaway DE created", createDE(deName, deKey), "OK");
assert("control: Name and CustomerKey differ", deName === deKey ? "same" : "different", "different");
var inserted = Platform.Function.UpsertData(deName, ["Id", "Grp"], ["typed", "group-a"], ["Txt", "Num", "Dec", "Flag", "Dt"], ["value", 42, 3.14, true, new Date(2024, 0, 15)]);
assert("the five-argument array call inserts one row", inserted, 1);
assert("the return value has typeof number", String(typeof inserted), "number");
assert("setup: a second row shares the Grp filter value", Platform.Function.InsertData(deName, ["Id", "Grp", "Txt"], ["sibling", "group-a", "seed"]), 1);
assert("one filter column alone matches both rows", Platform.Function.UpsertData(deName, ["Grp"], ["group-a"], ["Txt"], ["broad"]), 2);
assert("adding a second filter column narrows the match with AND logic", Platform.Function.UpsertData(deName, ["Grp", "Id"], ["group-a", "typed"], ["Txt", "Num"], ["updated", 42]), 1);
assert("the aligned multi-field update persisted its first value", String(Platform.Function.Lookup(deName, "Txt", "Id", "typed")), "updated");
assert("the AND filter left the sibling row untouched", String(Platform.Function.Lookup(deName, "Txt", "Id", "sibling")), "broad");
assert("Number columns persist as numbers", String(typeof Platform.Function.Lookup(deName, "Num", "Txt", "updated")), "number");
assert("Decimal columns persist as numbers", String(typeof Platform.Function.Lookup(deName, "Dec", "Num", 42)), "number");
assert("Boolean columns persist as booleans", String(typeof Platform.Function.Lookup(deName, "Flag", "Dec", 3.14)), "boolean");
assert("Date columns persist as Date-like objects", String(typeof Platform.Function.Lookup(deName, "Dt", "Flag", true)), "object");
assertThrows("DEV scalar whereFieldNames throws (docs: string or string[])", function () { return Platform.Function.UpsertData(deName, "Id", ["typed"], ["Txt"], ["x"]); });
assertThrows("DEV scalar whereFieldValues throws (docs: string or array)", function () { return Platform.Function.UpsertData(deName, ["Id"], "typed", ["Txt"], ["x"]); });
assertThrows("scalar fieldNames throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"], "Txt", ["x"]); });
assertThrows("scalar fieldValues throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"], ["Txt"], "x"); });
assertThrows("empty filter arrays throw", function () { return Platform.Function.UpsertData(deName, [], [], ["Txt"], ["x"]); });
assertThrows("empty field arrays throw", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"], [], []); });
assertThrows("more filter names than values throws", function () { return Platform.Function.UpsertData(deName, ["Id", "Grp"], ["typed"], ["Txt"], ["x"]); });
assertThrows("more filter values than names throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed", "group-b"], ["Txt"], ["x"]); });
assertThrows("more field names than values throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"], ["Txt", "Grp"], ["x"]); });
assertThrows("more field values than names throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"], ["Txt"], ["x", "y"]); });
assertThrows("arity 0 throws", function () { return Platform.Function.UpsertData(); });
assertThrows("arity 1 throws", function () { return Platform.Function.UpsertData(deName); });
assertThrows("arity 2 throws", function () { return Platform.Function.UpsertData(deName, ["Id"]); });
assertThrows("arity 3 throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"]); });
assertThrows("arity 4 throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"], ["Txt"]); });
assertThrows("arity 6 throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"], ["Txt"], ["x"], "extra"); });
assertThrows("DEV the CustomerKey form throws (docs: data extension name)", function () { return Platform.Function.UpsertData(deKey, ["Id"], ["key"], ["Txt"], ["x"]); });
assertThrows("an unknown DE throws", function () { return Platform.Function.UpsertData("ssjsg_up_no_such_de", ["Id"], ["x"], ["Txt"], ["x"]); });
assertThrows("an unknown filter column throws", function () { return Platform.Function.UpsertData(deName, ["NoSuchFilter"], ["x"], ["Txt"], ["x"]); });
assertThrows("an unknown field column throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["typed"], ["NoSuchField"], ["x"]); });
assert("none of the rejected calls added a row", countRows(deName, "Id", "typed"), 1);
assert("cleanup: throwaway DE deleted", dropDE(deKey), "OK");
</script>
Description
UpsertData checks for rows matching all supplied filter pairs:
- If there are no matches, it inserts one row using both the filter pairs and field pairs.
- If there is one match, it updates that row in place.
- If there are multiple matches, it updates all of them.
The return value is a number: 1 for a new insert, 1 for one updated match, or the affected-row count for multiple matches.
The official reference permits scalar strings for one whereFieldNames / whereFieldValues pair, and also shows a flat/variadic form. Neither works: runtime calls require nonempty, positionally aligned arrays in all four name/value positions, and every scalar form throws. The DE is also resolved by Name only, not the external key / CustomerKey.
Show test script — array-only signature
<script runat="server">
/*
* Differs-from-docs claim: the official reference permits scalar strings for a
* single whereFieldNames / whereFieldValues pair, and the runtime rejects them.
* Proves:
* 1. Each of the four name/value positions throws when given a scalar string.
* 2. The recommended workaround — one-element arrays — inserts and updates.
* 3. The DE is resolved by Name; the CustomerKey form throws.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function addField(de, name, len, isKey) {
var field = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(field, "Name", name);
Platform.Function.SetObjectProperty(field, "FieldType", "Text");
Platform.Function.SetObjectProperty(field, "MaxLength", len);
Platform.Function.SetObjectProperty(field, "IsPrimaryKey", isKey ? "true" : "false");
Platform.Function.SetObjectProperty(field, "IsRequired", isKey ? "true" : "false");
Platform.Function.AddObjectArrayItem(de, "Fields", field);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "Id", "50", true);
addField(de, "Txt", "100", false);
var status = [0, 0];
return String(Platform.Function.InvokeCreate(de, status, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var status = [0, 0];
return String(Platform.Function.InvokeDelete(de, status, null));
}
var deName = "ssjsg_up_arr_2247_name", deKey = "ssjsg_up_arr_2247_key";
assert("setup: throwaway DE created", createDE(deName, deKey), "OK");
assert("control: Name and CustomerKey differ", deName === deKey ? "same" : "different", "different");
assertThrows("DEV scalar whereFieldNames throws (docs: string or string[])", function () { return Platform.Function.UpsertData(deName, "Id", ["a"], ["Txt"], ["wrong"]); });
assertThrows("DEV scalar whereFieldValues throws (docs: string or array)", function () { return Platform.Function.UpsertData(deName, ["Id"], "a", ["Txt"], ["wrong"]); });
assertThrows("scalar fieldNames throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["a"], "Txt", ["wrong"]); });
assertThrows("scalar fieldValues throws", function () { return Platform.Function.UpsertData(deName, ["Id"], ["a"], ["Txt"], "wrong"); });
assertThrows("DEV the CustomerKey form throws (docs: data extension name)", function () { return Platform.Function.UpsertData(deKey, ["Id"], ["a"], ["Txt"], ["wrong"]); });
assert("workaround: one-element arrays insert a new row", Platform.Function.UpsertData(deName, ["Id"], ["a"], ["Txt"], ["inserted"]), 1);
assert("workaround: one-element arrays update the existing row", Platform.Function.UpsertData(deName, ["Id"], ["a"], ["Txt"], ["updated"]), 1);
assert("the workaround update committed", String(Platform.Function.Lookup(deName, "Id", "Txt", "updated")), "a");
assert("cleanup: throwaway DE deleted", dropDE(deKey), "OK");
</script>
This is the most robust write operation for Data Extensions — use it instead of InsertData when you’re not sure if the row already exists.
Show test script
<script runat="server">
/*
* Chapter: Description
* Proves the three documented branches independently:
* 1. No match -> one row is inserted from the filter pairs AND the field pairs; returns 1.
* 2. One match -> that row is updated in place, the row count stays 1; returns 1.
* 3. Many matches -> every match is updated; returns the affected-row count (2).
* Also proves the return value is always a number.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function addField(de, name, len, isKey, isRequired) {
var field = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(field, "Name", name);
Platform.Function.SetObjectProperty(field, "FieldType", "Text");
Platform.Function.SetObjectProperty(field, "MaxLength", len);
Platform.Function.SetObjectProperty(field, "IsPrimaryKey", isKey ? "true" : "false");
Platform.Function.SetObjectProperty(field, "IsRequired", isRequired ? "true" : "false");
Platform.Function.AddObjectArrayItem(de, "Fields", field);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "Id", "50", true, true);
addField(de, "Grp", "50", false, false);
addField(de, "Txt", "100", false, false);
addField(de, "Req", "50", false, true);
var status = [0, 0];
return String(Platform.Function.InvokeCreate(de, status, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var status = [0, 0];
return String(Platform.Function.InvokeDelete(de, status, null));
}
function countRows(name, field, value) {
var rows = Platform.Function.LookupRows(name, field, value);
return rows === null ? 0 : rows.length;
}
var deName = "ssjsg_up_dsc_2247_name", deKey = "ssjsg_up_dsc_2247_key";
assert("setup: throwaway DE created", createDE(deName, deKey), "OK");
var insertResult = Platform.Function.UpsertData(deName, ["Id", "Grp"], ["new", "insert"], ["Txt", "Req"], ["inserted", "required"]);
assert("no match inserts exactly one row", insertResult, 1);
assert("the insert return has typeof number", String(typeof insertResult), "number");
assert("the filter pairs became part of the inserted row", String(Platform.Function.Lookup(deName, "Grp", "Id", "new")), "insert");
assert("every supplied field pair persisted on the inserted row", String(Platform.Function.Lookup(deName, "Req", "Txt", "inserted")), "required");
var updateResult = Platform.Function.UpsertData(deName, ["Id"], ["new"], ["Txt"], ["updated"]);
assert("one match updates exactly one row", updateResult, 1);
assert("the one-match return has typeof number", String(typeof updateResult), "number");
assert("the one-match branch keeps the row count at one", countRows(deName, "Id", "new"), 1);
assert("the one-match branch updated the row in place", String(Platform.Function.Lookup(deName, "Id", "Txt", "updated")), "new");
assert("setup: first multi-match row inserted", Platform.Function.InsertData(deName, ["Id", "Grp", "Txt", "Req"], ["many-1", "multi", "seed", "required"]), 1);
assert("setup: second multi-match row inserted", Platform.Function.InsertData(deName, ["Id", "Grp", "Txt", "Req"], ["many-2", "multi", "seed", "required"]), 1);
var manyResult = Platform.Function.UpsertData(deName, ["Grp"], ["multi"], ["Txt"], ["many-updated"]);
assert("multiple matches return the affected-row count", manyResult, 2);
assert("the multiple-match return has typeof number", String(typeof manyResult), "number");
assert("every matching row was updated", countRows(deName, "Txt", "many-updated"), 2);
assert("cleanup: throwaway DE deleted", dropDE(deKey), "OK");
</script>
Examples
Standard array syntax (preferred)
var rowsAffected = Platform.Function.UpsertData(
"Subscribers",
["SubscriberKey"], // whereFieldNames (filter/identity)
[subscriberKey], // whereFieldValues
["Email", "FirstName", "City"], // fieldNames (data columns)
[email, firstName, city] // fieldValues
);
Multiple primary keys
Platform.Function.UpsertData(
"OrderItems",
["OrderID", "ProductSKU"], // whereFieldNames: composite key
[orderId, sku], // whereFieldValues
["Quantity", "Price"], // fieldNames
[qty, price] // fieldValues
);
Single column upsert
// Even a single column/key must be passed as arrays
Platform.Function.UpsertData(
"PageViews",
["PageID"], [pageId], // whereFieldNames / whereFieldValues (key)
["Count"], [viewCount] // fieldNames / fieldValues (column to set)
);
Track login with upsert
Platform.Function.UpsertData(
"UserActivity",
["SubscriberKey"],
[sk],
["LastLogin", "LoginCount", "Status"],
[Platform.Function.Now(), loginCount, "active"]
);
Error handling
try {
Platform.Function.UpsertData(
"Registrations",
["Email"],
[email],
["Name", "RegisteredAt"],
[name, Platform.Function.Now()]
);
Platform.Response.Redirect("/confirmation", false);
} catch (e) {
Write("Save failed: " + e.message);
}
Show test script
<script runat="server">
/*
* Chapter: Examples
* Proves every example shape on the page:
* 1. Standard array syntax — one filter column, three data columns.
* 2. Multiple primary keys — a composite filter inserts, then updates the same row.
* 3. Single column upsert — one-element arrays in all four positions.
* 4. Track login with upsert — Platform.Function.Now(), a Number and a Text together.
* 5. Error handling — a failing upsert is a catchable exception with a message.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function addField(de, name, type, len, isKey) {
var field = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(field, "Name", name);
Platform.Function.SetObjectProperty(field, "FieldType", type);
if (len) { Platform.Function.SetObjectProperty(field, "MaxLength", len); }
Platform.Function.SetObjectProperty(field, "IsPrimaryKey", isKey ? "true" : "false");
Platform.Function.SetObjectProperty(field, "IsRequired", isKey ? "true" : "false");
Platform.Function.AddObjectArrayItem(de, "Fields", field);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "Id", "Text", "50", true);
addField(de, "Email", "Text", "100", false);
addField(de, "FirstName", "Text", "50", false);
addField(de, "City", "Text", "50", false);
addField(de, "OrderID", "Text", "50", false);
addField(de, "ProductSKU", "Text", "50", false);
addField(de, "Quantity", "Number", null, false);
addField(de, "Count", "Number", null, false);
addField(de, "LastLogin", "Date", null, false);
addField(de, "Status", "Text", "50", false);
var status = [0, 0];
return String(Platform.Function.InvokeCreate(de, status, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var status = [0, 0];
return String(Platform.Function.InvokeDelete(de, status, null));
}
var deName = "ssjsg_up_exa_2247_name", deKey = "ssjsg_up_exa_2247_key";
assert("setup: throwaway DE created", createDE(deName, deKey), "OK");
assert("standard array syntax inserts one row", Platform.Function.UpsertData(deName, ["Id"], ["subscriber"], ["Email", "FirstName", "City"], ["a@example.com", "Ada", "London"]), 1);
assert("the standard example persisted every data column", String(Platform.Function.Lookup(deName, "Email", "City", "London")), "a@example.com");
assert("a composite filter inserts when nothing matches", Platform.Function.UpsertData(deName, ["Id", "OrderID"], ["order-1", "ORD-1"], ["ProductSKU", "Quantity"], ["SKU-9", 2]), 1);
assert("the same composite filter then updates that one row", Platform.Function.UpsertData(deName, ["Id", "OrderID"], ["order-1", "ORD-1"], ["Quantity"], [5]), 1);
assert("the composite-key update committed", String(Platform.Function.Lookup(deName, "Quantity", "ProductSKU", "SKU-9")), "5");
assert("a single-column upsert inserts with one-element arrays", Platform.Function.UpsertData(deName, ["Id"], ["page-1"], ["Count"], [7]), 1);
assert("the single-column upsert then updates the same row", Platform.Function.UpsertData(deName, ["Id"], ["page-1"], ["Count"], [8]), 1);
assert("the single-column value committed", String(Platform.Function.Lookup(deName, "Id", "Count", 8)), "page-1");
assert("the track-login example writes a Date, a Number and a Text together", Platform.Function.UpsertData(deName, ["Id"], ["subscriber"], ["LastLogin", "Count", "Status"], [Platform.Function.Now(), 3, "active"]), 1);
assert("the track-login Date column persisted as an object", String(typeof Platform.Function.Lookup(deName, "LastLogin", "Status", "active")), "object");
var caught = "";
try {
Platform.Function.UpsertData(deName, ["Id"], ["registration"], ["NoSuchColumn"], ["boom"]);
} catch (e) {
caught = e.message;
}
assert("the error-handling example catches a failing upsert", caught === "" ? "not caught" : "caught", "caught");
assert("the caught error exposes a nonempty message", String(caught).length > 0 ? "nonempty" : "empty", "nonempty");
assert("cleanup: throwaway DE deleted", dropDE(deKey), "OK");
</script>
Notes
- Resolves the DE by Name, not external key / CustomerKey.
- All four filter and field name/value parameters require nonempty, equal-length arrays.
- The filter columns do not have to be primary-key columns. If they match several rows, every match is updated; if they match none, their values become part of the inserted row.
- Missing required insert fields and primary-key conflicts throw.
- Number, Boolean, Date, and array values coerce when written to Text fields. Explicit
"",null, andundefinedpersist as ordinary empty strings in nullable Text. - DE names, column names, and Text filter values matched case-insensitively in the tested business unit.
Platform.Function.UpsertDataworks without Core;Platform.Load("core", ...)does not create a bareUpsertDataglobal.UpsertDEperforms the same upsert but returnsnullinstead of a row count. Its insert and update branches should be verified independently rather than inferred from this function.- For large batch upserts, consider WSProxy’s
updateBatchfor better performance.
Show test script
<script runat="server">
/*
* Chapter: Notes
* Proves:
* 1. The DE resolves by Name; the CustomerKey form throws.
* 2. Filter columns need not be primary keys — a non-key filter updates every match.
* 3. Missing required insert fields and primary-key conflicts throw.
* 4. Number, Boolean, Date and array values coerce into Text columns.
* 5. Explicit "", null and undefined persist as ordinary empty strings in nullable Text.
* 6. DE names, column names and Text filter values match case-insensitively in this BU.
* 7. Platform.Function.UpsertData works before Platform.Load("core", …), and Core does
* not create a callable bare UpsertData global.
* 8. UpsertDE performs the same upsert but returns null instead of a row count.
* NON-ASSERTIONS (documented, not deterministically observable from this harness):
* - email, automation and triggered-send execution contexts — the harness is a CloudPage.
* - the WSProxy updateBatch performance recommendation for large batches — a
* throughput claim, not an observable return value.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function addField(de, name, len, isKey) {
var field = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(field, "Name", name);
Platform.Function.SetObjectProperty(field, "FieldType", "Text");
Platform.Function.SetObjectProperty(field, "MaxLength", len);
Platform.Function.SetObjectProperty(field, "IsPrimaryKey", isKey ? "true" : "false");
Platform.Function.SetObjectProperty(field, "IsRequired", isKey ? "true" : "false");
Platform.Function.AddObjectArrayItem(de, "Fields", field);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "Id", "50", true);
addField(de, "Grp", "50", false);
addField(de, "Txt", "100", false);
var status = [0, 0];
return String(Platform.Function.InvokeCreate(de, status, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var status = [0, 0];
return String(Platform.Function.InvokeDelete(de, status, null));
}
function countRows(name, field, value) {
var rows = Platform.Function.LookupRows(name, field, value);
return rows === null ? 0 : rows.length;
}
var deName = "ssjsg_up_not_2247_name", deKey = "ssjsg_up_not_2247_key";
assert("setup: throwaway DE created", createDE(deName, deKey), "OK");
assert("the qualified call works before Platform.Load(\"core\")", Platform.Function.UpsertData(deName, ["Id"], ["before"], ["Grp", "Txt"], ["control", "before-ok"]), 1);
assertThrows("a bare UpsertData call throws before Core is loaded", function () { return UpsertData(deName, ["Id"], ["bare-before"], ["Txt"], ["wrong"]); });
Platform.Load("core", "1.1.5");
assert("the qualified call still works after Core is loaded", Platform.Function.UpsertData(deName, ["Id"], ["after"], ["Grp", "Txt"], ["control", "after-ok"]), 1);
assert("Core does not create a bare UpsertData global", String(typeof UpsertData), "undefined");
assertThrows("a bare UpsertData call still throws after Core is loaded", function () { return UpsertData(deName, ["Id"], ["bare-after"], ["Txt"], ["wrong"]); });
assertThrows("the CustomerKey is rejected (the DE resolves by Name)", function () { return Platform.Function.UpsertData(deKey, ["Id"], ["key"], ["Txt"], ["wrong"]); });
assertThrows("an insert missing the required primary-key column throws", function () { return Platform.Function.UpsertData(deName, ["Grp"], ["ghost-one"], ["Txt"], ["wrong"]); });
assertThrows("an insert that duplicates an existing primary key throws", function () { return Platform.Function.UpsertData(deName, ["Grp"], ["ghost-two"], ["Id", "Txt"], ["before", "wrong"]); });
assert("this BU matches DE names case-insensitively", Platform.Function.UpsertData(deName.toUpperCase(), ["Id"], ["case-de"], ["Grp", "Txt"], ["case", "de-ok"]), 1);
assert("this BU matches filter columns case-insensitively", Platform.Function.UpsertData(deName, ["ID"], ["case-filter"], ["Grp", "Txt"], ["case", "filter-ok"]), 1);
assert("this BU matches field columns case-insensitively", Platform.Function.UpsertData(deName, ["Id"], ["case-field"], ["Grp", "TXT"], ["case", "field-ok"]), 1);
assert("a non-key filter matches every row and Text values are case-insensitive", Platform.Function.UpsertData(deName, ["Grp"], ["CASE"], ["Txt"], ["case-updated"]), 3);
assert("number values coerce into Text", Platform.Function.UpsertData(deName, ["Id"], ["num"], ["Grp", "Txt"], ["coerce", 123]), 1);
assert("boolean values coerce into Text", Platform.Function.UpsertData(deName, ["Id"], ["bool"], ["Grp", "Txt"], ["coerce", true]), 1);
assert("Date values coerce into Text", Platform.Function.UpsertData(deName, ["Id"], ["date"], ["Grp", "Txt"], ["coerce", new Date(2024, 0, 15)]), 1);
assert("array values coerce into Text", Platform.Function.UpsertData(deName, ["Id"], ["array"], ["Grp", "Txt"], ["coerce", ["a", "b"]]), 1);
assert("an explicit empty string writes to nullable Text", Platform.Function.UpsertData(deName, ["Id"], ["empty"], ["Grp", "Txt"], ["empty", ""]), 1);
assert("null writes to nullable Text", Platform.Function.UpsertData(deName, ["Id"], ["null"], ["Grp", "Txt"], ["null", null]), 1);
assert("undefined writes to nullable Text", Platform.Function.UpsertData(deName, ["Id"], ["undefined"], ["Grp", "Txt"], ["undefined", undefined]), 1);
assert("a number persisted into Text reads back as its digits", String(Platform.Function.Lookup(deName, "Txt", "Id", "num")), "123");
assert("a boolean persisted into Text reads back capitalised", String(Platform.Function.Lookup(deName, "Txt", "Id", "bool")), "True");
assert("an array persisted into Text reads back as the CLR list name", String(Platform.Function.Lookup(deName, "Txt", "Id", "array")), "System.Collections.ArrayList");
var emptyRows = Platform.Function.LookupRows(deName, "Grp", "empty");
assert("an explicit empty string persists with typeof string", String(typeof emptyRows[0]["Txt"]), "string");
assert("an explicit empty string persists as an empty string", String(emptyRows[0]["Txt"]), "");
var nullRows = Platform.Function.LookupRows(deName, "Grp", "null");
assert("null persists as an ordinary empty string", String(nullRows[0]["Txt"]), "");
var undefinedRows = Platform.Function.LookupRows(deName, "Grp", "undefined");
assert("undefined persists as an ordinary empty string", String(undefinedRows[0]["Txt"]), "");
var upsertDeResult = Platform.Function.UpsertDE(deName, ["Id"], ["de-form"], ["Grp", "Txt"], ["de", "de-ok"]);
assert("UpsertDE returns null instead of a row count", upsertDeResult === null ? "null" : "not null", "null");
assert("UpsertDE still committed its row", countRows(deName, "Txt", "de-ok"), 1);
assert("the pre-Core insert persisted", countRows(deName, "Txt", "before-ok"), 1);
assert("the post-Core insert persisted", countRows(deName, "Txt", "after-ok"), 1);
assert("no bare-name or rejected call wrote a row", countRows(deName, "Txt", "wrong"), 0);
assert("cleanup: throwaway DE deleted", dropDE(deKey), "OK");
</script>