From 31b668e7b3b4a201a9ae788a43d5fde1853abe2e Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Mon, 6 Apr 2026 14:31:38 -0600
Subject: [PATCH 01/35] feat: DH-21235: Column Restrction JS API
---
.../io/deephaven/web/client/api/Column.java | 36 +-
.../client/api/barrage/WebBarrageUtils.java | 364 +++++++++++++++++-
.../api/barrage/def/ColumnDefinition.java | 20 +-
.../barrage/def/InitialTableDefinition.java | 10 +
.../api/barrage/def/InputTableMetadata.java | 58 +++
.../web/client/state/ClientTableState.java | 10 +
6 files changed, 490 insertions(+), 8 deletions(-)
create mode 100644 web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/Column.java b/web/client-api/src/main/java/io/deephaven/web/client/api/Column.java
index a44100f1f60..2353272e9db 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/Column.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/Column.java
@@ -5,6 +5,7 @@
import com.vertispan.tsdefs.annotations.TsName;
import com.vertispan.tsdefs.annotations.TsTypeRef;
+import elemental2.core.JsArray;
import io.deephaven.web.client.api.filter.FilterValue;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsNullable;
@@ -48,6 +49,7 @@ public class Column {
private String description;
private final boolean isInputTableKeyColumn;
private final boolean isInputTableValueColumn;
+ private final JsArray columnRestrictions;
/**
* Format entire rows colors using the expression specified. Returns a {@code CustomColumn} object to apply to a
@@ -81,7 +83,8 @@ public static CustomColumn createCustomColumn(
public Column(int jsIndex, int index, Integer formatColumnIndex, Integer styleColumnIndex, String type, String name,
boolean isPartitionColumn, Integer formatStringColumnIndex, String description,
- boolean inputTableKeyColumn, boolean inputTableValueColumn, boolean isSortable) {
+ boolean inputTableKeyColumn, boolean inputTableValueColumn, boolean isSortable,
+ JsArray columnRestrictions) {
this.jsIndex = jsIndex;
this.index = index;
assert Objects.equals(formatColumnIndex, styleColumnIndex);
@@ -94,6 +97,18 @@ public Column(int jsIndex, int index, Integer formatColumnIndex, Integer styleCo
this.isInputTableKeyColumn = inputTableKeyColumn;
this.isInputTableValueColumn = inputTableValueColumn;
this.isSortable = isSortable;
+ this.columnRestrictions = columnRestrictions;
+ }
+
+ /**
+ * @deprecated Use the constructor with columnRestrictions parameter instead
+ */
+ @Deprecated
+ public Column(int jsIndex, int index, Integer formatColumnIndex, Integer styleColumnIndex, String type, String name,
+ boolean isPartitionColumn, Integer formatStringColumnIndex, String description,
+ boolean inputTableKeyColumn, boolean inputTableValueColumn, boolean isSortable) {
+ this(jsIndex, index, formatColumnIndex, styleColumnIndex, type, name, isPartitionColumn,
+ formatStringColumnIndex, description, inputTableKeyColumn, inputTableValueColumn, isSortable, null);
}
/**
@@ -222,6 +237,19 @@ public boolean getIsSortable() {
return isSortable;
}
+ /**
+ * Returns the column restrictions for input table columns, or null if this is not an input table column
+ * or if no restrictions are defined. The restrictions are implementation-specific constraints that the
+ * server enforces on column values.
+ *
+ * @return Array of column restrictions, or null if none are defined
+ */
+ @JsProperty
+ @JsNullable
+ public JsArray getColumnRestrictions() {
+ return columnRestrictions;
+ }
+
/**
* Creates a new value for use in filters based on this column. Used either as a parameter to another filter
* operation, or as a builder to create a filter operation.
@@ -320,11 +348,13 @@ public int hashCode() {
public Column withFormatStringColumnIndex(int formatStringColumnIndex) {
return new Column(jsIndex, index, styleColumnIndex, styleColumnIndex, type, name, isPartitionColumn,
- formatStringColumnIndex, description, isInputTableKeyColumn, isInputTableValueColumn, isSortable);
+ formatStringColumnIndex, description, isInputTableKeyColumn, isInputTableValueColumn, isSortable,
+ columnRestrictions);
}
public Column withStyleColumnIndex(int styleColumnIndex) {
return new Column(jsIndex, index, styleColumnIndex, styleColumnIndex, type, name, isPartitionColumn,
- formatStringColumnIndex, description, isInputTableKeyColumn, isInputTableValueColumn, isSortable);
+ formatStringColumnIndex, description, isInputTableKeyColumn, isInputTableValueColumn, isSortable,
+ columnRestrictions);
}
}
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
index ea493615c2b..2cd586dc026 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
@@ -9,6 +9,7 @@
import io.deephaven.barrage.flatbuf.BarrageMessageWrapper;
import io.deephaven.web.client.api.barrage.def.ColumnDefinition;
import io.deephaven.web.client.api.barrage.def.InitialTableDefinition;
+import io.deephaven.web.client.api.barrage.def.InputTableMetadata;
import io.deephaven.web.client.api.barrage.def.TableAttributesDefinition;
import io.deephaven.web.shared.data.*;
import org.apache.arrow.flatbuf.KeyValue;
@@ -65,11 +66,372 @@ public static InitialTableDefinition readTableDefinition(Schema schema) {
keyValuePairs("deephaven:attribute_type.", schema.customMetadataLength(), schema::customMetadata),
keyValuePairs("deephaven:unsent.attribute.", schema.customMetadataLength(), schema::customMetadata)
.keySet());
+
+ // Parse input table metadata if present
+ InputTableMetadata inputTableMetadata = parseInputTableMetadata(schema, cols);
+
return new InitialTableDefinition()
.setAttributes(attributes)
- .setColumns(cols);
+ .setColumns(cols)
+ .setInputTableMetadata(inputTableMetadata);
+ }
+
+ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnDefinition[] cols) {
+ // Extract the tableMetadata from schema custom metadata
+ Map schemaMetadata =
+ keyValuePairs("deephaven:", schema.customMetadataLength(), schema::customMetadata);
+
+ consoleLog("parseInputTableMetadata: Checking for tableMetadata in schema");
+
+ String tableMetadataBase64 = schemaMetadata.get("tableMetadata");
+ if (tableMetadataBase64 == null || tableMetadataBase64.isEmpty()) {
+ consoleLog("parseInputTableMetadata: No tableMetadata found in schema (null or empty)");
+ return null;
+ }
+
+ consoleLog("parseInputTableMetadata: Found tableMetadata, length=" + tableMetadataBase64.length());
+ InputTableMetadata metadata = new InputTableMetadata();
+
+ try {
+ // Decode base64 to Uint8Array (like Java's Base64.getDecoder().decode())
+ consoleLog("parseInputTableMetadata: Decoding base64...");
+ Uint8Array bytes = decodeBase64(tableMetadataBase64);
+ consoleLog("parseInputTableMetadata: Decoded to " + bytes.length + " bytes");
+
+ // The issue: JavaScript protobuf deserialization fails on google.protobuf.Any types
+ // Solution: Parse the protobuf manually at the binary level to extract what we need
+ consoleLog("parseInputTableMetadata: Attempting manual protobuf parsing...");
+ jsinterop.base.Any result = parseProtoManually(bytes);
+
+ if (result == null) {
+ consoleLog("parseInputTableMetadata: Manual parsing returned null");
+ return metadata;
+ }
+
+ consoleLog("parseInputTableMetadata: Successfully parsed, extracting column info");
+
+ // Extract column restrictions from the manually parsed data
+ for (ColumnDefinition col : cols) {
+ String columnName = col.getName();
+ jsinterop.base.Any columnInfo = getPropertyFromMap(result, columnName);
+
+ if (columnInfo != null) {
+ consoleLog("parseInputTableMetadata: Found info for column '" + columnName + "'");
+
+ // Get restrictions array if available
+ jsinterop.base.Any restrictionsList = getProperty(columnInfo, "restrictions");
+ if (restrictionsList != null && isArray(restrictionsList)) {
+ elemental2.core.JsArray restrictions =
+ jsinterop.base.Js.uncheckedCast(restrictionsList);
+
+ if (restrictions.length > 0) {
+ consoleLog("parseInputTableMetadata: Column '" + columnName + "' has " +
+ restrictions.length + " restrictions");
+
+ InputTableMetadata.ColumnRestrictions colRestrictions =
+ new InputTableMetadata.ColumnRestrictions();
+ for (int i = 0; i < restrictions.length; i++) {
+ colRestrictions.addRestriction(restrictions.getAt(i));
+ }
+ metadata.addColumnRestrictions(columnName, colRestrictions);
+ }
+ }
+ }
+ }
+
+ consoleLog("parseInputTableMetadata: Successfully parsed metadata");
+ } catch (Exception e) {
+ consoleError("parseInputTableMetadata: Exception during parsing", e);
+ }
+
+ return metadata;
}
+ // Native JavaScript methods to decode and parse protobuf without generated classes
+
+ private static native Uint8Array decodeBase64(String base64) /*-{
+ // Convert base64 string to Uint8Array
+ var binaryString = atob(base64);
+ var bytes = new Uint8Array(binaryString.length);
+ for (var i = 0; i < binaryString.length; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+ return bytes;
+ }-*/;
+
+ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*-{
+ // Manual protobuf parsing to work around missing google.protobuf.Any
+ // Based on the pattern from AddToInputTable.java
+ // Use the BinaryReader from dhinternal.jspb which is the JsInterop wrapper
+ try {
+ console.log("parseProtoManually: Starting manual protobuf parsing");
+ console.log("parseProtoManually: Bytes length =", bytes.length);
+
+ // Use the JsInterop BinaryReader class
+ var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
+ var reader = new BinaryReader(bytes);
+ var result = {};
+
+ // Parse the DeephavenTableMetadata message
+ // Field 1 is InputTableMetadata
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ console.log("parseProtoManually: Reading field", field);
+
+ if (field === 1) {
+ // This is the InputTableMetadata field
+ var inputTableMetadata = {};
+ reader.readMessage(inputTableMetadata, function(metadata, rdr) {
+ // Parse InputTableMetadata
+ // Field 1 is columnInfoMap (map)
+ while (rdr.nextField()) {
+ if (rdr.isEndGroup()) {
+ break;
+ }
+ var subfield = rdr.getFieldNumber();
+ console.log("parseProtoManually: InputTableMetadata field", subfield);
+
+ if (subfield === 1) {
+ // This is the columnInfoMap
+ var mapEntry = {};
+ rdr.readMessage(mapEntry, function(entry, mapReader) {
+ var key = null;
+ var value = null;
+
+ while (mapReader.nextField()) {
+ if (mapReader.isEndGroup()) {
+ break;
+ }
+ var mapField = mapReader.getFieldNumber();
+
+ if (mapField === 1) {
+ // Map key (column name)
+ key = mapReader.readString();
+ console.log("parseProtoManually: Column name =", key);
+ } else if (mapField === 2) {
+ // Map value (InputTableColumnInfo)
+ value = {};
+ var restrictions = [];
+
+ mapReader.readMessage(value, function(columnInfo, colReader) {
+ while (colReader.nextField()) {
+ if (colReader.isEndGroup()) {
+ break;
+ }
+ var colField = colReader.getFieldNumber();
+ console.log("parseProtoManually: InputTableColumnInfo field", colField);
+
+ if (colField === 1) {
+ // kind field
+ columnInfo.kind = colReader.readEnum();
+ } else if (colField === 2) {
+ // restrictions field (repeated google.protobuf.Any)
+ // Parse the Any message to extract type_url and value
+ try {
+ var anyBytes = colReader.readBytes();
+ console.log("parseProtoManually: Got Any bytes, length =", anyBytes.length);
+
+ // Parse the google.protobuf.Any message
+ // Field 1 = type_url (string)
+ // Field 2 = value (bytes)
+ var anyReader = new BinaryReader(anyBytes);
+ var typeUrl = null;
+ var valueBytes = null;
+
+ while (anyReader.nextField()) {
+ if (anyReader.isEndGroup()) {
+ break;
+ }
+ var anyField = anyReader.getFieldNumber();
+ if (anyField === 1) {
+ typeUrl = anyReader.readString();
+ console.log("parseProtoManually: Restriction type_url =", typeUrl);
+ } else if (anyField === 2) {
+ valueBytes = anyReader.readBytes();
+ console.log("parseProtoManually: Restriction value bytes length =", valueBytes.length);
+ }
+ }
+
+ // Now parse the actual restriction based on type_url
+ var restriction = @io.deephaven.web.client.api.barrage.WebBarrageUtils::parseRestriction(*)(typeUrl, valueBytes);
+ if (restriction) {
+ restrictions.push(restriction);
+ }
+ } catch (e) {
+ console.warn("parseProtoManually: Failed to parse restriction:", e);
+ }
+ }
+ }
+ columnInfo.restrictions = restrictions;
+ });
+ }
+ }
+
+ if (key !== null && value !== null) {
+ if (!metadata.columnInfoMap) {
+ metadata.columnInfoMap = {};
+ }
+ metadata.columnInfoMap[key] = value;
+ console.log("parseProtoManually: Added column", key, "with", value.restrictions ? value.restrictions.length : 0, "restrictions");
+ }
+ });
+ }
+ }
+ });
+
+ result = inputTableMetadata.columnInfoMap || {};
+ console.log("parseProtoManually: Parsed", Object.keys(result).length, "columns");
+ }
+ }
+
+ return result;
+
+ } catch (e) {
+ console.error("parseProtoManually: Failed to manually parse protobuf:", e);
+ console.error("Stack:", e.stack);
+ return null;
+ }
+ }-*/;
+
+ private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8Array valueBytes) /*-{
+ // Parse specific restriction types based on typeUrl
+ // Types from inputtable.proto:
+ // - IntegerRangeRestriction
+ // - DoubleRangeRestriction
+ // - NotNullRestriction
+ // - NonEmptyRestriction
+ // - StringListRestriction
+
+ try {
+ console.log("parseRestriction: Parsing restriction type:", typeUrl);
+
+ var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
+ var reader = new BinaryReader(valueBytes);
+
+ // Extract the message type from the type URL
+ // Format: "type.googleapis.com/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
+ // or "docs.deephaven.io/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
+ var typeName = typeUrl.substring(typeUrl.lastIndexOf('/') + 1);
+ var shortName = typeName.substring(typeName.lastIndexOf('.') + 1);
+ console.log("parseRestriction: Message type:", shortName);
+
+ var restriction = {
+ type: shortName,
+ typeUrl: typeUrl
+ };
+
+ if (shortName === 'IntegerRangeRestriction') {
+ // Field 1 = min_inclusive (int64)
+ // Field 2 = max_inclusive (int64)
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ restriction.minInclusive = reader.readInt64();
+ console.log("parseRestriction: minInclusive =", restriction.minInclusive);
+ } else if (field === 2) {
+ restriction.maxInclusive = reader.readInt64();
+ console.log("parseRestriction: maxInclusive =", restriction.maxInclusive);
+ }
+ }
+ } else if (shortName === 'DoubleRangeRestriction') {
+ // Field 1 = min_inclusive (double)
+ // Field 2 = max_inclusive (double)
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ restriction.minInclusive = reader.readDouble();
+ console.log("parseRestriction: minInclusive =", restriction.minInclusive);
+ } else if (field === 2) {
+ restriction.maxInclusive = reader.readDouble();
+ console.log("parseRestriction: maxInclusive =", restriction.maxInclusive);
+ }
+ }
+ } else if (shortName === 'NotNullRestriction') {
+ // No fields - just the type
+ restriction.notNull = true;
+ console.log("parseRestriction: NotNull restriction");
+ } else if (shortName === 'NonEmptyRestriction') {
+ // No fields - just the type
+ restriction.nonEmpty = true;
+ console.log("parseRestriction: NonEmpty restriction");
+ } else if (shortName === 'StringListRestriction') {
+ // Field 1 = allowed_values (repeated string)
+ restriction.allowedValues = [];
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ var value = reader.readString();
+ restriction.allowedValues.push(value);
+ console.log("parseRestriction: allowed value =", value);
+ }
+ }
+ } else {
+ console.warn("parseRestriction: Unknown restriction type:", shortName);
+ restriction.raw = valueBytes;
+ }
+
+ return restriction;
+
+ } catch (e) {
+ console.error("parseRestriction: Failed to parse restriction:", e);
+ return null;
+ }
+ }-*/;
+
+ private static native jsinterop.base.Any getPropertyFromMap(jsinterop.base.Any map, String key) /*-{
+ if (map == null) return null;
+ return map[key] || null;
+ }-*/;
+
+ private static native jsinterop.base.Any getProperty(jsinterop.base.Any obj, String propertyName) /*-{
+ if (obj == null) return null;
+ var getter = 'get' + propertyName.charAt(0).toUpperCase() + propertyName.slice(1);
+ if (typeof obj[getter] === 'function') {
+ return obj[getter]();
+ }
+ return obj[propertyName];
+ }-*/;
+
+ private static native jsinterop.base.Any getMapEntry(jsinterop.base.Any map, String key) /*-{
+ if (map == null) return null;
+ if (typeof map.get === 'function') {
+ return map.get(key);
+ }
+ if (typeof map.getMap === 'function') {
+ var m = map.getMap();
+ return m ? m[key] : null;
+ }
+ return map[key];
+ }-*/;
+
+ private static native boolean isArray(jsinterop.base.Any obj) /*-{
+ return Array.isArray(obj);
+ }-*/;
+
+ private static native void consoleLog(String message) /*-{
+ console.log(message);
+ }-*/;
+
+ private static native void consoleError(String message, Exception e) /*-{
+ console.error(message, e);
+ }-*/;
+
+ private static native void logProtoObject(String label, jsinterop.base.Any obj) /*-{
+ console.log(label + ":", obj);
+ if (obj != null) {
+ console.log(label + " keys:", Object.keys(obj));
+ console.log(label + " methods:", Object.getOwnPropertyNames(Object.getPrototypeOf(obj)).filter(function(name) {
+ return typeof obj[name] === 'function';
+ }));
+ }
+ }-*/;
+
private static ColumnDefinition[] readColumnDefinitions(Schema schema) {
ColumnDefinition[] cols = new ColumnDefinition[(int) schema.fieldsLength()];
for (int i = 0; i < schema.fieldsLength(); i++) {
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/ColumnDefinition.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/ColumnDefinition.java
index 90c0d549613..c1d08e3dd62 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/ColumnDefinition.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/ColumnDefinition.java
@@ -3,7 +3,9 @@
//
package io.deephaven.web.client.api.barrage.def;
+import elemental2.core.JsArray;
import io.deephaven.web.client.api.Column;
+import jsinterop.base.Any;
import org.apache.arrow.flatbuf.Field;
import java.util.Map;
@@ -36,6 +38,7 @@ public class ColumnDefinition {
private final boolean isInputTableKeyColumn;
private final boolean isInputTableValueColumn;
private final String description;
+ private JsArray columnRestrictions;
public ColumnDefinition(int index, Field field) {
Map fieldMetadata =
@@ -130,9 +133,17 @@ public String getDescription() {
return description;
}
+ public JsArray getColumnRestrictions() {
+ return columnRestrictions;
+ }
+
+ public void setColumnRestrictions(JsArray columnRestrictions) {
+ this.columnRestrictions = columnRestrictions;
+ }
+
public Column makeJsColumn(int index, Map> map) {
if (isForRow()) {
- return makeColumn(-1, this, null, null, false, null, null, false, false);
+ return makeColumn(-1, this, null, null, false, null, null, false, false, null);
}
Map byNameMap = map.get(isRollupConstituentNodeColumn());
ColumnDefinition format = byNameMap.get(getFormatColumnName());
@@ -146,16 +157,17 @@ public Column makeJsColumn(int index, Map
format == null ? null : format.getColumnIndex(),
getDescription(),
isInputTableKeyColumn(),
- isInputTableValueColumn());
+ isInputTableValueColumn(),
+ getColumnRestrictions());
}
private static Column makeColumn(int jsIndex, ColumnDefinition definition, Integer numberFormatIndex,
Integer styleIndex, boolean isPartitionColumn, Integer formatStringIndex, String description,
- boolean inputTableKeyColumn, boolean inputTableValueColumn) {
+ boolean inputTableKeyColumn, boolean inputTableValueColumn, JsArray columnRestrictions) {
return new Column(jsIndex, definition.getColumnIndex(), numberFormatIndex, styleIndex, definition.getType(),
definition.getName(), isPartitionColumn, formatStringIndex, description, inputTableKeyColumn,
inputTableValueColumn,
- definition.isSortable());
+ definition.isSortable(), columnRestrictions);
}
public boolean isHierarchicalExpandByColumn() {
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InitialTableDefinition.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InitialTableDefinition.java
index fdd09139abb..4300758f31f 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InitialTableDefinition.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InitialTableDefinition.java
@@ -20,6 +20,7 @@ public class InitialTableDefinition {
private ColumnDefinition[] columns;
private TableAttributesDefinition attributes;
+ private InputTableMetadata inputTableMetadata;
public ColumnDefinition[] getColumns() {
return columns;
@@ -39,6 +40,15 @@ public InitialTableDefinition setAttributes(final TableAttributesDefinition attr
return this;
}
+ public InputTableMetadata getInputTableMetadata() {
+ return inputTableMetadata;
+ }
+
+ public InitialTableDefinition setInputTableMetadata(final InputTableMetadata inputTableMetadata) {
+ this.inputTableMetadata = inputTableMetadata;
+ return this;
+ }
+
public Map> getColumnsByName() {
return Arrays.stream(columns)
.collect(Collectors.partitioningBy(ColumnDefinition::isRollupConstituentNodeColumn,
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
new file mode 100644
index 00000000000..ab375473050
--- /dev/null
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
@@ -0,0 +1,58 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.web.client.api.barrage.def;
+
+import elemental2.core.JsArray;
+import jsinterop.annotations.JsIgnore;
+import jsinterop.annotations.JsNullable;
+import jsinterop.base.Any;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents input table metadata parsed from the schema.
+ */
+public class InputTableMetadata {
+ private final Map columnRestrictions;
+
+ @JsIgnore
+ public InputTableMetadata() {
+ this.columnRestrictions = new HashMap<>();
+ }
+
+ @JsIgnore
+ public void addColumnRestrictions(String columnName, ColumnRestrictions restrictions) {
+ columnRestrictions.put(columnName, restrictions);
+ }
+
+ @JsIgnore
+ @JsNullable
+ public ColumnRestrictions getColumnRestrictions(String columnName) {
+ return columnRestrictions.get(columnName);
+ }
+
+ /**
+ * Represents restrictions on a column's values.
+ */
+ public static class ColumnRestrictions {
+ private final JsArray restrictions;
+
+ @JsIgnore
+ public ColumnRestrictions() {
+ this.restrictions = new JsArray<>();
+ }
+
+ @JsIgnore
+ public void addRestriction(Any restriction) {
+ restrictions.push(restriction);
+ }
+
+ @JsIgnore
+ public JsArray getRestrictions() {
+ return restrictions;
+ }
+ }
+}
+
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/state/ClientTableState.java b/web/client-api/src/main/java/io/deephaven/web/client/state/ClientTableState.java
index 949264feb46..5aa7aadde64 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/state/ClientTableState.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/state/ClientTableState.java
@@ -447,6 +447,16 @@ private void setTableDef(InitialTableDefinition tableDef) {
if (create) {
ColumnDefinition[] columnDefinitions = tableDef.getColumns();
+ // Populate column restrictions from input table metadata if available
+ if (tableDef.getInputTableMetadata() != null) {
+ for (ColumnDefinition definition : columnDefinitions) {
+ var restrictions = tableDef.getInputTableMetadata().getColumnRestrictions(definition.getName());
+ if (restrictions != null) {
+ definition.setColumnRestrictions(restrictions.getRestrictions());
+ }
+ }
+ }
+
// iterate through the columns, combine format columns into the normal model
Map> byNameMap = tableDef.getColumnsByName();
Column[] columns = new Column[0];
From aa2f17d17cf60249a721b23e31a409598212674b Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Tue, 7 Apr 2026 07:32:41 -0600
Subject: [PATCH 02/35] non-native decode
---
.../client/api/barrage/WebBarrageUtils.java | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
index 2cd586dc026..572d472a9fe 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
@@ -5,6 +5,7 @@
import com.google.flatbuffers.FlatBufferBuilder;
import elemental2.core.*;
+import elemental2.dom.DomGlobal;
import io.deephaven.barrage.flatbuf.BarrageMessageType;
import io.deephaven.barrage.flatbuf.BarrageMessageWrapper;
import io.deephaven.web.client.api.barrage.def.ColumnDefinition;
@@ -147,17 +148,18 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
return metadata;
}
- // Native JavaScript methods to decode and parse protobuf without generated classes
+ // Decode base64 string to Uint8Array using elemental2
+ private static Uint8Array decodeBase64(String base64) {
+ // Use DomGlobal.atob() to decode base64 to binary string
+ String binaryString = DomGlobal.atob(base64);
- private static native Uint8Array decodeBase64(String base64) /*-{
- // Convert base64 string to Uint8Array
- var binaryString = atob(base64);
- var bytes = new Uint8Array(binaryString.length);
- for (var i = 0; i < binaryString.length; i++) {
- bytes[i] = binaryString.charCodeAt(i);
+ // Convert binary string to Uint8Array
+ Uint8Array bytes = new Uint8Array(binaryString.length());
+ for (int i = 0; i < binaryString.length(); i++) {
+ bytes.setAt(i, (double) (binaryString.charAt(i) & 0xff));
}
return bytes;
- }-*/;
+ }
private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*-{
// Manual protobuf parsing to work around missing google.protobuf.Any
From 55145d31cf759a123b5433b3e51dca25e6a16b7f Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Tue, 7 Apr 2026 09:20:13 -0600
Subject: [PATCH 03/35] update comment
---
.../engine/util/input/InputTableUpdater.java | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java b/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
index e09f774997c..450229e3d78 100644
--- a/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
+++ b/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
@@ -57,12 +57,17 @@ default List getValueNames() {
}
/**
- * If there are client-side defined restrictions on this column; return them as a JSON string to be interpreted by
- * the client for properly displaying the edit field.
+ * If there are client-side defined restrictions on this column, return them as a list of protobuf Any messages.
+ * These restrictions are used by the client for properly displaying and validating the edit field.
+ *
+ *
+ * The restrictions are packed as {@code google.protobuf.Any} messages, which allows for different restriction
+ * types (e.g., {@code IntegerRangeRestriction}, {@code DoubleRangeRestriction}, {@code StringListRestriction},
+ * etc.) to be sent to the client. The client is responsible for unpacking and interpreting these restrictions.
*
* @param columnName the column name to query
- * @return a string representing the restrictions for this column, or null if no client-side restrictions are
- * supplied for this column
+ * @return a list of protobuf Any messages representing the restrictions for this column, or null if no
+ * client-side restrictions are supplied for this column
*/
@Nullable
default List getColumnRestrictions(final String columnName) {
From 180bc15306c02c25ad9df09a09e5057bce46ee9b Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Tue, 7 Apr 2026 10:24:25 -0600
Subject: [PATCH 04/35] send restrictions as json
---
.../engine/util/input/InputTableUpdater.java | 13 +-
extensions/barrage/build.gradle | 1 +
.../extensions/barrage/util/BarrageUtil.java | 30 +-
.../client/api/barrage/WebBarrageUtils.java | 301 ++----------------
4 files changed, 64 insertions(+), 281 deletions(-)
diff --git a/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java b/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
index 450229e3d78..e09f774997c 100644
--- a/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
+++ b/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
@@ -57,17 +57,12 @@ default List getValueNames() {
}
/**
- * If there are client-side defined restrictions on this column, return them as a list of protobuf Any messages.
- * These restrictions are used by the client for properly displaying and validating the edit field.
- *
- *
- * The restrictions are packed as {@code google.protobuf.Any} messages, which allows for different restriction
- * types (e.g., {@code IntegerRangeRestriction}, {@code DoubleRangeRestriction}, {@code StringListRestriction},
- * etc.) to be sent to the client. The client is responsible for unpacking and interpreting these restrictions.
+ * If there are client-side defined restrictions on this column; return them as a JSON string to be interpreted by
+ * the client for properly displaying the edit field.
*
* @param columnName the column name to query
- * @return a list of protobuf Any messages representing the restrictions for this column, or null if no
- * client-side restrictions are supplied for this column
+ * @return a string representing the restrictions for this column, or null if no client-side restrictions are
+ * supplied for this column
*/
@Nullable
default List getColumnRestrictions(final String columnName) {
diff --git a/extensions/barrage/build.gradle b/extensions/barrage/build.gradle
index dff44595c32..350e81803c4 100644
--- a/extensions/barrage/build.gradle
+++ b/extensions/barrage/build.gradle
@@ -15,6 +15,7 @@ dependencies {
api project(':engine-table')
implementation project(':proto:proto-backplane-grpc-flight')
+ implementation libs.protobuf.java.util
implementation project(':log-factory')
api libs.deephaven.barrage.format
implementation libs.hdrhistogram
diff --git a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java
index da8c32a5df3..dcb37270134 100755
--- a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java
+++ b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java
@@ -8,6 +8,8 @@
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.ByteStringAccess;
+import com.google.protobuf.util.JsonFormat;
+import com.google.protobuf.util.JsonFormat.TypeRegistry;
import com.google.rpc.Code;
import io.deephaven.UncheckedDeephavenException;
import io.deephaven.api.util.NameValidator;
@@ -53,9 +55,14 @@
import io.deephaven.internal.log.LoggerFactory;
import io.deephaven.io.logger.Logger;
import io.deephaven.proto.backplane.grpc.DeephavenTableMetadata;
+import io.deephaven.proto.backplane.grpc.DoubleRangeRestriction;
import io.deephaven.proto.backplane.grpc.ExportedTableCreationResponse;
import io.deephaven.proto.backplane.grpc.InputTableMetadata;
import io.deephaven.proto.backplane.grpc.InputTableColumnInfo;
+import io.deephaven.proto.backplane.grpc.IntegerRangeRestriction;
+import io.deephaven.proto.backplane.grpc.NonEmptyRestriction;
+import io.deephaven.proto.backplane.grpc.NotNullRestriction;
+import io.deephaven.proto.backplane.grpc.StringListRestriction;
import io.deephaven.proto.flight.util.MessageHelper;
import io.deephaven.proto.flight.util.SchemaHelper;
import io.deephaven.proto.util.Exceptions;
@@ -128,6 +135,17 @@ public class BarrageUtil {
private static final Logger log = LoggerFactory.getLogger(BarrageUtil.class);
+ /**
+ * TypeRegistry for JSON serialization of InputTableMetadata with restriction types.
+ */
+ private static final TypeRegistry INPUT_TABLE_TYPE_REGISTRY = TypeRegistry.newBuilder()
+ .add(IntegerRangeRestriction.getDescriptor())
+ .add(DoubleRangeRestriction.getDescriptor())
+ .add(NotNullRestriction.getDescriptor())
+ .add(NonEmptyRestriction.getDescriptor())
+ .add(StringListRestriction.getDescriptor())
+ .build();
+
public static final double TARGET_SNAPSHOT_PERCENTAGE =
Configuration.getInstance().getDoubleForClassWithDefault(BarrageUtil.class,
"targetSnapshotPercentage", 0.25);
@@ -555,9 +573,15 @@ private static void maybeAddInputTableMetadata(@NotNull TableDefinition tableDef
final DeephavenTableMetadata metadata =
DeephavenTableMetadata.newBuilder().setInputTableMetadata(builder).build();
- final byte[] bytes = metadata.toByteArray();
- final String base64 = Base64.getEncoder().encodeToString(bytes);
- putMetadata(schemaMetadata, ATTR_PROTO_METADATA_TAG, base64);
+ try {
+ final String json = JsonFormat.printer()
+ .usingTypeRegistry(INPUT_TABLE_TYPE_REGISTRY)
+ .print(metadata);
+ final String base64 = Base64.getEncoder().encodeToString(json.getBytes(java.nio.charset.StandardCharsets.UTF_8));
+ putMetadata(schemaMetadata, ATTR_PROTO_METADATA_TAG, base64);
+ } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+ throw new UncheckedDeephavenException("Failed to convert InputTableMetadata to JSON", e);
+ }
}
@NotNull
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
index 572d472a9fe..67180c7c387 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
@@ -94,27 +94,40 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
InputTableMetadata metadata = new InputTableMetadata();
try {
- // Decode base64 to Uint8Array (like Java's Base64.getDecoder().decode())
- consoleLog("parseInputTableMetadata: Decoding base64...");
- Uint8Array bytes = decodeBase64(tableMetadataBase64);
- consoleLog("parseInputTableMetadata: Decoded to " + bytes.length + " bytes");
-
- // The issue: JavaScript protobuf deserialization fails on google.protobuf.Any types
- // Solution: Parse the protobuf manually at the binary level to extract what we need
- consoleLog("parseInputTableMetadata: Attempting manual protobuf parsing...");
- jsinterop.base.Any result = parseProtoManually(bytes);
-
- if (result == null) {
- consoleLog("parseInputTableMetadata: Manual parsing returned null");
+ // Decode base64 to JSON string using atob
+ consoleLog("parseInputTableMetadata: Decoding base64 to JSON...");
+ String jsonString = DomGlobal.atob(tableMetadataBase64);
+ consoleLog("parseInputTableMetadata: Decoded JSON string: " + jsonString);
+
+ // Parse JSON string to JavaScript object
+ consoleLog("parseInputTableMetadata: Parsing JSON...");
+ jsinterop.base.Any jsonObject = parseJson(jsonString);
+
+ if (jsonObject == null) {
+ consoleLog("parseInputTableMetadata: JSON parsing returned null");
+ return metadata;
+ }
+
+ consoleLog("parseInputTableMetadata: Successfully parsed JSON, extracting column info");
+
+ // Extract inputTableMetadata from the parsed JSON
+ jsinterop.base.Any inputTableMetadata = getProperty(jsonObject, "inputTableMetadata");
+ if (inputTableMetadata == null) {
+ consoleLog("parseInputTableMetadata: No inputTableMetadata found in JSON");
return metadata;
}
- consoleLog("parseInputTableMetadata: Successfully parsed, extracting column info");
+ // Extract columnInfo from inputTableMetadata
+ jsinterop.base.Any columnInfoMap = getProperty(inputTableMetadata, "columnInfo");
+ if (columnInfoMap == null) {
+ consoleLog("parseInputTableMetadata: No columnInfo found in inputTableMetadata");
+ return metadata;
+ }
- // Extract column restrictions from the manually parsed data
+ // Extract column restrictions from the JSON data
for (ColumnDefinition col : cols) {
String columnName = col.getName();
- jsinterop.base.Any columnInfo = getPropertyFromMap(result, columnName);
+ jsinterop.base.Any columnInfo = getPropertyFromMap(columnInfoMap, columnName);
if (columnInfo != null) {
consoleLog("parseInputTableMetadata: Found info for column '" + columnName + "'");
@@ -148,240 +161,12 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
return metadata;
}
- // Decode base64 string to Uint8Array using elemental2
- private static Uint8Array decodeBase64(String base64) {
- // Use DomGlobal.atob() to decode base64 to binary string
- String binaryString = DomGlobal.atob(base64);
-
- // Convert binary string to Uint8Array
- Uint8Array bytes = new Uint8Array(binaryString.length());
- for (int i = 0; i < binaryString.length(); i++) {
- bytes.setAt(i, (double) (binaryString.charAt(i) & 0xff));
- }
- return bytes;
- }
-
- private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*-{
- // Manual protobuf parsing to work around missing google.protobuf.Any
- // Based on the pattern from AddToInputTable.java
- // Use the BinaryReader from dhinternal.jspb which is the JsInterop wrapper
+ // Parse JSON string to JavaScript object
+ private static native jsinterop.base.Any parseJson(String jsonString) /*-{
try {
- console.log("parseProtoManually: Starting manual protobuf parsing");
- console.log("parseProtoManually: Bytes length =", bytes.length);
-
- // Use the JsInterop BinaryReader class
- var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
- var reader = new BinaryReader(bytes);
- var result = {};
-
- // Parse the DeephavenTableMetadata message
- // Field 1 is InputTableMetadata
- while (reader.nextField()) {
- if (reader.isEndGroup()) {
- break;
- }
- var field = reader.getFieldNumber();
- console.log("parseProtoManually: Reading field", field);
-
- if (field === 1) {
- // This is the InputTableMetadata field
- var inputTableMetadata = {};
- reader.readMessage(inputTableMetadata, function(metadata, rdr) {
- // Parse InputTableMetadata
- // Field 1 is columnInfoMap (map)
- while (rdr.nextField()) {
- if (rdr.isEndGroup()) {
- break;
- }
- var subfield = rdr.getFieldNumber();
- console.log("parseProtoManually: InputTableMetadata field", subfield);
-
- if (subfield === 1) {
- // This is the columnInfoMap
- var mapEntry = {};
- rdr.readMessage(mapEntry, function(entry, mapReader) {
- var key = null;
- var value = null;
-
- while (mapReader.nextField()) {
- if (mapReader.isEndGroup()) {
- break;
- }
- var mapField = mapReader.getFieldNumber();
-
- if (mapField === 1) {
- // Map key (column name)
- key = mapReader.readString();
- console.log("parseProtoManually: Column name =", key);
- } else if (mapField === 2) {
- // Map value (InputTableColumnInfo)
- value = {};
- var restrictions = [];
-
- mapReader.readMessage(value, function(columnInfo, colReader) {
- while (colReader.nextField()) {
- if (colReader.isEndGroup()) {
- break;
- }
- var colField = colReader.getFieldNumber();
- console.log("parseProtoManually: InputTableColumnInfo field", colField);
-
- if (colField === 1) {
- // kind field
- columnInfo.kind = colReader.readEnum();
- } else if (colField === 2) {
- // restrictions field (repeated google.protobuf.Any)
- // Parse the Any message to extract type_url and value
- try {
- var anyBytes = colReader.readBytes();
- console.log("parseProtoManually: Got Any bytes, length =", anyBytes.length);
-
- // Parse the google.protobuf.Any message
- // Field 1 = type_url (string)
- // Field 2 = value (bytes)
- var anyReader = new BinaryReader(anyBytes);
- var typeUrl = null;
- var valueBytes = null;
-
- while (anyReader.nextField()) {
- if (anyReader.isEndGroup()) {
- break;
- }
- var anyField = anyReader.getFieldNumber();
- if (anyField === 1) {
- typeUrl = anyReader.readString();
- console.log("parseProtoManually: Restriction type_url =", typeUrl);
- } else if (anyField === 2) {
- valueBytes = anyReader.readBytes();
- console.log("parseProtoManually: Restriction value bytes length =", valueBytes.length);
- }
- }
-
- // Now parse the actual restriction based on type_url
- var restriction = @io.deephaven.web.client.api.barrage.WebBarrageUtils::parseRestriction(*)(typeUrl, valueBytes);
- if (restriction) {
- restrictions.push(restriction);
- }
- } catch (e) {
- console.warn("parseProtoManually: Failed to parse restriction:", e);
- }
- }
- }
- columnInfo.restrictions = restrictions;
- });
- }
- }
-
- if (key !== null && value !== null) {
- if (!metadata.columnInfoMap) {
- metadata.columnInfoMap = {};
- }
- metadata.columnInfoMap[key] = value;
- console.log("parseProtoManually: Added column", key, "with", value.restrictions ? value.restrictions.length : 0, "restrictions");
- }
- });
- }
- }
- });
-
- result = inputTableMetadata.columnInfoMap || {};
- console.log("parseProtoManually: Parsed", Object.keys(result).length, "columns");
- }
- }
-
- return result;
-
+ return JSON.parse(jsonString);
} catch (e) {
- console.error("parseProtoManually: Failed to manually parse protobuf:", e);
- console.error("Stack:", e.stack);
- return null;
- }
- }-*/;
-
- private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8Array valueBytes) /*-{
- // Parse specific restriction types based on typeUrl
- // Types from inputtable.proto:
- // - IntegerRangeRestriction
- // - DoubleRangeRestriction
- // - NotNullRestriction
- // - NonEmptyRestriction
- // - StringListRestriction
-
- try {
- console.log("parseRestriction: Parsing restriction type:", typeUrl);
-
- var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
- var reader = new BinaryReader(valueBytes);
-
- // Extract the message type from the type URL
- // Format: "type.googleapis.com/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
- // or "docs.deephaven.io/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
- var typeName = typeUrl.substring(typeUrl.lastIndexOf('/') + 1);
- var shortName = typeName.substring(typeName.lastIndexOf('.') + 1);
- console.log("parseRestriction: Message type:", shortName);
-
- var restriction = {
- type: shortName,
- typeUrl: typeUrl
- };
-
- if (shortName === 'IntegerRangeRestriction') {
- // Field 1 = min_inclusive (int64)
- // Field 2 = max_inclusive (int64)
- while (reader.nextField()) {
- if (reader.isEndGroup()) break;
- var field = reader.getFieldNumber();
- if (field === 1) {
- restriction.minInclusive = reader.readInt64();
- console.log("parseRestriction: minInclusive =", restriction.minInclusive);
- } else if (field === 2) {
- restriction.maxInclusive = reader.readInt64();
- console.log("parseRestriction: maxInclusive =", restriction.maxInclusive);
- }
- }
- } else if (shortName === 'DoubleRangeRestriction') {
- // Field 1 = min_inclusive (double)
- // Field 2 = max_inclusive (double)
- while (reader.nextField()) {
- if (reader.isEndGroup()) break;
- var field = reader.getFieldNumber();
- if (field === 1) {
- restriction.minInclusive = reader.readDouble();
- console.log("parseRestriction: minInclusive =", restriction.minInclusive);
- } else if (field === 2) {
- restriction.maxInclusive = reader.readDouble();
- console.log("parseRestriction: maxInclusive =", restriction.maxInclusive);
- }
- }
- } else if (shortName === 'NotNullRestriction') {
- // No fields - just the type
- restriction.notNull = true;
- console.log("parseRestriction: NotNull restriction");
- } else if (shortName === 'NonEmptyRestriction') {
- // No fields - just the type
- restriction.nonEmpty = true;
- console.log("parseRestriction: NonEmpty restriction");
- } else if (shortName === 'StringListRestriction') {
- // Field 1 = allowed_values (repeated string)
- restriction.allowedValues = [];
- while (reader.nextField()) {
- if (reader.isEndGroup()) break;
- var field = reader.getFieldNumber();
- if (field === 1) {
- var value = reader.readString();
- restriction.allowedValues.push(value);
- console.log("parseRestriction: allowed value =", value);
- }
- }
- } else {
- console.warn("parseRestriction: Unknown restriction type:", shortName);
- restriction.raw = valueBytes;
- }
-
- return restriction;
-
- } catch (e) {
- console.error("parseRestriction: Failed to parse restriction:", e);
+ console.error("Failed to parse JSON:", e);
return null;
}
}-*/;
@@ -400,18 +185,6 @@ private static native jsinterop.base.Any getProperty(jsinterop.base.Any obj, Str
return obj[propertyName];
}-*/;
- private static native jsinterop.base.Any getMapEntry(jsinterop.base.Any map, String key) /*-{
- if (map == null) return null;
- if (typeof map.get === 'function') {
- return map.get(key);
- }
- if (typeof map.getMap === 'function') {
- var m = map.getMap();
- return m ? m[key] : null;
- }
- return map[key];
- }-*/;
-
private static native boolean isArray(jsinterop.base.Any obj) /*-{
return Array.isArray(obj);
}-*/;
@@ -424,16 +197,6 @@ private static native void consoleError(String message, Exception e) /*-{
console.error(message, e);
}-*/;
- private static native void logProtoObject(String label, jsinterop.base.Any obj) /*-{
- console.log(label + ":", obj);
- if (obj != null) {
- console.log(label + " keys:", Object.keys(obj));
- console.log(label + " methods:", Object.getOwnPropertyNames(Object.getPrototypeOf(obj)).filter(function(name) {
- return typeof obj[name] === 'function';
- }));
- }
- }-*/;
-
private static ColumnDefinition[] readColumnDefinitions(Schema schema) {
ColumnDefinition[] cols = new ColumnDefinition[(int) schema.fieldsLength()];
for (int i = 0; i < schema.fieldsLength(); i++) {
From 69391277aa7f4097a56ccefc96b6c0893212745e Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Tue, 7 Apr 2026 12:59:50 -0600
Subject: [PATCH 05/35] Revert "send restrictions as json"
This reverts commit 180bc15306c02c25ad9df09a09e5057bce46ee9b.
---
.../engine/util/input/InputTableUpdater.java | 13 +-
extensions/barrage/build.gradle | 1 -
.../extensions/barrage/util/BarrageUtil.java | 30 +-
.../client/api/barrage/WebBarrageUtils.java | 301 ++++++++++++++++--
4 files changed, 281 insertions(+), 64 deletions(-)
diff --git a/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java b/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
index e09f774997c..450229e3d78 100644
--- a/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
+++ b/engine/table/src/main/java/io/deephaven/engine/util/input/InputTableUpdater.java
@@ -57,12 +57,17 @@ default List getValueNames() {
}
/**
- * If there are client-side defined restrictions on this column; return them as a JSON string to be interpreted by
- * the client for properly displaying the edit field.
+ * If there are client-side defined restrictions on this column, return them as a list of protobuf Any messages.
+ * These restrictions are used by the client for properly displaying and validating the edit field.
+ *
+ *
+ * The restrictions are packed as {@code google.protobuf.Any} messages, which allows for different restriction
+ * types (e.g., {@code IntegerRangeRestriction}, {@code DoubleRangeRestriction}, {@code StringListRestriction},
+ * etc.) to be sent to the client. The client is responsible for unpacking and interpreting these restrictions.
*
* @param columnName the column name to query
- * @return a string representing the restrictions for this column, or null if no client-side restrictions are
- * supplied for this column
+ * @return a list of protobuf Any messages representing the restrictions for this column, or null if no
+ * client-side restrictions are supplied for this column
*/
@Nullable
default List getColumnRestrictions(final String columnName) {
diff --git a/extensions/barrage/build.gradle b/extensions/barrage/build.gradle
index 350e81803c4..dff44595c32 100644
--- a/extensions/barrage/build.gradle
+++ b/extensions/barrage/build.gradle
@@ -15,7 +15,6 @@ dependencies {
api project(':engine-table')
implementation project(':proto:proto-backplane-grpc-flight')
- implementation libs.protobuf.java.util
implementation project(':log-factory')
api libs.deephaven.barrage.format
implementation libs.hdrhistogram
diff --git a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java
index dcb37270134..da8c32a5df3 100755
--- a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java
+++ b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java
@@ -8,8 +8,6 @@
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.ByteStringAccess;
-import com.google.protobuf.util.JsonFormat;
-import com.google.protobuf.util.JsonFormat.TypeRegistry;
import com.google.rpc.Code;
import io.deephaven.UncheckedDeephavenException;
import io.deephaven.api.util.NameValidator;
@@ -55,14 +53,9 @@
import io.deephaven.internal.log.LoggerFactory;
import io.deephaven.io.logger.Logger;
import io.deephaven.proto.backplane.grpc.DeephavenTableMetadata;
-import io.deephaven.proto.backplane.grpc.DoubleRangeRestriction;
import io.deephaven.proto.backplane.grpc.ExportedTableCreationResponse;
import io.deephaven.proto.backplane.grpc.InputTableMetadata;
import io.deephaven.proto.backplane.grpc.InputTableColumnInfo;
-import io.deephaven.proto.backplane.grpc.IntegerRangeRestriction;
-import io.deephaven.proto.backplane.grpc.NonEmptyRestriction;
-import io.deephaven.proto.backplane.grpc.NotNullRestriction;
-import io.deephaven.proto.backplane.grpc.StringListRestriction;
import io.deephaven.proto.flight.util.MessageHelper;
import io.deephaven.proto.flight.util.SchemaHelper;
import io.deephaven.proto.util.Exceptions;
@@ -135,17 +128,6 @@ public class BarrageUtil {
private static final Logger log = LoggerFactory.getLogger(BarrageUtil.class);
- /**
- * TypeRegistry for JSON serialization of InputTableMetadata with restriction types.
- */
- private static final TypeRegistry INPUT_TABLE_TYPE_REGISTRY = TypeRegistry.newBuilder()
- .add(IntegerRangeRestriction.getDescriptor())
- .add(DoubleRangeRestriction.getDescriptor())
- .add(NotNullRestriction.getDescriptor())
- .add(NonEmptyRestriction.getDescriptor())
- .add(StringListRestriction.getDescriptor())
- .build();
-
public static final double TARGET_SNAPSHOT_PERCENTAGE =
Configuration.getInstance().getDoubleForClassWithDefault(BarrageUtil.class,
"targetSnapshotPercentage", 0.25);
@@ -573,15 +555,9 @@ private static void maybeAddInputTableMetadata(@NotNull TableDefinition tableDef
final DeephavenTableMetadata metadata =
DeephavenTableMetadata.newBuilder().setInputTableMetadata(builder).build();
- try {
- final String json = JsonFormat.printer()
- .usingTypeRegistry(INPUT_TABLE_TYPE_REGISTRY)
- .print(metadata);
- final String base64 = Base64.getEncoder().encodeToString(json.getBytes(java.nio.charset.StandardCharsets.UTF_8));
- putMetadata(schemaMetadata, ATTR_PROTO_METADATA_TAG, base64);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw new UncheckedDeephavenException("Failed to convert InputTableMetadata to JSON", e);
- }
+ final byte[] bytes = metadata.toByteArray();
+ final String base64 = Base64.getEncoder().encodeToString(bytes);
+ putMetadata(schemaMetadata, ATTR_PROTO_METADATA_TAG, base64);
}
@NotNull
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
index 67180c7c387..572d472a9fe 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
@@ -94,40 +94,27 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
InputTableMetadata metadata = new InputTableMetadata();
try {
- // Decode base64 to JSON string using atob
- consoleLog("parseInputTableMetadata: Decoding base64 to JSON...");
- String jsonString = DomGlobal.atob(tableMetadataBase64);
- consoleLog("parseInputTableMetadata: Decoded JSON string: " + jsonString);
-
- // Parse JSON string to JavaScript object
- consoleLog("parseInputTableMetadata: Parsing JSON...");
- jsinterop.base.Any jsonObject = parseJson(jsonString);
-
- if (jsonObject == null) {
- consoleLog("parseInputTableMetadata: JSON parsing returned null");
- return metadata;
- }
-
- consoleLog("parseInputTableMetadata: Successfully parsed JSON, extracting column info");
-
- // Extract inputTableMetadata from the parsed JSON
- jsinterop.base.Any inputTableMetadata = getProperty(jsonObject, "inputTableMetadata");
- if (inputTableMetadata == null) {
- consoleLog("parseInputTableMetadata: No inputTableMetadata found in JSON");
+ // Decode base64 to Uint8Array (like Java's Base64.getDecoder().decode())
+ consoleLog("parseInputTableMetadata: Decoding base64...");
+ Uint8Array bytes = decodeBase64(tableMetadataBase64);
+ consoleLog("parseInputTableMetadata: Decoded to " + bytes.length + " bytes");
+
+ // The issue: JavaScript protobuf deserialization fails on google.protobuf.Any types
+ // Solution: Parse the protobuf manually at the binary level to extract what we need
+ consoleLog("parseInputTableMetadata: Attempting manual protobuf parsing...");
+ jsinterop.base.Any result = parseProtoManually(bytes);
+
+ if (result == null) {
+ consoleLog("parseInputTableMetadata: Manual parsing returned null");
return metadata;
}
- // Extract columnInfo from inputTableMetadata
- jsinterop.base.Any columnInfoMap = getProperty(inputTableMetadata, "columnInfo");
- if (columnInfoMap == null) {
- consoleLog("parseInputTableMetadata: No columnInfo found in inputTableMetadata");
- return metadata;
- }
+ consoleLog("parseInputTableMetadata: Successfully parsed, extracting column info");
- // Extract column restrictions from the JSON data
+ // Extract column restrictions from the manually parsed data
for (ColumnDefinition col : cols) {
String columnName = col.getName();
- jsinterop.base.Any columnInfo = getPropertyFromMap(columnInfoMap, columnName);
+ jsinterop.base.Any columnInfo = getPropertyFromMap(result, columnName);
if (columnInfo != null) {
consoleLog("parseInputTableMetadata: Found info for column '" + columnName + "'");
@@ -161,12 +148,240 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
return metadata;
}
- // Parse JSON string to JavaScript object
- private static native jsinterop.base.Any parseJson(String jsonString) /*-{
+ // Decode base64 string to Uint8Array using elemental2
+ private static Uint8Array decodeBase64(String base64) {
+ // Use DomGlobal.atob() to decode base64 to binary string
+ String binaryString = DomGlobal.atob(base64);
+
+ // Convert binary string to Uint8Array
+ Uint8Array bytes = new Uint8Array(binaryString.length());
+ for (int i = 0; i < binaryString.length(); i++) {
+ bytes.setAt(i, (double) (binaryString.charAt(i) & 0xff));
+ }
+ return bytes;
+ }
+
+ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*-{
+ // Manual protobuf parsing to work around missing google.protobuf.Any
+ // Based on the pattern from AddToInputTable.java
+ // Use the BinaryReader from dhinternal.jspb which is the JsInterop wrapper
try {
- return JSON.parse(jsonString);
+ console.log("parseProtoManually: Starting manual protobuf parsing");
+ console.log("parseProtoManually: Bytes length =", bytes.length);
+
+ // Use the JsInterop BinaryReader class
+ var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
+ var reader = new BinaryReader(bytes);
+ var result = {};
+
+ // Parse the DeephavenTableMetadata message
+ // Field 1 is InputTableMetadata
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ console.log("parseProtoManually: Reading field", field);
+
+ if (field === 1) {
+ // This is the InputTableMetadata field
+ var inputTableMetadata = {};
+ reader.readMessage(inputTableMetadata, function(metadata, rdr) {
+ // Parse InputTableMetadata
+ // Field 1 is columnInfoMap (map)
+ while (rdr.nextField()) {
+ if (rdr.isEndGroup()) {
+ break;
+ }
+ var subfield = rdr.getFieldNumber();
+ console.log("parseProtoManually: InputTableMetadata field", subfield);
+
+ if (subfield === 1) {
+ // This is the columnInfoMap
+ var mapEntry = {};
+ rdr.readMessage(mapEntry, function(entry, mapReader) {
+ var key = null;
+ var value = null;
+
+ while (mapReader.nextField()) {
+ if (mapReader.isEndGroup()) {
+ break;
+ }
+ var mapField = mapReader.getFieldNumber();
+
+ if (mapField === 1) {
+ // Map key (column name)
+ key = mapReader.readString();
+ console.log("parseProtoManually: Column name =", key);
+ } else if (mapField === 2) {
+ // Map value (InputTableColumnInfo)
+ value = {};
+ var restrictions = [];
+
+ mapReader.readMessage(value, function(columnInfo, colReader) {
+ while (colReader.nextField()) {
+ if (colReader.isEndGroup()) {
+ break;
+ }
+ var colField = colReader.getFieldNumber();
+ console.log("parseProtoManually: InputTableColumnInfo field", colField);
+
+ if (colField === 1) {
+ // kind field
+ columnInfo.kind = colReader.readEnum();
+ } else if (colField === 2) {
+ // restrictions field (repeated google.protobuf.Any)
+ // Parse the Any message to extract type_url and value
+ try {
+ var anyBytes = colReader.readBytes();
+ console.log("parseProtoManually: Got Any bytes, length =", anyBytes.length);
+
+ // Parse the google.protobuf.Any message
+ // Field 1 = type_url (string)
+ // Field 2 = value (bytes)
+ var anyReader = new BinaryReader(anyBytes);
+ var typeUrl = null;
+ var valueBytes = null;
+
+ while (anyReader.nextField()) {
+ if (anyReader.isEndGroup()) {
+ break;
+ }
+ var anyField = anyReader.getFieldNumber();
+ if (anyField === 1) {
+ typeUrl = anyReader.readString();
+ console.log("parseProtoManually: Restriction type_url =", typeUrl);
+ } else if (anyField === 2) {
+ valueBytes = anyReader.readBytes();
+ console.log("parseProtoManually: Restriction value bytes length =", valueBytes.length);
+ }
+ }
+
+ // Now parse the actual restriction based on type_url
+ var restriction = @io.deephaven.web.client.api.barrage.WebBarrageUtils::parseRestriction(*)(typeUrl, valueBytes);
+ if (restriction) {
+ restrictions.push(restriction);
+ }
+ } catch (e) {
+ console.warn("parseProtoManually: Failed to parse restriction:", e);
+ }
+ }
+ }
+ columnInfo.restrictions = restrictions;
+ });
+ }
+ }
+
+ if (key !== null && value !== null) {
+ if (!metadata.columnInfoMap) {
+ metadata.columnInfoMap = {};
+ }
+ metadata.columnInfoMap[key] = value;
+ console.log("parseProtoManually: Added column", key, "with", value.restrictions ? value.restrictions.length : 0, "restrictions");
+ }
+ });
+ }
+ }
+ });
+
+ result = inputTableMetadata.columnInfoMap || {};
+ console.log("parseProtoManually: Parsed", Object.keys(result).length, "columns");
+ }
+ }
+
+ return result;
+
} catch (e) {
- console.error("Failed to parse JSON:", e);
+ console.error("parseProtoManually: Failed to manually parse protobuf:", e);
+ console.error("Stack:", e.stack);
+ return null;
+ }
+ }-*/;
+
+ private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8Array valueBytes) /*-{
+ // Parse specific restriction types based on typeUrl
+ // Types from inputtable.proto:
+ // - IntegerRangeRestriction
+ // - DoubleRangeRestriction
+ // - NotNullRestriction
+ // - NonEmptyRestriction
+ // - StringListRestriction
+
+ try {
+ console.log("parseRestriction: Parsing restriction type:", typeUrl);
+
+ var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
+ var reader = new BinaryReader(valueBytes);
+
+ // Extract the message type from the type URL
+ // Format: "type.googleapis.com/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
+ // or "docs.deephaven.io/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
+ var typeName = typeUrl.substring(typeUrl.lastIndexOf('/') + 1);
+ var shortName = typeName.substring(typeName.lastIndexOf('.') + 1);
+ console.log("parseRestriction: Message type:", shortName);
+
+ var restriction = {
+ type: shortName,
+ typeUrl: typeUrl
+ };
+
+ if (shortName === 'IntegerRangeRestriction') {
+ // Field 1 = min_inclusive (int64)
+ // Field 2 = max_inclusive (int64)
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ restriction.minInclusive = reader.readInt64();
+ console.log("parseRestriction: minInclusive =", restriction.minInclusive);
+ } else if (field === 2) {
+ restriction.maxInclusive = reader.readInt64();
+ console.log("parseRestriction: maxInclusive =", restriction.maxInclusive);
+ }
+ }
+ } else if (shortName === 'DoubleRangeRestriction') {
+ // Field 1 = min_inclusive (double)
+ // Field 2 = max_inclusive (double)
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ restriction.minInclusive = reader.readDouble();
+ console.log("parseRestriction: minInclusive =", restriction.minInclusive);
+ } else if (field === 2) {
+ restriction.maxInclusive = reader.readDouble();
+ console.log("parseRestriction: maxInclusive =", restriction.maxInclusive);
+ }
+ }
+ } else if (shortName === 'NotNullRestriction') {
+ // No fields - just the type
+ restriction.notNull = true;
+ console.log("parseRestriction: NotNull restriction");
+ } else if (shortName === 'NonEmptyRestriction') {
+ // No fields - just the type
+ restriction.nonEmpty = true;
+ console.log("parseRestriction: NonEmpty restriction");
+ } else if (shortName === 'StringListRestriction') {
+ // Field 1 = allowed_values (repeated string)
+ restriction.allowedValues = [];
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ var value = reader.readString();
+ restriction.allowedValues.push(value);
+ console.log("parseRestriction: allowed value =", value);
+ }
+ }
+ } else {
+ console.warn("parseRestriction: Unknown restriction type:", shortName);
+ restriction.raw = valueBytes;
+ }
+
+ return restriction;
+
+ } catch (e) {
+ console.error("parseRestriction: Failed to parse restriction:", e);
return null;
}
}-*/;
@@ -185,6 +400,18 @@ private static native jsinterop.base.Any getProperty(jsinterop.base.Any obj, Str
return obj[propertyName];
}-*/;
+ private static native jsinterop.base.Any getMapEntry(jsinterop.base.Any map, String key) /*-{
+ if (map == null) return null;
+ if (typeof map.get === 'function') {
+ return map.get(key);
+ }
+ if (typeof map.getMap === 'function') {
+ var m = map.getMap();
+ return m ? m[key] : null;
+ }
+ return map[key];
+ }-*/;
+
private static native boolean isArray(jsinterop.base.Any obj) /*-{
return Array.isArray(obj);
}-*/;
@@ -197,6 +424,16 @@ private static native void consoleError(String message, Exception e) /*-{
console.error(message, e);
}-*/;
+ private static native void logProtoObject(String label, jsinterop.base.Any obj) /*-{
+ console.log(label + ":", obj);
+ if (obj != null) {
+ console.log(label + " keys:", Object.keys(obj));
+ console.log(label + " methods:", Object.getOwnPropertyNames(Object.getPrototypeOf(obj)).filter(function(name) {
+ return typeof obj[name] === 'function';
+ }));
+ }
+ }-*/;
+
private static ColumnDefinition[] readColumnDefinitions(Schema schema) {
ColumnDefinition[] cols = new ColumnDefinition[(int) schema.fieldsLength()];
for (int i = 0; i < schema.fieldsLength(); i++) {
From 75bf43ea2d547c73694a85cb981caaa47fe9d553 Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 8 Apr 2026 07:28:20 -0600
Subject: [PATCH 06/35] abstract base class for validating input tables
---
.../AbstractBaseValidatingInputTable.java | 100 ++++++++++++++++++
.../RangeValidatingInputTable.java | 57 +---------
2 files changed, 102 insertions(+), 55 deletions(-)
create mode 100644 server/src/main/java/io/deephaven/server/table/inputtables/AbstractBaseValidatingInputTable.java
diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/AbstractBaseValidatingInputTable.java b/server/src/main/java/io/deephaven/server/table/inputtables/AbstractBaseValidatingInputTable.java
new file mode 100644
index 00000000000..c7635e23a05
--- /dev/null
+++ b/server/src/main/java/io/deephaven/server/table/inputtables/AbstractBaseValidatingInputTable.java
@@ -0,0 +1,100 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.server.table.inputtables;
+
+import com.google.protobuf.Any;
+import io.deephaven.engine.table.Table;
+import io.deephaven.engine.table.TableDefinition;
+import io.deephaven.engine.util.input.InputTableStatusListener;
+import io.deephaven.engine.util.input.InputTableUpdater;
+import io.deephaven.util.annotations.TestUseOnly;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * An abstract base class for {@link InputTableUpdater} implementations that wrap an existing input table.
+ *
+ *
+ * This class provides a default implementation for most methods by delegating to the wrapped input table.
+ * Subclasses should override {@link #getColumnRestrictions(String)} and {@link #validateAddOrModify(Table)}
+ * to provide custom validation logic.
+ *
+ *
+ *
+ * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
+ * be changed or removed at any time.
+ *
*/
@TestUseOnly
-public class RangeValidatingInputTable implements InputTableUpdater {
- private final InputTableUpdater wrapped;
+public class RangeValidatingInputTable extends AbstractBaseValidatingInputTable {
private final String column;
private final int min;
private final int max;
@@ -66,7 +61,7 @@ private RangeValidatingInputTable(InputTableUpdater wrapped,
final String column,
final int min,
final int max) {
- this.wrapped = wrapped;
+ super(wrapped);
this.column = column;
final Class> dataType = getTableDefinition().getColumn(column).getDataType();
if (dataType != int.class) {
@@ -76,15 +71,6 @@ private RangeValidatingInputTable(InputTableUpdater wrapped,
this.max = max;
}
- @Override
- public List getKeyNames() {
- return wrapped.getKeyNames();
- }
-
- @Override
- public List getValueNames() {
- return wrapped.getValueNames();
- }
@Override
public @Nullable List getColumnRestrictions(String columnName) {
@@ -103,10 +89,6 @@ public List getValueNames() {
return result;
}
- @Override
- public TableDefinition getTableDefinition() {
- return wrapped.getTableDefinition();
- }
@Override
public void validateAddOrModify(Table tableToApply) {
@@ -128,39 +110,4 @@ public void validateAddOrModify(Table tableToApply) {
wrapped.validateAddOrModify(tableToApply);
}
-
- @Override
- public void validateDelete(Table tableToDelete) {
- wrapped.validateDelete(tableToDelete);
- }
-
- @Override
- public void add(Table newData) throws IOException {
- wrapped.add(newData);
- }
-
- @Override
- public void addAsync(Table newData, InputTableStatusListener listener) {
- wrapped.addAsync(newData, listener);
- }
-
- @Override
- public void delete(Table table) throws IOException {
- wrapped.delete(table);
- }
-
- @Override
- public void deleteAsync(Table table, InputTableStatusListener listener) {
- wrapped.deleteAsync(table, listener);
- }
-
- @Override
- public boolean isKey(String columnName) {
- return wrapped.isKey(columnName);
- }
-
- @Override
- public boolean hasColumn(String columnName) {
- return wrapped.hasColumn(columnName);
- }
}
From 89ac34ca79e807e3c216d2392f0af0be36937f86 Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 8 Apr 2026 08:28:48 -0600
Subject: [PATCH 07/35] additional validators
---
.../DoubleRangeValidatingInputTable.java | 114 ++++++++++++++++++
.../NonEmptyValidatingInputTable.java | 106 ++++++++++++++++
.../NotNullValidatingInputTable.java | 102 ++++++++++++++++
.../StringListValidatingInputTable.java | 114 ++++++++++++++++++
4 files changed, 436 insertions(+)
create mode 100644 server/src/main/java/io/deephaven/server/table/inputtables/DoubleRangeValidatingInputTable.java
create mode 100644 server/src/main/java/io/deephaven/server/table/inputtables/NonEmptyValidatingInputTable.java
create mode 100644 server/src/main/java/io/deephaven/server/table/inputtables/NotNullValidatingInputTable.java
create mode 100644 server/src/main/java/io/deephaven/server/table/inputtables/StringListValidatingInputTable.java
diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/DoubleRangeValidatingInputTable.java b/server/src/main/java/io/deephaven/server/table/inputtables/DoubleRangeValidatingInputTable.java
new file mode 100644
index 00000000000..6d4160b9bc6
--- /dev/null
+++ b/server/src/main/java/io/deephaven/server/table/inputtables/DoubleRangeValidatingInputTable.java
@@ -0,0 +1,114 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.server.table.inputtables;
+
+import com.google.protobuf.Any;
+import io.deephaven.engine.primitive.iterator.CloseablePrimitiveIteratorOfDouble;
+import io.deephaven.engine.table.Table;
+import io.deephaven.engine.util.input.InputTableUpdater;
+import io.deephaven.engine.util.input.InputTableValidationException;
+import io.deephaven.engine.util.input.StructuredErrorImpl;
+import io.deephaven.proto.backplane.grpc.DoubleRangeRestriction;
+import io.deephaven.util.annotations.TestUseOnly;
+import io.deephaven.util.mutable.MutableInt;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is an example of an {@link InputTableUpdater} that validates that the values in a Double column are within a
+ * given range.
+ *
+ *
+ * This class wraps an existing input table, and before performing the underlying validation performs its own validation
+ * on the range of the column.
+ *
+ *
+ *
+ * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
+ * be changed or removed at any time.
+ *
+ */
+@TestUseOnly
+public class DoubleRangeValidatingInputTable extends AbstractBaseValidatingInputTable {
+ private final String column;
+ private final double min;
+ private final double max;
+
+ /**
+ * Wrap {@code input}, which must be an input table into a new input table that validates that the values in
+ * {@code column} are within the range {@code ([min, max]}.
+ *
+ * @param input the table to wrap
+ * @param column the column to validate, must be a double type
+ * @param min the minimum value allowed, inclusive
+ * @param max the maximum value allowed, inclusive
+ * @return a new input table that validates the range of {@code column}
+ */
+ public static Table make(Table input, final String column,
+ final double min,
+ final double max) {
+ final InputTableUpdater updater = (InputTableUpdater) input.getAttribute(Table.INPUT_TABLE_ATTRIBUTE);
+ final DoubleRangeValidatingInputTable validatedUpdater = new DoubleRangeValidatingInputTable(updater, column, min, max);
+ return input.withAttributes(Map.of(Table.INPUT_TABLE_ATTRIBUTE, validatedUpdater));
+ }
+
+
+ private DoubleRangeValidatingInputTable(InputTableUpdater wrapped,
+ final String column,
+ final double min,
+ final double max) {
+ super(wrapped);
+ this.column = column;
+ final Class> dataType = getTableDefinition().getColumn(column).getDataType();
+ if (dataType != double.class) {
+ throw new IllegalArgumentException("Range column must be a double, but " + column + " is " + dataType);
+ }
+ this.min = min;
+ this.max = max;
+ }
+
+
+ @Override
+ public @Nullable List getColumnRestrictions(String columnName) {
+ final List columnRestrictions = wrapped.getColumnRestrictions(columnName);
+ if (!columnName.equals(column)) {
+ return columnRestrictions;
+ }
+
+ final List result = new ArrayList<>();
+ if (columnRestrictions != null) {
+ result.addAll(columnRestrictions);
+ }
+ final DoubleRangeRestriction rangeRestriction =
+ DoubleRangeRestriction.newBuilder().setMinInclusive(min).setMaxInclusive(max).build();
+ result.add(Any.pack(rangeRestriction, "docs.deephaven.io"));
+ return result;
+ }
+
+
+ @Override
+ public void validateAddOrModify(Table tableToApply) {
+ final List errors = new ArrayList<>();
+ final MutableInt position = new MutableInt(0);
+ try (final CloseablePrimitiveIteratorOfDouble vals = tableToApply.doubleColumnIterator(column)) {
+ vals.forEachRemaining((double val) -> {
+ if (val < min || val > max) {
+ errors.add(new StructuredErrorImpl(
+ "Value out of range: " + val + " must be between " + min + " and " + max + " inclusive",
+ column, position.get()));
+ }
+ position.increment();
+ });
+ }
+ if (!errors.isEmpty()) {
+ throw new InputTableValidationException(errors);
+ }
+
+ wrapped.validateAddOrModify(tableToApply);
+ }
+}
+
diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/NonEmptyValidatingInputTable.java b/server/src/main/java/io/deephaven/server/table/inputtables/NonEmptyValidatingInputTable.java
new file mode 100644
index 00000000000..8052401ac52
--- /dev/null
+++ b/server/src/main/java/io/deephaven/server/table/inputtables/NonEmptyValidatingInputTable.java
@@ -0,0 +1,106 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.server.table.inputtables;
+
+import com.google.protobuf.Any;
+import io.deephaven.engine.rowset.RowSequence;
+import io.deephaven.engine.table.ColumnSource;
+import io.deephaven.engine.table.Table;
+import io.deephaven.engine.util.input.InputTableUpdater;
+import io.deephaven.engine.util.input.InputTableValidationException;
+import io.deephaven.engine.util.input.StructuredErrorImpl;
+import io.deephaven.proto.backplane.grpc.NonEmptyRestriction;
+import io.deephaven.util.annotations.TestUseOnly;
+import io.deephaven.util.mutable.MutableInt;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is an example of an {@link InputTableUpdater} that validates that the values in a String column are not empty.
+ *
+ *
+ * This class wraps an existing input table, and before performing the underlying validation performs its own validation
+ * that the column does not contain empty strings.
+ *
+ *
+ *
+ * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
+ * be changed or removed at any time.
+ *
+ */
+@TestUseOnly
+public class NonEmptyValidatingInputTable extends AbstractBaseValidatingInputTable {
+ private final String column;
+
+ /**
+ * Wrap {@code input}, which must be an input table into a new input table that validates that the values in
+ * {@code column} are not empty.
+ *
+ * @param input the table to wrap
+ * @param column the column to validate, must be a String type
+ * @return a new input table that validates {@code column} is not empty
+ */
+ public static Table make(Table input, final String column) {
+ final InputTableUpdater updater = (InputTableUpdater) input.getAttribute(Table.INPUT_TABLE_ATTRIBUTE);
+ final NonEmptyValidatingInputTable validatedUpdater = new NonEmptyValidatingInputTable(updater, column);
+ return input.withAttributes(Map.of(Table.INPUT_TABLE_ATTRIBUTE, validatedUpdater));
+ }
+
+
+ private NonEmptyValidatingInputTable(InputTableUpdater wrapped, final String column) {
+ super(wrapped);
+ this.column = column;
+ final Class> dataType = getTableDefinition().getColumn(column).getDataType();
+ if (dataType != String.class) {
+ throw new IllegalArgumentException("Non-empty validation only applies to String columns, but " + column + " is " + dataType);
+ }
+ }
+
+
+ @Override
+ public @Nullable List getColumnRestrictions(String columnName) {
+ final List columnRestrictions = wrapped.getColumnRestrictions(columnName);
+ if (!columnName.equals(column)) {
+ return columnRestrictions;
+ }
+
+ final List result = new ArrayList<>();
+ if (columnRestrictions != null) {
+ result.addAll(columnRestrictions);
+ }
+ final NonEmptyRestriction nonEmptyRestriction = NonEmptyRestriction.newBuilder().build();
+ result.add(Any.pack(nonEmptyRestriction, "docs.deephaven.io"));
+ return result;
+ }
+
+
+ @Override
+ public void validateAddOrModify(Table tableToApply) {
+ final List errors = new ArrayList<>();
+ final MutableInt position = new MutableInt(0);
+ final ColumnSource columnSource = tableToApply.getColumnSource(column, String.class);
+
+ try (final RowSequence rowSequence = tableToApply.getRowSet().getRowSequenceByPosition(0, tableToApply.size())) {
+ rowSequence.forAllRowKeys(rowKey -> {
+ final String value = columnSource.get(rowKey);
+ if (value != null && value.isEmpty()) {
+ errors.add(new StructuredErrorImpl(
+ "Value must not be empty",
+ column, position.get()));
+ }
+ position.increment();
+ });
+ }
+
+ if (!errors.isEmpty()) {
+ throw new InputTableValidationException(errors);
+ }
+
+ wrapped.validateAddOrModify(tableToApply);
+ }
+}
+
diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/NotNullValidatingInputTable.java b/server/src/main/java/io/deephaven/server/table/inputtables/NotNullValidatingInputTable.java
new file mode 100644
index 00000000000..f277c0b8469
--- /dev/null
+++ b/server/src/main/java/io/deephaven/server/table/inputtables/NotNullValidatingInputTable.java
@@ -0,0 +1,102 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.server.table.inputtables;
+
+import com.google.protobuf.Any;
+import io.deephaven.engine.rowset.RowSequence;
+import io.deephaven.engine.table.ColumnSource;
+import io.deephaven.engine.table.Table;
+import io.deephaven.engine.util.input.InputTableUpdater;
+import io.deephaven.engine.util.input.InputTableValidationException;
+import io.deephaven.engine.util.input.StructuredErrorImpl;
+import io.deephaven.proto.backplane.grpc.NotNullRestriction;
+import io.deephaven.util.annotations.TestUseOnly;
+import io.deephaven.util.mutable.MutableInt;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is an example of an {@link InputTableUpdater} that validates that the values in a column are not null.
+ *
+ *
+ * This class wraps an existing input table, and before performing the underlying validation performs its own validation
+ * that the column does not contain null values.
+ *
+ *
+ *
+ * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
+ * be changed or removed at any time.
+ *
+ */
+@TestUseOnly
+public class NotNullValidatingInputTable extends AbstractBaseValidatingInputTable {
+ private final String column;
+
+ /**
+ * Wrap {@code input}, which must be an input table into a new input table that validates that the values in
+ * {@code column} are not null.
+ *
+ * @param input the table to wrap
+ * @param column the column to validate
+ * @return a new input table that validates {@code column} is not null
+ */
+ public static Table make(Table input, final String column) {
+ final InputTableUpdater updater = (InputTableUpdater) input.getAttribute(Table.INPUT_TABLE_ATTRIBUTE);
+ final NotNullValidatingInputTable validatedUpdater = new NotNullValidatingInputTable(updater, column);
+ return input.withAttributes(Map.of(Table.INPUT_TABLE_ATTRIBUTE, validatedUpdater));
+ }
+
+
+ private NotNullValidatingInputTable(InputTableUpdater wrapped, final String column) {
+ super(wrapped);
+ this.column = column;
+ }
+
+
+ @Override
+ public @Nullable List getColumnRestrictions(String columnName) {
+ final List columnRestrictions = wrapped.getColumnRestrictions(columnName);
+ if (!columnName.equals(column)) {
+ return columnRestrictions;
+ }
+
+ final List result = new ArrayList<>();
+ if (columnRestrictions != null) {
+ result.addAll(columnRestrictions);
+ }
+ final NotNullRestriction notNullRestriction = NotNullRestriction.newBuilder().build();
+ result.add(Any.pack(notNullRestriction, "docs.deephaven.io"));
+ return result;
+ }
+
+
+ @Override
+ public void validateAddOrModify(Table tableToApply) {
+ final List errors = new ArrayList<>();
+ final MutableInt position = new MutableInt(0);
+ final ColumnSource> columnSource = tableToApply.getColumnSource(column);
+
+ try (final RowSequence rowSequence = tableToApply.getRowSet().getRowSequenceByPosition(0, tableToApply.size())) {
+ rowSequence.forAllRowKeys(rowKey -> {
+ final Object value = columnSource.get(rowKey);
+ if (value == null) {
+ errors.add(new StructuredErrorImpl(
+ "Value must not be null",
+ column, position.get()));
+ }
+ position.increment();
+ });
+ }
+
+ if (!errors.isEmpty()) {
+ throw new InputTableValidationException(errors);
+ }
+
+ wrapped.validateAddOrModify(tableToApply);
+ }
+}
+
diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/StringListValidatingInputTable.java b/server/src/main/java/io/deephaven/server/table/inputtables/StringListValidatingInputTable.java
new file mode 100644
index 00000000000..2a6e0d6ec44
--- /dev/null
+++ b/server/src/main/java/io/deephaven/server/table/inputtables/StringListValidatingInputTable.java
@@ -0,0 +1,114 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.server.table.inputtables;
+
+import com.google.protobuf.Any;
+import io.deephaven.engine.rowset.RowSequence;
+import io.deephaven.engine.table.ColumnSource;
+import io.deephaven.engine.table.Table;
+import io.deephaven.engine.util.input.InputTableUpdater;
+import io.deephaven.engine.util.input.InputTableValidationException;
+import io.deephaven.engine.util.input.StructuredErrorImpl;
+import io.deephaven.proto.backplane.grpc.StringListRestriction;
+import io.deephaven.util.annotations.TestUseOnly;
+import io.deephaven.util.mutable.MutableInt;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is an example of an {@link InputTableUpdater} that validates that the values in a String column belong to a
+ * given set of allowed values.
+ *
+ *
+ * This class wraps an existing input table, and before performing the underlying validation performs its own validation
+ * that the column values are in the allowed set (or null).
+ *
+ *
+ *
+ * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
+ * be changed or removed at any time.
+ *
+ */
+@TestUseOnly
+public class StringListValidatingInputTable extends AbstractBaseValidatingInputTable {
+ private final String column;
+ private final Set allowedValues;
+ private final List allowedValuesList;
+
+ /**
+ * Wrap {@code input}, which must be an input table into a new input table that validates that the values in
+ * {@code column} belong to the given set of allowed values.
+ *
+ * @param input the table to wrap
+ * @param column the column to validate, must be a String type
+ * @param allowedValues the array of allowed values
+ * @return a new input table that validates {@code column} values are in the allowed set
+ */
+ public static Table make(Table input, final String column, final String... allowedValues) {
+ final InputTableUpdater updater = (InputTableUpdater) input.getAttribute(Table.INPUT_TABLE_ATTRIBUTE);
+ final StringListValidatingInputTable validatedUpdater = new StringListValidatingInputTable(updater, column, allowedValues);
+ return input.withAttributes(Map.of(Table.INPUT_TABLE_ATTRIBUTE, validatedUpdater));
+ }
+
+
+ private StringListValidatingInputTable(InputTableUpdater wrapped, final String column, final String... allowedValues) {
+ super(wrapped);
+ this.column = column;
+ this.allowedValuesList = List.of(allowedValues);
+ this.allowedValues = Set.of(allowedValues);
+ final Class> dataType = getTableDefinition().getColumn(column).getDataType();
+ if (dataType != String.class) {
+ throw new IllegalArgumentException("String list validation only applies to String columns, but " + column + " is " + dataType);
+ }
+ }
+
+
+ @Override
+ public @Nullable List getColumnRestrictions(String columnName) {
+ final List columnRestrictions = wrapped.getColumnRestrictions(columnName);
+ if (!columnName.equals(column)) {
+ return columnRestrictions;
+ }
+
+ final List result = new ArrayList<>();
+ if (columnRestrictions != null) {
+ result.addAll(columnRestrictions);
+ }
+ final StringListRestriction stringListRestriction =
+ StringListRestriction.newBuilder().addAllAllowedValues(allowedValuesList).build();
+ result.add(Any.pack(stringListRestriction, "docs.deephaven.io"));
+ return result;
+ }
+
+
+ @Override
+ public void validateAddOrModify(Table tableToApply) {
+ final List errors = new ArrayList<>();
+ final MutableInt position = new MutableInt(0);
+ final ColumnSource columnSource = tableToApply.getColumnSource(column, String.class);
+
+ try (final RowSequence rowSequence = tableToApply.getRowSet().getRowSequenceByPosition(0, tableToApply.size())) {
+ rowSequence.forAllRowKeys(rowKey -> {
+ final String value = columnSource.get(rowKey);
+ if (!allowedValues.contains(value)) {
+ errors.add(new StructuredErrorImpl(
+ "Value '" + value + "' is not in the allowed list: " + allowedValuesList,
+ column, position.get()));
+ }
+ position.increment();
+ });
+ }
+
+ if (!errors.isEmpty()) {
+ throw new InputTableValidationException(errors);
+ }
+
+ wrapped.validateAddOrModify(tableToApply);
+ }
+}
+
From b7cb56fb090eb9e6f5f685b2e95ceac6cc3cd6f4 Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 8 Apr 2026 09:51:55 -0600
Subject: [PATCH 08/35] Add ColumnRestriction class
---
.../io/deephaven/web/client/api/Column.java | 6 +-
.../web/client/api/ColumnRestriction.java | 128 ++++++++++++++++++
.../api/barrage/def/ColumnDefinition.java | 10 +-
.../api/barrage/def/InputTableMetadata.java | 40 +++++-
4 files changed, 172 insertions(+), 12 deletions(-)
create mode 100644 web/client-api/src/main/java/io/deephaven/web/client/api/ColumnRestriction.java
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/Column.java b/web/client-api/src/main/java/io/deephaven/web/client/api/Column.java
index 2353272e9db..3958c857e09 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/Column.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/Column.java
@@ -49,7 +49,7 @@ public class Column {
private String description;
private final boolean isInputTableKeyColumn;
private final boolean isInputTableValueColumn;
- private final JsArray columnRestrictions;
+ private final JsArray columnRestrictions;
/**
* Format entire rows colors using the expression specified. Returns a {@code CustomColumn} object to apply to a
@@ -84,7 +84,7 @@ public static CustomColumn createCustomColumn(
public Column(int jsIndex, int index, Integer formatColumnIndex, Integer styleColumnIndex, String type, String name,
boolean isPartitionColumn, Integer formatStringColumnIndex, String description,
boolean inputTableKeyColumn, boolean inputTableValueColumn, boolean isSortable,
- JsArray columnRestrictions) {
+ JsArray columnRestrictions) {
this.jsIndex = jsIndex;
this.index = index;
assert Objects.equals(formatColumnIndex, styleColumnIndex);
@@ -246,7 +246,7 @@ public boolean getIsSortable() {
*/
@JsProperty
@JsNullable
- public JsArray getColumnRestrictions() {
+ public JsArray getColumnRestrictions() {
return columnRestrictions;
}
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/ColumnRestriction.java b/web/client-api/src/main/java/io/deephaven/web/client/api/ColumnRestriction.java
new file mode 100644
index 00000000000..83bb2840ddd
--- /dev/null
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/ColumnRestriction.java
@@ -0,0 +1,128 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.web.client.api;
+
+import com.vertispan.tsdefs.annotations.TsName;
+import elemental2.core.JsArray;
+import jsinterop.annotations.JsNullable;
+import jsinterop.annotations.JsProperty;
+import jsinterop.base.Any;
+
+/**
+ * Represents a restriction on an input table column. Restrictions define constraints that the server enforces on
+ * column values. There are several types of restrictions:
+ *
+ *
IntegerRangeRestriction - validates integer values are within a range
+ *
DoubleRangeRestriction - validates double values are within a range
+ *
NotNullRestriction - validates values are not null
+ *
NonEmptyRestriction - validates string values are not empty
+ *
StringListRestriction - validates string values belong to a set of allowed values
+ *
+ */
+@TsName(namespace = "dh")
+public class ColumnRestriction {
+ private final String type;
+ private final double minValue;
+ private final double maxValue;
+ private final JsArray allowedValues;
+
+ /**
+ * Creates a range restriction (IntegerRangeRestriction or DoubleRangeRestriction).
+ *
+ * @param type The type of restriction (e.g., "IntegerRangeRestriction" or "DoubleRangeRestriction")
+ * @param minValue The minimum value (inclusive), or NaN if no minimum
+ * @param maxValue The maximum value (inclusive), or NaN if no maximum
+ */
+ public ColumnRestriction(String type, double minValue, double maxValue) {
+ this.type = type;
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.allowedValues = null;
+ }
+
+ /**
+ * Creates a string list restriction (StringListRestriction).
+ *
+ * @param type The type of restriction (should be "StringListRestriction")
+ * @param allowedValues The array of allowed values
+ */
+ public ColumnRestriction(String type, JsArray allowedValues) {
+ this.type = type;
+ this.minValue = Double.NaN;
+ this.maxValue = Double.NaN;
+ this.allowedValues = allowedValues;
+ }
+
+ /**
+ * Creates a simple restriction with no parameters (NotNullRestriction or NonEmptyRestriction).
+ *
+ * @param type The type of restriction (e.g., "NotNullRestriction" or "NonEmptyRestriction")
+ */
+ public ColumnRestriction(String type) {
+ this.type = type;
+ this.minValue = Double.NaN;
+ this.maxValue = Double.NaN;
+ this.allowedValues = null;
+ }
+
+ /**
+ * The type of restriction. Possible values:
+ *
+ *
"IntegerRangeRestriction" - integer values must be within a range
+ *
"DoubleRangeRestriction" - double values must be within a range
+ *
"NotNullRestriction" - values must not be null
+ *
"NonEmptyRestriction" - string values must not be empty
+ *
"StringListRestriction" - string values must be in the allowed list
+ *
+ *
+ * @return The restriction type
+ */
+ @JsProperty
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * For range restrictions (IntegerRangeRestriction or DoubleRangeRestriction), returns an array with two elements:
+ * [minValue, maxValue]. Either value may be NaN if not specified. Returns null for non-range restrictions.
+ *
+ * @return Array of [min, max] values for range restrictions, or null for other restriction types
+ */
+ @JsProperty
+ @JsNullable
+ public JsArray getRange() {
+ if (Double.isNaN(minValue) && Double.isNaN(maxValue)) {
+ return null;
+ }
+ JsArray range = new JsArray<>();
+ range.push(minValue);
+ range.push(maxValue);
+ return range;
+ }
+
+ /**
+ * For StringListRestriction, returns the array of allowed values. Returns null for other restriction types.
+ *
+ * @return Array of allowed values for StringListRestriction, or null for other restriction types
+ */
+ @JsProperty
+ @JsNullable
+ public JsArray getValues() {
+ return allowedValues;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("ColumnRestriction{type='" + type + "'");
+ if (!Double.isNaN(minValue) || !Double.isNaN(maxValue)) {
+ sb.append(", range=[").append(minValue).append(", ").append(maxValue).append("]");
+ }
+ if (allowedValues != null) {
+ sb.append(", values=").append(allowedValues);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+}
+
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/ColumnDefinition.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/ColumnDefinition.java
index c1d08e3dd62..aa297c73068 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/ColumnDefinition.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/ColumnDefinition.java
@@ -5,7 +5,7 @@
import elemental2.core.JsArray;
import io.deephaven.web.client.api.Column;
-import jsinterop.base.Any;
+import io.deephaven.web.client.api.ColumnRestriction;
import org.apache.arrow.flatbuf.Field;
import java.util.Map;
@@ -38,7 +38,7 @@ public class ColumnDefinition {
private final boolean isInputTableKeyColumn;
private final boolean isInputTableValueColumn;
private final String description;
- private JsArray columnRestrictions;
+ private JsArray columnRestrictions;
public ColumnDefinition(int index, Field field) {
Map fieldMetadata =
@@ -133,11 +133,11 @@ public String getDescription() {
return description;
}
- public JsArray getColumnRestrictions() {
+ public JsArray getColumnRestrictions() {
return columnRestrictions;
}
- public void setColumnRestrictions(JsArray columnRestrictions) {
+ public void setColumnRestrictions(JsArray columnRestrictions) {
this.columnRestrictions = columnRestrictions;
}
@@ -163,7 +163,7 @@ public Column makeJsColumn(int index, Map
private static Column makeColumn(int jsIndex, ColumnDefinition definition, Integer numberFormatIndex,
Integer styleIndex, boolean isPartitionColumn, Integer formatStringIndex, String description,
- boolean inputTableKeyColumn, boolean inputTableValueColumn, JsArray columnRestrictions) {
+ boolean inputTableKeyColumn, boolean inputTableValueColumn, JsArray columnRestrictions) {
return new Column(jsIndex, definition.getColumnIndex(), numberFormatIndex, styleIndex, definition.getType(),
definition.getName(), isPartitionColumn, formatStringIndex, description, inputTableKeyColumn,
inputTableValueColumn,
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
index ab375473050..110772f8a77 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
@@ -4,6 +4,7 @@
package io.deephaven.web.client.api.barrage.def;
import elemental2.core.JsArray;
+import io.deephaven.web.client.api.ColumnRestriction;
import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsNullable;
import jsinterop.base.Any;
@@ -37,7 +38,7 @@ public ColumnRestrictions getColumnRestrictions(String columnName) {
* Represents restrictions on a column's values.
*/
public static class ColumnRestrictions {
- private final JsArray restrictions;
+ private final JsArray restrictions;
@JsIgnore
public ColumnRestrictions() {
@@ -45,14 +46,45 @@ public ColumnRestrictions() {
}
@JsIgnore
- public void addRestriction(Any restriction) {
- restrictions.push(restriction);
+ public void addRestriction(Any restrictionData) {
+ // Convert the parsed restriction data into a ColumnRestriction object
+ ColumnRestriction restriction = convertRestriction(restrictionData);
+ if (restriction != null) {
+ restrictions.push(restriction);
+ }
}
@JsIgnore
- public JsArray getRestrictions() {
+ public JsArray getRestrictions() {
return restrictions;
}
+
+ private static native ColumnRestriction convertRestriction(Any restrictionData) /*-{
+ if (!restrictionData) return null;
+
+ var type = restrictionData.type || "Unknown";
+ console.log("convertRestriction: Converting restriction of type:", type);
+
+ // Create the appropriate ColumnRestriction based on type
+ if (type === 'IntegerRangeRestriction' || type === 'DoubleRangeRestriction') {
+ var minValue = restrictionData.minInclusive !== undefined ? restrictionData.minInclusive : NaN;
+ var maxValue = restrictionData.maxInclusive !== undefined ? restrictionData.maxInclusive : NaN;
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;DD)(
+ type, minValue, maxValue
+ );
+ } else if (type === 'StringListRestriction') {
+ var allowedValues = restrictionData.allowedValues || [];
+ // Use the JS array directly - it will be cast to JsArray automatically
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;Lelemental2/core/JsArray;)(
+ type, allowedValues
+ );
+ } else if (type === 'NotNullRestriction' || type === 'NonEmptyRestriction') {
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;)(type);
+ } else {
+ console.warn("convertRestriction: Unknown restriction type:", type);
+ return null;
+ }
+ }-*/;
}
}
From 962de5e7d6ec5df0d079f8a2c63b7d88fd3d0e0d Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 8 Apr 2026 10:25:17 -0600
Subject: [PATCH 09/35] clean up logging
---
.../client/api/barrage/WebBarrageUtils.java | 78 ++-----------------
1 file changed, 5 insertions(+), 73 deletions(-)
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
index 572d472a9fe..b83dd6ed16d 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
@@ -12,6 +12,7 @@
import io.deephaven.web.client.api.barrage.def.InitialTableDefinition;
import io.deephaven.web.client.api.barrage.def.InputTableMetadata;
import io.deephaven.web.client.api.barrage.def.TableAttributesDefinition;
+import io.deephaven.web.client.fu.JsLog;
import io.deephaven.web.shared.data.*;
import org.apache.arrow.flatbuf.KeyValue;
import org.apache.arrow.flatbuf.Message;
@@ -82,43 +83,31 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
Map schemaMetadata =
keyValuePairs("deephaven:", schema.customMetadataLength(), schema::customMetadata);
- consoleLog("parseInputTableMetadata: Checking for tableMetadata in schema");
-
String tableMetadataBase64 = schemaMetadata.get("tableMetadata");
if (tableMetadataBase64 == null || tableMetadataBase64.isEmpty()) {
- consoleLog("parseInputTableMetadata: No tableMetadata found in schema (null or empty)");
return null;
}
- consoleLog("parseInputTableMetadata: Found tableMetadata, length=" + tableMetadataBase64.length());
InputTableMetadata metadata = new InputTableMetadata();
try {
// Decode base64 to Uint8Array (like Java's Base64.getDecoder().decode())
- consoleLog("parseInputTableMetadata: Decoding base64...");
Uint8Array bytes = decodeBase64(tableMetadataBase64);
- consoleLog("parseInputTableMetadata: Decoded to " + bytes.length + " bytes");
// The issue: JavaScript protobuf deserialization fails on google.protobuf.Any types
// Solution: Parse the protobuf manually at the binary level to extract what we need
- consoleLog("parseInputTableMetadata: Attempting manual protobuf parsing...");
jsinterop.base.Any result = parseProtoManually(bytes);
if (result == null) {
- consoleLog("parseInputTableMetadata: Manual parsing returned null");
return metadata;
}
- consoleLog("parseInputTableMetadata: Successfully parsed, extracting column info");
-
// Extract column restrictions from the manually parsed data
for (ColumnDefinition col : cols) {
String columnName = col.getName();
jsinterop.base.Any columnInfo = getPropertyFromMap(result, columnName);
if (columnInfo != null) {
- consoleLog("parseInputTableMetadata: Found info for column '" + columnName + "'");
-
// Get restrictions array if available
jsinterop.base.Any restrictionsList = getProperty(columnInfo, "restrictions");
if (restrictionsList != null && isArray(restrictionsList)) {
@@ -126,9 +115,6 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
jsinterop.base.Js.uncheckedCast(restrictionsList);
if (restrictions.length > 0) {
- consoleLog("parseInputTableMetadata: Column '" + columnName + "' has " +
- restrictions.length + " restrictions");
-
InputTableMetadata.ColumnRestrictions colRestrictions =
new InputTableMetadata.ColumnRestrictions();
for (int i = 0; i < restrictions.length; i++) {
@@ -139,10 +125,8 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
}
}
}
-
- consoleLog("parseInputTableMetadata: Successfully parsed metadata");
} catch (Exception e) {
- consoleError("parseInputTableMetadata: Exception during parsing", e);
+ JsLog.warn("Failed to parse input table metadata:", e);
}
return metadata;
@@ -166,9 +150,6 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
// Based on the pattern from AddToInputTable.java
// Use the BinaryReader from dhinternal.jspb which is the JsInterop wrapper
try {
- console.log("parseProtoManually: Starting manual protobuf parsing");
- console.log("parseProtoManually: Bytes length =", bytes.length);
-
// Use the JsInterop BinaryReader class
var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
var reader = new BinaryReader(bytes);
@@ -181,7 +162,6 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
break;
}
var field = reader.getFieldNumber();
- console.log("parseProtoManually: Reading field", field);
if (field === 1) {
// This is the InputTableMetadata field
@@ -194,7 +174,6 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
break;
}
var subfield = rdr.getFieldNumber();
- console.log("parseProtoManually: InputTableMetadata field", subfield);
if (subfield === 1) {
// This is the columnInfoMap
@@ -212,7 +191,6 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
if (mapField === 1) {
// Map key (column name)
key = mapReader.readString();
- console.log("parseProtoManually: Column name =", key);
} else if (mapField === 2) {
// Map value (InputTableColumnInfo)
value = {};
@@ -224,7 +202,6 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
break;
}
var colField = colReader.getFieldNumber();
- console.log("parseProtoManually: InputTableColumnInfo field", colField);
if (colField === 1) {
// kind field
@@ -234,7 +211,6 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
// Parse the Any message to extract type_url and value
try {
var anyBytes = colReader.readBytes();
- console.log("parseProtoManually: Got Any bytes, length =", anyBytes.length);
// Parse the google.protobuf.Any message
// Field 1 = type_url (string)
@@ -250,10 +226,8 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
var anyField = anyReader.getFieldNumber();
if (anyField === 1) {
typeUrl = anyReader.readString();
- console.log("parseProtoManually: Restriction type_url =", typeUrl);
} else if (anyField === 2) {
valueBytes = anyReader.readBytes();
- console.log("parseProtoManually: Restriction value bytes length =", valueBytes.length);
}
}
@@ -263,7 +237,7 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
restrictions.push(restriction);
}
} catch (e) {
- console.warn("parseProtoManually: Failed to parse restriction:", e);
+ @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to parse restriction:", e);
}
}
}
@@ -277,7 +251,6 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
metadata.columnInfoMap = {};
}
metadata.columnInfoMap[key] = value;
- console.log("parseProtoManually: Added column", key, "with", value.restrictions ? value.restrictions.length : 0, "restrictions");
}
});
}
@@ -285,15 +258,13 @@ private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*
});
result = inputTableMetadata.columnInfoMap || {};
- console.log("parseProtoManually: Parsed", Object.keys(result).length, "columns");
}
}
return result;
} catch (e) {
- console.error("parseProtoManually: Failed to manually parse protobuf:", e);
- console.error("Stack:", e.stack);
+ @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to manually parse protobuf:", e);
return null;
}
}-*/;
@@ -308,8 +279,6 @@ private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8A
// - StringListRestriction
try {
- console.log("parseRestriction: Parsing restriction type:", typeUrl);
-
var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
var reader = new BinaryReader(valueBytes);
@@ -318,7 +287,6 @@ private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8A
// or "docs.deephaven.io/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
var typeName = typeUrl.substring(typeUrl.lastIndexOf('/') + 1);
var shortName = typeName.substring(typeName.lastIndexOf('.') + 1);
- console.log("parseRestriction: Message type:", shortName);
var restriction = {
type: shortName,
@@ -333,10 +301,8 @@ private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8A
var field = reader.getFieldNumber();
if (field === 1) {
restriction.minInclusive = reader.readInt64();
- console.log("parseRestriction: minInclusive =", restriction.minInclusive);
} else if (field === 2) {
restriction.maxInclusive = reader.readInt64();
- console.log("parseRestriction: maxInclusive =", restriction.maxInclusive);
}
}
} else if (shortName === 'DoubleRangeRestriction') {
@@ -347,20 +313,16 @@ private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8A
var field = reader.getFieldNumber();
if (field === 1) {
restriction.minInclusive = reader.readDouble();
- console.log("parseRestriction: minInclusive =", restriction.minInclusive);
} else if (field === 2) {
restriction.maxInclusive = reader.readDouble();
- console.log("parseRestriction: maxInclusive =", restriction.maxInclusive);
}
}
} else if (shortName === 'NotNullRestriction') {
// No fields - just the type
restriction.notNull = true;
- console.log("parseRestriction: NotNull restriction");
} else if (shortName === 'NonEmptyRestriction') {
// No fields - just the type
restriction.nonEmpty = true;
- console.log("parseRestriction: NonEmpty restriction");
} else if (shortName === 'StringListRestriction') {
// Field 1 = allowed_values (repeated string)
restriction.allowedValues = [];
@@ -370,18 +332,16 @@ private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8A
if (field === 1) {
var value = reader.readString();
restriction.allowedValues.push(value);
- console.log("parseRestriction: allowed value =", value);
}
}
} else {
- console.warn("parseRestriction: Unknown restriction type:", shortName);
restriction.raw = valueBytes;
}
return restriction;
} catch (e) {
- console.error("parseRestriction: Failed to parse restriction:", e);
+ @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to parse restriction:", e);
return null;
}
}-*/;
@@ -400,39 +360,11 @@ private static native jsinterop.base.Any getProperty(jsinterop.base.Any obj, Str
return obj[propertyName];
}-*/;
- private static native jsinterop.base.Any getMapEntry(jsinterop.base.Any map, String key) /*-{
- if (map == null) return null;
- if (typeof map.get === 'function') {
- return map.get(key);
- }
- if (typeof map.getMap === 'function') {
- var m = map.getMap();
- return m ? m[key] : null;
- }
- return map[key];
- }-*/;
private static native boolean isArray(jsinterop.base.Any obj) /*-{
return Array.isArray(obj);
}-*/;
- private static native void consoleLog(String message) /*-{
- console.log(message);
- }-*/;
-
- private static native void consoleError(String message, Exception e) /*-{
- console.error(message, e);
- }-*/;
-
- private static native void logProtoObject(String label, jsinterop.base.Any obj) /*-{
- console.log(label + ":", obj);
- if (obj != null) {
- console.log(label + " keys:", Object.keys(obj));
- console.log(label + " methods:", Object.getOwnPropertyNames(Object.getPrototypeOf(obj)).filter(function(name) {
- return typeof obj[name] === 'function';
- }));
- }
- }-*/;
private static ColumnDefinition[] readColumnDefinitions(Schema schema) {
ColumnDefinition[] cols = new ColumnDefinition[(int) schema.fieldsLength()];
From e7d828eb78bf4e21ce84b885e236a4d19bbe0cbf Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 8 Apr 2026 10:35:51 -0600
Subject: [PATCH 10/35] remove more logging
---
.../web/client/api/barrage/def/InputTableMetadata.java | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
index 110772f8a77..a2b88624c10 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
@@ -63,7 +63,6 @@ private static native ColumnRestriction convertRestriction(Any restrictionData)
if (!restrictionData) return null;
var type = restrictionData.type || "Unknown";
- console.log("convertRestriction: Converting restriction of type:", type);
// Create the appropriate ColumnRestriction based on type
if (type === 'IntegerRangeRestriction' || type === 'DoubleRangeRestriction') {
@@ -80,10 +79,9 @@ private static native ColumnRestriction convertRestriction(Any restrictionData)
);
} else if (type === 'NotNullRestriction' || type === 'NonEmptyRestriction') {
return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;)(type);
- } else {
- console.warn("convertRestriction: Unknown restriction type:", type);
- return null;
}
+
+ return null;
}-*/;
}
}
From 65f3327a24b7592337b9e276344bb8b6da7d48e4 Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 8 Apr 2026 11:25:58 -0600
Subject: [PATCH 11/35] refactor to util
---
.../client/api/barrage/WebBarrageUtils.java | 203 +------------
.../api/barrage/def/InputTableMetadata.java | 28 +-
.../barrage/util/ColumnRestrictionUtils.java | 267 ++++++++++++++++++
3 files changed, 271 insertions(+), 227 deletions(-)
create mode 100644 web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionUtils.java
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
index b83dd6ed16d..2221b569f7b 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
@@ -12,6 +12,7 @@
import io.deephaven.web.client.api.barrage.def.InitialTableDefinition;
import io.deephaven.web.client.api.barrage.def.InputTableMetadata;
import io.deephaven.web.client.api.barrage.def.TableAttributesDefinition;
+import io.deephaven.web.client.api.barrage.util.ColumnRestrictionUtils;
import io.deephaven.web.client.fu.JsLog;
import io.deephaven.web.shared.data.*;
import org.apache.arrow.flatbuf.KeyValue;
@@ -96,7 +97,7 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
// The issue: JavaScript protobuf deserialization fails on google.protobuf.Any types
// Solution: Parse the protobuf manually at the binary level to extract what we need
- jsinterop.base.Any result = parseProtoManually(bytes);
+ jsinterop.base.Any result = ColumnRestrictionUtils.parseProtoManually(bytes);
if (result == null) {
return metadata;
@@ -145,206 +146,6 @@ private static Uint8Array decodeBase64(String base64) {
return bytes;
}
- private static native jsinterop.base.Any parseProtoManually(Uint8Array bytes) /*-{
- // Manual protobuf parsing to work around missing google.protobuf.Any
- // Based on the pattern from AddToInputTable.java
- // Use the BinaryReader from dhinternal.jspb which is the JsInterop wrapper
- try {
- // Use the JsInterop BinaryReader class
- var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
- var reader = new BinaryReader(bytes);
- var result = {};
-
- // Parse the DeephavenTableMetadata message
- // Field 1 is InputTableMetadata
- while (reader.nextField()) {
- if (reader.isEndGroup()) {
- break;
- }
- var field = reader.getFieldNumber();
-
- if (field === 1) {
- // This is the InputTableMetadata field
- var inputTableMetadata = {};
- reader.readMessage(inputTableMetadata, function(metadata, rdr) {
- // Parse InputTableMetadata
- // Field 1 is columnInfoMap (map)
- while (rdr.nextField()) {
- if (rdr.isEndGroup()) {
- break;
- }
- var subfield = rdr.getFieldNumber();
-
- if (subfield === 1) {
- // This is the columnInfoMap
- var mapEntry = {};
- rdr.readMessage(mapEntry, function(entry, mapReader) {
- var key = null;
- var value = null;
-
- while (mapReader.nextField()) {
- if (mapReader.isEndGroup()) {
- break;
- }
- var mapField = mapReader.getFieldNumber();
-
- if (mapField === 1) {
- // Map key (column name)
- key = mapReader.readString();
- } else if (mapField === 2) {
- // Map value (InputTableColumnInfo)
- value = {};
- var restrictions = [];
-
- mapReader.readMessage(value, function(columnInfo, colReader) {
- while (colReader.nextField()) {
- if (colReader.isEndGroup()) {
- break;
- }
- var colField = colReader.getFieldNumber();
-
- if (colField === 1) {
- // kind field
- columnInfo.kind = colReader.readEnum();
- } else if (colField === 2) {
- // restrictions field (repeated google.protobuf.Any)
- // Parse the Any message to extract type_url and value
- try {
- var anyBytes = colReader.readBytes();
-
- // Parse the google.protobuf.Any message
- // Field 1 = type_url (string)
- // Field 2 = value (bytes)
- var anyReader = new BinaryReader(anyBytes);
- var typeUrl = null;
- var valueBytes = null;
-
- while (anyReader.nextField()) {
- if (anyReader.isEndGroup()) {
- break;
- }
- var anyField = anyReader.getFieldNumber();
- if (anyField === 1) {
- typeUrl = anyReader.readString();
- } else if (anyField === 2) {
- valueBytes = anyReader.readBytes();
- }
- }
-
- // Now parse the actual restriction based on type_url
- var restriction = @io.deephaven.web.client.api.barrage.WebBarrageUtils::parseRestriction(*)(typeUrl, valueBytes);
- if (restriction) {
- restrictions.push(restriction);
- }
- } catch (e) {
- @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to parse restriction:", e);
- }
- }
- }
- columnInfo.restrictions = restrictions;
- });
- }
- }
-
- if (key !== null && value !== null) {
- if (!metadata.columnInfoMap) {
- metadata.columnInfoMap = {};
- }
- metadata.columnInfoMap[key] = value;
- }
- });
- }
- }
- });
-
- result = inputTableMetadata.columnInfoMap || {};
- }
- }
-
- return result;
-
- } catch (e) {
- @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to manually parse protobuf:", e);
- return null;
- }
- }-*/;
-
- private static native jsinterop.base.Any parseRestriction(String typeUrl, Uint8Array valueBytes) /*-{
- // Parse specific restriction types based on typeUrl
- // Types from inputtable.proto:
- // - IntegerRangeRestriction
- // - DoubleRangeRestriction
- // - NotNullRestriction
- // - NonEmptyRestriction
- // - StringListRestriction
-
- try {
- var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
- var reader = new BinaryReader(valueBytes);
-
- // Extract the message type from the type URL
- // Format: "type.googleapis.com/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
- // or "docs.deephaven.io/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
- var typeName = typeUrl.substring(typeUrl.lastIndexOf('/') + 1);
- var shortName = typeName.substring(typeName.lastIndexOf('.') + 1);
-
- var restriction = {
- type: shortName,
- typeUrl: typeUrl
- };
-
- if (shortName === 'IntegerRangeRestriction') {
- // Field 1 = min_inclusive (int64)
- // Field 2 = max_inclusive (int64)
- while (reader.nextField()) {
- if (reader.isEndGroup()) break;
- var field = reader.getFieldNumber();
- if (field === 1) {
- restriction.minInclusive = reader.readInt64();
- } else if (field === 2) {
- restriction.maxInclusive = reader.readInt64();
- }
- }
- } else if (shortName === 'DoubleRangeRestriction') {
- // Field 1 = min_inclusive (double)
- // Field 2 = max_inclusive (double)
- while (reader.nextField()) {
- if (reader.isEndGroup()) break;
- var field = reader.getFieldNumber();
- if (field === 1) {
- restriction.minInclusive = reader.readDouble();
- } else if (field === 2) {
- restriction.maxInclusive = reader.readDouble();
- }
- }
- } else if (shortName === 'NotNullRestriction') {
- // No fields - just the type
- restriction.notNull = true;
- } else if (shortName === 'NonEmptyRestriction') {
- // No fields - just the type
- restriction.nonEmpty = true;
- } else if (shortName === 'StringListRestriction') {
- // Field 1 = allowed_values (repeated string)
- restriction.allowedValues = [];
- while (reader.nextField()) {
- if (reader.isEndGroup()) break;
- var field = reader.getFieldNumber();
- if (field === 1) {
- var value = reader.readString();
- restriction.allowedValues.push(value);
- }
- }
- } else {
- restriction.raw = valueBytes;
- }
-
- return restriction;
-
- } catch (e) {
- @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to parse restriction:", e);
- return null;
- }
- }-*/;
private static native jsinterop.base.Any getPropertyFromMap(jsinterop.base.Any map, String key) /*-{
if (map == null) return null;
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
index a2b88624c10..ea457366ea0 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
@@ -5,6 +5,7 @@
import elemental2.core.JsArray;
import io.deephaven.web.client.api.ColumnRestriction;
+import io.deephaven.web.client.api.barrage.util.ColumnRestrictionUtils;
import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsNullable;
import jsinterop.base.Any;
@@ -48,7 +49,7 @@ public ColumnRestrictions() {
@JsIgnore
public void addRestriction(Any restrictionData) {
// Convert the parsed restriction data into a ColumnRestriction object
- ColumnRestriction restriction = convertRestriction(restrictionData);
+ ColumnRestriction restriction = ColumnRestrictionUtils.convertRestriction(restrictionData);
if (restriction != null) {
restrictions.push(restriction);
}
@@ -58,31 +59,6 @@ public void addRestriction(Any restrictionData) {
public JsArray getRestrictions() {
return restrictions;
}
-
- private static native ColumnRestriction convertRestriction(Any restrictionData) /*-{
- if (!restrictionData) return null;
-
- var type = restrictionData.type || "Unknown";
-
- // Create the appropriate ColumnRestriction based on type
- if (type === 'IntegerRangeRestriction' || type === 'DoubleRangeRestriction') {
- var minValue = restrictionData.minInclusive !== undefined ? restrictionData.minInclusive : NaN;
- var maxValue = restrictionData.maxInclusive !== undefined ? restrictionData.maxInclusive : NaN;
- return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;DD)(
- type, minValue, maxValue
- );
- } else if (type === 'StringListRestriction') {
- var allowedValues = restrictionData.allowedValues || [];
- // Use the JS array directly - it will be cast to JsArray automatically
- return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;Lelemental2/core/JsArray;)(
- type, allowedValues
- );
- } else if (type === 'NotNullRestriction' || type === 'NonEmptyRestriction') {
- return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;)(type);
- }
-
- return null;
- }-*/;
}
}
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionUtils.java
new file mode 100644
index 00000000000..4787e116bd1
--- /dev/null
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionUtils.java
@@ -0,0 +1,267 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.web.client.api.barrage.util;
+
+import elemental2.core.JsArray;
+import elemental2.core.Uint8Array;
+import io.deephaven.web.client.api.ColumnRestriction;
+import io.deephaven.web.client.fu.JsLog;
+import jsinterop.base.Any;
+
+/**
+ * Utility class for parsing column restrictions from protobuf data.
+ */
+public class ColumnRestrictionUtils {
+
+ private ColumnRestrictionUtils() {
+ // Utility class - no instances
+ }
+
+ /**
+ * Manually parse protobuf bytes to extract input table metadata and column restrictions.
+ * This works around issues with google.protobuf.Any deserialization in JavaScript.
+ *
+ * @param bytes The protobuf bytes to parse
+ * @return A map of column names to their metadata, or null if parsing fails
+ */
+ public static native Any parseProtoManually(Uint8Array bytes) /*-{
+ // Manual protobuf parsing to work around missing google.protobuf.Any
+ // Based on the pattern from AddToInputTable.java
+ // Use the BinaryReader from dhinternal.jspb which is the JsInterop wrapper
+ try {
+ // Use the JsInterop BinaryReader class
+ var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
+ var reader = new BinaryReader(bytes);
+ var result = {};
+
+ // Parse the DeephavenTableMetadata message
+ // Field 1 is InputTableMetadata
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+
+ if (field === 1) {
+ // This is the InputTableMetadata field
+ var inputTableMetadata = {};
+ reader.readMessage(inputTableMetadata, function(metadata, rdr) {
+ // Parse InputTableMetadata
+ // Field 1 is columnInfoMap (map)
+ while (rdr.nextField()) {
+ if (rdr.isEndGroup()) {
+ break;
+ }
+ var subfield = rdr.getFieldNumber();
+
+ if (subfield === 1) {
+ // This is the columnInfoMap
+ var mapEntry = {};
+ rdr.readMessage(mapEntry, function(entry, mapReader) {
+ var key = null;
+ var value = null;
+
+ while (mapReader.nextField()) {
+ if (mapReader.isEndGroup()) {
+ break;
+ }
+ var mapField = mapReader.getFieldNumber();
+
+ if (mapField === 1) {
+ // Map key (column name)
+ key = mapReader.readString();
+ } else if (mapField === 2) {
+ // Map value (InputTableColumnInfo)
+ value = {};
+ var restrictions = [];
+
+ mapReader.readMessage(value, function(columnInfo, colReader) {
+ while (colReader.nextField()) {
+ if (colReader.isEndGroup()) {
+ break;
+ }
+ var colField = colReader.getFieldNumber();
+
+ if (colField === 1) {
+ // kind field
+ columnInfo.kind = colReader.readEnum();
+ } else if (colField === 2) {
+ // restrictions field (repeated google.protobuf.Any)
+ // Parse the Any message to extract type_url and value
+ try {
+ var anyBytes = colReader.readBytes();
+
+ // Parse the google.protobuf.Any message
+ // Field 1 = type_url (string)
+ // Field 2 = value (bytes)
+ var anyReader = new BinaryReader(anyBytes);
+ var typeUrl = null;
+ var valueBytes = null;
+
+ while (anyReader.nextField()) {
+ if (anyReader.isEndGroup()) {
+ break;
+ }
+ var anyField = anyReader.getFieldNumber();
+ if (anyField === 1) {
+ typeUrl = anyReader.readString();
+ } else if (anyField === 2) {
+ valueBytes = anyReader.readBytes();
+ }
+ }
+
+ // Now parse the actual restriction based on type_url
+ var restriction = @io.deephaven.web.client.api.barrage.util.ColumnRestrictionUtils::parseRestriction(*)(typeUrl, valueBytes);
+ if (restriction) {
+ restrictions.push(restriction);
+ }
+ } catch (e) {
+ @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to parse restriction:", e);
+ }
+ }
+ }
+ columnInfo.restrictions = restrictions;
+ });
+ }
+ }
+
+ if (key !== null && value !== null) {
+ if (!metadata.columnInfoMap) {
+ metadata.columnInfoMap = {};
+ }
+ metadata.columnInfoMap[key] = value;
+ }
+ });
+ }
+ }
+ });
+
+ result = inputTableMetadata.columnInfoMap || {};
+ }
+ }
+
+ return result;
+
+ } catch (e) {
+ @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to manually parse protobuf:", e);
+ return null;
+ }
+ }-*/;
+
+ /**
+ * Parse a specific restriction type from protobuf bytes.
+ *
+ * @param typeUrl The type URL of the restriction
+ * @param valueBytes The protobuf bytes containing the restriction data
+ * @return The parsed restriction data, or null if parsing fails
+ */
+ private static native Any parseRestriction(String typeUrl, Uint8Array valueBytes) /*-{
+ // Parse specific restriction types based on typeUrl
+ // Types from inputtable.proto:
+ // - IntegerRangeRestriction
+ // - DoubleRangeRestriction
+ // - NotNullRestriction
+ // - NonEmptyRestriction
+ // - StringListRestriction
+
+ try {
+ var BinaryReader = @io.deephaven.javascript.proto.dhinternal.jspb.BinaryReader::new(Lelemental2/core/Uint8Array;);
+ var reader = new BinaryReader(valueBytes);
+
+ // Extract the message type from the type URL
+ // Format: "type.googleapis.com/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
+ // or "docs.deephaven.io/io.deephaven.proto.backplane.grpc.IntegerRangeRestriction"
+ var typeName = typeUrl.substring(typeUrl.lastIndexOf('/') + 1);
+ var shortName = typeName.substring(typeName.lastIndexOf('.') + 1);
+
+ var restriction = {
+ type: shortName,
+ typeUrl: typeUrl
+ };
+
+ if (shortName === 'IntegerRangeRestriction') {
+ // Field 1 = min_inclusive (int64)
+ // Field 2 = max_inclusive (int64)
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ restriction.minInclusive = reader.readInt64();
+ } else if (field === 2) {
+ restriction.maxInclusive = reader.readInt64();
+ }
+ }
+ } else if (shortName === 'DoubleRangeRestriction') {
+ // Field 1 = min_inclusive (double)
+ // Field 2 = max_inclusive (double)
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ restriction.minInclusive = reader.readDouble();
+ } else if (field === 2) {
+ restriction.maxInclusive = reader.readDouble();
+ }
+ }
+ } else if (shortName === 'NotNullRestriction') {
+ // No fields - just the type
+ restriction.notNull = true;
+ } else if (shortName === 'NonEmptyRestriction') {
+ // No fields - just the type
+ restriction.nonEmpty = true;
+ } else if (shortName === 'StringListRestriction') {
+ // Field 1 = allowed_values (repeated string)
+ restriction.allowedValues = [];
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) break;
+ var field = reader.getFieldNumber();
+ if (field === 1) {
+ var value = reader.readString();
+ restriction.allowedValues.push(value);
+ }
+ }
+ } else {
+ restriction.raw = valueBytes;
+ }
+
+ return restriction;
+
+ } catch (e) {
+ @io.deephaven.web.client.fu.JsLog::warn(*)("Failed to parse restriction:", e);
+ return null;
+ }
+ }-*/;
+
+ /**
+ * Convert parsed restriction data into a ColumnRestriction object.
+ *
+ * @param restrictionData The parsed restriction data from protobuf
+ * @return A ColumnRestriction object, or null if conversion fails
+ */
+ public static native ColumnRestriction convertRestriction(Any restrictionData) /*-{
+ if (!restrictionData) return null;
+
+ var type = restrictionData.type || "Unknown";
+
+ // Create the appropriate ColumnRestriction based on type
+ if (type === 'IntegerRangeRestriction' || type === 'DoubleRangeRestriction') {
+ var minValue = restrictionData.minInclusive !== undefined ? restrictionData.minInclusive : NaN;
+ var maxValue = restrictionData.maxInclusive !== undefined ? restrictionData.maxInclusive : NaN;
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;DD)(
+ type, minValue, maxValue
+ );
+ } else if (type === 'StringListRestriction') {
+ var allowedValues = restrictionData.allowedValues || [];
+ // Use the JS array directly - it will be cast to JsArray automatically
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;Lelemental2/core/JsArray;)(
+ type, allowedValues
+ );
+ } else if (type === 'NotNullRestriction' || type === 'NonEmptyRestriction') {
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;)(type);
+ }
+
+ return null;
+ }-*/;
+}
+
From fce1f27377b4a0f5ab6da90401e1b900a052a50b Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 8 Apr 2026 14:31:29 -0600
Subject: [PATCH 12/35] register converter
---
.../client/api/barrage/WebBarrageUtils.java | 48 +++++++++++-
.../api/barrage/def/InputTableMetadata.java | 7 +-
.../util/ColumnRestrictionConverter.java | 22 ++++++
.../barrage/util/ColumnRestrictionUtils.java | 76 ++++++++++++++-----
4 files changed, 127 insertions(+), 26 deletions(-)
create mode 100644 web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionConverter.java
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
index 2221b569f7b..e0f2beb8f1b 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/WebBarrageUtils.java
@@ -12,6 +12,7 @@
import io.deephaven.web.client.api.barrage.def.InitialTableDefinition;
import io.deephaven.web.client.api.barrage.def.InputTableMetadata;
import io.deephaven.web.client.api.barrage.def.TableAttributesDefinition;
+import io.deephaven.web.client.api.barrage.util.ColumnRestrictionConverter;
import io.deephaven.web.client.api.barrage.util.ColumnRestrictionUtils;
import io.deephaven.web.client.fu.JsLog;
import io.deephaven.web.shared.data.*;
@@ -33,6 +34,37 @@
public class WebBarrageUtils {
public static final int FLATBUFFER_MAGIC = 0x6E687064;
+ private static final Map restrictionConverters = new HashMap<>();
+
+ static {
+ // Register default converters
+ registerColumnRestrictionConverter("IntegerRangeRestriction", ColumnRestrictionUtils::convertIntegerRangeRestriction);
+ registerColumnRestrictionConverter("DoubleRangeRestriction", ColumnRestrictionUtils::convertDoubleRangeRestriction);
+ registerColumnRestrictionConverter("NotNullRestriction", ColumnRestrictionUtils::convertNotNullRestriction);
+ registerColumnRestrictionConverter("NonEmptyRestriction", ColumnRestrictionUtils::convertNonEmptyRestriction);
+ registerColumnRestrictionConverter("StringListRestriction", ColumnRestrictionUtils::convertStringListRestriction);
+ }
+
+ /**
+ * Register a converter for a specific restriction type.
+ *
+ * @param restrictionType The type name of the restriction (e.g., "IntegerRangeRestriction")
+ * @param converter The converter function to convert the restriction data
+ */
+ public static void registerColumnRestrictionConverter(String restrictionType, ColumnRestrictionConverter converter) {
+ restrictionConverters.put(restrictionType, converter);
+ }
+
+ /**
+ * Get the converter for a specific restriction type.
+ *
+ * @param restrictionType The type name of the restriction
+ * @return The converter, or null if none is registered
+ */
+ static ColumnRestrictionConverter getColumnRestrictionConverter(String restrictionType) {
+ return restrictionConverters.get(restrictionType);
+ }
+
public static Uint8Array wrapMessage(FlatBufferBuilder innerBuilder, byte messageType) {
FlatBufferBuilder outerBuilder = new FlatBufferBuilder(1024);
int messageOffset = BarrageMessageWrapper.createMsgPayloadVector(outerBuilder, innerBuilder.dataBuffer());
@@ -119,7 +151,17 @@ private static InputTableMetadata parseInputTableMetadata(Schema schema, ColumnD
InputTableMetadata.ColumnRestrictions colRestrictions =
new InputTableMetadata.ColumnRestrictions();
for (int i = 0; i < restrictions.length; i++) {
- colRestrictions.addRestriction(restrictions.getAt(i));
+ jsinterop.base.Any restrictionData = restrictions.getAt(i);
+
+ // Get the restriction type and convert it
+ String restrictionType = getRestrictionType(restrictionData);
+ if (restrictionType != null) {
+ ColumnRestrictionConverter converter = getColumnRestrictionConverter(restrictionType);
+ if (converter != null) {
+ io.deephaven.web.client.api.ColumnRestriction restriction = converter.convert(restrictionData);
+ colRestrictions.addRestriction(restriction);
+ }
+ }
}
metadata.addColumnRestrictions(columnName, colRestrictions);
}
@@ -146,6 +188,10 @@ private static Uint8Array decodeBase64(String base64) {
return bytes;
}
+ private static native String getRestrictionType(jsinterop.base.Any restrictionData) /*-{
+ if (!restrictionData) return null;
+ return restrictionData.type || null;
+ }-*/;
private static native jsinterop.base.Any getPropertyFromMap(jsinterop.base.Any map, String key) /*-{
if (map == null) return null;
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
index ea457366ea0..6fd4b62a8cd 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/def/InputTableMetadata.java
@@ -5,10 +5,8 @@
import elemental2.core.JsArray;
import io.deephaven.web.client.api.ColumnRestriction;
-import io.deephaven.web.client.api.barrage.util.ColumnRestrictionUtils;
import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsNullable;
-import jsinterop.base.Any;
import java.util.HashMap;
import java.util.Map;
@@ -47,14 +45,13 @@ public ColumnRestrictions() {
}
@JsIgnore
- public void addRestriction(Any restrictionData) {
- // Convert the parsed restriction data into a ColumnRestriction object
- ColumnRestriction restriction = ColumnRestrictionUtils.convertRestriction(restrictionData);
+ public void addRestriction(ColumnRestriction restriction) {
if (restriction != null) {
restrictions.push(restriction);
}
}
+
@JsIgnore
public JsArray getRestrictions() {
return restrictions;
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionConverter.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionConverter.java
new file mode 100644
index 00000000000..cab0c71677e
--- /dev/null
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionConverter.java
@@ -0,0 +1,22 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.web.client.api.barrage.util;
+
+import io.deephaven.web.client.api.ColumnRestriction;
+import jsinterop.base.Any;
+
+/**
+ * Functional interface for converting parsed restriction data into a ColumnRestriction object.
+ */
+@FunctionalInterface
+public interface ColumnRestrictionConverter {
+ /**
+ * Convert parsed restriction data into a ColumnRestriction object.
+ *
+ * @param restrictionData The parsed restriction data from protobuf
+ * @return A ColumnRestriction object, or null if conversion fails
+ */
+ ColumnRestriction convert(Any restrictionData);
+}
+
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionUtils.java
index 4787e116bd1..858d217c820 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionUtils.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/barrage/util/ColumnRestrictionUtils.java
@@ -234,34 +234,70 @@ private static native Any parseRestriction(String typeUrl, Uint8Array valueBytes
}-*/;
/**
- * Convert parsed restriction data into a ColumnRestriction object.
+ * Convert IntegerRangeRestriction data into a ColumnRestriction object.
*
* @param restrictionData The parsed restriction data from protobuf
* @return A ColumnRestriction object, or null if conversion fails
*/
- public static native ColumnRestriction convertRestriction(Any restrictionData) /*-{
+ public static native ColumnRestriction convertIntegerRangeRestriction(Any restrictionData) /*-{
if (!restrictionData) return null;
+ var minValue = restrictionData.minInclusive !== undefined ? restrictionData.minInclusive : NaN;
+ var maxValue = restrictionData.maxInclusive !== undefined ? restrictionData.maxInclusive : NaN;
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;DD)(
+ "IntegerRangeRestriction", minValue, maxValue
+ );
+ }-*/;
- var type = restrictionData.type || "Unknown";
+ /**
+ * Convert DoubleRangeRestriction data into a ColumnRestriction object.
+ *
+ * @param restrictionData The parsed restriction data from protobuf
+ * @return A ColumnRestriction object, or null if conversion fails
+ */
+ public static native ColumnRestriction convertDoubleRangeRestriction(Any restrictionData) /*-{
+ if (!restrictionData) return null;
+ var minValue = restrictionData.minInclusive !== undefined ? restrictionData.minInclusive : NaN;
+ var maxValue = restrictionData.maxInclusive !== undefined ? restrictionData.maxInclusive : NaN;
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;DD)(
+ "DoubleRangeRestriction", minValue, maxValue
+ );
+ }-*/;
- // Create the appropriate ColumnRestriction based on type
- if (type === 'IntegerRangeRestriction' || type === 'DoubleRangeRestriction') {
- var minValue = restrictionData.minInclusive !== undefined ? restrictionData.minInclusive : NaN;
- var maxValue = restrictionData.maxInclusive !== undefined ? restrictionData.maxInclusive : NaN;
- return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;DD)(
- type, minValue, maxValue
- );
- } else if (type === 'StringListRestriction') {
- var allowedValues = restrictionData.allowedValues || [];
- // Use the JS array directly - it will be cast to JsArray automatically
- return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;Lelemental2/core/JsArray;)(
- type, allowedValues
- );
- } else if (type === 'NotNullRestriction' || type === 'NonEmptyRestriction') {
- return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;)(type);
- }
+ /**
+ * Convert NotNullRestriction data into a ColumnRestriction object.
+ *
+ * @param restrictionData The parsed restriction data from protobuf
+ * @return A ColumnRestriction object, or null if conversion fails
+ */
+ public static native ColumnRestriction convertNotNullRestriction(Any restrictionData) /*-{
+ if (!restrictionData) return null;
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;)("NotNullRestriction");
+ }-*/;
- return null;
+ /**
+ * Convert NonEmptyRestriction data into a ColumnRestriction object.
+ *
+ * @param restrictionData The parsed restriction data from protobuf
+ * @return A ColumnRestriction object, or null if conversion fails
+ */
+ public static native ColumnRestriction convertNonEmptyRestriction(Any restrictionData) /*-{
+ if (!restrictionData) return null;
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;)("NonEmptyRestriction");
+ }-*/;
+
+ /**
+ * Convert StringListRestriction data into a ColumnRestriction object.
+ *
+ * @param restrictionData The parsed restriction data from protobuf
+ * @return A ColumnRestriction object, or null if conversion fails
+ */
+ public static native ColumnRestriction convertStringListRestriction(Any restrictionData) /*-{
+ if (!restrictionData) return null;
+ var allowedValues = restrictionData.allowedValues || [];
+ // Use the JS array directly - it will be cast to JsArray automatically
+ return @io.deephaven.web.client.api.ColumnRestriction::new(Ljava/lang/String;Lelemental2/core/JsArray;)(
+ "StringListRestriction", allowedValues
+ );
}-*/;
}
From feed9b9a0d7f42832a08bf3eac3858d463dc7fba Mon Sep 17 00:00:00 2001
From: Colin Alworth
Date: Thu, 9 Apr 2026 11:03:22 -0500
Subject: [PATCH 13/35] Generated input table types
---
.../inputtable_pb/DeephavenTableMetadata.java | 113 ++++++++
.../inputtable_pb/DoubleRangeRestriction.java | 90 +++++++
.../inputtable_pb/InputTableColumnInfo.java | 245 ++++++++++++++++++
.../inputtable_pb/InputTableMetadata.java | 77 ++++++
.../IntegerRangeRestriction.java | 90 +++++++
.../inputtable_pb/NonEmptyRestriction.java | 29 +++
.../inputtable_pb/NotNullRestriction.java | 29 +++
.../inputtable_pb/StringListRestriction.java | 88 +++++++
.../inputtablecolumninfo/KindMap.java | 40 +++
9 files changed, 801 insertions(+)
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/DeephavenTableMetadata.java
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/DoubleRangeRestriction.java
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/InputTableColumnInfo.java
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/InputTableMetadata.java
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/IntegerRangeRestriction.java
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/NonEmptyRestriction.java
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/NotNullRestriction.java
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/StringListRestriction.java
create mode 100644 web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/inputtablecolumninfo/KindMap.java
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/DeephavenTableMetadata.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/DeephavenTableMetadata.java
new file mode 100644
index 00000000000..60381f04764
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/DeephavenTableMetadata.java
@@ -0,0 +1,113 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.inputtable_pb;
+
+import elemental2.core.JsArray;
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.inputtable_pb.DeephavenTableMetadata",
+ namespace = JsPackage.GLOBAL)
+public class DeephavenTableMetadata {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface InputTableMetadataFieldType {
+ @JsOverlay
+ static DeephavenTableMetadata.ToObjectReturnType.InputTableMetadataFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ JsArray> getColumnInfoMap();
+
+ @JsProperty
+ void setColumnInfoMap(JsArray> columnInfoMap);
+
+ @JsOverlay
+ default void setColumnInfoMap(Object[][] columnInfoMap) {
+ setColumnInfoMap(Js.>>uncheckedCast(columnInfoMap));
+ }
+ }
+
+ @JsOverlay
+ static DeephavenTableMetadata.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ DeephavenTableMetadata.ToObjectReturnType.InputTableMetadataFieldType getInputTableMetadata();
+
+ @JsProperty
+ void setInputTableMetadata(
+ DeephavenTableMetadata.ToObjectReturnType.InputTableMetadataFieldType inputTableMetadata);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface InputTableMetadataFieldType {
+ @JsOverlay
+ static DeephavenTableMetadata.ToObjectReturnType0.InputTableMetadataFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ JsArray> getColumnInfoMap();
+
+ @JsProperty
+ void setColumnInfoMap(JsArray> columnInfoMap);
+
+ @JsOverlay
+ default void setColumnInfoMap(Object[][] columnInfoMap) {
+ setColumnInfoMap(Js.>>uncheckedCast(columnInfoMap));
+ }
+ }
+
+ @JsOverlay
+ static DeephavenTableMetadata.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ DeephavenTableMetadata.ToObjectReturnType0.InputTableMetadataFieldType getInputTableMetadata();
+
+ @JsProperty
+ void setInputTableMetadata(
+ DeephavenTableMetadata.ToObjectReturnType0.InputTableMetadataFieldType inputTableMetadata);
+ }
+
+ public static native DeephavenTableMetadata deserializeBinary(Uint8Array bytes);
+
+ public static native DeephavenTableMetadata deserializeBinaryFromReader(
+ DeephavenTableMetadata message, Object reader);
+
+ public static native void serializeBinaryToWriter(DeephavenTableMetadata message, Object writer);
+
+ public static native DeephavenTableMetadata.ToObjectReturnType toObject(
+ boolean includeInstance, DeephavenTableMetadata msg);
+
+ public native void clearInputTableMetadata();
+
+ public native InputTableMetadata getInputTableMetadata();
+
+ public native boolean hasInputTableMetadata();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setInputTableMetadata();
+
+ public native void setInputTableMetadata(InputTableMetadata value);
+
+ public native DeephavenTableMetadata.ToObjectReturnType0 toObject();
+
+ public native DeephavenTableMetadata.ToObjectReturnType0 toObject(boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/DoubleRangeRestriction.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/DoubleRangeRestriction.java
new file mode 100644
index 00000000000..19378461d48
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/DoubleRangeRestriction.java
@@ -0,0 +1,90 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.inputtable_pb;
+
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.inputtable_pb.DoubleRangeRestriction",
+ namespace = JsPackage.GLOBAL)
+public class DoubleRangeRestriction {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsOverlay
+ static DoubleRangeRestriction.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ double getMaxInclusive();
+
+ @JsProperty
+ double getMinInclusive();
+
+ @JsProperty
+ void setMaxInclusive(double maxInclusive);
+
+ @JsProperty
+ void setMinInclusive(double minInclusive);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsOverlay
+ static DoubleRangeRestriction.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ double getMaxInclusive();
+
+ @JsProperty
+ double getMinInclusive();
+
+ @JsProperty
+ void setMaxInclusive(double maxInclusive);
+
+ @JsProperty
+ void setMinInclusive(double minInclusive);
+ }
+
+ public static native DoubleRangeRestriction deserializeBinary(Uint8Array bytes);
+
+ public static native DoubleRangeRestriction deserializeBinaryFromReader(
+ DoubleRangeRestriction message, Object reader);
+
+ public static native void serializeBinaryToWriter(DoubleRangeRestriction message, Object writer);
+
+ public static native DoubleRangeRestriction.ToObjectReturnType toObject(
+ boolean includeInstance, DoubleRangeRestriction msg);
+
+ public native void clearMaxInclusive();
+
+ public native void clearMinInclusive();
+
+ public native double getMaxInclusive();
+
+ public native double getMinInclusive();
+
+ public native boolean hasMaxInclusive();
+
+ public native boolean hasMinInclusive();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setMaxInclusive(double value);
+
+ public native void setMinInclusive(double value);
+
+ public native DoubleRangeRestriction.ToObjectReturnType0 toObject();
+
+ public native DoubleRangeRestriction.ToObjectReturnType0 toObject(boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/InputTableColumnInfo.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/InputTableColumnInfo.java
new file mode 100644
index 00000000000..a18790b13ae
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/inputtable_pb/InputTableColumnInfo.java
@@ -0,0 +1,245 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.inputtable_pb;
+
+import elemental2.core.JsArray;
+import elemental2.core.Uint8Array;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.inputtable_pb.inputtablecolumninfo.KindMap;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.inputtable_pb.InputTableColumnInfo",
+ namespace = JsPackage.GLOBAL)
+public class InputTableColumnInfo {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface RestrictionsListFieldType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetValueUnionType {
+ @JsOverlay
+ static InputTableColumnInfo.ToObjectReturnType.RestrictionsListFieldType.GetValueUnionType of(
+ Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsOverlay
+ static InputTableColumnInfo.ToObjectReturnType.RestrictionsListFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ String getTypeUrl();
+
+ @JsProperty
+ InputTableColumnInfo.ToObjectReturnType.RestrictionsListFieldType.GetValueUnionType getValue();
+
+ @JsProperty
+ void setTypeUrl(String typeUrl);
+
+ @JsProperty
+ void setValue(
+ InputTableColumnInfo.ToObjectReturnType.RestrictionsListFieldType.GetValueUnionType value);
+
+ @JsOverlay
+ default void setValue(String value) {
+ setValue(
+ Js.uncheckedCast(
+ value));
+ }
+
+ @JsOverlay
+ default void setValue(Uint8Array value) {
+ setValue(
+ Js.uncheckedCast(
+ value));
+ }
+ }
+
+ @JsOverlay
+ static InputTableColumnInfo.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ double getKind();
+
+ @JsProperty
+ JsArray getRestrictionsList();
+
+ @JsProperty
+ void setKind(double kind);
+
+ @JsProperty
+ void setRestrictionsList(
+ JsArray restrictionsList);
+
+ @JsOverlay
+ default void setRestrictionsList(
+ InputTableColumnInfo.ToObjectReturnType.RestrictionsListFieldType[] restrictionsList) {
+ setRestrictionsList(
+ Js.>uncheckedCast(
+ restrictionsList));
+ }
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface RestrictionsListFieldType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetValueUnionType {
+ @JsOverlay
+ static InputTableColumnInfo.ToObjectReturnType0.RestrictionsListFieldType.GetValueUnionType of(
+ Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsOverlay
+ static InputTableColumnInfo.ToObjectReturnType0.RestrictionsListFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ String getTypeUrl();
+
+ @JsProperty
+ InputTableColumnInfo.ToObjectReturnType0.RestrictionsListFieldType.GetValueUnionType getValue();
+
+ @JsProperty
+ void setTypeUrl(String typeUrl);
+
+ @JsProperty
+ void setValue(
+ InputTableColumnInfo.ToObjectReturnType0.RestrictionsListFieldType.GetValueUnionType value);
+
+ @JsOverlay
+ default void setValue(String value) {
+ setValue(
+ Js.uncheckedCast(
+ value));
+ }
+
+ @JsOverlay
+ default void setValue(Uint8Array value) {
+ setValue(
+ Js.uncheckedCast(
+ value));
+ }
+ }
+
+ @JsOverlay
+ static InputTableColumnInfo.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ double getKind();
+
+ @JsProperty
+ JsArray getRestrictionsList();
+
+ @JsProperty
+ void setKind(double kind);
+
+ @JsProperty
+ void setRestrictionsList(
+ JsArray restrictionsList);
+
+ @JsOverlay
+ default void setRestrictionsList(
+ InputTableColumnInfo.ToObjectReturnType0.RestrictionsListFieldType[] restrictionsList) {
+ setRestrictionsList(
+ Js.>uncheckedCast(
+ restrictionsList));
+ }
+ }
+
+ public static KindMap Kind;
+
+ public static native InputTableColumnInfo deserializeBinary(Uint8Array bytes);
+
+ public static native InputTableColumnInfo deserializeBinaryFromReader(
+ InputTableColumnInfo message, Object reader);
+
+ public static native void serializeBinaryToWriter(InputTableColumnInfo message, Object writer);
+
+ public static native InputTableColumnInfo.ToObjectReturnType toObject(
+ boolean includeInstance, InputTableColumnInfo msg);
+
+ public native Object addRestrictions();
+
+ public native Object addRestrictions(Object value, double index);
+
+ public native Object addRestrictions(Object value);
+
+ public native void clearRestrictionsList();
+
+ public native double getKind();
+
+ public native JsArray
- *
- *
- * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
- * be changed or removed at any time.
- *
*/
@TestUseOnly
public class NonEmptyValidatingInputTable extends AbstractBaseValidatingInputTable {
diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/NotNullValidatingInputTable.java b/server/src/main/java/io/deephaven/server/table/inputtables/NotNullValidatingInputTable.java
index c9dd68078c8..19d04ed9996 100644
--- a/server/src/main/java/io/deephaven/server/table/inputtables/NotNullValidatingInputTable.java
+++ b/server/src/main/java/io/deephaven/server/table/inputtables/NotNullValidatingInputTable.java
@@ -24,11 +24,6 @@
* This class wraps an existing input table, and before performing the underlying validation performs its own validation
* that the column does not contain null values.
*
- *
- *
- * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
- * be changed or removed at any time.
- *
*/
@TestUseOnly
public class NotNullValidatingInputTable extends AbstractBaseValidatingInputTable {
diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/RangeValidatingInputTable.java b/server/src/main/java/io/deephaven/server/table/inputtables/RangeValidatingInputTable.java
index 027aa700aed..95dfbd7483b 100644
--- a/server/src/main/java/io/deephaven/server/table/inputtables/RangeValidatingInputTable.java
+++ b/server/src/main/java/io/deephaven/server/table/inputtables/RangeValidatingInputTable.java
@@ -25,11 +25,6 @@
* This class wraps an existing input table, and before performing the underlying validation performs its own validation
* on the range of the column.
*
- *
- *
- * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
- * be changed or removed at any time.
- *
*/
@TestUseOnly
public class RangeValidatingInputTable extends AbstractBaseValidatingInputTable {
diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/StringListValidatingInputTable.java b/server/src/main/java/io/deephaven/server/table/inputtables/StringListValidatingInputTable.java
index 100e13360e4..180a3922b38 100644
--- a/server/src/main/java/io/deephaven/server/table/inputtables/StringListValidatingInputTable.java
+++ b/server/src/main/java/io/deephaven/server/table/inputtables/StringListValidatingInputTable.java
@@ -26,11 +26,6 @@
* This class wraps an existing input table, and before performing the underlying validation performs its own validation
* that the column values are in the allowed set (or null).
*
- *
- *
- * This class is intended for testing and demonstrating validation functionality, it is not production ready and may
- * be changed or removed at any time.
- *
*/
@TestUseOnly
public class StringListValidatingInputTable extends AbstractBaseValidatingInputTable {
From 28f86d928c7a8336bb5162926b53acc51027a044 Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 29 Apr 2026 09:14:04 -0600
Subject: [PATCH 29/35] groovy doc update
---
docs/groovy/how-to-guides/input-tables.md | 75 +++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/docs/groovy/how-to-guides/input-tables.md b/docs/groovy/how-to-guides/input-tables.md
index 6e3cf58e4aa..ba5c1075abb 100644
--- a/docs/groovy/how-to-guides/input-tables.md
+++ b/docs/groovy/how-to-guides/input-tables.md
@@ -172,6 +172,81 @@ result = AppendOnlyArrayBackedInputTable.make(definition)

+## Input table validators
+
+Input table validators allow you to add validation rules to input tables, ensuring that data entered (either programmatically or manually through the UI) meets specific criteria. Validators wrap an existing input table and check data before it's added, throwing validation exceptions if the data doesn't meet the requirements.
+
+Deephaven provides several built-in validators:
+
+- **RangeValidatingInputTable** - Validates that integer values fall within a specified range (min/max inclusive)
+- **DoubleRangeValidatingInputTable** - Validates that double values fall within a specified range (min/max inclusive)
+- **NotNullValidatingInputTable** - Validates that values in a column are not null
+- **NonEmptyValidatingInputTable** - Validates that string values are not empty
+- **StringListValidatingInputTable** - Validates that string values belong to a predefined set of allowed values
+
+### Creating validated input tables
+
+To create a validated input table, first create a base input table, then wrap it with one or more validators. Here's an example showing all available validators:
+
+```groovy order=intRangeValidator,doubleRangeValidator,notNullValidator,notNullValidatorInt,nonEmptyValidator,stringListValidator
+import io.deephaven.engine.table.impl.util.KeyedArrayBackedInputTable
+import io.deephaven.server.table.inputtables.RangeValidatingInputTable
+import io.deephaven.server.table.inputtables.DoubleRangeValidatingInputTable
+import io.deephaven.server.table.inputtables.NotNullValidatingInputTable
+import io.deephaven.server.table.inputtables.NonEmptyValidatingInputTable
+import io.deephaven.server.table.inputtables.StringListValidatingInputTable
+
+// Create source table with various column types
+_source = newTable(
+ stringCol("Key", "Apple", "Banana", "Carrot", "Date", "Eggplant"),
+ intCol("IntValue", 1, 2, 3, 50, 75),
+ doubleCol("DoubleValue", 1.5, 2.5, 3.5, 50.5, 75.5),
+ stringCol("Category", "Fruit", "Fruit", "Vegetable", "Fruit", "Vegetable"),
+ stringCol("Description", "Red", "Yellow", "Orange", "Sweet", "Purple")
+)
+
+// Example 1: Integer Range Validator (0-100)
+intRangeValidator = RangeValidatingInputTable.make(
+ KeyedArrayBackedInputTable.make(_source, "Key"),
+ "IntValue",
+ 0,
+ 100
+)
+
+// Example 2: Double Range Validator (0.0-100.0)
+doubleRangeValidator = DoubleRangeValidatingInputTable.make(
+ KeyedArrayBackedInputTable.make(_source, "Key"),
+ "DoubleValue",
+ 0.0,
+ 100.0
+)
+
+// Example 3: Not Null Validator on Category column
+notNullValidator = NotNullValidatingInputTable.make(
+ KeyedArrayBackedInputTable.make(_source, "Key"),
+ "Category"
+)
+
+// Example 3.1: Not Null Validator on IntValue column
+notNullValidatorInt = NotNullValidatingInputTable.make(
+ KeyedArrayBackedInputTable.make(_source, "Key"),
+ "IntValue"
+)
+
+// Example 4: Non-Empty Validator on Description column
+nonEmptyValidator = NonEmptyValidatingInputTable.make(
+ KeyedArrayBackedInputTable.make(_source, "Key"),
+ "Description"
+)
+
+// Example 5: String List Validator - Category must be "Fruit", "Vegetable", or "Grain"
+stringListValidator = StringListValidatingInputTable.make(
+ KeyedArrayBackedInputTable.make(_source, "Key"),
+ "Category",
+ "Fruit", "Vegetable", "Grain"
+)
+```
+
## Related documentation
- [Input Table](../reference/table-operations/create/InputTable.md)
From a9394c58b32c0c8fdb572d55edeaf9f497258a44 Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 29 Apr 2026 09:16:46 -0600
Subject: [PATCH 30/35] python docs
---
docs/python/how-to-guides/input-tables.md | 79 +++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/docs/python/how-to-guides/input-tables.md b/docs/python/how-to-guides/input-tables.md
index 18b06063343..b6da9c798e8 100644
--- a/docs/python/how-to-guides/input-tables.md
+++ b/docs/python/how-to-guides/input-tables.md
@@ -222,6 +222,85 @@ result = input_table(col_defs=my_col_defs)

+## Input table validators
+
+Input table validators allow you to add validation rules to input tables, ensuring that data entered (either programmatically or manually through the UI) meets specific criteria. Validators wrap an existing input table and check data before it's added, throwing validation exceptions if the data doesn't meet the requirements.
+
+Deephaven provides several built-in validators:
+
+- **RangeValidatingInputTable** - Validates that integer values fall within a specified range (min/max inclusive)
+- **DoubleRangeValidatingInputTable** - Validates that double values fall within a specified range (min/max inclusive)
+- **NotNullValidatingInputTable** - Validates that values in a column are not null
+- **NonEmptyValidatingInputTable** - Validates that string values are not empty
+- **StringListValidatingInputTable** - Validates that string values belong to a predefined set of allowed values
+
+### Creating validated input tables
+
+To create a validated input table, first create a base input table, then wrap it with one or more validators. Here's an example showing all available validators:
+
+```python order=intRangeValidator,doubleRangeValidator,notNullValidator,notNullValidatorInt,nonEmptyValidator,stringListValidator
+from deephaven import new_table, input_table
+from deephaven.column import string_col, int_col, double_col
+
+# Create source table with various column types
+_source = new_table([
+ string_col("Key", ["Apple", "Banana", "Carrot", "Date", "Eggplant"]),
+ int_col("IntValue", [1, 2, 3, 50, 75]),
+ double_col("DoubleValue", [1.5, 2.5, 3.5, 50.5, 75.5]),
+ string_col("Category", ["Fruit", "Fruit", "Vegetable", "Fruit", "Vegetable"]),
+ string_col("Description", ["Red", "Yellow", "Orange", "Sweet", "Purple"])
+])
+
+# Import Java classes for validators (not available in Python API yet)
+import jpy
+RangeValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.RangeValidatingInputTable")
+DoubleRangeValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.DoubleRangeValidatingInputTable")
+NotNullValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.NotNullValidatingInputTable")
+NonEmptyValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.NonEmptyValidatingInputTable")
+StringListValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.StringListValidatingInputTable")
+
+# Example 1: Integer Range Validator (0-100)
+intRangeValidator = RangeValidatingInputTable.make(
+ input_table(init_table=_source, key_cols="Key").j_table,
+ "IntValue",
+ 0,
+ 100
+)
+
+# Example 2: Double Range Validator (0.0-100.0)
+doubleRangeValidator = DoubleRangeValidatingInputTable.make(
+ input_table(init_table=_source, key_cols="Key").j_table,
+ "DoubleValue",
+ 0.0,
+ 100.0
+)
+
+# Example 3: Not Null Validator on Category column
+notNullValidator = NotNullValidatingInputTable.make(
+ input_table(init_table=_source, key_cols="Key").j_table,
+ "Category"
+)
+
+# Example 3.1: Not Null Validator on IntValue column
+notNullValidatorInt = NotNullValidatingInputTable.make(
+ input_table(init_table=_source, key_cols="Key").j_table,
+ "IntValue"
+)
+
+# Example 4: Non-Empty Validator on Description column
+nonEmptyValidator = NonEmptyValidatingInputTable.make(
+ input_table(init_table=_source, key_cols="Key").j_table,
+ "Description"
+)
+
+# Example 5: String List Validator - Category must be "Fruit" or "Vegetable"
+stringListValidator = StringListValidatingInputTable.make(
+ input_table(init_table=_source, key_cols="Key").j_table,
+ "Category",
+ "Fruit", "Vegetable", "Grain"
+)
+```
+
## Related documentation
- [`input_table`](../reference/table-operations/create/input-table.md)
From 4c21aa593874a58b33dad3953dbca3f11cdf6cd7 Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 29 Apr 2026 09:19:13 -0600
Subject: [PATCH 31/35] fix case
---
docs/python/how-to-guides/input-tables.md | 38 +++++++++++------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/docs/python/how-to-guides/input-tables.md b/docs/python/how-to-guides/input-tables.md
index b6da9c798e8..c7fe5bc59ab 100644
--- a/docs/python/how-to-guides/input-tables.md
+++ b/docs/python/how-to-guides/input-tables.md
@@ -238,12 +238,12 @@ Deephaven provides several built-in validators:
To create a validated input table, first create a base input table, then wrap it with one or more validators. Here's an example showing all available validators:
-```python order=intRangeValidator,doubleRangeValidator,notNullValidator,notNullValidatorInt,nonEmptyValidator,stringListValidator
+```python order=int_range_validator,double_range_validator,not_null_validator,not_null_validator_int,non_empty_validator,string_list_validator
from deephaven import new_table, input_table
from deephaven.column import string_col, int_col, double_col
# Create source table with various column types
-_source = new_table([
+source = new_table([
string_col("Key", ["Apple", "Banana", "Carrot", "Date", "Eggplant"]),
int_col("IntValue", [1, 2, 3, 50, 75]),
double_col("DoubleValue", [1.5, 2.5, 3.5, 50.5, 75.5]),
@@ -253,49 +253,49 @@ _source = new_table([
# Import Java classes for validators (not available in Python API yet)
import jpy
-RangeValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.RangeValidatingInputTable")
-DoubleRangeValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.DoubleRangeValidatingInputTable")
-NotNullValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.NotNullValidatingInputTable")
-NonEmptyValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.NonEmptyValidatingInputTable")
-StringListValidatingInputTable = jpy.get_type("io.deephaven.server.table.inputtables.StringListValidatingInputTable")
+range_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.RangeValidatingInputTable")
+double_range_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.DoubleRangeValidatingInputTable")
+not_null_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.NotNullValidatingInputTable")
+non_empty_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.NonEmptyValidatingInputTable")
+string_list_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.StringListValidatingInputTable")
# Example 1: Integer Range Validator (0-100)
-intRangeValidator = RangeValidatingInputTable.make(
- input_table(init_table=_source, key_cols="Key").j_table,
+int_range_validator = range_validating_input_table.make(
+ input_table(init_table=source, key_cols="Key").j_table,
"IntValue",
0,
100
)
# Example 2: Double Range Validator (0.0-100.0)
-doubleRangeValidator = DoubleRangeValidatingInputTable.make(
- input_table(init_table=_source, key_cols="Key").j_table,
+double_range_validator = double_range_validating_input_table.make(
+ input_table(init_table=source, key_cols="Key").j_table,
"DoubleValue",
0.0,
100.0
)
# Example 3: Not Null Validator on Category column
-notNullValidator = NotNullValidatingInputTable.make(
- input_table(init_table=_source, key_cols="Key").j_table,
+not_null_validator = not_null_validating_input_table.make(
+ input_table(init_table=source, key_cols="Key").j_table,
"Category"
)
# Example 3.1: Not Null Validator on IntValue column
-notNullValidatorInt = NotNullValidatingInputTable.make(
- input_table(init_table=_source, key_cols="Key").j_table,
+not_null_validator_int = not_null_validating_input_table.make(
+ input_table(init_table=source, key_cols="Key").j_table,
"IntValue"
)
# Example 4: Non-Empty Validator on Description column
-nonEmptyValidator = NonEmptyValidatingInputTable.make(
- input_table(init_table=_source, key_cols="Key").j_table,
+non_empty_validator = non_empty_validating_input_table.make(
+ input_table(init_table=source, key_cols="Key").j_table,
"Description"
)
# Example 5: String List Validator - Category must be "Fruit" or "Vegetable"
-stringListValidator = StringListValidatingInputTable.make(
- input_table(init_table=_source, key_cols="Key").j_table,
+string_list_validator = string_list_validating_input_table.make(
+ input_table(init_table=source, key_cols="Key").j_table,
"Category",
"Fruit", "Vegetable", "Grain"
)
From 5b5b29116a446021f2c00bf95b1ad82f0247573a Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 29 Apr 2026 09:38:32 -0600
Subject: [PATCH 32/35] format
---
docs/python/how-to-guides/input-tables.md | 62 +++++++++++++----------
1 file changed, 34 insertions(+), 28 deletions(-)
diff --git a/docs/python/how-to-guides/input-tables.md b/docs/python/how-to-guides/input-tables.md
index c7fe5bc59ab..6e8571e4365 100644
--- a/docs/python/how-to-guides/input-tables.md
+++ b/docs/python/how-to-guides/input-tables.md
@@ -243,61 +243,67 @@ from deephaven import new_table, input_table
from deephaven.column import string_col, int_col, double_col
# Create source table with various column types
-source = new_table([
- string_col("Key", ["Apple", "Banana", "Carrot", "Date", "Eggplant"]),
- int_col("IntValue", [1, 2, 3, 50, 75]),
- double_col("DoubleValue", [1.5, 2.5, 3.5, 50.5, 75.5]),
- string_col("Category", ["Fruit", "Fruit", "Vegetable", "Fruit", "Vegetable"]),
- string_col("Description", ["Red", "Yellow", "Orange", "Sweet", "Purple"])
-])
+source = new_table(
+ [
+ string_col("Key", ["Apple", "Banana", "Carrot", "Date", "Eggplant"]),
+ int_col("IntValue", [1, 2, 3, 50, 75]),
+ double_col("DoubleValue", [1.5, 2.5, 3.5, 50.5, 75.5]),
+ string_col("Category", ["Fruit", "Fruit", "Vegetable", "Fruit", "Vegetable"]),
+ string_col("Description", ["Red", "Yellow", "Orange", "Sweet", "Purple"]),
+ ]
+)
# Import Java classes for validators (not available in Python API yet)
import jpy
-range_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.RangeValidatingInputTable")
-double_range_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.DoubleRangeValidatingInputTable")
-not_null_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.NotNullValidatingInputTable")
-non_empty_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.NonEmptyValidatingInputTable")
-string_list_validating_input_table = jpy.get_type("io.deephaven.server.table.inputtables.StringListValidatingInputTable")
+
+range_validating_input_table = jpy.get_type(
+ "io.deephaven.server.table.inputtables.RangeValidatingInputTable"
+)
+double_range_validating_input_table = jpy.get_type(
+ "io.deephaven.server.table.inputtables.DoubleRangeValidatingInputTable"
+)
+not_null_validating_input_table = jpy.get_type(
+ "io.deephaven.server.table.inputtables.NotNullValidatingInputTable"
+)
+non_empty_validating_input_table = jpy.get_type(
+ "io.deephaven.server.table.inputtables.NonEmptyValidatingInputTable"
+)
+string_list_validating_input_table = jpy.get_type(
+ "io.deephaven.server.table.inputtables.StringListValidatingInputTable"
+)
# Example 1: Integer Range Validator (0-100)
int_range_validator = range_validating_input_table.make(
- input_table(init_table=source, key_cols="Key").j_table,
- "IntValue",
- 0,
- 100
+ input_table(init_table=source, key_cols="Key").j_table, "IntValue", 0, 100
)
# Example 2: Double Range Validator (0.0-100.0)
double_range_validator = double_range_validating_input_table.make(
- input_table(init_table=source, key_cols="Key").j_table,
- "DoubleValue",
- 0.0,
- 100.0
+ input_table(init_table=source, key_cols="Key").j_table, "DoubleValue", 0.0, 100.0
)
# Example 3: Not Null Validator on Category column
not_null_validator = not_null_validating_input_table.make(
- input_table(init_table=source, key_cols="Key").j_table,
- "Category"
+ input_table(init_table=source, key_cols="Key").j_table, "Category"
)
# Example 3.1: Not Null Validator on IntValue column
not_null_validator_int = not_null_validating_input_table.make(
- input_table(init_table=source, key_cols="Key").j_table,
- "IntValue"
+ input_table(init_table=source, key_cols="Key").j_table, "IntValue"
)
# Example 4: Non-Empty Validator on Description column
non_empty_validator = non_empty_validating_input_table.make(
- input_table(init_table=source, key_cols="Key").j_table,
- "Description"
+ input_table(init_table=source, key_cols="Key").j_table, "Description"
)
# Example 5: String List Validator - Category must be "Fruit" or "Vegetable"
string_list_validator = string_list_validating_input_table.make(
input_table(init_table=source, key_cols="Key").j_table,
- "Category",
- "Fruit", "Vegetable", "Grain"
+ "Category",
+ "Fruit",
+ "Vegetable",
+ "Grain",
)
```
From 1d006226b74919f54f479803e1eccb9bbba7e2a3 Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 29 Apr 2026 09:48:07 -0600
Subject: [PATCH 33/35] update snapshots
---
docs/groovy/snapshots/52261de3cea59a05c348dcdee2078741.json | 1 +
docs/python/snapshots/1c93fc2136637c6bbc241e13ab956466.json | 1 +
2 files changed, 2 insertions(+)
create mode 100644 docs/groovy/snapshots/52261de3cea59a05c348dcdee2078741.json
create mode 100644 docs/python/snapshots/1c93fc2136637c6bbc241e13ab956466.json
diff --git a/docs/groovy/snapshots/52261de3cea59a05c348dcdee2078741.json b/docs/groovy/snapshots/52261de3cea59a05c348dcdee2078741.json
new file mode 100644
index 00000000000..18948727473
--- /dev/null
+++ b/docs/groovy/snapshots/52261de3cea59a05c348dcdee2078741.json
@@ -0,0 +1 @@
+{"file":"how-to-guides/input-tables.md","objects":{"_source":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"intRangeValidator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"doubleRangeValidator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"notNullValidator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"notNullValidatorInt":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"nonEmptyValidator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"stringListValidator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}}}}
\ No newline at end of file
diff --git a/docs/python/snapshots/1c93fc2136637c6bbc241e13ab956466.json b/docs/python/snapshots/1c93fc2136637c6bbc241e13ab956466.json
new file mode 100644
index 00000000000..cab4e621e40
--- /dev/null
+++ b/docs/python/snapshots/1c93fc2136637c6bbc241e13ab956466.json
@@ -0,0 +1 @@
+{"file":"how-to-guides/input-tables.md","objects":{"source":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"int_range_validator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"double_range_validator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"not_null_validator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"not_null_validator_int":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"non_empty_validator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}},"string_list_validator":{"type":"Table","data":{"columns":[{"name":"Key","type":"java.lang.String"},{"name":"IntValue","type":"int"},{"name":"DoubleValue","type":"double"},{"name":"Category","type":"java.lang.String"},{"name":"Description","type":"java.lang.String"}],"rows":[[{"value":"Apple"},{"value":"1"},{"value":"1.5000"},{"value":"Fruit"},{"value":"Red"}],[{"value":"Banana"},{"value":"2"},{"value":"2.5000"},{"value":"Fruit"},{"value":"Yellow"}],[{"value":"Carrot"},{"value":"3"},{"value":"3.5000"},{"value":"Vegetable"},{"value":"Orange"}],[{"value":"Date"},{"value":"50"},{"value":"50.5000"},{"value":"Fruit"},{"value":"Sweet"}],[{"value":"Eggplant"},{"value":"75"},{"value":"75.5000"},{"value":"Vegetable"},{"value":"Purple"}]]}}}}
\ No newline at end of file
From 136a5b44646d16aef3601dd0141ffcfe9ca565c8 Mon Sep 17 00:00:00 2001
From: dgodinez-dh
Date: Wed, 29 Apr 2026 13:03:14 -0600
Subject: [PATCH 34/35] Apply suggestions from code review
Co-authored-by: margaretkennedy <82049573+margaretkennedy@users.noreply.github.com>
---
docs/groovy/how-to-guides/input-tables.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/groovy/how-to-guides/input-tables.md b/docs/groovy/how-to-guides/input-tables.md
index ba5c1075abb..489b23149f4 100644
--- a/docs/groovy/how-to-guides/input-tables.md
+++ b/docs/groovy/how-to-guides/input-tables.md
@@ -178,11 +178,11 @@ Input table validators allow you to add validation rules to input tables, ensuri
Deephaven provides several built-in validators:
-- **RangeValidatingInputTable** - Validates that integer values fall within a specified range (min/max inclusive)
-- **DoubleRangeValidatingInputTable** - Validates that double values fall within a specified range (min/max inclusive)
-- **NotNullValidatingInputTable** - Validates that values in a column are not null
-- **NonEmptyValidatingInputTable** - Validates that string values are not empty
-- **StringListValidatingInputTable** - Validates that string values belong to a predefined set of allowed values
+- **`RangeValidatingInputTable`** - Validates that integer values fall within a specified range (min/max inclusive).
+- **`DoubleRangeValidatingInputTable`** - Validates that double values fall within a specified range (min/max inclusive).
+- **`NotNullValidatingInputTable`** - Validates that values in a column are not null.
+- **`NonEmptyValidatingInputTable`** - Validates that string values are not empty.
+- **`StringListValidatingInputTable`** - Validates that string values belong to a predefined set of allowed values.
### Creating validated input tables
From 3fe5ae79867e541cdb4d5fa2419d08a46ac4d2fe Mon Sep 17 00:00:00 2001
From: davidgodinez
Date: Wed, 29 Apr 2026 13:06:52 -0600
Subject: [PATCH 35/35] python doc style update
---
docs/python/how-to-guides/input-tables.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/python/how-to-guides/input-tables.md b/docs/python/how-to-guides/input-tables.md
index 6e8571e4365..028015e0b49 100644
--- a/docs/python/how-to-guides/input-tables.md
+++ b/docs/python/how-to-guides/input-tables.md
@@ -228,11 +228,11 @@ Input table validators allow you to add validation rules to input tables, ensuri
Deephaven provides several built-in validators:
-- **RangeValidatingInputTable** - Validates that integer values fall within a specified range (min/max inclusive)
-- **DoubleRangeValidatingInputTable** - Validates that double values fall within a specified range (min/max inclusive)
-- **NotNullValidatingInputTable** - Validates that values in a column are not null
-- **NonEmptyValidatingInputTable** - Validates that string values are not empty
-- **StringListValidatingInputTable** - Validates that string values belong to a predefined set of allowed values
+- **`RangeValidatingInputTable`** - Validates that integer values fall within a specified range (min/max inclusive).
+- **`DoubleRangeValidatingInputTable`** - Validates that double values fall within a specified range (min/max inclusive).
+- **`NotNullValidatingInputTable`** - Validates that values in a column are not null.
+- **`NonEmptyValidatingInputTable`** - Validates that string values are not empty.
+- **`StringListValidatingInputTable`** - Validates that string values belong to a predefined set of allowed values.
### Creating validated input tables