diff --git a/docs/groovy/assets/conceptual/remote_and_local_server.png b/docs/groovy/assets/conceptual/remote_and_local_server.png new file mode 100644 index 00000000000..83066a8032e --- /dev/null +++ b/docs/groovy/assets/conceptual/remote_and_local_server.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05c6414d4dee0befd25d64b415465bc752db84494710f96773b925f026200688 +size 144784 diff --git a/docs/groovy/conceptual/what-is-barrage.md b/docs/groovy/conceptual/what-is-barrage.md new file mode 100644 index 00000000000..a598cdc4eea --- /dev/null +++ b/docs/groovy/conceptual/what-is-barrage.md @@ -0,0 +1,128 @@ +--- +title: What is Barrage? +--- + +Barrage is Deephaven's streaming data protocol for efficient, real-time table transport between servers and clients. Built on top of [Apache Arrow Flight](https://arrow.apache.org/docs/format/Flight.html), Barrage extends the standard Flight protocol to support Deephaven's [incremental update model](./table-update-model.md)—enabling clients to receive only the data that has changed rather than entire table snapshots. + +## Why Barrage? + +When working with real-time data, traditional approaches have significant limitations: + +- **Polling**: Repeatedly fetching entire tables wastes bandwidth and CPU cycles, especially for large tables with small changes. +- **Standard Flight**: Apache Arrow Flight provides efficient data transport, but lacks native support for incrementally updating datasets. + +Barrage solves these problems by: + +- Streaming only the rows that have been added, removed, or modified. +- Supporting viewports so clients can subscribe to just the visible portion of a table. +- Batching updates to balance latency against network efficiency. + +## When to use Barrage + +| Use Case | Barrage Feature | +| ------------------------------------------------------------------------------- | ---------------------------------------- | +| **Real-time dashboards**: Display live-updating tables in a UI | Subscribe to streaming updates | +| **Server-to-server data sharing**: Move tables between Deephaven instances | Subscribe or snapshot via shared tickets | +| **Point-in-time analysis**: Capture table state for offline processing | Snapshot to get a static copy | +| **Large table visualization**: Display a scrollable view of a million-row table | Viewports (via web UI or JS client) | + +## Key concepts + +### Subscriptions vs. snapshots + +Barrage supports two primary modes of retrieving data: + +- **Subscription**: Opens a persistent connection that streams updates as the source table changes. The client receives an initial snapshot followed by incremental updates. Use this for ticking tables you want to monitor in real time. + +- **Snapshot**: Retrieves a one-time, static copy of the table. The connection closes after the data is delivered. Use this for static tables or when you need a point-in-time capture. + +```python skip-test +from deephaven.barrage import barrage_session + +session = barrage_session(host="remote-server", port=10000) + +# Subscription: receives ongoing updates +streaming_table = session.subscribe(ticket_bytes) + +# Snapshot: one-time static copy +static_table = session.snapshot(ticket_bytes) +``` + +### Shared tickets + +Shared tickets are endpoints that allow tables to be published and consumed across different sessions. A client can publish a table to a shared ticket, and other clients (or servers) can subscribe to or snapshot that ticket. + +```python skip-test +from pydeephaven import Session +from pydeephaven.ticket import SharedTicket + +# Publish a table to a shared ticket +client = Session(host="remote-server", port=10000) +ticket = SharedTicket.random_ticket() +client.publish_table(ticket, my_table_ref) +``` + +See [Capture Python client tables](../how-to-guides/capture-tables.md) for complete examples. + +### Viewports + +A viewport defines a window over a table — a range of row positions and a subset of columns. Viewports are essential for interactive applications where users scroll through large tables. Rather than streaming millions of rows, the server sends only the data visible in the current view. + +Viewports are automatically managed by Deephaven's web UI and JavaScript client. When a user scrolls or resizes a table view, the client updates its viewport subscription accordingly. + +> [!NOTE] +> The Python `BarrageSession` API currently supports full-table subscriptions. Viewport functionality is available through the JavaScript client or by subscribing to pre-filtered tables. +> The Python `BarrageSession` API currently supports full-table subscriptions. Viewport functionality is available through the JavaScript client. + +### Update intervals and batching + +Barrage aggregates table updates before sending them to subscribers. This batching reduces network overhead when tables update frequently. The update interval is configurable: + +- **Server default**: Set via `-Dbarrage.minUpdateInterval` (milliseconds). Default: 1000 (1 second). +- **Per-subscription**: Configurable when initiating the subscription (advanced use). + +A shorter interval reduces latency but increases network traffic. A longer interval reduces traffic but introduces delay. + +## Architecture overview + +![Barrage architecture](../assets/conceptual/remote_and_local_server.png) + +1. **Remote server** hosts a table referenced by a ticket — the ticket is just a reference, not the data itself. Tickets can be scope tickets (variables in the global scope), export tickets, or shared tickets for cross-session access. +2. **Barrage protocol** transports the actual data using Arrow Flight with incremental update metadata. +3. **Local server** subscribes via `BarrageSession` and receives a full local copy of the data that stays synchronized with the source. This local table can participate in downstream queries (joins, filters, aggregations) that execute on the local server. + +> [!NOTE] +> Java and Groovy clients are unique among Deephaven clients — they can perform downstream computation locally because they run the full Deephaven engine. Other clients (e.g., JavaScript, C++, and Python) receive data but rely on the server for query execution. + +## Barrage vs. Arrow Flight + +| Feature | Standard Arrow Flight | Barrage | +| ------------------------ | --------------------- | --------------------- | +| Data format | Arrow columnar format | Arrow columnar format | +| Static table transfer | ✅ Supported | ✅ Supported | +| Incremental updates | ❌ Not supported | ✅ Native support | +| Viewports | ❌ Not supported | ✅ Supported | +| Update batching | ❌ N/A | ✅ Configurable | +| Row shifts/modifications | ❌ N/A | ✅ Efficient encoding | + +Barrage is fully compatible with Arrow Flight — you can use a standard Flight client to fetch static snapshots via `DoGet`. The incremental update features require a Barrage-aware client. + +## Performance considerations + +- **Large initial snapshots**: When subscribing to a large table, the initial snapshot can be memory-intensive. Use [subscription growth controls](../how-to-guides/performance/barrage-performance.md#control-subscription-snapshot-size) to break large snapshots into smaller chunks. + +- **High-frequency updates**: Tables that tick rapidly can generate significant network traffic. Consider increasing `barrage.minUpdateInterval` or filtering data before subscription. + +- **Column selection**: Subscribe only to the columns you need. Fewer columns means less data to transfer. + +- **Monitoring**: Use the [Barrage performance tables](../how-to-guides/performance/barrage-performance.md) to track subscription health and identify bottlenecks. + +## Related documentation + +- [Capture Python client tables](../how-to-guides/capture-tables.md) - Complete tutorial for using Barrage with the Python client +- [Barrage metrics](../how-to-guides/performance/barrage-performance.md) - Monitor Barrage performance +- [Interpret Barrage metrics](./barrage-metrics.md) - Understand what the metrics mean +- [Barrage schema annotation](../how-to-guides/data-import-export/barrage-schema.md) - Annotate schemas for complex types +- [Incremental update model](./table-update-model.md) - How Deephaven represents table changes +- [Core API design](./deephaven-core-api.md) - Technical details on the Deephaven API +- [Barrage protocol documentation](/barrage/docs) - Low-level wire format reference diff --git a/docs/groovy/how-to-guides/capture-tables.md b/docs/groovy/how-to-guides/capture-tables.md index 98c446ecca6..6966ea4ebb8 100644 --- a/docs/groovy/how-to-guides/capture-tables.md +++ b/docs/groovy/how-to-guides/capture-tables.md @@ -119,6 +119,76 @@ local_t_static = my_barrage_session.snapshot(ticket.bytes) Voila! You now have _real_ Deephaven server tables called `local_t_streaming` and `local_t_static`. These are not just references to Deephaven tables - they are _real_ Deephaven server tables that can be used in any Deephaven query. +## Subscription lifecycle management + +Understanding when and how to manage Barrage subscriptions helps you build efficient, reliable applications. + +### Choose between subscribe and snapshot + +Use **subscribe** when: + +- You need real-time updates as the source table changes. +- You're building a live dashboard or monitoring system. +- The source table is ticking (updating periodically). + +Use **snapshot** when: + +- You need a one-time, static copy of the data. +- The source table is static and won't change. +- You want to capture a point-in-time state for analysis. +- You need to reduce ongoing resource consumption. + +### Subscription resource usage + +Each active subscription consumes resources on both the server and client: + +| Resource | Server Impact | Client Impact | +| -------- | ------------------------------------------------ | ------------------------------------------------- | +| Memory | Maintains subscriber state and pending updates | Stores table data and applies incremental updates | +| CPU | Aggregates and serializes updates per subscriber | Deserializes and processes incoming updates | +| Network | Sends periodic update batches to each subscriber | Receives and buffers incoming data | + +For tables with frequent updates or many subscribers, these costs can add up. Monitor subscription health using the [Barrage performance tables](./performance/barrage-performance.md). + +### Close subscriptions when finished + +When you no longer need real-time updates, close the Barrage session to release resources: + +```python skip-test +# Close the session when done +my_barrage_session.close() +``` + +If you need to keep the session open for other subscriptions but want to release a specific table, you can drop the reference to the subscribed table. However, the underlying subscription may remain active until the session is closed. + +### Handle connection issues + +Barrage subscriptions can be affected by network interruptions. Consider these patterns for production applications: + +- **Reconnection**: If the session disconnects, you'll need to create a new `barrage_session` and resubscribe. The remote table must still be published to the same shared ticket. + +- **Ticket lifetime**: Shared tickets remain valid as long as the publishing session is active. If the publishing session closes, the ticket becomes invalid and subscribers will lose their connection. + +- **Authentication expiry**: If using authenticated connections, ensure tokens or credentials remain valid for the duration of long-running subscriptions. + +### Memory considerations for large tables + +When subscribing to large ticking tables: + +- **Initial snapshot size**: The first update contains a complete snapshot of the table. For very large tables, this can consume significant memory. The server breaks large snapshots into chunks by default (see [snapshot size control](./performance/barrage-performance.md#control-subscription-snapshot-size)). + +- **Incremental updates**: After the initial snapshot, only changed rows are transmitted. This is typically much smaller than the full table. + +- **Server-side filtering**: If you only need a subset of the data, consider filtering the table on the remote server before subscribing. This reduces both network and memory usage. (Note: this is distinct from viewports, which define a scrollable window over row positions.) + +```python skip-test +# On the remote server: filter before publishing +filtered_ref = client_session.open_table("large_table").where("Region = `EAST`") +client_session.publish_table(ticket, filtered_ref) + +# The subscriber now receives only the filtered data +``` + ## Related documentation diff --git a/docs/groovy/how-to-guides/data-import-export/barrage-schema.md b/docs/groovy/how-to-guides/data-import-export/barrage-schema.md index 9b6fd5979c2..8f48477bc95 100644 --- a/docs/groovy/how-to-guides/data-import-export/barrage-schema.md +++ b/docs/groovy/how-to-guides/data-import-export/barrage-schema.md @@ -1,6 +1,6 @@ --- -title: Barrage Schema Annotation -sidebar_label: Barrage Schema Annotation +title: Barrage schema annotation +sidebar_label: Barrage schema annotation --- Deephaven tables support Object-typed columns that can hold arbitrary Java objects. When exporting these tables over Flight using the Barrage format, Deephaven uses Apache Arrow schemas to describe the data. By default, if a column is typed as `Object`, the Arrow schema may not capture the intended structure of the data, which can lead to inefficient serialization or loss of type information. Use the `Table.BARRAGE_SCHEMA_ATTRIBUTE` to inject explicit Arrow schema information, which ensures that the Flight export uses the correct wire format. @@ -82,7 +82,7 @@ table_w_attributes = table.withAttributes(java.util.Map.of(Table.BARRAGE_SCHEMA_ ## Example: Annotate `Map` Columns -The following example creates a table with a column of `Map`. The Arrow schema annotates the column as an Arrow `Map` with the correct types for key and values. The final table can be exported over Flight / Barrage without error. +The following example creates a table with a column of `Map`. The Arrow schema annotates the column as an Arrow `Map` with the correct types for key and values. The final table can be exported over Flight / Barrage without error. ```groovy order=table,table_w_attributes // Table creation @@ -369,3 +369,9 @@ def new_schema = new Schema(fields) // Apply attributes, creating a new table reference which can be used for export; the original table is unchanged table_w_attributes = table.withAttributes(java.util.Map.of(Table.BARRAGE_SCHEMA_ATTRIBUTE, new_schema)) ``` + +## Related documentation + +- [What is Barrage?](../../conceptual/what-is-barrage.md) +- [withAttributes](../../reference/table-operations/select/withAttributes.md) +- [Arrow Flight integration](./arrow-flight.md) diff --git a/docs/groovy/how-to-guides/performance/barrage-performance.md b/docs/groovy/how-to-guides/performance/barrage-performance.md index f273378758b..bac890d1014 100644 --- a/docs/groovy/how-to-guides/performance/barrage-performance.md +++ b/docs/groovy/how-to-guides/performance/barrage-performance.md @@ -82,6 +82,93 @@ Here are server-side flags that change the behavior of Barrage metrics. - `-DBarragePerformanceLog.enableAll`: record metrics for tables that do not have an explicit `TableKey` (default: `true`). - `-DBarragePerformanceLog.cycleDurationMillis`: the interval to flush aggregated statistics (default: `60000` - once per minute). +## Control subscription snapshot size + +When a client subscribes to a ticking table, the server sends an initial snapshot of the table data. For large tables, constructing this snapshot requires holding the data in memory on the server side, which can lead to out-of-memory (OOM) errors. + +To address this, Deephaven can break the initial snapshot into smaller chunks spread across multiple update cycles. This behavior is controlled by the following properties: + +- `-DBarrageMessageProducer.subscriptionGrowthEnabled`: When `true` (the default), the server limits the size of each snapshot chunk. When `false`, the server sends the entire snapshot at once (unlimited size). + +When subscription growth is enabled, these additional properties control the chunk size: + +- `-DBarrageUtil.minSnapshotCellCount`: The minimum number of cells (rows × columns) per snapshot chunk. Default: `8192`. +- `-DBarrageUtil.maxSnapshotCellCount`: The maximum number of cells per snapshot chunk. Default: `16777216` (approximately 16 million). + +The server adaptively adjusts the chunk size between these bounds based on how long each snapshot takes to generate, targeting a percentage of the update graph cycle time. + +### Reducing publisher memory usage + +For systems serving very large tables to many subscribers, you can reduce publisher-side memory usage by lowering `maxSnapshotCellCount`. Setting `minSnapshotCellCount` equal to `maxSnapshotCellCount` fixes the chunk size and disables adaptive sizing: + +```bash +-DBarrageMessageProducer.subscriptionGrowthEnabled=true +-DBarrageUtil.minSnapshotCellCount=1000000 +-DBarrageUtil.maxSnapshotCellCount=1000000 +``` + +This configuration limits each snapshot chunk to exactly 1 million cells — well below the 16 million default maximum. Subscribers receive the full table data incrementally over multiple update cycles rather than all at once. + +> [!NOTE] +> Setting smaller snapshot sizes increases the time required for subscribers to receive the initial table state but reduces peak memory usage on the server. These settings only affect initial snapshots — incremental updates are unaffected and must still be maintained in memory. + +## Additional Barrage configuration + +The following properties control other aspects of Barrage behavior: + +### Update interval + +- `-Dbarrage.minUpdateInterval`: The minimum interval (in milliseconds) between update batches sent to subscribers. Default: `1000` (1 second). Lower values reduce latency but increase CPU and network usage. + +### Message batching + +- `-DBarrageMessageWriterImpl.batchSize`: Maximum rows per Arrow record batch. Default: `Integer.MAX_VALUE`. Reduce this if clients have trouble processing very large batches. +- `-DBarrageMessageWriterImpl.initialBatchSize`: Initial batch size for the first message. Default: `4096`. A smaller initial batch ensures clients receive data quickly while the server calibrates optimal batch sizes. +- `-DBarrageMessageWriterImpl.maxOutboundMessageSize`: Maximum size (in bytes) for outbound messages. Default: `104857600` (100 MB). This matches the default incoming message limit for Java clients. + +## Troubleshooting + +Use the metrics tables described above to diagnose common Barrage issues. + +### High `SnapshotMillis` + +If `SnapshotMillis` is consistently high: + +- The source table may be very large. Consider using viewports or filtering data before subscription. +- The update graph may be holding a lock. Check for long-running operations blocking the cycle. +- Consider enabling subscription growth with smaller chunk sizes (see [Control subscription snapshot size](#control-subscription-snapshot-size)). + +### High `WriteMillis` or `WriteMegabits` + +If `WriteMillis` is high or `WriteMegabits` is large: + +- Network bandwidth may be saturated. Check network utilization. +- Consider subscribing to fewer columns or using viewports to reduce data volume. +- Increase `barrage.minUpdateInterval` to batch more updates together. + +### High `PropagateMillis` + +If `PropagateMillis` is consistently high: + +- Many subscribers may be connected to the same table. Consider load balancing across multiple server instances. +- The server may be under memory pressure. Check JVM heap usage and garbage collection metrics. + +### Subscription errors + +Common subscription issues: + +- **"Ticket not found"**: The table was released or the session that published it closed. Ensure the publishing session remains active. +- **Authentication failures**: Verify that the `auth_type` and `auth_token` match the server configuration. +- **Connection refused**: Ensure the server is running and the host/port are correct. Check firewall rules. + +### Memory issues + +If the server experiences out-of-memory errors during subscriptions: + +- Enable subscription growth: `-DBarrageMessageProducer.subscriptionGrowthEnabled=true` +- Lower snapshot cell counts to reduce peak memory usage. +- Monitor the metrics tables to identify which tables consume the most resources. + ## Related documentation - [Interpret Barrage metrics](../../conceptual/barrage-metrics.md) diff --git a/docs/groovy/sidebar.json b/docs/groovy/sidebar.json index 403a748b7a2..e5dd4a3b843 100644 --- a/docs/groovy/sidebar.json +++ b/docs/groovy/sidebar.json @@ -132,6 +132,10 @@ { "label": "Servers & clients", "path": "conceptual/client-server-model.md" + }, + { + "label": "What is Barrage?", + "path": "conceptual/what-is-barrage.md" } ] }, diff --git a/docs/python/assets/conceptual/remote_and_local_server.png b/docs/python/assets/conceptual/remote_and_local_server.png new file mode 100644 index 00000000000..83066a8032e --- /dev/null +++ b/docs/python/assets/conceptual/remote_and_local_server.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05c6414d4dee0befd25d64b415465bc752db84494710f96773b925f026200688 +size 144784 diff --git a/docs/python/conceptual/what-is-barrage.md b/docs/python/conceptual/what-is-barrage.md new file mode 100644 index 00000000000..f4e42458e6d --- /dev/null +++ b/docs/python/conceptual/what-is-barrage.md @@ -0,0 +1,128 @@ +--- +title: What is Barrage? +--- + +Barrage is Deephaven's streaming data protocol for efficient, real-time table transport between servers and clients. Built on top of [Apache Arrow Flight](https://arrow.apache.org/docs/format/Flight.html), Barrage extends the standard Flight protocol to support Deephaven's [incremental update model](./table-update-model.md)—enabling clients to receive only the data that has changed rather than entire table snapshots. + +## Why Barrage? + +When working with real-time data, traditional approaches have significant limitations: + +- **Polling**: Repeatedly fetching entire tables wastes bandwidth and CPU cycles, especially for large tables with small changes. +- **Standard Flight**: Apache Arrow Flight provides efficient data transport, but lacks native support for incrementally updating datasets. + +Barrage solves these problems by: + +- Streaming only the rows that have been added, removed, or modified. +- Supporting viewports so clients can subscribe to just the visible portion of a table. +- Batching updates to balance latency against network efficiency. + +## When to use Barrage + +| Use Case | Barrage Feature | +| ------------------------------------------------------------------------------- | ---------------------------------------- | +| **Real-time dashboards**: Display live-updating tables in a UI | Subscribe to streaming updates | +| **Server-to-server data sharing**: Move tables between Deephaven instances | Subscribe or snapshot via shared tickets | +| **Point-in-time analysis**: Capture table state for offline processing | Snapshot to get a static copy | +| **Large table visualization**: Display a scrollable view of a million-row table | Viewports (via web UI or JS client) | + +## Key concepts + +### Subscriptions vs. snapshots + +Barrage supports two primary modes of retrieving data: + +- **Subscription**: Opens a persistent connection that streams updates as the source table changes. The client receives an initial snapshot followed by incremental updates. Use this for ticking tables you want to monitor in real time. + +- **Snapshot**: Retrieves a one-time, static copy of the table. The connection closes after the data is delivered. Use this for static tables or when you need a point-in-time capture. + +```python skip-test +from deephaven.barrage import barrage_session + +session = barrage_session(host="remote-server", port=10000) + +# Subscription: receives ongoing updates +streaming_table = session.subscribe(ticket_bytes) + +# Snapshot: one-time static copy +static_table = session.snapshot(ticket_bytes) +``` + +### Shared tickets + +Shared tickets are endpoints that allow tables to be published and consumed across different sessions. A client can publish a table to a shared ticket, and other clients (or servers) can subscribe to or snapshot that ticket. + +```python skip-test +from pydeephaven import Session +from pydeephaven.ticket import SharedTicket + +# Publish a table to a shared ticket +client = Session(host="remote-server", port=10000) +ticket = SharedTicket.random_ticket() +client.publish_table(ticket, my_table_ref) +``` + +See [Capture Python client tables](../how-to-guides/capture-tables.md) for complete examples. + +### Viewports + +A viewport defines a window over a table — a range of row positions and a subset of columns. Viewports are essential for interactive applications where users scroll through large tables. Rather than streaming millions of rows, the server sends only the data visible in the current view. + +Viewports are automatically managed by Deephaven's web UI and JavaScript client. When a user scrolls or resizes a table view, the client updates its viewport subscription accordingly. + +> [!NOTE] +> The Python `BarrageSession` API currently supports full-table subscriptions. Viewport functionality is available through the JavaScript client or by subscribing to pre-filtered tables. + +### Update intervals and batching + +Barrage aggregates table updates before sending them to subscribers. This batching reduces network overhead when tables update frequently. The update interval is configurable: + +- **Server default**: Set via `-Dbarrage.minUpdateInterval` (milliseconds). Default: 1000 (1 second). +- **Per-subscription**: Configurable when initiating the subscription (advanced use). + +A shorter interval reduces latency but increases network traffic. A longer interval reduces traffic but introduces delay. + +## Architecture overview + +![Barrage architecture](../assets/conceptual/remote_and_local_server.png) + +1. **Remote server** hosts a table referenced by a ticket — the ticket is just a reference, not the data itself. Tickets can be scope tickets (variables in the global scope), export tickets, or shared tickets for cross-session access. +2. **Barrage protocol** transports the actual data using Arrow Flight with incremental update metadata. +3. **Local server** subscribes via `BarrageSession` and receives a full local copy of the data that stays synchronized with the source. This local table can participate in downstream queries (joins, filters, aggregations) that execute on the local server. + +> [!NOTE] +> Java and Groovy clients are unique among Deephaven clients — they can perform downstream computation locally because they run the full Deephaven engine. Other clients (Python via pydeephaven, JavaScript, C++) receive data but rely on the server for query execution. + +## Barrage vs. Arrow Flight + +| Feature | Standard Arrow Flight | Barrage | +| ------------------------ | --------------------- | --------------------- | +| Data format | Arrow columnar format | Arrow columnar format | +| Static table transfer | ✅ Supported | ✅ Supported | +| Incremental updates | ❌ Not supported | ✅ Native support | +| Viewports | ❌ Not supported | ✅ Supported | +| Update batching | ❌ N/A | ✅ Configurable | +| Row shifts/modifications | ❌ N/A | ✅ Efficient encoding | + +Barrage is fully compatible with Arrow Flight — you can use a standard Flight client to fetch static snapshots via `DoGet`. The incremental update features require a Barrage-aware client. + +## Performance considerations + +- **Large initial snapshots**: When subscribing to a large table, the initial snapshot can be memory-intensive. Use [subscription growth controls](../how-to-guides/performance/barrage-performance.md#control-subscription-snapshot-size) to break large snapshots into smaller chunks. + +- **High-frequency updates**: Tables that tick rapidly can generate significant network traffic. Consider increasing `barrage.minUpdateInterval` or filtering data before subscription. + +- **Column selection**: Subscribe only to the columns you need. Fewer columns means less data to transfer. + +- **Monitoring**: Use the [Barrage performance tables](../how-to-guides/performance/barrage-performance.md) to track subscription health and identify bottlenecks. + +## Related documentation + +- [`barrage_session` reference](../reference/data-import-export/barrage/barrage-session.md) - API reference for creating Barrage sessions +- [Capture Python client tables](../how-to-guides/capture-tables.md) - Complete tutorial for using Barrage with the Python client +- [Barrage metrics](../how-to-guides/performance/barrage-performance.md) - Monitor Barrage performance +- [Interpret Barrage metrics](./barrage-metrics.md) - Understand what the metrics mean +- [Barrage schema annotation](../how-to-guides/data-import-export/barrage-schema.md) - Annotate schemas for complex types +- [Incremental update model](./table-update-model.md) - How Deephaven represents table changes +- [Core API design](./deephaven-core-api.md) - Technical details on the Deephaven API +- [Barrage protocol documentation](/barrage/docs) - Low-level wire format reference diff --git a/docs/python/how-to-guides/capture-tables.md b/docs/python/how-to-guides/capture-tables.md index 3bc00a6e08d..4b224754698 100644 --- a/docs/python/how-to-guides/capture-tables.md +++ b/docs/python/how-to-guides/capture-tables.md @@ -119,6 +119,76 @@ local_t_static = my_barrage_session.snapshot(ticket.bytes) Voila! You now have _real_ Deephaven server tables called `local_t_streaming` and `local_t_static`. These are not just references to Deephaven tables - they are _real_ Deephaven server tables that can be used in any Deephaven query. +## Subscription lifecycle management + +Understanding when and how to manage Barrage subscriptions helps you build efficient, reliable applications. + +### Choose between subscribe and snapshot + +Use **subscribe** when: + +- You need real-time updates as the source table changes. +- You're building a live dashboard or monitoring system. +- The source table is ticking (updating periodically). + +Use **snapshot** when: + +- You need a one-time, static copy of the data. +- The source table is static and won't change. +- You want to capture a point-in-time state for analysis. +- You need to reduce ongoing resource consumption. + +### Subscription resource usage + +Each active subscription consumes resources on both the server and client: + +| Resource | Server Impact | Client Impact | +| -------- | ------------------------------------------------ | ------------------------------------------------- | +| Memory | Maintains subscriber state and pending updates | Stores table data and applies incremental updates | +| CPU | Aggregates and serializes updates per subscriber | Deserializes and processes incoming updates | +| Network | Sends periodic update batches to each subscriber | Receives and buffers incoming data | + +For tables with frequent updates or many subscribers, these costs can add up. Monitor subscription health using the [Barrage performance tables](./performance/barrage-performance.md). + +### Close subscriptions when finished + +When you no longer need real-time updates, close the Barrage session to release resources: + +```python skip-test +# Close the session when done +my_barrage_session.close() +``` + +If you need to keep the session open for other subscriptions but want to release a specific table, you can drop the reference to the subscribed table. However, the underlying subscription may remain active until the session is closed. + +### Handle connection issues + +Barrage subscriptions can be affected by network interruptions. Consider these patterns for production applications: + +- **Reconnection**: If the session disconnects, you'll need to create a new `barrage_session` and resubscribe. The remote table must still be published to the same shared ticket. + +- **Ticket lifetime**: Shared tickets remain valid as long as the publishing session is active. If the publishing session closes, the ticket becomes invalid and subscribers will lose their connection. + +- **Authentication expiry**: If using authenticated connections, ensure tokens or credentials remain valid for the duration of long-running subscriptions. + +### Memory considerations for large tables + +When subscribing to large ticking tables: + +- **Initial snapshot size**: The first update contains a complete snapshot of the table. For very large tables, this can consume significant memory. The server breaks large snapshots into chunks by default (see [snapshot size control](./performance/barrage-performance.md#control-subscription-snapshot-size)). + +- **Incremental updates**: After the initial snapshot, only changed rows are transmitted. This is typically much smaller than the full table. + +- **Server-side filtering**: If you only need a subset of the data, consider filtering the table on the remote server before subscribing. This reduces both network and memory usage. (Note: this is distinct from viewports, which define a scrollable window over row positions.) + +```python skip-test +# On the remote server: filter before publishing +filtered_ref = client_session.open_table("large_table").where("Region = `EAST`") +client_session.publish_table(ticket, filtered_ref) + +# The subscriber now receives only the filtered data +``` + ## Related documentation diff --git a/docs/python/how-to-guides/data-import-export/barrage-schema.md b/docs/python/how-to-guides/data-import-export/barrage-schema.md new file mode 100644 index 00000000000..8bc0720ddb2 --- /dev/null +++ b/docs/python/how-to-guides/data-import-export/barrage-schema.md @@ -0,0 +1,53 @@ +--- +title: Barrage schema annotation +sidebar_label: Barrage schema annotation +--- + +Deephaven tables support Object-typed columns that can hold arbitrary Java objects. When exporting these tables over Flight using the Barrage format, Deephaven uses Apache Arrow schemas to describe the data. By default, if a column is typed as `Object`, the Arrow schema may not capture the intended structure of the data, which can lead to inefficient serialization or loss of type information. Use the `Table.BARRAGE_SCHEMA_ATTRIBUTE` to inject explicit Arrow schema information, which ensures that the Flight export uses the correct wire format. + +Use this when your Deephaven column type is too generic for the intended wire type (for example, `Object` columns that should be exported as `Union` or `Map`). + +## When to use schema annotation + +Schema annotation is needed when: + +- Exporting `Object`-typed columns that contain `Union` types (e.g., columns that may hold either `String` or `Double` values) +- Exporting `Map` columns where key/value types need explicit Arrow type definitions +- The default schema inference produces incorrect or inefficient wire formats + +## How it works + +1. Extract a base schema with `BarrageUtil.schemaFromTable`. +2. Replace the target field with explicit Arrow types (e.g., `ArrowType.Utf8`, `ArrowType.Union`, `ArrowType.Map`). +3. Attach the schema using [`with_attributes`](../../reference/table-operations/create/withAttributes.md). + +> [!NOTE] +> `with_attributes` returns a new table. If you later transform the table (for example, with `select`, `view`, or `update`), attributes may not be preserved and you may need to re-apply the schema. Apply the schema as late as possible before export to minimize this risk. + +## Supported types + +The following complex Arrow types can be annotated: + +- **Union** (Dense or Sparse): For columns containing multiple possible types +- **Map**: For key-value pair columns with explicit key/value type definitions +- **Nested combinations**: Maps with Union values, etc. + +## Working examples + +Schema annotation requires direct manipulation of Apache Arrow Java types via `jpy`. This involves careful handling of Java constructor overloads, null values, and collection types that can be complex in Python. + +**For working, tested examples, see the [Groovy Barrage schema annotation guide](/core/groovy/docs/how-to-guides/data-import-export/barrage-schema).** The Groovy examples demonstrate: + +- Annotating `Union` columns +- Annotating `Map` columns +- Annotating `Map` columns +- Annotating `Map` columns + +The Groovy patterns can be adapted for Python use with `jpy`, but require attention to how Python maps to Java types. + +## Related documentation + +- [What is Barrage?](../../conceptual/what-is-barrage.md) +- [with_attributes](../../reference/table-operations/create/withAttributes.md) +- [Groovy Barrage schema annotation guide](/core/groovy/docs/how-to-guides/data-import-export/barrage-schema) +- [Arrow Flight integration](./arrow-flight.md) diff --git a/docs/python/how-to-guides/performance/barrage-performance.md b/docs/python/how-to-guides/performance/barrage-performance.md index 60be9d0134a..8b19d4f8bc7 100644 --- a/docs/python/how-to-guides/performance/barrage-performance.md +++ b/docs/python/how-to-guides/performance/barrage-performance.md @@ -86,6 +86,93 @@ Here are server-side flags that change the behavior of Barrage metrics. - `-DBarragePerformanceLog.enableAll`: record metrics for tables that do not have an explicit `TableKey` (default: `true`). - `-DBarragePerformanceLog.cycleDurationMillis`: the interval to flush aggregated statistics (default: `60000` - once per minute). +## Control subscription snapshot size + +When a client subscribes to a ticking table, the server sends an initial snapshot of the table data. For large tables, constructing this snapshot requires holding the data in memory on the server side, which can lead to out-of-memory (OOM) errors. + +To address this, Deephaven can break the initial snapshot into smaller chunks spread across multiple update cycles. This behavior is controlled by the following properties: + +- `-DBarrageMessageProducer.subscriptionGrowthEnabled`: When `true` (the default), the server limits the size of each snapshot chunk. When `false`, the server sends the entire snapshot at once (unlimited size). + +When subscription growth is enabled, these additional properties control the chunk size: + +- `-DBarrageUtil.minSnapshotCellCount`: The minimum number of cells (rows × columns) per snapshot chunk. Default: `8192`. +- `-DBarrageUtil.maxSnapshotCellCount`: The maximum number of cells per snapshot chunk. Default: `16777216` (approximately 16 million). + +The server adaptively adjusts the chunk size between these bounds based on how long each snapshot takes to generate, targeting a percentage of the update graph cycle time. + +### Reducing publisher memory usage + +For systems serving very large tables to many subscribers, you can reduce publisher-side memory usage by lowering `maxSnapshotCellCount`. Setting `minSnapshotCellCount` equal to `maxSnapshotCellCount` fixes the chunk size and disables adaptive sizing: + +```bash +-DBarrageMessageProducer.subscriptionGrowthEnabled=true +-DBarrageUtil.minSnapshotCellCount=1000000 +-DBarrageUtil.maxSnapshotCellCount=1000000 +``` + +This configuration limits each snapshot chunk to exactly 1 million cells — well below the 16 million default maximum. Subscribers receive the full table data incrementally over multiple update cycles rather than all at once. + +> [!NOTE] +> Setting smaller snapshot sizes increases the time required for subscribers to receive the initial table state but reduces peak memory usage on the server. These settings only affect initial snapshots — incremental updates are unaffected and must still be maintained in memory. + +## Additional Barrage configuration + +The following properties control other aspects of Barrage behavior: + +### Update interval + +- `-Dbarrage.minUpdateInterval`: The minimum interval (in milliseconds) between update batches sent to subscribers. Default: `1000` (1 second). Lower values reduce latency but increase CPU and network usage. + +### Message batching + +- `-DBarrageMessageWriterImpl.batchSize`: Maximum rows per Arrow record batch. Default: `Integer.MAX_VALUE`. Reduce this if clients have trouble processing very large batches. +- `-DBarrageMessageWriterImpl.initialBatchSize`: Initial batch size for the first message. Default: `4096`. A smaller initial batch ensures clients receive data quickly while the server calibrates optimal batch sizes. +- `-DBarrageMessageWriterImpl.maxOutboundMessageSize`: Maximum size (in bytes) for outbound messages. Default: `104857600` (100 MB). This matches the default incoming message limit for Java clients. + +## Troubleshooting + +Use the metrics tables described above to diagnose common Barrage issues. + +### High `SnapshotMillis` + +If `SnapshotMillis` is consistently high: + +- The source table may be very large. Consider using viewports or filtering data before subscription. +- The update graph may be holding a lock. Check for long-running operations blocking the cycle. +- Consider enabling subscription growth with smaller chunk sizes (see [Control subscription snapshot size](#control-subscription-snapshot-size)). + +### High `WriteMillis` or `WriteMegabits` + +If `WriteMillis` is high or `WriteMegabits` is large: + +- Network bandwidth may be saturated. Check network utilization. +- Consider subscribing to fewer columns or using viewports to reduce data volume. +- Increase `barrage.minUpdateInterval` to batch more updates together. + +### High `PropagateMillis` + +If `PropagateMillis` is consistently high: + +- Many subscribers may be connected to the same table. Consider load balancing across multiple server instances. +- The server may be under memory pressure. Check JVM heap usage and garbage collection metrics. + +### Subscription errors + +Common subscription issues: + +- **"Ticket not found"**: The table was released or the session that published it closed. Ensure the publishing session remains active. +- **Authentication failures**: Verify that the `auth_type` and `auth_token` match the server configuration. +- **Connection refused**: Ensure the server is running and the host/port are correct. Check firewall rules. + +### Memory issues + +If the server experiences out-of-memory errors during subscriptions: + +- Enable subscription growth: `-DBarrageMessageProducer.subscriptionGrowthEnabled=true` +- Lower snapshot cell counts to reduce peak memory usage. +- Monitor the metrics tables to identify which tables consume the most resources. + ## Related documentation - [Interpret Barrage metrics](../../conceptual/barrage-metrics.md) diff --git a/docs/python/reference/community-questions/average-true-range.md b/docs/python/reference/community-questions/average-true-range.md index 95be8d5c15c..64085b4ed0b 100644 --- a/docs/python/reference/community-questions/average-true-range.md +++ b/docs/python/reference/community-questions/average-true-range.md @@ -17,7 +17,7 @@ from deephaven.calendar import calendar cal = nyse_cal = calendar("USNYSE_EXAMPLE") trades = ( - time_table("PT00:00:01", "2024-09-01T00:00 ET") + time_table("PT00:00:01", "2025-05-01T00:00 ET") .update( [ "Date = formatDate(Timestamp, 'ET')", diff --git a/docs/python/sidebar.json b/docs/python/sidebar.json index 28af7e37866..a239a161a4a 100644 --- a/docs/python/sidebar.json +++ b/docs/python/sidebar.json @@ -157,6 +157,10 @@ { "label": "Servers & clients", "path": "conceptual/client-server-model.md" + }, + { + "label": "What is Barrage?", + "path": "conceptual/what-is-barrage.md" } ] }, @@ -231,6 +235,10 @@ "label": "Arrow Flight", "path": "how-to-guides/data-import-export/arrow-flight.md" }, + { + "label": "Arrow/Barrage Schema", + "path": "how-to-guides/data-import-export/barrage-schema.md" + }, { "label": "Iceberg", "path": "how-to-guides/data-import-export/iceberg.md" diff --git a/docs/python/snapshots/388dd19b3d0a740fbf6580f36794f5ed.json b/docs/python/snapshots/388dd19b3d0a740fbf6580f36794f5ed.json deleted file mode 100644 index 05687708235..00000000000 --- a/docs/python/snapshots/388dd19b3d0a740fbf6580f36794f5ed.json +++ /dev/null @@ -1 +0,0 @@ -{"file":"core/docs/reference/community-questions/average-true-range.md","objects":{"trades":{"type":"Table","data":{"columns":[{"name":"Timestamp","type":"java.time.Instant"},{"name":"Date","type":"java.lang.String"},{"name":"Sym","type":"java.lang.String"},{"name":"Price","type":"double"}],"rows":[[{"value":"2024-09-03 00:00:00.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:01.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:02.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:03.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:04.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:05.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:06.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:07.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:08.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:09.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:10.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:11.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:12.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:13.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:14.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:15.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:16.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:17.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:18.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:19.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:20.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:21.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:22.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:23.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:24.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:25.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:26.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:27.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:28.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:29.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:30.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:31.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:32.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:33.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:34.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:35.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:36.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:37.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:38.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:39.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:40.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:41.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:42.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:43.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:44.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:45.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:46.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:47.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:48.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:49.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:50.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:51.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:52.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:53.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:54.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:55.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:56.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:57.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:00:58.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:00:59.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:00.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:01.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:02.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:03.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:04.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:05.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:06.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:07.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:08.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:09.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:10.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:11.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:12.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:13.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:14.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:15.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:16.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:17.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:18.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:19.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:20.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:21.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:22.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:23.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:24.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:25.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:26.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:27.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:28.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:29.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:30.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:31.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:32.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:33.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:34.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:35.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:36.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:37.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}],[{"value":"2024-09-03 00:01:38.000"},{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"}],[{"value":"2024-09-03 00:01:39.000"},{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"}]]}},"data":{"type":"Table","data":{"columns":[{"name":"Date","type":"java.lang.String"},{"name":"Sym","type":"java.lang.String"},{"name":"Open","type":"double"},{"name":"Close","type":"double"},{"name":"High","type":"double"},{"name":"Low","type":"double"}],"rows":[[{"value":"2024-09-03"},{"value":"COS"},{"value":"0.9851"},{"value":"0.9666"},{"value":"0.9851"},{"value":"0.9666"}],[{"value":"2024-09-03"},{"value":"SIN"},{"value":"0.0173"},{"value":"0.0259"},{"value":"0.0259"},{"value":"0.0173"}],[{"value":"2024-09-04"},{"value":"COS"},{"value":"0.9666"},{"value":"0.9409"},{"value":"0.9666"},{"value":"0.9409"}],[{"value":"2024-09-04"},{"value":"SIN"},{"value":"0.0259"},{"value":"0.0346"},{"value":"0.0346"},{"value":"0.0259"}],[{"value":"2024-09-05"},{"value":"COS"},{"value":"0.9409"},{"value":"0.9081"},{"value":"0.9409"},{"value":"0.9081"}],[{"value":"2024-09-05"},{"value":"SIN"},{"value":"0.0346"},{"value":"0.0432"},{"value":"0.0432"},{"value":"0.0346"}],[{"value":"2024-09-06"},{"value":"COS"},{"value":"0.9081"},{"value":"0.8686"},{"value":"0.9081"},{"value":"0.8686"}],[{"value":"2024-09-06"},{"value":"SIN"},{"value":"0.0432"},{"value":"0.0518"},{"value":"0.0518"},{"value":"0.0432"}],[{"value":"2024-09-09"},{"value":"COS"},{"value":"0.7705"},{"value":"0.7126"},{"value":"0.7705"},{"value":"0.7126"}],[{"value":"2024-09-09"},{"value":"SIN"},{"value":"0.0691"},{"value":"0.0777"},{"value":"0.0777"},{"value":"0.0691"}],[{"value":"2024-09-10"},{"value":"COS"},{"value":"0.7126"},{"value":"0.6494"},{"value":"0.7126"},{"value":"0.6494"}],[{"value":"2024-09-10"},{"value":"SIN"},{"value":"0.0777"},{"value":"0.0863"},{"value":"0.0863"},{"value":"0.0777"}],[{"value":"2024-09-11"},{"value":"COS"},{"value":"0.6494"},{"value":"0.5814"},{"value":"0.6494"},{"value":"0.5814"}],[{"value":"2024-09-11"},{"value":"SIN"},{"value":"0.0863"},{"value":"0.0949"},{"value":"0.0949"},{"value":"0.0863"}],[{"value":"2024-09-12"},{"value":"COS"},{"value":"0.5814"},{"value":"0.5090"},{"value":"0.5814"},{"value":"0.5090"}],[{"value":"2024-09-12"},{"value":"SIN"},{"value":"0.0949"},{"value":"0.1035"},{"value":"0.1035"},{"value":"0.0949"}],[{"value":"2024-09-13"},{"value":"COS"},{"value":"0.5090"},{"value":"0.4328"},{"value":"0.5090"},{"value":"0.4328"}],[{"value":"2024-09-13"},{"value":"SIN"},{"value":"0.1035"},{"value":"0.1121"},{"value":"0.1121"},{"value":"0.1035"}],[{"value":"2024-09-16"},{"value":"COS"},{"value":"0.2714"},{"value":"0.1873"},{"value":"0.2714"},{"value":"0.1873"}],[{"value":"2024-09-16"},{"value":"SIN"},{"value":"0.1292"},{"value":"0.1378"},{"value":"0.1378"},{"value":"0.1292"}],[{"value":"2024-09-17"},{"value":"COS"},{"value":"0.1873"},{"value":"0.1018"},{"value":"0.1873"},{"value":"0.1018"}],[{"value":"2024-09-17"},{"value":"SIN"},{"value":"0.1378"},{"value":"0.1464"},{"value":"0.1464"},{"value":"0.1378"}],[{"value":"2024-09-18"},{"value":"COS"},{"value":"0.1018"},{"value":"0.0156"},{"value":"0.1018"},{"value":"0.0156"}],[{"value":"2024-09-18"},{"value":"SIN"},{"value":"0.1464"},{"value":"0.1549"},{"value":"0.1549"},{"value":"0.1464"}],[{"value":"2024-09-19"},{"value":"COS"},{"value":"0.0156"},{"value":"-0.0707"},{"value":"0.0156"},{"value":"-0.0707"}],[{"value":"2024-09-19"},{"value":"SIN"},{"value":"0.1549"},{"value":"0.1634"},{"value":"0.1634"},{"value":"0.1549"}],[{"value":"2024-09-20"},{"value":"COS"},{"value":"-0.0707"},{"value":"-0.1566"},{"value":"-0.0707"},{"value":"-0.1566"}],[{"value":"2024-09-20"},{"value":"SIN"},{"value":"0.1634"},{"value":"0.1719"},{"value":"0.1719"},{"value":"0.1634"}],[{"value":"2024-09-23"},{"value":"COS"},{"value":"-0.3240"},{"value":"-0.4045"},{"value":"-0.3240"},{"value":"-0.4045"}],[{"value":"2024-09-23"},{"value":"SIN"},{"value":"0.1889"},{"value":"0.1974"},{"value":"0.1974"},{"value":"0.1889"}],[{"value":"2024-09-24"},{"value":"COS"},{"value":"-0.4045"},{"value":"-0.4819"},{"value":"-0.4045"},{"value":"-0.4819"}],[{"value":"2024-09-24"},{"value":"SIN"},{"value":"0.1974"},{"value":"0.2059"},{"value":"0.2059"},{"value":"0.1974"}],[{"value":"2024-09-25"},{"value":"COS"},{"value":"-0.4819"},{"value":"-0.5557"},{"value":"-0.4819"},{"value":"-0.5557"}],[{"value":"2024-09-25"},{"value":"SIN"},{"value":"0.2059"},{"value":"0.2143"},{"value":"0.2143"},{"value":"0.2059"}],[{"value":"2024-09-26"},{"value":"COS"},{"value":"-0.5557"},{"value":"-0.6254"},{"value":"-0.5557"},{"value":"-0.6254"}],[{"value":"2024-09-26"},{"value":"SIN"},{"value":"0.2143"},{"value":"0.2228"},{"value":"0.2228"},{"value":"0.2143"}],[{"value":"2024-09-27"},{"value":"COS"},{"value":"-0.6254"},{"value":"-0.6904"},{"value":"-0.6254"},{"value":"-0.6904"}],[{"value":"2024-09-27"},{"value":"SIN"},{"value":"0.2228"},{"value":"0.2312"},{"value":"0.2312"},{"value":"0.2228"}],[{"value":"2024-09-30"},{"value":"COS"},{"value":"-0.8045"},{"value":"-0.8527"},{"value":"-0.8045"},{"value":"-0.8527"}],[{"value":"2024-09-30"},{"value":"SIN"},{"value":"0.2479"},{"value":"0.2563"},{"value":"0.2563"},{"value":"0.2479"}],[{"value":"2024-10-01"},{"value":"COS"},{"value":"-0.8527"},{"value":"-0.8946"},{"value":"-0.8527"},{"value":"-0.8946"}],[{"value":"2024-10-01"},{"value":"SIN"},{"value":"0.2563"},{"value":"0.2646"},{"value":"0.2646"},{"value":"0.2563"}],[{"value":"2024-10-02"},{"value":"COS"},{"value":"-0.8946"},{"value":"-0.9298"},{"value":"-0.8946"},{"value":"-0.9298"}],[{"value":"2024-10-02"},{"value":"SIN"},{"value":"0.2646"},{"value":"0.2730"},{"value":"0.2730"},{"value":"0.2646"}],[{"value":"2024-10-03"},{"value":"COS"},{"value":"-0.9298"},{"value":"-0.9581"},{"value":"-0.9298"},{"value":"-0.9581"}],[{"value":"2024-10-03"},{"value":"SIN"},{"value":"0.2730"},{"value":"0.2813"},{"value":"0.2813"},{"value":"0.2730"}],[{"value":"2024-10-04"},{"value":"COS"},{"value":"-0.9581"},{"value":"-0.9793"},{"value":"-0.9581"},{"value":"-0.9793"}],[{"value":"2024-10-04"},{"value":"SIN"},{"value":"0.2813"},{"value":"0.2896"},{"value":"0.2896"},{"value":"0.2813"}],[{"value":"2024-10-07"},{"value":"COS"},{"value":"-0.9995"},{"value":"-0.9985"},{"value":"-0.9985"},{"value":"-1.0000"}],[{"value":"2024-10-07"},{"value":"SIN"},{"value":"0.3060"},{"value":"0.3143"},{"value":"0.3143"},{"value":"0.3060"}],[{"value":"2024-10-08"},{"value":"COS"},{"value":"-0.9985"},{"value":"-0.9900"},{"value":"-0.9900"},{"value":"-0.9985"}],[{"value":"2024-10-08"},{"value":"SIN"},{"value":"0.3143"},{"value":"0.3225"},{"value":"0.3225"},{"value":"0.3143"}],[{"value":"2024-10-09"},{"value":"COS"},{"value":"-0.9900"},{"value":"-0.9741"},{"value":"-0.9741"},{"value":"-0.9900"}],[{"value":"2024-10-09"},{"value":"SIN"},{"value":"0.3225"},{"value":"0.3306"},{"value":"0.3306"},{"value":"0.3225"}],[{"value":"2024-10-10"},{"value":"COS"},{"value":"-0.9741"},{"value":"-0.9510"},{"value":"-0.9510"},{"value":"-0.9741"}],[{"value":"2024-10-10"},{"value":"SIN"},{"value":"0.3306"},{"value":"0.3388"},{"value":"0.3388"},{"value":"0.3306"}],[{"value":"2024-10-11"},{"value":"COS"},{"value":"-0.9510"},{"value":"-0.9207"},{"value":"-0.9207"},{"value":"-0.9510"}],[{"value":"2024-10-11"},{"value":"SIN"},{"value":"0.3388"},{"value":"0.3469"},{"value":"0.3469"},{"value":"0.3388"}],[{"value":"2024-10-14"},{"value":"COS"},{"value":"-0.8399"},{"value":"-0.7900"},{"value":"-0.7900"},{"value":"-0.8399"}],[{"value":"2024-10-14"},{"value":"SIN"},{"value":"0.3630"},{"value":"0.3711"},{"value":"0.3711"},{"value":"0.3630"}],[{"value":"2024-10-15"},{"value":"COS"},{"value":"-0.7900"},{"value":"-0.7341"},{"value":"-0.7341"},{"value":"-0.7900"}],[{"value":"2024-10-15"},{"value":"SIN"},{"value":"0.3711"},{"value":"0.3791"},{"value":"0.3791"},{"value":"0.3711"}],[{"value":"2024-10-16"},{"value":"COS"},{"value":"-0.7341"},{"value":"-0.6728"},{"value":"-0.6728"},{"value":"-0.7341"}],[{"value":"2024-10-16"},{"value":"SIN"},{"value":"0.3791"},{"value":"0.3871"},{"value":"0.3871"},{"value":"0.3791"}],[{"value":"2024-10-17"},{"value":"COS"},{"value":"-0.6728"},{"value":"-0.6065"},{"value":"-0.6065"},{"value":"-0.6728"}],[{"value":"2024-10-17"},{"value":"SIN"},{"value":"0.3871"},{"value":"0.3950"},{"value":"0.3950"},{"value":"0.3871"}],[{"value":"2024-10-18"},{"value":"COS"},{"value":"-0.6065"},{"value":"-0.5356"},{"value":"-0.5356"},{"value":"-0.6065"}],[{"value":"2024-10-18"},{"value":"SIN"},{"value":"0.3950"},{"value":"0.4029"},{"value":"0.4029"},{"value":"0.3950"}],[{"value":"2024-10-21"},{"value":"COS"},{"value":"-0.3824"},{"value":"-0.3012"},{"value":"-0.3012"},{"value":"-0.3824"}],[{"value":"2024-10-21"},{"value":"SIN"},{"value":"0.4187"},{"value":"0.4265"},{"value":"0.4265"},{"value":"0.4187"}],[{"value":"2024-10-22"},{"value":"COS"},{"value":"-0.3012"},{"value":"-0.2633"},{"value":"-0.2633"},{"value":"-0.3012"}],[{"value":"2024-10-22"},{"value":"SIN"},{"value":"0.4265"},{"value":"0.4301"},{"value":"0.4301"},{"value":"0.4265"}]]}}}} \ No newline at end of file diff --git a/docs/python/snapshots/5e84aa23fb7612af36d342103bc4b7e9.json b/docs/python/snapshots/5e84aa23fb7612af36d342103bc4b7e9.json index fb61a2cfa07..db5d3f42344 100644 --- a/docs/python/snapshots/5e84aa23fb7612af36d342103bc4b7e9.json +++ b/docs/python/snapshots/5e84aa23fb7612af36d342103bc4b7e9.json @@ -1 +1 @@ -{"file":"core/docs/reference/community-questions/average-true-range.md","objects":{"atr":{"type":"Table","data":{"columns":[{"name":"Date","type":"java.lang.String"},{"name":"Sym","type":"java.lang.String"},{"name":"Open","type":"double"},{"name":"Close","type":"double"},{"name":"High","type":"double"},{"name":"Low","type":"double"},{"name":"PriorBusDay","type":"java.lang.String"},{"name":"ClosePrior","type":"double"},{"name":"TR","type":"double"},{"name":"ATR","type":"double"}],"rows":[[{"value":"2024-09-04"},{"value":"COS"},{"value":"0.9666"},{"value":"0.9409"},{"value":"0.9666"},{"value":"0.9409"},{"value":"2024-09-03"},{"value":"0.9666"},{"value":"0.0257"},{"value":"0.0257"}],[{"value":"2024-09-04"},{"value":"SIN"},{"value":"0.0259"},{"value":"0.0346"},{"value":"0.0346"},{"value":"0.0259"},{"value":"2024-09-03"},{"value":"0.0259"},{"value":"0.0086"},{"value":"0.0086"}],[{"value":"2024-09-05"},{"value":"COS"},{"value":"0.9409"},{"value":"0.9081"},{"value":"0.9409"},{"value":"0.9081"},{"value":"2024-09-04"},{"value":"0.9409"},{"value":"0.0327"},{"value":"0.0292"}],[{"value":"2024-09-05"},{"value":"SIN"},{"value":"0.0346"},{"value":"0.0432"},{"value":"0.0432"},{"value":"0.0346"},{"value":"2024-09-04"},{"value":"0.0346"},{"value":"0.0086"},{"value":"0.0086"}],[{"value":"2024-09-06"},{"value":"COS"},{"value":"0.9081"},{"value":"0.8686"},{"value":"0.9081"},{"value":"0.8686"},{"value":"2024-09-05"},{"value":"0.9081"},{"value":"0.0395"},{"value":"0.0327"}],[{"value":"2024-09-06"},{"value":"SIN"},{"value":"0.0432"},{"value":"0.0518"},{"value":"0.0518"},{"value":"0.0432"},{"value":"2024-09-05"},{"value":"0.0432"},{"value":"0.0086"},{"value":"0.0086"}],[{"value":"2024-09-09"},{"value":"COS"},{"value":"0.7705"},{"value":"0.7126"},{"value":"0.7705"},{"value":"0.7126"},{"value":"2024-09-06"},{"value":"0.8686"},{"value":"0.1560"},{"value":"0.0635"}],[{"value":"2024-09-09"},{"value":"SIN"},{"value":"0.0691"},{"value":"0.0777"},{"value":"0.0777"},{"value":"0.0691"},{"value":"2024-09-06"},{"value":"0.0518"},{"value":"0.0259"},{"value":"0.0129"}],[{"value":"2024-09-10"},{"value":"COS"},{"value":"0.7126"},{"value":"0.6494"},{"value":"0.7126"},{"value":"0.6494"},{"value":"2024-09-09"},{"value":"0.7126"},{"value":"0.0632"},{"value":"0.0634"}],[{"value":"2024-09-10"},{"value":"SIN"},{"value":"0.0777"},{"value":"0.0863"},{"value":"0.0863"},{"value":"0.0777"},{"value":"2024-09-09"},{"value":"0.0777"},{"value":"0.0086"},{"value":"0.0121"}],[{"value":"2024-09-11"},{"value":"COS"},{"value":"0.6494"},{"value":"0.5814"},{"value":"0.6494"},{"value":"0.5814"},{"value":"2024-09-10"},{"value":"0.6494"},{"value":"0.0680"},{"value":"0.0642"}],[{"value":"2024-09-11"},{"value":"SIN"},{"value":"0.0863"},{"value":"0.0949"},{"value":"0.0949"},{"value":"0.0863"},{"value":"2024-09-10"},{"value":"0.0863"},{"value":"0.0086"},{"value":"0.0115"}],[{"value":"2024-09-12"},{"value":"COS"},{"value":"0.5814"},{"value":"0.5090"},{"value":"0.5814"},{"value":"0.5090"},{"value":"2024-09-11"},{"value":"0.5814"},{"value":"0.0724"},{"value":"0.0654"}],[{"value":"2024-09-12"},{"value":"SIN"},{"value":"0.0949"},{"value":"0.1035"},{"value":"0.1035"},{"value":"0.0949"},{"value":"2024-09-11"},{"value":"0.0949"},{"value":"0.0086"},{"value":"0.0111"}],[{"value":"2024-09-13"},{"value":"COS"},{"value":"0.5090"},{"value":"0.4328"},{"value":"0.5090"},{"value":"0.4328"},{"value":"2024-09-12"},{"value":"0.5090"},{"value":"0.0762"},{"value":"0.0667"}],[{"value":"2024-09-13"},{"value":"SIN"},{"value":"0.1035"},{"value":"0.1121"},{"value":"0.1121"},{"value":"0.1035"},{"value":"2024-09-12"},{"value":"0.1035"},{"value":"0.0086"},{"value":"0.0108"}],[{"value":"2024-09-16"},{"value":"COS"},{"value":"0.2714"},{"value":"0.1873"},{"value":"0.2714"},{"value":"0.1873"},{"value":"2024-09-13"},{"value":"0.4328"},{"value":"0.2455"},{"value":"0.0866"}],[{"value":"2024-09-16"},{"value":"SIN"},{"value":"0.1292"},{"value":"0.1378"},{"value":"0.1378"},{"value":"0.1292"},{"value":"2024-09-13"},{"value":"0.1121"},{"value":"0.0257"},{"value":"0.0124"}],[{"value":"2024-09-17"},{"value":"COS"},{"value":"0.1873"},{"value":"0.1018"},{"value":"0.1873"},{"value":"0.1018"},{"value":"2024-09-16"},{"value":"0.1873"},{"value":"0.0855"},{"value":"0.0865"}],[{"value":"2024-09-17"},{"value":"SIN"},{"value":"0.1378"},{"value":"0.1464"},{"value":"0.1464"},{"value":"0.1378"},{"value":"2024-09-16"},{"value":"0.1378"},{"value":"0.0086"},{"value":"0.0120"}],[{"value":"2024-09-18"},{"value":"COS"},{"value":"0.1018"},{"value":"0.0156"},{"value":"0.1018"},{"value":"0.0156"},{"value":"2024-09-17"},{"value":"0.1018"},{"value":"0.0862"},{"value":"0.0865"}],[{"value":"2024-09-18"},{"value":"SIN"},{"value":"0.1464"},{"value":"0.1549"},{"value":"0.1549"},{"value":"0.1464"},{"value":"2024-09-17"},{"value":"0.1464"},{"value":"0.0085"},{"value":"0.0117"}],[{"value":"2024-09-19"},{"value":"COS"},{"value":"0.0156"},{"value":"-0.0707"},{"value":"0.0156"},{"value":"-0.0707"},{"value":"2024-09-18"},{"value":"0.0156"},{"value":"0.0863"},{"value":"0.0864"}],[{"value":"2024-09-19"},{"value":"SIN"},{"value":"0.1549"},{"value":"0.1634"},{"value":"0.1634"},{"value":"0.1549"},{"value":"2024-09-18"},{"value":"0.1549"},{"value":"0.0085"},{"value":"0.0115"}],[{"value":"2024-09-20"},{"value":"COS"},{"value":"-0.0707"},{"value":"-0.1566"},{"value":"-0.0707"},{"value":"-0.1566"},{"value":"2024-09-19"},{"value":"-0.0707"},{"value":"0.0858"},{"value":"0.0864"}],[{"value":"2024-09-20"},{"value":"SIN"},{"value":"0.1634"},{"value":"0.1719"},{"value":"0.1719"},{"value":"0.1634"},{"value":"2024-09-19"},{"value":"0.1634"},{"value":"0.0085"},{"value":"0.0112"}],[{"value":"2024-09-23"},{"value":"COS"},{"value":"-0.3240"},{"value":"-0.4045"},{"value":"-0.3240"},{"value":"-0.4045"},{"value":"2024-09-20"},{"value":"-0.1566"},{"value":"0.2479"},{"value":"0.0979"}],[{"value":"2024-09-23"},{"value":"SIN"},{"value":"0.1889"},{"value":"0.1974"},{"value":"0.1974"},{"value":"0.1889"},{"value":"2024-09-20"},{"value":"0.1719"},{"value":"0.0255"},{"value":"0.0122"}],[{"value":"2024-09-24"},{"value":"COS"},{"value":"-0.4045"},{"value":"-0.4819"},{"value":"-0.4045"},{"value":"-0.4819"},{"value":"2024-09-23"},{"value":"-0.4045"},{"value":"0.0774"},{"value":"0.1016"}],[{"value":"2024-09-24"},{"value":"SIN"},{"value":"0.1974"},{"value":"0.2059"},{"value":"0.2059"},{"value":"0.1974"},{"value":"2024-09-23"},{"value":"0.1974"},{"value":"0.0085"},{"value":"0.0122"}],[{"value":"2024-09-25"},{"value":"COS"},{"value":"-0.4819"},{"value":"-0.5557"},{"value":"-0.4819"},{"value":"-0.5557"},{"value":"2024-09-24"},{"value":"-0.4819"},{"value":"0.0738"},{"value":"0.1046"}],[{"value":"2024-09-25"},{"value":"SIN"},{"value":"0.2059"},{"value":"0.2143"},{"value":"0.2143"},{"value":"0.2059"},{"value":"2024-09-24"},{"value":"0.2059"},{"value":"0.0084"},{"value":"0.0122"}],[{"value":"2024-09-26"},{"value":"COS"},{"value":"-0.5557"},{"value":"-0.6254"},{"value":"-0.5557"},{"value":"-0.6254"},{"value":"2024-09-25"},{"value":"-0.5557"},{"value":"0.0697"},{"value":"0.1067"}],[{"value":"2024-09-26"},{"value":"SIN"},{"value":"0.2143"},{"value":"0.2228"},{"value":"0.2228"},{"value":"0.2143"},{"value":"2024-09-25"},{"value":"0.2143"},{"value":"0.0084"},{"value":"0.0122"}],[{"value":"2024-09-27"},{"value":"COS"},{"value":"-0.6254"},{"value":"-0.6904"},{"value":"-0.6254"},{"value":"-0.6904"},{"value":"2024-09-26"},{"value":"-0.6254"},{"value":"0.0650"},{"value":"0.1002"}],[{"value":"2024-09-27"},{"value":"SIN"},{"value":"0.2228"},{"value":"0.2312"},{"value":"0.2312"},{"value":"0.2228"},{"value":"2024-09-26"},{"value":"0.2228"},{"value":"0.0084"},{"value":"0.0110"}],[{"value":"2024-09-30"},{"value":"COS"},{"value":"-0.8045"},{"value":"-0.8527"},{"value":"-0.8045"},{"value":"-0.8527"},{"value":"2024-09-27"},{"value":"-0.6904"},{"value":"0.1624"},{"value":"0.1073"}],[{"value":"2024-09-30"},{"value":"SIN"},{"value":"0.2479"},{"value":"0.2563"},{"value":"0.2563"},{"value":"0.2479"},{"value":"2024-09-27"},{"value":"0.2312"},{"value":"0.0251"},{"value":"0.0121"}],[{"value":"2024-10-01"},{"value":"COS"},{"value":"-0.8527"},{"value":"-0.8946"},{"value":"-0.8527"},{"value":"-0.8946"},{"value":"2024-09-30"},{"value":"-0.8527"},{"value":"0.0419"},{"value":"0.1054"}],[{"value":"2024-10-01"},{"value":"SIN"},{"value":"0.2563"},{"value":"0.2646"},{"value":"0.2646"},{"value":"0.2563"},{"value":"2024-09-30"},{"value":"0.2563"},{"value":"0.0083"},{"value":"0.0121"}],[{"value":"2024-10-02"},{"value":"COS"},{"value":"-0.8946"},{"value":"-0.9298"},{"value":"-0.8946"},{"value":"-0.9298"},{"value":"2024-10-01"},{"value":"-0.8946"},{"value":"0.0352"},{"value":"0.1028"}],[{"value":"2024-10-02"},{"value":"SIN"},{"value":"0.2646"},{"value":"0.2730"},{"value":"0.2730"},{"value":"0.2646"},{"value":"2024-10-01"},{"value":"0.2646"},{"value":"0.0083"},{"value":"0.0121"}],[{"value":"2024-10-03"},{"value":"COS"},{"value":"-0.9298"},{"value":"-0.9581"},{"value":"-0.9298"},{"value":"-0.9581"},{"value":"2024-10-02"},{"value":"-0.9298"},{"value":"0.0283"},{"value":"0.0994"}],[{"value":"2024-10-03"},{"value":"SIN"},{"value":"0.2730"},{"value":"0.2813"},{"value":"0.2813"},{"value":"0.2730"},{"value":"2024-10-02"},{"value":"0.2730"},{"value":"0.0083"},{"value":"0.0121"}],[{"value":"2024-10-04"},{"value":"COS"},{"value":"-0.9581"},{"value":"-0.9793"},{"value":"-0.9581"},{"value":"-0.9793"},{"value":"2024-10-03"},{"value":"-0.9581"},{"value":"0.0211"},{"value":"0.0833"}],[{"value":"2024-10-04"},{"value":"SIN"},{"value":"0.2813"},{"value":"0.2896"},{"value":"0.2896"},{"value":"0.2813"},{"value":"2024-10-03"},{"value":"0.2813"},{"value":"0.0083"},{"value":"0.0108"}],[{"value":"2024-10-07"},{"value":"COS"},{"value":"-0.9995"},{"value":"-0.9985"},{"value":"-0.9985"},{"value":"-1.0000"},{"value":"2024-10-04"},{"value":"-0.9793"},{"value":"0.0207"},{"value":"0.0787"}],[{"value":"2024-10-07"},{"value":"SIN"},{"value":"0.3060"},{"value":"0.3143"},{"value":"0.3143"},{"value":"0.3060"},{"value":"2024-10-04"},{"value":"0.2896"},{"value":"0.0247"},{"value":"0.0120"}],[{"value":"2024-10-08"},{"value":"COS"},{"value":"-0.9985"},{"value":"-0.9900"},{"value":"-0.9900"},{"value":"-0.9985"},{"value":"2024-10-07"},{"value":"-0.9985"},{"value":"0.0085"},{"value":"0.0731"}],[{"value":"2024-10-08"},{"value":"SIN"},{"value":"0.3143"},{"value":"0.3225"},{"value":"0.3225"},{"value":"0.3143"},{"value":"2024-10-07"},{"value":"0.3143"},{"value":"0.0082"},{"value":"0.0120"}],[{"value":"2024-10-09"},{"value":"COS"},{"value":"-0.9900"},{"value":"-0.9741"},{"value":"-0.9741"},{"value":"-0.9900"},{"value":"2024-10-08"},{"value":"-0.9900"},{"value":"0.0159"},{"value":"0.0681"}],[{"value":"2024-10-09"},{"value":"SIN"},{"value":"0.3225"},{"value":"0.3306"},{"value":"0.3306"},{"value":"0.3225"},{"value":"2024-10-08"},{"value":"0.3225"},{"value":"0.0082"},{"value":"0.0119"}],[{"value":"2024-10-10"},{"value":"COS"},{"value":"-0.9741"},{"value":"-0.9510"},{"value":"-0.9510"},{"value":"-0.9741"},{"value":"2024-10-09"},{"value":"-0.9741"},{"value":"0.0231"},{"value":"0.0636"}],[{"value":"2024-10-10"},{"value":"SIN"},{"value":"0.3306"},{"value":"0.3388"},{"value":"0.3388"},{"value":"0.3306"},{"value":"2024-10-09"},{"value":"0.3306"},{"value":"0.0081"},{"value":"0.0119"}],[{"value":"2024-10-11"},{"value":"COS"},{"value":"-0.9510"},{"value":"-0.9207"},{"value":"-0.9207"},{"value":"-0.9510"},{"value":"2024-10-10"},{"value":"-0.9510"},{"value":"0.0302"},{"value":"0.0481"}],[{"value":"2024-10-11"},{"value":"SIN"},{"value":"0.3388"},{"value":"0.3469"},{"value":"0.3469"},{"value":"0.3388"},{"value":"2024-10-10"},{"value":"0.3388"},{"value":"0.0081"},{"value":"0.0107"}],[{"value":"2024-10-14"},{"value":"COS"},{"value":"-0.8399"},{"value":"-0.7900"},{"value":"-0.7900"},{"value":"-0.8399"},{"value":"2024-10-11"},{"value":"-0.9207"},{"value":"0.1308"},{"value":"0.0519"}],[{"value":"2024-10-14"},{"value":"SIN"},{"value":"0.3630"},{"value":"0.3711"},{"value":"0.3711"},{"value":"0.3630"},{"value":"2024-10-11"},{"value":"0.3469"},{"value":"0.0242"},{"value":"0.0118"}],[{"value":"2024-10-15"},{"value":"COS"},{"value":"-0.7900"},{"value":"-0.7341"},{"value":"-0.7341"},{"value":"-0.7900"},{"value":"2024-10-14"},{"value":"-0.7900"},{"value":"0.0559"},{"value":"0.0506"}],[{"value":"2024-10-15"},{"value":"SIN"},{"value":"0.3711"},{"value":"0.3791"},{"value":"0.3791"},{"value":"0.3711"},{"value":"2024-10-14"},{"value":"0.3711"},{"value":"0.0080"},{"value":"0.0118"}],[{"value":"2024-10-16"},{"value":"COS"},{"value":"-0.7341"},{"value":"-0.6728"},{"value":"-0.6728"},{"value":"-0.7341"},{"value":"2024-10-15"},{"value":"-0.7341"},{"value":"0.0613"},{"value":"0.0500"}],[{"value":"2024-10-16"},{"value":"SIN"},{"value":"0.3791"},{"value":"0.3871"},{"value":"0.3871"},{"value":"0.3791"},{"value":"2024-10-15"},{"value":"0.3791"},{"value":"0.0080"},{"value":"0.0117"}],[{"value":"2024-10-17"},{"value":"COS"},{"value":"-0.6728"},{"value":"-0.6065"},{"value":"-0.6065"},{"value":"-0.6728"},{"value":"2024-10-16"},{"value":"-0.6728"},{"value":"0.0664"},{"value":"0.0501"}],[{"value":"2024-10-17"},{"value":"SIN"},{"value":"0.3871"},{"value":"0.3950"},{"value":"0.3950"},{"value":"0.3871"},{"value":"2024-10-16"},{"value":"0.3871"},{"value":"0.0080"},{"value":"0.0117"}],[{"value":"2024-10-18"},{"value":"COS"},{"value":"-0.6065"},{"value":"-0.5356"},{"value":"-0.5356"},{"value":"-0.6065"},{"value":"2024-10-17"},{"value":"-0.6065"},{"value":"0.0709"},{"value":"0.0436"}],[{"value":"2024-10-18"},{"value":"SIN"},{"value":"0.3950"},{"value":"0.4029"},{"value":"0.4029"},{"value":"0.3950"},{"value":"2024-10-17"},{"value":"0.3950"},{"value":"0.0079"},{"value":"0.0105"}],[{"value":"2024-10-21"},{"value":"COS"},{"value":"-0.3824"},{"value":"-0.3012"},{"value":"-0.3012"},{"value":"-0.3824"},{"value":"2024-10-18"},{"value":"-0.5356"},{"value":"0.2343"},{"value":"0.0573"}],[{"value":"2024-10-21"},{"value":"SIN"},{"value":"0.4187"},{"value":"0.4265"},{"value":"0.4265"},{"value":"0.4187"},{"value":"2024-10-18"},{"value":"0.4029"},{"value":"0.0236"},{"value":"0.0116"}],[{"value":"2024-10-22"},{"value":"COS"},{"value":"-0.3012"},{"value":"-0.2633"},{"value":"-0.2633"},{"value":"-0.3012"},{"value":"2024-10-21"},{"value":"-0.3012"},{"value":"0.0380"},{"value":"0.0575"}],[{"value":"2024-10-22"},{"value":"SIN"},{"value":"0.4265"},{"value":"0.4301"},{"value":"0.4301"},{"value":"0.4265"},{"value":"2024-10-21"},{"value":"0.4265"},{"value":"0.0036"},{"value":"0.0112"}]]}}}} \ No newline at end of file +{"file":"reference/community-questions/average-true-range.md","objects":{"atr":{"type":"Table","data":{"columns":[{"name":"Date","type":"java.lang.String"},{"name":"Sym","type":"java.lang.String"},{"name":"Open","type":"double"},{"name":"Close","type":"double"},{"name":"High","type":"double"},{"name":"Low","type":"double"},{"name":"PriorBusDay","type":"java.lang.String"},{"name":"ClosePrior","type":"double"},{"name":"TR","type":"double"},{"name":"ATR","type":"double"}],"rows":[[{"value":"2025-05-02"},{"value":"COS"},{"value":"0.9963"},{"value":"0.9851"},{"value":"0.9963"},{"value":"0.9851"},{"value":"2025-05-01"},{"value":"0.9963"},{"value":"0.0112"},{"value":"0.0112"}],[{"value":"2025-05-02"},{"value":"SIN"},{"value":"0.0086"},{"value":"0.0173"},{"value":"0.0173"},{"value":"0.0086"},{"value":"2025-05-01"},{"value":"0.0086"},{"value":"0.0086"},{"value":"0.0086"}],[{"value":"2025-05-05"},{"value":"COS"},{"value":"0.9409"},{"value":"0.9081"},{"value":"0.9409"},{"value":"0.9081"},{"value":"2025-05-02"},{"value":"0.9851"},{"value":"0.0770"},{"value":"0.0441"}],[{"value":"2025-05-05"},{"value":"SIN"},{"value":"0.0346"},{"value":"0.0432"},{"value":"0.0432"},{"value":"0.0346"},{"value":"2025-05-02"},{"value":"0.0173"},{"value":"0.0259"},{"value":"0.0173"}],[{"value":"2025-05-06"},{"value":"COS"},{"value":"0.9081"},{"value":"0.8686"},{"value":"0.9081"},{"value":"0.8686"},{"value":"2025-05-05"},{"value":"0.9081"},{"value":"0.0395"},{"value":"0.0426"}],[{"value":"2025-05-06"},{"value":"SIN"},{"value":"0.0432"},{"value":"0.0518"},{"value":"0.0518"},{"value":"0.0432"},{"value":"2025-05-05"},{"value":"0.0432"},{"value":"0.0086"},{"value":"0.0144"}],[{"value":"2025-05-07"},{"value":"COS"},{"value":"0.8686"},{"value":"0.8226"},{"value":"0.8686"},{"value":"0.8226"},{"value":"2025-05-06"},{"value":"0.8686"},{"value":"0.0460"},{"value":"0.0434"}],[{"value":"2025-05-07"},{"value":"SIN"},{"value":"0.0518"},{"value":"0.0604"},{"value":"0.0604"},{"value":"0.0518"},{"value":"2025-05-06"},{"value":"0.0518"},{"value":"0.0086"},{"value":"0.0130"}],[{"value":"2025-05-08"},{"value":"COS"},{"value":"0.8226"},{"value":"0.7705"},{"value":"0.8226"},{"value":"0.7705"},{"value":"2025-05-07"},{"value":"0.8226"},{"value":"0.0521"},{"value":"0.0452"}],[{"value":"2025-05-08"},{"value":"SIN"},{"value":"0.0604"},{"value":"0.0691"},{"value":"0.0691"},{"value":"0.0604"},{"value":"2025-05-07"},{"value":"0.0604"},{"value":"0.0086"},{"value":"0.0121"}],[{"value":"2025-05-09"},{"value":"COS"},{"value":"0.7705"},{"value":"0.7126"},{"value":"0.7705"},{"value":"0.7126"},{"value":"2025-05-08"},{"value":"0.7705"},{"value":"0.0579"},{"value":"0.0473"}],[{"value":"2025-05-09"},{"value":"SIN"},{"value":"0.0691"},{"value":"0.0777"},{"value":"0.0777"},{"value":"0.0691"},{"value":"2025-05-08"},{"value":"0.0691"},{"value":"0.0086"},{"value":"0.0115"}],[{"value":"2025-05-12"},{"value":"COS"},{"value":"0.5814"},{"value":"0.5090"},{"value":"0.5814"},{"value":"0.5090"},{"value":"2025-05-09"},{"value":"0.7126"},{"value":"0.2036"},{"value":"0.0696"}],[{"value":"2025-05-12"},{"value":"SIN"},{"value":"0.0949"},{"value":"0.1035"},{"value":"0.1035"},{"value":"0.0949"},{"value":"2025-05-09"},{"value":"0.0777"},{"value":"0.0258"},{"value":"0.0136"}],[{"value":"2025-05-13"},{"value":"COS"},{"value":"0.5090"},{"value":"0.4328"},{"value":"0.5090"},{"value":"0.4328"},{"value":"2025-05-12"},{"value":"0.5090"},{"value":"0.0762"},{"value":"0.0704"}],[{"value":"2025-05-13"},{"value":"SIN"},{"value":"0.1035"},{"value":"0.1121"},{"value":"0.1121"},{"value":"0.1035"},{"value":"2025-05-12"},{"value":"0.1035"},{"value":"0.0086"},{"value":"0.0129"}],[{"value":"2025-05-14"},{"value":"COS"},{"value":"0.4328"},{"value":"0.3534"},{"value":"0.4328"},{"value":"0.3534"},{"value":"2025-05-13"},{"value":"0.4328"},{"value":"0.0794"},{"value":"0.0714"}],[{"value":"2025-05-14"},{"value":"SIN"},{"value":"0.1121"},{"value":"0.1207"},{"value":"0.1207"},{"value":"0.1121"},{"value":"2025-05-13"},{"value":"0.1121"},{"value":"0.0086"},{"value":"0.0124"}],[{"value":"2025-05-15"},{"value":"COS"},{"value":"0.3534"},{"value":"0.2714"},{"value":"0.3534"},{"value":"0.2714"},{"value":"2025-05-14"},{"value":"0.3534"},{"value":"0.0820"},{"value":"0.0725"}],[{"value":"2025-05-15"},{"value":"SIN"},{"value":"0.1207"},{"value":"0.1292"},{"value":"0.1292"},{"value":"0.1207"},{"value":"2025-05-14"},{"value":"0.1207"},{"value":"0.0086"},{"value":"0.0121"}],[{"value":"2025-05-16"},{"value":"COS"},{"value":"0.2714"},{"value":"0.1873"},{"value":"0.2714"},{"value":"0.1873"},{"value":"2025-05-15"},{"value":"0.2714"},{"value":"0.0841"},{"value":"0.0735"}],[{"value":"2025-05-16"},{"value":"SIN"},{"value":"0.1292"},{"value":"0.1378"},{"value":"0.1378"},{"value":"0.1292"},{"value":"2025-05-15"},{"value":"0.1292"},{"value":"0.0086"},{"value":"0.0117"}],[{"value":"2025-05-19"},{"value":"COS"},{"value":"0.0156"},{"value":"-0.0707"},{"value":"0.0156"},{"value":"-0.0707"},{"value":"2025-05-16"},{"value":"0.1873"},{"value":"0.2580"},{"value":"0.0889"}],[{"value":"2025-05-19"},{"value":"SIN"},{"value":"0.1549"},{"value":"0.1634"},{"value":"0.1634"},{"value":"0.1549"},{"value":"2025-05-16"},{"value":"0.1378"},{"value":"0.0256"},{"value":"0.0129"}],[{"value":"2025-05-20"},{"value":"COS"},{"value":"-0.0707"},{"value":"-0.1566"},{"value":"-0.0707"},{"value":"-0.1566"},{"value":"2025-05-19"},{"value":"-0.0707"},{"value":"0.0858"},{"value":"0.0887"}],[{"value":"2025-05-20"},{"value":"SIN"},{"value":"0.1634"},{"value":"0.1719"},{"value":"0.1719"},{"value":"0.1634"},{"value":"2025-05-19"},{"value":"0.1634"},{"value":"0.0085"},{"value":"0.0126"}],[{"value":"2025-05-21"},{"value":"COS"},{"value":"-0.1566"},{"value":"-0.2412"},{"value":"-0.1566"},{"value":"-0.2412"},{"value":"2025-05-20"},{"value":"-0.1566"},{"value":"0.0846"},{"value":"0.0884"}],[{"value":"2025-05-21"},{"value":"SIN"},{"value":"0.1719"},{"value":"0.1804"},{"value":"0.1804"},{"value":"0.1719"},{"value":"2025-05-20"},{"value":"0.1719"},{"value":"0.0085"},{"value":"0.0123"}],[{"value":"2025-05-22"},{"value":"COS"},{"value":"-0.2412"},{"value":"-0.3240"},{"value":"-0.2412"},{"value":"-0.3240"},{"value":"2025-05-21"},{"value":"-0.2412"},{"value":"0.0828"},{"value":"0.0935"}],[{"value":"2025-05-22"},{"value":"SIN"},{"value":"0.1804"},{"value":"0.1889"},{"value":"0.1889"},{"value":"0.1804"},{"value":"2025-05-21"},{"value":"0.1804"},{"value":"0.0085"},{"value":"0.0123"}],[{"value":"2025-05-23"},{"value":"COS"},{"value":"-0.3240"},{"value":"-0.4045"},{"value":"-0.3240"},{"value":"-0.4045"},{"value":"2025-05-22"},{"value":"-0.3240"},{"value":"0.0804"},{"value":"0.0938"}],[{"value":"2025-05-23"},{"value":"SIN"},{"value":"0.1889"},{"value":"0.1974"},{"value":"0.1974"},{"value":"0.1889"},{"value":"2025-05-22"},{"value":"0.1889"},{"value":"0.0085"},{"value":"0.0110"}],[{"value":"2025-05-27"},{"value":"COS"},{"value":"-0.6254"},{"value":"-0.6904"},{"value":"-0.6254"},{"value":"-0.6904"},{"value":"2025-05-23"},{"value":"-0.4045"},{"value":"0.2859"},{"value":"0.1114"}],[{"value":"2025-05-27"},{"value":"SIN"},{"value":"0.2228"},{"value":"0.2312"},{"value":"0.2312"},{"value":"0.2228"},{"value":"2025-05-23"},{"value":"0.1974"},{"value":"0.0338"},{"value":"0.0128"}],[{"value":"2025-05-28"},{"value":"COS"},{"value":"-0.6904"},{"value":"-0.7502"},{"value":"-0.6904"},{"value":"-0.7502"},{"value":"2025-05-27"},{"value":"-0.6904"},{"value":"0.0599"},{"value":"0.1123"}],[{"value":"2025-05-28"},{"value":"SIN"},{"value":"0.2312"},{"value":"0.2396"},{"value":"0.2396"},{"value":"0.2312"},{"value":"2025-05-27"},{"value":"0.2312"},{"value":"0.0084"},{"value":"0.0128"}],[{"value":"2025-05-29"},{"value":"COS"},{"value":"-0.7502"},{"value":"-0.8045"},{"value":"-0.7502"},{"value":"-0.8045"},{"value":"2025-05-28"},{"value":"-0.7502"},{"value":"0.0543"},{"value":"0.1125"}],[{"value":"2025-05-29"},{"value":"SIN"},{"value":"0.2396"},{"value":"0.2479"},{"value":"0.2479"},{"value":"0.2396"},{"value":"2025-05-28"},{"value":"0.2396"},{"value":"0.0084"},{"value":"0.0128"}],[{"value":"2025-05-30"},{"value":"COS"},{"value":"-0.8045"},{"value":"-0.8527"},{"value":"-0.8045"},{"value":"-0.8527"},{"value":"2025-05-29"},{"value":"-0.8045"},{"value":"0.0483"},{"value":"0.1118"}],[{"value":"2025-05-30"},{"value":"SIN"},{"value":"0.2479"},{"value":"0.2563"},{"value":"0.2563"},{"value":"0.2479"},{"value":"2025-05-29"},{"value":"0.2479"},{"value":"0.0084"},{"value":"0.0128"}],[{"value":"2025-06-02"},{"value":"COS"},{"value":"-0.9298"},{"value":"-0.9581"},{"value":"-0.9298"},{"value":"-0.9581"},{"value":"2025-05-30"},{"value":"-0.8527"},{"value":"0.1054"},{"value":"0.1048"}],[{"value":"2025-06-02"},{"value":"SIN"},{"value":"0.2730"},{"value":"0.2813"},{"value":"0.2813"},{"value":"0.2730"},{"value":"2025-05-30"},{"value":"0.2563"},{"value":"0.0250"},{"value":"0.0127"}],[{"value":"2025-06-03"},{"value":"COS"},{"value":"-0.9581"},{"value":"-0.9793"},{"value":"-0.9581"},{"value":"-0.9793"},{"value":"2025-06-02"},{"value":"-0.9581"},{"value":"0.0211"},{"value":"0.1009"}],[{"value":"2025-06-03"},{"value":"SIN"},{"value":"0.2813"},{"value":"0.2896"},{"value":"0.2896"},{"value":"0.2813"},{"value":"2025-06-02"},{"value":"0.2813"},{"value":"0.0083"},{"value":"0.0127"}],[{"value":"2025-06-04"},{"value":"COS"},{"value":"-0.9793"},{"value":"-0.9931"},{"value":"-0.9793"},{"value":"-0.9931"},{"value":"2025-06-03"},{"value":"-0.9793"},{"value":"0.0138"},{"value":"0.0962"}],[{"value":"2025-06-04"},{"value":"SIN"},{"value":"0.2896"},{"value":"0.2978"},{"value":"0.2978"},{"value":"0.2896"},{"value":"2025-06-03"},{"value":"0.2896"},{"value":"0.0083"},{"value":"0.0127"}],[{"value":"2025-06-05"},{"value":"COS"},{"value":"-0.9931"},{"value":"-0.9995"},{"value":"-0.9931"},{"value":"-0.9995"},{"value":"2025-06-04"},{"value":"-0.9931"},{"value":"0.0064"},{"value":"0.0908"}],[{"value":"2025-06-05"},{"value":"SIN"},{"value":"0.2978"},{"value":"0.3060"},{"value":"0.3060"},{"value":"0.2978"},{"value":"2025-06-04"},{"value":"0.2978"},{"value":"0.0082"},{"value":"0.0126"}],[{"value":"2025-06-06"},{"value":"COS"},{"value":"-0.9995"},{"value":"-0.9985"},{"value":"-0.9985"},{"value":"-1.0000"},{"value":"2025-06-05"},{"value":"-0.9995"},{"value":"0.0015"},{"value":"0.0849"}],[{"value":"2025-06-06"},{"value":"SIN"},{"value":"0.3060"},{"value":"0.3143"},{"value":"0.3143"},{"value":"0.3060"},{"value":"2025-06-05"},{"value":"0.3060"},{"value":"0.0082"},{"value":"0.0126"}],[{"value":"2025-06-09"},{"value":"COS"},{"value":"-0.9741"},{"value":"-0.9510"},{"value":"-0.9510"},{"value":"-0.9741"},{"value":"2025-06-06"},{"value":"-0.9985"},{"value":"0.0475"},{"value":"0.0698"}],[{"value":"2025-06-09"},{"value":"SIN"},{"value":"0.3306"},{"value":"0.3388"},{"value":"0.3388"},{"value":"0.3306"},{"value":"2025-06-06"},{"value":"0.3143"},{"value":"0.0245"},{"value":"0.0125"}],[{"value":"2025-06-10"},{"value":"COS"},{"value":"-0.9510"},{"value":"-0.9207"},{"value":"-0.9207"},{"value":"-0.9510"},{"value":"2025-06-09"},{"value":"-0.9510"},{"value":"0.0302"},{"value":"0.0659"}],[{"value":"2025-06-10"},{"value":"SIN"},{"value":"0.3388"},{"value":"0.3469"},{"value":"0.3469"},{"value":"0.3388"},{"value":"2025-06-09"},{"value":"0.3388"},{"value":"0.0081"},{"value":"0.0125"}],[{"value":"2025-06-11"},{"value":"COS"},{"value":"-0.9207"},{"value":"-0.8836"},{"value":"-0.8836"},{"value":"-0.9207"},{"value":"2025-06-10"},{"value":"-0.9207"},{"value":"0.0371"},{"value":"0.0625"}],[{"value":"2025-06-11"},{"value":"SIN"},{"value":"0.3469"},{"value":"0.3550"},{"value":"0.3550"},{"value":"0.3469"},{"value":"2025-06-10"},{"value":"0.3469"},{"value":"0.0081"},{"value":"0.0125"}],[{"value":"2025-06-12"},{"value":"COS"},{"value":"-0.8836"},{"value":"-0.8399"},{"value":"-0.8399"},{"value":"-0.8836"},{"value":"2025-06-11"},{"value":"-0.8836"},{"value":"0.0437"},{"value":"0.0597"}],[{"value":"2025-06-12"},{"value":"SIN"},{"value":"0.3550"},{"value":"0.3630"},{"value":"0.3630"},{"value":"0.3550"},{"value":"2025-06-11"},{"value":"0.3550"},{"value":"0.0081"},{"value":"0.0124"}],[{"value":"2025-06-13"},{"value":"COS"},{"value":"-0.8399"},{"value":"-0.7900"},{"value":"-0.7900"},{"value":"-0.8399"},{"value":"2025-06-12"},{"value":"-0.8399"},{"value":"0.0500"},{"value":"0.0575"}],[{"value":"2025-06-13"},{"value":"SIN"},{"value":"0.3630"},{"value":"0.3711"},{"value":"0.3711"},{"value":"0.3630"},{"value":"2025-06-12"},{"value":"0.3630"},{"value":"0.0080"},{"value":"0.0124"}],[{"value":"2025-06-16"},{"value":"COS"},{"value":"-0.6728"},{"value":"-0.6065"},{"value":"-0.6065"},{"value":"-0.6728"},{"value":"2025-06-13"},{"value":"-0.7900"},{"value":"0.1835"},{"value":"0.0502"}],[{"value":"2025-06-16"},{"value":"SIN"},{"value":"0.3871"},{"value":"0.3950"},{"value":"0.3950"},{"value":"0.3871"},{"value":"2025-06-13"},{"value":"0.3711"},{"value":"0.0239"},{"value":"0.0117"}],[{"value":"2025-06-17"},{"value":"COS"},{"value":"-0.6065"},{"value":"-0.5356"},{"value":"-0.5356"},{"value":"-0.6065"},{"value":"2025-06-16"},{"value":"-0.6065"},{"value":"0.0709"},{"value":"0.0510"}],[{"value":"2025-06-17"},{"value":"SIN"},{"value":"0.3950"},{"value":"0.4029"},{"value":"0.4029"},{"value":"0.3950"},{"value":"2025-06-16"},{"value":"0.3950"},{"value":"0.0079"},{"value":"0.0117"}],[{"value":"2025-06-18"},{"value":"COS"},{"value":"-0.5356"},{"value":"-0.4607"},{"value":"-0.4607"},{"value":"-0.5356"},{"value":"2025-06-17"},{"value":"-0.5356"},{"value":"0.0749"},{"value":"0.0525"}],[{"value":"2025-06-18"},{"value":"SIN"},{"value":"0.4029"},{"value":"0.4108"},{"value":"0.4108"},{"value":"0.4029"},{"value":"2025-06-17"},{"value":"0.4029"},{"value":"0.0079"},{"value":"0.0116"}],[{"value":"2025-06-20"},{"value":"COS"},{"value":"-0.3824"},{"value":"-0.3012"},{"value":"-0.3012"},{"value":"-0.3824"},{"value":"2025-06-18"},{"value":"-0.4607"},{"value":"0.1595"},{"value":"0.0604"}],[{"value":"2025-06-20"},{"value":"SIN"},{"value":"0.4187"},{"value":"0.4265"},{"value":"0.4265"},{"value":"0.4187"},{"value":"2025-06-18"},{"value":"0.4108"},{"value":"0.0157"},{"value":"0.0122"}],[{"value":"2025-06-23"},{"value":"COS"},{"value":"-0.1328"},{"value":"-0.0468"},{"value":"-0.0468"},{"value":"-0.1328"},{"value":"2025-06-20"},{"value":"-0.3012"},{"value":"0.2545"},{"value":"0.0710"}],[{"value":"2025-06-23"},{"value":"SIN"},{"value":"0.4421"},{"value":"0.4498"},{"value":"0.4498"},{"value":"0.4421"},{"value":"2025-06-20"},{"value":"0.4265"},{"value":"0.0233"},{"value":"0.0120"}],[{"value":"2025-06-24"},{"value":"COS"},{"value":"-0.0468"},{"value":"0.0396"},{"value":"0.0396"},{"value":"-0.0468"},{"value":"2025-06-23"},{"value":"-0.0468"},{"value":"0.0864"},{"value":"0.0757"}],[{"value":"2025-06-24"},{"value":"SIN"},{"value":"0.4498"},{"value":"0.4575"},{"value":"0.4575"},{"value":"0.4498"},{"value":"2025-06-23"},{"value":"0.4498"},{"value":"0.0077"},{"value":"0.0120"}],[{"value":"2025-06-25"},{"value":"COS"},{"value":"0.0396"},{"value":"0.1257"},{"value":"0.1257"},{"value":"0.0396"},{"value":"2025-06-24"},{"value":"0.0396"},{"value":"0.0861"},{"value":"0.0809"}],[{"value":"2025-06-25"},{"value":"SIN"},{"value":"0.4575"},{"value":"0.4652"},{"value":"0.4652"},{"value":"0.4575"},{"value":"2025-06-24"},{"value":"0.4575"},{"value":"0.0077"},{"value":"0.0120"}],[{"value":"2025-06-26"},{"value":"COS"},{"value":"0.1257"},{"value":"0.2108"},{"value":"0.2108"},{"value":"0.1257"},{"value":"2025-06-25"},{"value":"0.1257"},{"value":"0.0851"},{"value":"0.0865"}],[{"value":"2025-06-26"},{"value":"SIN"},{"value":"0.4652"},{"value":"0.4728"},{"value":"0.4728"},{"value":"0.4652"},{"value":"2025-06-25"},{"value":"0.4652"},{"value":"0.0076"},{"value":"0.0119"}],[{"value":"2025-06-27"},{"value":"COS"},{"value":"0.2108"},{"value":"0.2944"},{"value":"0.2944"},{"value":"0.2108"},{"value":"2025-06-26"},{"value":"0.2108"},{"value":"0.0836"},{"value":"0.0923"}],[{"value":"2025-06-27"},{"value":"SIN"},{"value":"0.4728"},{"value":"0.4804"},{"value":"0.4804"},{"value":"0.4728"},{"value":"2025-06-26"},{"value":"0.4728"},{"value":"0.0076"},{"value":"0.0119"}],[{"value":"2025-06-30"},{"value":"COS"},{"value":"0.4543"},{"value":"0.5295"},{"value":"0.5295"},{"value":"0.4543"},{"value":"2025-06-27"},{"value":"0.2944"},{"value":"0.2351"},{"value":"0.1057"}],[{"value":"2025-06-30"},{"value":"SIN"},{"value":"0.4955"},{"value":"0.5030"},{"value":"0.5030"},{"value":"0.4955"},{"value":"2025-06-27"},{"value":"0.4804"},{"value":"0.0226"},{"value":"0.0117"}],[{"value":"2025-07-01"},{"value":"COS"},{"value":"0.5295"},{"value":"0.6007"},{"value":"0.6007"},{"value":"0.5295"},{"value":"2025-06-30"},{"value":"0.5295"},{"value":"0.0712"},{"value":"0.1087"}],[{"value":"2025-07-01"},{"value":"SIN"},{"value":"0.5030"},{"value":"0.5104"},{"value":"0.5104"},{"value":"0.5030"},{"value":"2025-06-30"},{"value":"0.5030"},{"value":"0.0074"},{"value":"0.0117"}],[{"value":"2025-07-02"},{"value":"COS"},{"value":"0.6007"},{"value":"0.6675"},{"value":"0.6675"},{"value":"0.6007"},{"value":"2025-07-01"},{"value":"0.6007"},{"value":"0.0667"},{"value":"0.1108"}],[{"value":"2025-07-02"},{"value":"SIN"},{"value":"0.5104"},{"value":"0.5178"},{"value":"0.5178"},{"value":"0.5104"},{"value":"2025-07-01"},{"value":"0.5104"},{"value":"0.0074"},{"value":"0.0116"}],[{"value":"2025-07-03"},{"value":"COS"},{"value":"0.6675"},{"value":"0.7292"},{"value":"0.7292"},{"value":"0.6675"},{"value":"2025-07-02"},{"value":"0.6675"},{"value":"0.0618"},{"value":"0.1121"}],[{"value":"2025-07-03"},{"value":"SIN"},{"value":"0.5178"},{"value":"0.5252"},{"value":"0.5252"},{"value":"0.5178"},{"value":"2025-07-02"},{"value":"0.5178"},{"value":"0.0074"},{"value":"0.0116"}],[{"value":"2025-07-07"},{"value":"COS"},{"value":"0.8803"},{"value":"0.9179"},{"value":"0.9179"},{"value":"0.8803"},{"value":"2025-07-03"},{"value":"0.7292"},{"value":"0.1887"},{"value":"0.1220"}],[{"value":"2025-07-07"},{"value":"SIN"},{"value":"0.5471"},{"value":"0.5543"},{"value":"0.5543"},{"value":"0.5471"},{"value":"2025-07-03"},{"value":"0.5252"},{"value":"0.0291"},{"value":"0.0131"}],[{"value":"2025-07-08"},{"value":"COS"},{"value":"0.9179"},{"value":"0.9487"},{"value":"0.9487"},{"value":"0.9179"},{"value":"2025-07-07"},{"value":"0.9179"},{"value":"0.0308"},{"value":"0.1111"}],[{"value":"2025-07-08"},{"value":"SIN"},{"value":"0.5543"},{"value":"0.5615"},{"value":"0.5615"},{"value":"0.5543"},{"value":"2025-07-07"},{"value":"0.5543"},{"value":"0.0072"},{"value":"0.0119"}],[{"value":"2025-07-09"},{"value":"COS"},{"value":"0.9487"},{"value":"0.9725"},{"value":"0.9725"},{"value":"0.9487"},{"value":"2025-07-08"},{"value":"0.9487"},{"value":"0.0237"},{"value":"0.1077"}],[{"value":"2025-07-09"},{"value":"SIN"},{"value":"0.5615"},{"value":"0.5686"},{"value":"0.5686"},{"value":"0.5615"},{"value":"2025-07-08"},{"value":"0.5615"},{"value":"0.0071"},{"value":"0.0118"}],[{"value":"2025-07-10"},{"value":"COS"},{"value":"0.9725"},{"value":"0.9890"},{"value":"0.9890"},{"value":"0.9725"},{"value":"2025-07-09"},{"value":"0.9725"},{"value":"0.0165"},{"value":"0.1035"}],[{"value":"2025-07-10"},{"value":"SIN"},{"value":"0.5686"},{"value":"0.5757"},{"value":"0.5757"},{"value":"0.5686"},{"value":"2025-07-09"},{"value":"0.5686"},{"value":"0.0071"},{"value":"0.0118"}],[{"value":"2025-07-11"},{"value":"COS"},{"value":"0.9890"},{"value":"0.9981"},{"value":"0.9981"},{"value":"0.9890"},{"value":"2025-07-10"},{"value":"0.9890"},{"value":"0.0091"},{"value":"0.0928"}],[{"value":"2025-07-11"},{"value":"SIN"},{"value":"0.5757"},{"value":"0.5827"},{"value":"0.5827"},{"value":"0.5757"},{"value":"2025-07-10"},{"value":"0.5757"},{"value":"0.0070"},{"value":"0.0112"}],[{"value":"2025-07-14"},{"value":"COS"},{"value":"0.9939"},{"value":"0.9807"},{"value":"0.9939"},{"value":"0.9807"},{"value":"2025-07-11"},{"value":"0.9981"},{"value":"0.0174"},{"value":"0.0759"}],[{"value":"2025-07-14"},{"value":"SIN"},{"value":"0.5967"},{"value":"0.6036"},{"value":"0.6036"},{"value":"0.5967"},{"value":"2025-07-11"},{"value":"0.5827"},{"value":"0.0209"},{"value":"0.0110"}],[{"value":"2025-07-15"},{"value":"COS"},{"value":"0.9807"},{"value":"0.9602"},{"value":"0.9807"},{"value":"0.9602"},{"value":"2025-07-14"},{"value":"0.9807"},{"value":"0.0205"},{"value":"0.0712"}],[{"value":"2025-07-15"},{"value":"SIN"},{"value":"0.6036"},{"value":"0.6105"},{"value":"0.6105"},{"value":"0.6036"},{"value":"2025-07-14"},{"value":"0.6036"},{"value":"0.0069"},{"value":"0.0109"}]]}}}} \ No newline at end of file diff --git a/docs/python/snapshots/d0db79e156f32579f7f87ff63aa1eb55.json b/docs/python/snapshots/d0db79e156f32579f7f87ff63aa1eb55.json new file mode 100644 index 00000000000..8e2bc8146cf --- /dev/null +++ b/docs/python/snapshots/d0db79e156f32579f7f87ff63aa1eb55.json @@ -0,0 +1 @@ +{"file":"reference/community-questions/average-true-range.md","objects":{"trades":{"type":"Table","data":{"columns":[{"name":"Timestamp","type":"java.time.Instant"},{"name":"Date","type":"java.lang.String"},{"name":"Sym","type":"java.lang.String"},{"name":"Price","type":"double"}],"rows":[[{"value":"2025-05-01 00:00:00.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:01.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:02.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:03.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:04.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:05.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:06.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:07.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:08.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:09.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:10.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:11.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:12.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:13.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:14.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:15.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:16.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:17.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:18.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:19.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:20.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:21.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:22.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:23.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:24.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:25.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:26.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:27.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:28.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:29.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:30.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:31.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:32.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:33.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:34.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:35.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:36.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:37.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:38.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:39.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:40.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:41.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:42.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:43.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:44.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:45.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:46.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:47.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:48.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:49.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:50.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:51.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:52.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:53.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:54.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:55.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:56.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:57.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:00:58.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:00:59.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:00.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:01.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:02.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:03.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:04.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:05.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:06.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:07.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:08.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:09.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:10.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:11.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:12.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:13.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:14.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:15.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:16.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:17.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:18.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:19.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:20.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:21.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:22.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:23.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:24.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:25.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:26.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:27.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:28.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:29.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:30.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:31.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:32.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:33.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:34.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:35.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:36.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:37.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}],[{"value":"2025-05-01 00:01:38.000"},{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"}],[{"value":"2025-05-01 00:01:39.000"},{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"}]]}},"data":{"type":"Table","data":{"columns":[{"name":"Date","type":"java.lang.String"},{"name":"Sym","type":"java.lang.String"},{"name":"Open","type":"double"},{"name":"Close","type":"double"},{"name":"High","type":"double"},{"name":"Low","type":"double"}],"rows":[[{"value":"2025-05-01"},{"value":"COS"},{"value":"1.0000"},{"value":"0.9963"},{"value":"1.0000"},{"value":"0.9963"}],[{"value":"2025-05-01"},{"value":"SIN"},{"value":"0.0000"},{"value":"0.0086"},{"value":"0.0086"},{"value":"0.0000"}],[{"value":"2025-05-02"},{"value":"COS"},{"value":"0.9963"},{"value":"0.9851"},{"value":"0.9963"},{"value":"0.9851"}],[{"value":"2025-05-02"},{"value":"SIN"},{"value":"0.0086"},{"value":"0.0173"},{"value":"0.0173"},{"value":"0.0086"}],[{"value":"2025-05-05"},{"value":"COS"},{"value":"0.9409"},{"value":"0.9081"},{"value":"0.9409"},{"value":"0.9081"}],[{"value":"2025-05-05"},{"value":"SIN"},{"value":"0.0346"},{"value":"0.0432"},{"value":"0.0432"},{"value":"0.0346"}],[{"value":"2025-05-06"},{"value":"COS"},{"value":"0.9081"},{"value":"0.8686"},{"value":"0.9081"},{"value":"0.8686"}],[{"value":"2025-05-06"},{"value":"SIN"},{"value":"0.0432"},{"value":"0.0518"},{"value":"0.0518"},{"value":"0.0432"}],[{"value":"2025-05-07"},{"value":"COS"},{"value":"0.8686"},{"value":"0.8226"},{"value":"0.8686"},{"value":"0.8226"}],[{"value":"2025-05-07"},{"value":"SIN"},{"value":"0.0518"},{"value":"0.0604"},{"value":"0.0604"},{"value":"0.0518"}],[{"value":"2025-05-08"},{"value":"COS"},{"value":"0.8226"},{"value":"0.7705"},{"value":"0.8226"},{"value":"0.7705"}],[{"value":"2025-05-08"},{"value":"SIN"},{"value":"0.0604"},{"value":"0.0691"},{"value":"0.0691"},{"value":"0.0604"}],[{"value":"2025-05-09"},{"value":"COS"},{"value":"0.7705"},{"value":"0.7126"},{"value":"0.7705"},{"value":"0.7126"}],[{"value":"2025-05-09"},{"value":"SIN"},{"value":"0.0691"},{"value":"0.0777"},{"value":"0.0777"},{"value":"0.0691"}],[{"value":"2025-05-12"},{"value":"COS"},{"value":"0.5814"},{"value":"0.5090"},{"value":"0.5814"},{"value":"0.5090"}],[{"value":"2025-05-12"},{"value":"SIN"},{"value":"0.0949"},{"value":"0.1035"},{"value":"0.1035"},{"value":"0.0949"}],[{"value":"2025-05-13"},{"value":"COS"},{"value":"0.5090"},{"value":"0.4328"},{"value":"0.5090"},{"value":"0.4328"}],[{"value":"2025-05-13"},{"value":"SIN"},{"value":"0.1035"},{"value":"0.1121"},{"value":"0.1121"},{"value":"0.1035"}],[{"value":"2025-05-14"},{"value":"COS"},{"value":"0.4328"},{"value":"0.3534"},{"value":"0.4328"},{"value":"0.3534"}],[{"value":"2025-05-14"},{"value":"SIN"},{"value":"0.1121"},{"value":"0.1207"},{"value":"0.1207"},{"value":"0.1121"}],[{"value":"2025-05-15"},{"value":"COS"},{"value":"0.3534"},{"value":"0.2714"},{"value":"0.3534"},{"value":"0.2714"}],[{"value":"2025-05-15"},{"value":"SIN"},{"value":"0.1207"},{"value":"0.1292"},{"value":"0.1292"},{"value":"0.1207"}],[{"value":"2025-05-16"},{"value":"COS"},{"value":"0.2714"},{"value":"0.1873"},{"value":"0.2714"},{"value":"0.1873"}],[{"value":"2025-05-16"},{"value":"SIN"},{"value":"0.1292"},{"value":"0.1378"},{"value":"0.1378"},{"value":"0.1292"}],[{"value":"2025-05-19"},{"value":"COS"},{"value":"0.0156"},{"value":"-0.0707"},{"value":"0.0156"},{"value":"-0.0707"}],[{"value":"2025-05-19"},{"value":"SIN"},{"value":"0.1549"},{"value":"0.1634"},{"value":"0.1634"},{"value":"0.1549"}],[{"value":"2025-05-20"},{"value":"COS"},{"value":"-0.0707"},{"value":"-0.1566"},{"value":"-0.0707"},{"value":"-0.1566"}],[{"value":"2025-05-20"},{"value":"SIN"},{"value":"0.1634"},{"value":"0.1719"},{"value":"0.1719"},{"value":"0.1634"}],[{"value":"2025-05-21"},{"value":"COS"},{"value":"-0.1566"},{"value":"-0.2412"},{"value":"-0.1566"},{"value":"-0.2412"}],[{"value":"2025-05-21"},{"value":"SIN"},{"value":"0.1719"},{"value":"0.1804"},{"value":"0.1804"},{"value":"0.1719"}],[{"value":"2025-05-22"},{"value":"COS"},{"value":"-0.2412"},{"value":"-0.3240"},{"value":"-0.2412"},{"value":"-0.3240"}],[{"value":"2025-05-22"},{"value":"SIN"},{"value":"0.1804"},{"value":"0.1889"},{"value":"0.1889"},{"value":"0.1804"}],[{"value":"2025-05-23"},{"value":"COS"},{"value":"-0.3240"},{"value":"-0.4045"},{"value":"-0.3240"},{"value":"-0.4045"}],[{"value":"2025-05-23"},{"value":"SIN"},{"value":"0.1889"},{"value":"0.1974"},{"value":"0.1974"},{"value":"0.1889"}],[{"value":"2025-05-27"},{"value":"COS"},{"value":"-0.6254"},{"value":"-0.6904"},{"value":"-0.6254"},{"value":"-0.6904"}],[{"value":"2025-05-27"},{"value":"SIN"},{"value":"0.2228"},{"value":"0.2312"},{"value":"0.2312"},{"value":"0.2228"}],[{"value":"2025-05-28"},{"value":"COS"},{"value":"-0.6904"},{"value":"-0.7502"},{"value":"-0.6904"},{"value":"-0.7502"}],[{"value":"2025-05-28"},{"value":"SIN"},{"value":"0.2312"},{"value":"0.2396"},{"value":"0.2396"},{"value":"0.2312"}],[{"value":"2025-05-29"},{"value":"COS"},{"value":"-0.7502"},{"value":"-0.8045"},{"value":"-0.7502"},{"value":"-0.8045"}],[{"value":"2025-05-29"},{"value":"SIN"},{"value":"0.2396"},{"value":"0.2479"},{"value":"0.2479"},{"value":"0.2396"}],[{"value":"2025-05-30"},{"value":"COS"},{"value":"-0.8045"},{"value":"-0.8527"},{"value":"-0.8045"},{"value":"-0.8527"}],[{"value":"2025-05-30"},{"value":"SIN"},{"value":"0.2479"},{"value":"0.2563"},{"value":"0.2563"},{"value":"0.2479"}],[{"value":"2025-06-02"},{"value":"COS"},{"value":"-0.9298"},{"value":"-0.9581"},{"value":"-0.9298"},{"value":"-0.9581"}],[{"value":"2025-06-02"},{"value":"SIN"},{"value":"0.2730"},{"value":"0.2813"},{"value":"0.2813"},{"value":"0.2730"}],[{"value":"2025-06-03"},{"value":"COS"},{"value":"-0.9581"},{"value":"-0.9793"},{"value":"-0.9581"},{"value":"-0.9793"}],[{"value":"2025-06-03"},{"value":"SIN"},{"value":"0.2813"},{"value":"0.2896"},{"value":"0.2896"},{"value":"0.2813"}],[{"value":"2025-06-04"},{"value":"COS"},{"value":"-0.9793"},{"value":"-0.9931"},{"value":"-0.9793"},{"value":"-0.9931"}],[{"value":"2025-06-04"},{"value":"SIN"},{"value":"0.2896"},{"value":"0.2978"},{"value":"0.2978"},{"value":"0.2896"}],[{"value":"2025-06-05"},{"value":"COS"},{"value":"-0.9931"},{"value":"-0.9995"},{"value":"-0.9931"},{"value":"-0.9995"}],[{"value":"2025-06-05"},{"value":"SIN"},{"value":"0.2978"},{"value":"0.3060"},{"value":"0.3060"},{"value":"0.2978"}],[{"value":"2025-06-06"},{"value":"COS"},{"value":"-0.9995"},{"value":"-0.9985"},{"value":"-0.9985"},{"value":"-1.0000"}],[{"value":"2025-06-06"},{"value":"SIN"},{"value":"0.3060"},{"value":"0.3143"},{"value":"0.3143"},{"value":"0.3060"}],[{"value":"2025-06-09"},{"value":"COS"},{"value":"-0.9741"},{"value":"-0.9510"},{"value":"-0.9510"},{"value":"-0.9741"}],[{"value":"2025-06-09"},{"value":"SIN"},{"value":"0.3306"},{"value":"0.3388"},{"value":"0.3388"},{"value":"0.3306"}],[{"value":"2025-06-10"},{"value":"COS"},{"value":"-0.9510"},{"value":"-0.9207"},{"value":"-0.9207"},{"value":"-0.9510"}],[{"value":"2025-06-10"},{"value":"SIN"},{"value":"0.3388"},{"value":"0.3469"},{"value":"0.3469"},{"value":"0.3388"}],[{"value":"2025-06-11"},{"value":"COS"},{"value":"-0.9207"},{"value":"-0.8836"},{"value":"-0.8836"},{"value":"-0.9207"}],[{"value":"2025-06-11"},{"value":"SIN"},{"value":"0.3469"},{"value":"0.3550"},{"value":"0.3550"},{"value":"0.3469"}],[{"value":"2025-06-12"},{"value":"COS"},{"value":"-0.8836"},{"value":"-0.8399"},{"value":"-0.8399"},{"value":"-0.8836"}],[{"value":"2025-06-12"},{"value":"SIN"},{"value":"0.3550"},{"value":"0.3630"},{"value":"0.3630"},{"value":"0.3550"}],[{"value":"2025-06-13"},{"value":"COS"},{"value":"-0.8399"},{"value":"-0.7900"},{"value":"-0.7900"},{"value":"-0.8399"}],[{"value":"2025-06-13"},{"value":"SIN"},{"value":"0.3630"},{"value":"0.3711"},{"value":"0.3711"},{"value":"0.3630"}],[{"value":"2025-06-16"},{"value":"COS"},{"value":"-0.6728"},{"value":"-0.6065"},{"value":"-0.6065"},{"value":"-0.6728"}],[{"value":"2025-06-16"},{"value":"SIN"},{"value":"0.3871"},{"value":"0.3950"},{"value":"0.3950"},{"value":"0.3871"}],[{"value":"2025-06-17"},{"value":"COS"},{"value":"-0.6065"},{"value":"-0.5356"},{"value":"-0.5356"},{"value":"-0.6065"}],[{"value":"2025-06-17"},{"value":"SIN"},{"value":"0.3950"},{"value":"0.4029"},{"value":"0.4029"},{"value":"0.3950"}],[{"value":"2025-06-18"},{"value":"COS"},{"value":"-0.5356"},{"value":"-0.4607"},{"value":"-0.4607"},{"value":"-0.5356"}],[{"value":"2025-06-18"},{"value":"SIN"},{"value":"0.4029"},{"value":"0.4108"},{"value":"0.4108"},{"value":"0.4029"}],[{"value":"2025-06-20"},{"value":"COS"},{"value":"-0.3824"},{"value":"-0.3012"},{"value":"-0.3012"},{"value":"-0.3824"}],[{"value":"2025-06-20"},{"value":"SIN"},{"value":"0.4187"},{"value":"0.4265"},{"value":"0.4265"},{"value":"0.4187"}],[{"value":"2025-06-23"},{"value":"COS"},{"value":"-0.1328"},{"value":"-0.0468"},{"value":"-0.0468"},{"value":"-0.1328"}],[{"value":"2025-06-23"},{"value":"SIN"},{"value":"0.4421"},{"value":"0.4498"},{"value":"0.4498"},{"value":"0.4421"}],[{"value":"2025-06-24"},{"value":"COS"},{"value":"-0.0468"},{"value":"0.0396"},{"value":"0.0396"},{"value":"-0.0468"}],[{"value":"2025-06-24"},{"value":"SIN"},{"value":"0.4498"},{"value":"0.4575"},{"value":"0.4575"},{"value":"0.4498"}],[{"value":"2025-06-25"},{"value":"COS"},{"value":"0.0396"},{"value":"0.1257"},{"value":"0.1257"},{"value":"0.0396"}],[{"value":"2025-06-25"},{"value":"SIN"},{"value":"0.4575"},{"value":"0.4652"},{"value":"0.4652"},{"value":"0.4575"}],[{"value":"2025-06-26"},{"value":"COS"},{"value":"0.1257"},{"value":"0.2108"},{"value":"0.2108"},{"value":"0.1257"}],[{"value":"2025-06-26"},{"value":"SIN"},{"value":"0.4652"},{"value":"0.4728"},{"value":"0.4728"},{"value":"0.4652"}],[{"value":"2025-06-27"},{"value":"COS"},{"value":"0.2108"},{"value":"0.2944"},{"value":"0.2944"},{"value":"0.2108"}],[{"value":"2025-06-27"},{"value":"SIN"},{"value":"0.4728"},{"value":"0.4804"},{"value":"0.4804"},{"value":"0.4728"}],[{"value":"2025-06-30"},{"value":"COS"},{"value":"0.4543"},{"value":"0.5295"},{"value":"0.5295"},{"value":"0.4543"}],[{"value":"2025-06-30"},{"value":"SIN"},{"value":"0.4955"},{"value":"0.5030"},{"value":"0.5030"},{"value":"0.4955"}],[{"value":"2025-07-01"},{"value":"COS"},{"value":"0.5295"},{"value":"0.6007"},{"value":"0.6007"},{"value":"0.5295"}],[{"value":"2025-07-01"},{"value":"SIN"},{"value":"0.5030"},{"value":"0.5104"},{"value":"0.5104"},{"value":"0.5030"}],[{"value":"2025-07-02"},{"value":"COS"},{"value":"0.6007"},{"value":"0.6675"},{"value":"0.6675"},{"value":"0.6007"}],[{"value":"2025-07-02"},{"value":"SIN"},{"value":"0.5104"},{"value":"0.5178"},{"value":"0.5178"},{"value":"0.5104"}],[{"value":"2025-07-03"},{"value":"COS"},{"value":"0.6675"},{"value":"0.7292"},{"value":"0.7292"},{"value":"0.6675"}],[{"value":"2025-07-03"},{"value":"SIN"},{"value":"0.5178"},{"value":"0.5252"},{"value":"0.5252"},{"value":"0.5178"}],[{"value":"2025-07-07"},{"value":"COS"},{"value":"0.8803"},{"value":"0.9179"},{"value":"0.9179"},{"value":"0.8803"}],[{"value":"2025-07-07"},{"value":"SIN"},{"value":"0.5471"},{"value":"0.5543"},{"value":"0.5543"},{"value":"0.5471"}],[{"value":"2025-07-08"},{"value":"COS"},{"value":"0.9179"},{"value":"0.9487"},{"value":"0.9487"},{"value":"0.9179"}],[{"value":"2025-07-08"},{"value":"SIN"},{"value":"0.5543"},{"value":"0.5615"},{"value":"0.5615"},{"value":"0.5543"}],[{"value":"2025-07-09"},{"value":"COS"},{"value":"0.9487"},{"value":"0.9725"},{"value":"0.9725"},{"value":"0.9487"}],[{"value":"2025-07-09"},{"value":"SIN"},{"value":"0.5615"},{"value":"0.5686"},{"value":"0.5686"},{"value":"0.5615"}],[{"value":"2025-07-10"},{"value":"COS"},{"value":"0.9725"},{"value":"0.9890"},{"value":"0.9890"},{"value":"0.9725"}],[{"value":"2025-07-10"},{"value":"SIN"},{"value":"0.5686"},{"value":"0.5757"},{"value":"0.5757"},{"value":"0.5686"}],[{"value":"2025-07-11"},{"value":"COS"},{"value":"0.9890"},{"value":"0.9981"},{"value":"0.9981"},{"value":"0.9890"}],[{"value":"2025-07-11"},{"value":"SIN"},{"value":"0.5757"},{"value":"0.5827"},{"value":"0.5827"},{"value":"0.5757"}],[{"value":"2025-07-14"},{"value":"COS"},{"value":"0.9939"},{"value":"0.9807"},{"value":"0.9939"},{"value":"0.9807"}],[{"value":"2025-07-14"},{"value":"SIN"},{"value":"0.5967"},{"value":"0.6036"},{"value":"0.6036"},{"value":"0.5967"}]]}}}} \ No newline at end of file diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/SourceTable.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/SourceTable.java index 31a01f15cb3..b39f6b383f1 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/SourceTable.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/SourceTable.java @@ -307,18 +307,6 @@ protected Collection> filterLocationKeys protected final QueryTable doCoalesce() { initialize(); - if (!isRefreshing()) { - final Collection includedLocations = columnSourceManager.includedLocations(); - if (includedLocations.size() == 1) { - for (final SortColumn sc : includedLocations.iterator().next().getSortedColumns()) { - final SortingOrder order = sc.order() == SortColumn.Order.ASCENDING - ? SortingOrder.Ascending - : SortingOrder.Descending; - SortedColumnsAttribute.setOrderForColumn(this, sc.column().name(), order); - } - } - } - final OperationSnapshotControl snapshotControl = createSnapshotControlIfRefreshing((final BaseTable parent) -> new OperationSnapshotControl(parent) { @@ -337,6 +325,19 @@ public boolean subscribeForUpdates(@NotNull final TableUpdateListener listener) if (rowSet.isEmpty()) { resultTable.setAttribute(INITIALLY_EMPTY_COALESCED_SOURCE_TABLE_ATTRIBUTE, true); } + if (!isRefreshing()) { + // (Maybe) set the sorted attribute on the result table (not on the uncoalesced table). + // Its attributes may already have been published and frozen. + final Collection includedLocations = columnSourceManager.includedLocations(); + if (includedLocations.size() == 1) { + for (final SortColumn sc : includedLocations.iterator().next().getSortedColumns()) { + final SortingOrder order = sc.order() == SortColumn.Order.ASCENDING + ? SortingOrder.Ascending + : SortingOrder.Descending; + SortedColumnsAttribute.setOrderForColumn(resultTable, sc.column().name(), order); + } + } + } if (snapshotControl != null) { // noinspection MethodDoesntCallSuperMethod diff --git a/extensions/flight-sql/build.gradle b/extensions/flight-sql/build.gradle index 881ec839003..3c1c2a0b37c 100644 --- a/extensions/flight-sql/build.gradle +++ b/extensions/flight-sql/build.gradle @@ -32,11 +32,6 @@ dependencies { // :sql does not expose calcite as a dependency (maybe it should?); in the meantime, we want to make sure we can // provide reasonable error messages to the client implementation libs.calcite.core - constraints { - implementation(libs.json.smart) { - because 'CVE-2024-57699' - } - } implementation libs.dagger implementation libs.arrow.flight.sql diff --git a/extensions/parquet/table/src/test/java/io/deephaven/parquet/table/ParquetTableReadWriteTest.java b/extensions/parquet/table/src/test/java/io/deephaven/parquet/table/ParquetTableReadWriteTest.java index c5779b6121e..b84e34fcb12 100644 --- a/extensions/parquet/table/src/test/java/io/deephaven/parquet/table/ParquetTableReadWriteTest.java +++ b/extensions/parquet/table/src/test/java/io/deephaven/parquet/table/ParquetTableReadWriteTest.java @@ -623,9 +623,9 @@ public void testSortedColumnsAttributeRoundTrip() { final File dest = new File(rootFile, "ParquetTest_sortedColumnsAttribute_test.parquet"); writeTable(sorted, dest.getPath()); - // Coalescing populates the sort attribute from parquet metadata onto the SourceTable - final Table fromDisk = readTable(dest.getPath()); - fromDisk.coalesce(); + // Coalescing populates the sort attribute from parquet metadata onto the coalesced result table; the + // uncoalesced SourceTable's attributes are never mutated (they may already have been published) + final Table fromDisk = readTable(dest.getPath()).coalesce(); assertEquals(Optional.of(SortingOrder.Ascending), SortedColumnsAttribute.getOrderForColumn(fromDisk, "x")); assertEquals(Optional.empty(), @@ -636,8 +636,7 @@ public void testSortedColumnsAttributeRoundTrip() { final File dest2 = new File(rootFile, "ParquetTest_sortedColumnsAttribute_test2.parquet"); writeTable(sorted2, dest2.getPath()); - final Table fromDisk2 = readTable(dest2.getPath()); - fromDisk2.coalesce(); + final Table fromDisk2 = readTable(dest2.getPath()).coalesce(); assertEquals(Optional.of(SortingOrder.Ascending), SortedColumnsAttribute.getOrderForColumn(fromDisk2, "x")); @@ -646,8 +645,7 @@ public void testSortedColumnsAttributeRoundTrip() { final File dest3 = new File(rootFile, "ParquetTest_sortedColumnsAttribute_desc_test.parquet"); writeTable(sortedDesc, dest3.getPath()); - final Table fromDisk3 = readTable(dest3.getPath()); - fromDisk3.coalesce(); + final Table fromDisk3 = readTable(dest3.getPath()).coalesce(); assertEquals(Optional.of(SortingOrder.Descending), SortedColumnsAttribute.getOrderForColumn(fromDisk3, "x")); } diff --git a/extensions/parquet/table/src/test/java/io/deephaven/parquet/table/TestParquetTools.java b/extensions/parquet/table/src/test/java/io/deephaven/parquet/table/TestParquetTools.java index b0725102c48..06fc092fef7 100644 --- a/extensions/parquet/table/src/test/java/io/deephaven/parquet/table/TestParquetTools.java +++ b/extensions/parquet/table/src/test/java/io/deephaven/parquet/table/TestParquetTools.java @@ -16,6 +16,8 @@ import io.deephaven.engine.table.TableDefinition; import io.deephaven.engine.table.impl.InMemoryTable; import io.deephaven.engine.table.impl.QueryTable; +import io.deephaven.engine.table.impl.SortedColumnsAttribute; +import io.deephaven.engine.table.impl.SortingOrder; import io.deephaven.engine.table.impl.UncoalescedTable; import io.deephaven.engine.table.impl.indexer.DataIndexer; import io.deephaven.engine.table.impl.locations.TableDataException; @@ -1448,6 +1450,26 @@ public Optional adapt(@NotNull final CodecInfo original) { } } + /** + * Regression test for publishing a source table's attributes before its first coalesce. Reading a parquet file with + * sorting metadata, publishing the uncoalesced table's attributes (as the server's export path does when sending + * table metadata to clients), and then coalescing must not throw, and the coalesced result must still carry the + * sorted-columns attribute. + */ + @Test + public void testPublishAttributesBeforeCoalesce() { + final Path parquetFile = Path.of(testRoot, "testPublishAttributesBeforeCoalesce.parquet"); + final Table sorted = emptyTable(100).update("A = ii % 10").sort("A"); + ParquetTools.writeTable(sorted, parquetFile.toString()); + + final Table fromDisk = ParquetTools.readTable(parquetFile.toString()); + // Publish the (uncoalesced) table's attributes before the first coalesce + fromDisk.getAttributes(); + final Table coalesced = fromDisk.coalesce(); + assertEquals(Optional.of(SortingOrder.Ascending), SortedColumnsAttribute.getOrderForColumn(coalesced, "A")); + assertTableEquals(sorted, coalesced); + } + /** * An example non-standard column-type which simply wraps an array of int */ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3fac122724d..9b3dfaae8cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,9 +7,7 @@ avro = "1.12.1" awssdk = "2.29.52" aws-s3-tables-catalog-for-iceberg = "0.1.8" -# Note: when bumping Calcite version, see if we still need the version constraint for json-smart -calcite = "1.41.0" -json-smart = "2.5.2" +calcite = "1.42.0" classgraph = "4.8.184" commons-compress = "1.28.0" @@ -139,7 +137,6 @@ awssdk-netty-nio = { module = "software.amazon.awssdk:netty-nio-client" } s3-tables-catalog-for-iceberg = { module = "software.amazon.s3tables:s3-tables-catalog-for-iceberg", version.ref = "aws-s3-tables-catalog-for-iceberg" } calcite-core = { module = "org.apache.calcite:calcite-core", version.ref = "calcite" } -json-smart = { module = "net.minidev:json-smart", version.ref = "json-smart" } classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } diff --git a/py/jpy-integration/src/javaToPython/java/io/deephaven/jpy/integration/ReferenceCountingTest.java b/py/jpy-integration/src/javaToPython/java/io/deephaven/jpy/integration/ReferenceCountingTest.java index 8adb22b3da4..63d3df7c405 100644 --- a/py/jpy-integration/src/javaToPython/java/io/deephaven/jpy/integration/ReferenceCountingTest.java +++ b/py/jpy-integration/src/javaToPython/java/io/deephaven/jpy/integration/ReferenceCountingTest.java @@ -16,11 +16,7 @@ import org.jpy.PyInputMode; import org.jpy.PyModule; import org.jpy.PyObject; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.*; /** * Tests that when passing array from java to python, it is correctly recognized by jpy. @@ -95,7 +91,9 @@ public void viaPython() throws InterruptedException { } }); - Assert.assertTrue( + // Because the test is flaky, for now we will simply report it as "skipped" if it fails. + // We accomplish this by using assumeTrue rather than assertTrue. + Assume.assumeTrue( "Cleanup didn't happen. Reason: " + result, result == ReferenceCounting.CleanupResult.SUCCESS); } @@ -197,7 +195,9 @@ public void pythonObjectInJavaWillDestructAfterGC() throws InterruptedException () -> latch.getCount() == 0); - Assert.assertTrue( + // Because the test is flaky, for now we will simply report it as "skipped" if it fails. + // We accomplish this by using assumeTrue rather than assertTrue. + Assume.assumeTrue( "Cleanup didn't happen. Reason: " + result, result == ReferenceCounting.CleanupResult.SUCCESS); } diff --git a/sql/build.gradle b/sql/build.gradle index 4f58b2a49aa..a37bc9b4f6b 100644 --- a/sql/build.gradle +++ b/sql/build.gradle @@ -9,11 +9,6 @@ description = 'The Deephaven SQL parser' dependencies { api project(':qst') implementation libs.calcite.core - constraints { - implementation(libs.json.smart) { - because 'CVE-2024-57699' - } - } compileOnly project(':util-immutables') annotationProcessor libs.immutables.value diff --git a/sql/src/main/java/io/deephaven/sql/AggregateCallAdapterImpl.java b/sql/src/main/java/io/deephaven/sql/AggregateCallAdapterImpl.java index 0575373c838..b4f22f9722c 100644 --- a/sql/src/main/java/io/deephaven/sql/AggregateCallAdapterImpl.java +++ b/sql/src/main/java/io/deephaven/sql/AggregateCallAdapterImpl.java @@ -24,8 +24,14 @@ final class AggregateCallAdapterImpl { Map.entry(SqlStdOperatorTable.AVG, aggSpecFunction(AggSpec.avg())), Map.entry(SqlStdOperatorTable.SUM, aggSpecFunction(AggSpec.sum())), Map.entry(SqlStdOperatorTable.ANY_VALUE, aggSpecFunction(AggSpec.first())), - Map.entry(SqlStdOperatorTable.FIRST_VALUE, aggSpecFunction(AggSpec.first())), - Map.entry(SqlStdOperatorTable.LAST_VALUE, aggSpecFunction(AggSpec.last())), + // SQLTODO(window-functions): FIRST_VALUE / LAST_VALUE are window-only functions in the SQL standard + // (they require an OVER clause); their bare-aggregate form was a non-standard extension. As of Calcite + // 1.42, the validator enforces conformance and rejects the bare form, so it can no longer be mapped + // here. Windowed usage (OVER ...) produces a LogicalWindow RelNode that is not yet translated (see + // RelNodeVisitorAdapter); supporting it would map to Table#updateBy (QST UpdateByTable). Until then, + // these are unsupported. + // Map.entry(SqlStdOperatorTable.FIRST_VALUE, aggSpecFunction(AggSpec.first())), + // Map.entry(SqlStdOperatorTable.LAST_VALUE, aggSpecFunction(AggSpec.last())), Map.entry(SqlStdOperatorTable.STDDEV, aggSpecFunction(AggSpec.std())), Map.entry(SqlStdOperatorTable.VARIANCE, aggSpecFunction(AggSpec.var())), Map.entry(SqlStdOperatorTable.COUNT, AggregateCallAdapterImpl::count), diff --git a/sql/src/test/resources/io/deephaven/sql/qst-12.dot b/sql/src/test/resources/io/deephaven/sql/qst-12.dot index 5bd75bf846e..3405ff4a355 100644 --- a/sql/src/test/resources/io/deephaven/sql/qst-12.dot +++ b/sql/src/test/resources/io/deephaven/sql/qst-12.dot @@ -2,8 +2,8 @@ digraph { "op_0" ["label"="ticketTable(scan/books)"] "op_1" ["label"="view(__p_2_0=Id,__p_2_1=Title,__p_2_2=AuthorId)"] "op_2" ["label"="view(__a_1_0=__p_2_2,__a_1_1=__p_2_0)"] -"op_3" ["label"="aggBy([],[__p_0_0 = count, __p_0_1 = __a_1_0 aggregated with max, __p_0_2 = __a_1_1 aggregated with min, __p_0_3 = __a_1_1 aggregated with first, __p_0_4 = __a_1_1 aggregated with last, __p_0_5 = __a_1_1 aggregated with average])"] -"op_4" ["label"="view(my_count=__p_0_0,max_author_id=__p_0_1,min_id=__p_0_2,first_id=__p_0_3,last_id=__p_0_4,avg_id=__p_0_5,avg_id0=__p_0_5)"] +"op_3" ["label"="aggBy([],[__p_0_0 = count, __p_0_1 = __a_1_0 aggregated with max, __p_0_2 = __a_1_1 aggregated with min, __p_0_3 = __a_1_1 aggregated with average])"] +"op_4" ["label"="view(my_count=__p_0_0,max_author_id=__p_0_1,min_id=__p_0_2,avg_id=__p_0_3,avg_id0=__p_0_3)"] "op_1" -> "op_0" "op_2" -> "op_1" "op_3" -> "op_2" diff --git a/sql/src/test/resources/io/deephaven/sql/query-12.sql b/sql/src/test/resources/io/deephaven/sql/query-12.sql index eb78a0fec9d..389df3961d7 100644 --- a/sql/src/test/resources/io/deephaven/sql/query-12.sql +++ b/sql/src/test/resources/io/deephaven/sql/query-12.sql @@ -2,8 +2,6 @@ SELECT count(*) as my_count, max(AuthorId) as max_author_id, min(Id) as min_id, - FIRST_VALUE(Id) as first_id, - LAST_VALUE(Id) as last_id, avg(Id) as avg_id, avg(Id) as avg_id FROM diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/JoinableTable.java b/web/client-api/src/main/java/io/deephaven/web/client/api/JoinableTable.java index 080e922d319..2d1fbf06973 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/JoinableTable.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/JoinableTable.java @@ -13,7 +13,7 @@ import jsinterop.annotations.JsType; /** - * Represents a table which can be joined to another table. Current implementations are {@link JsTable} and + * Represents a table that can be joined to another table. Current implementations are {@link JsTable} and * {@link JsTotalsTable}. */ @JsType(namespace = "dh") @@ -24,6 +24,14 @@ public interface JoinableTable { @JsMethod Promise freeze(); + /** + * Creates a server-side snapshot of {@code baseTable} when this table updates. + * + * @param baseTable The table to snapshot. + * @param doInitialSnapshot Whether to create an initial snapshot immediately. + * @param stampColumns Optional list of column names to include in the result. + * @return A promise that resolves to the snapshot table. + */ @JsMethod Promise snapshot(JsTable baseTable, @JsOptional @JsNullable Boolean doInitialSnapshot, @JsOptional @JsNullable String[] stampColumns); @@ -31,14 +39,13 @@ Promise snapshot(JsTable baseTable, @JsOptional @JsNullable Boolean doI /** * Joins this table to the provided table, using one of the specified join types: *
    - *
  • AJ, ReverseAJ (or RAJ) - Inexact timeseries joins, based on the - * provided matching rule.
  • - *
  • CROSS_JOIN (or Join) - Cross join of all rows that have matching values in both - * tables.
  • - *
  • EXACT_JOIN (or ExactJoin) - Matches values in exactly one row in the right table, - * with errors if there is not exactly one.
  • - *
  • NATURAL_JOIN (or Natural - Matches values in at most one row in the right table, - * with nulls if there is no match or errors if there are multiple matches.
  • + *
  • {@code AJ}, {@code ReverseAJ} (or {@code RAJ}) - Inexact timeseries joins, based on the provided matching + * rule.
  • + *
  • {@code CROSS_JOIN} (or {@code Join}) - Cross join of all rows that have matching values in both tables.
  • + *
  • {@code EXACT_JOIN} (or {@code ExactJoin}) - Matches values in exactly one row in the right table, with errors + * if there is not exactly one.
  • + *
  • {@code NATURAL_JOIN} (or {@code Natural}) - Matches values in at most one row in the right table, with nulls + * if there is no match or errors if there are multiple matches.
  • *
* * Note that Left join is not supported here, unlike DHE. @@ -51,7 +58,7 @@ Promise snapshot(JsTable baseTable, @JsOptional @JsNullable Boolean doI * @param rightTable The table to match to values in this table. * @param columnsToMatch Columns that should match. * @param columnsToAdd Columns from the right table to add to the result - empty/null/absent to add all columns. - * @param asOfMatchRule If joinType is AJ/RAJ/ReverseAJ, the match rule to use. + * @param asOfMatchRule If joinType is {@code AJ}/{@code RAJ}/{@code ReverseAJ}, the match rule to use. * @return A promise that will resolve to the joined table. */ @JsMethod diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/JsColumnStatistics.java b/web/client-api/src/main/java/io/deephaven/web/client/api/JsColumnStatistics.java index a037bde8f8e..2bd2ae026de 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/JsColumnStatistics.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/JsColumnStatistics.java @@ -18,6 +18,12 @@ /** * Represents statistics for a given table column. + * + * Statistics are exposed via {@link #getStatisticsMap()}, keyed by display name (for example, "COUNT" or "AVG"). For + * each statistic name, {@link #getType(String)} returns the expected type name for formatting purposes, or {@code null} + * to indicate that the column's formatting should be used. + *

+ * If present, {@link #getUniqueValues()} returns a map of unique values (as strings) to their occurrence counts. */ @TsInterface @TsName(name = "ColumnStatistics", namespace = "dh") @@ -143,9 +149,8 @@ public JsColumnStatistics(TableData data) { } /** - * Gets the type of formatting that should be used for given statistic. - *

- * the format type for a statistic. A null return value means that the column formatting should be used. + * Gets the type of formatting that should be used for given statistic. A null return value means that the column + * formatting should be used. * * @param name the display name of the statistic * @return String @@ -156,9 +161,7 @@ public String getType(String name) { } /** - * Gets a map with the display name of statistics as keys and the numeric stat as a value. - *

- * A map of each statistic's name to its value. + * Gets a map of each statistic's display name to its value. * * @return Map of String and Object */ @@ -168,9 +171,8 @@ public JsMap getStatisticsMap() { } /** - * Gets a map with the name of each unique value as key and the count as the value. A map of each unique value's - * name to the count of how many times it occurred in the column. This map will be empty for tables containing more - * than 19 unique values. + * Gets a map of each unique value's name to the count of how many times it occurred in the column. This map will be + * empty for tables containing more than 19 unique values. * * @return Map of String double * diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/JsPartitionedTable.java b/web/client-api/src/main/java/io/deephaven/web/client/api/JsPartitionedTable.java index 5aaf8a215d6..ab6a34ed1a1 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/JsPartitionedTable.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/JsPartitionedTable.java @@ -35,14 +35,15 @@ import java.util.*; /** - * Represents a set of {@code Table}s each corresponding to some key. The keys are available locally, but a call must be - * made to the server to get each {@code Table}. All tables will have the same structure. + * Represents a set of {@link JsTable}s, each corresponding to some key. The keys are available locally, but a call must + * be made to the server to get each {@link JsTable}. All tables will have the same structure. */ @JsType(namespace = "dh", name = "PartitionedTable") public class JsPartitionedTable extends HasLifecycle implements ServerObject { /** - * Indicates that a new key has been added to the array of keys, which you can now fetch with {@code getTable}. + * Indicates that a new key has been added to the array of keys, which you can now fetch with + * {@link #getTable(Object) getTable}. */ public static final String EVENT_KEYADDED = "keyadded", EVENT_DISCONNECT = JsTable.EVENT_DISCONNECT, @@ -168,7 +169,7 @@ private void handleKeys(Event update) { * Fetch the table with the given key. If the key does not exist, returns {@code null}. * * @param key The key to fetch. An array of values for each key column, in the same order as the key columns are. - * @return Promise of {@code dh.Table}, or {@code null} if the key does not exist. + * @return Promise of {@link JsTable}, or {@code null} if the key does not exist. */ public Promise<@JsNullable JsTable> getTable(Object key) { // Wrap non-arrays in an array so we are consistent with how we track keys @@ -244,7 +245,7 @@ public Promise getMergedTable() { /** * The set of all currently known keys. This is kept up to date, so getting the list after adding an event listener - * for keyadded will ensure no keys are missed. + * for {@link #EVENT_KEYADDED} will ensure no keys are missed. * * @return Set of Object */ @@ -317,7 +318,9 @@ public Promise getBaseTable() { return baseTable.copy(); } - /** Close any subscriptions to underlying tables or key tables */ + /** + * Close any subscriptions to underlying tables or key tables. + */ private void closeSubscriptions() { if (baseTable != null) { baseTable.close(); diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/JsTable.java b/web/client-api/src/main/java/io/deephaven/web/client/api/JsTable.java index bffceb5d9db..e6104051272 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/JsTable.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/JsTable.java @@ -100,8 +100,12 @@ /** * Provides access to data in a table. Note that several methods present their response through Promises. This allows - * the client to both avoid actually connecting to the server until necessary, and also will permit some changes not to - * inform the UI right away that they have taken place. + * the client to: + * + *

    + *
  1. Avoid actually connecting to the server until necessary.
  2. + *
  3. Permit some changes not to inform the UI right away that they have taken place.
  4. + *
*/ @TsName(namespace = "dh", name = "Table") public class JsTable extends HasLifecycle implements HasTableBinding, JoinableTable, ServerObject { @@ -223,7 +227,7 @@ private JsTable(JsTable table) { } /** - * a {@code Sort} than can be used to reverse a table. This can be passed into n array in applySort. Note that Tree + * A {@link Sort} that can be used to reverse a table. This can be passed into an array in applySort. Note that Tree * Tables do not support {@code reverse}. * * @return {@link Sort} @@ -326,27 +330,28 @@ public ClientTableState state() { } /** - * {@code true} if this table represents a user Input Table (created by {@code InputTable.newInputTable}). When - * {@code true}, you may call {@code .inputTable()} to add or remove data from the underlying table. - * - * @return boolean + * {@code true} if this table represents a user Input Table (see {@link JsInputTable dh.InputTable}). When + * {@code true}, you may call {@link #inputTable()} to add or remove data from the underlying table. */ @JsProperty(name = "hasInputTable") public boolean hasInputTable() { return hasInputTable; } + /** + * Checks whether this table is a blink table. + * + * @return {@code true} if this table is a blink table; {@code false} otherwise. + */ @JsMethod public boolean isBlinkTable() { return isBlinkTable; } /** - * If {@code .hasInputTable} is {@code true}, you may call this method to gain access to an {@code InputTable} + * If {@link #hasInputTable()} is {@code true}, you may call this method to gain access to an {@link JsInputTable} * object which can be used to mutate the data within the table. If the table is not an Input Table, the promise * will be immediately rejected. - * - * @return Promise of {@code dh.InputTable} */ @JsMethod public Promise inputTable() { @@ -392,6 +397,11 @@ public void close() { subscriptions.clear(); } + /** + * Gets the names of attributes present on this table. + * + * @return String[] of attribute names. + */ @JsMethod public String[] getAttributes() { TableAttributesDefinition attrs = lastVisibleState().getTableDef().getAttributes(); @@ -401,8 +411,8 @@ public String[] getAttributes() { } /** - * {@code null} if no property exists, a string if it is an easily serializable property, or a {@code Promise - * <Table>} that will either resolve with a table or error out if the object can't be passed to JS. + * {@code null} if no attribute exists, a string if it is an easily serializable attribute value, or a + * {@link Promise} that resolves to a {@link JsTable} if the attribute value is a table. * * @param attributeName * @return Object @@ -445,9 +455,10 @@ public Object getAttribute(String attributeName) { /** * The columns that are present on this table. This is always all possible columns. If you specify fewer columns in - * {@code .setViewport()}, you will get only those columns in your {@code ViewportData}. {@code size} is the total - * count of rows in the table. The size can and will change; see the {@code sizechanged} event for details. Size - * will be negative in exceptional cases (eg. the table is uncoalesced, see the {@code isUncoalesced} property for + * {@link #setViewport(double, double, JsArray)}, you will get only those columns in your + * {@link io.deephaven.web.client.api.subscription.ViewportData ViewportData}. {@code size} is the total count of + * rows in the table. The size can and will change; see the {@link #EVENT_SIZECHANGED} event for details. Size will + * be negative in exceptional cases (eg. the table is uncoalesced, see the {@link #isUncoalesced()} property for * details). * * @return {@link Column} array @@ -457,6 +468,9 @@ public JsArray getColumns() { return Js.uncheckedCast(lastVisibleState().getColumns()); } + /** + * Layout hints for displaying this table. + */ @JsProperty @JsNullable public JsLayoutHints getLayoutHints() { @@ -469,9 +483,6 @@ public JsLayoutHints getLayoutHints() { * {@link #SIZE_UNCOALESCED}. Otherwise, the size will be updated when the server's update graph processes changes. *

* When the size changes, the {@link #EVENT_SIZECHANGED} event will be fired. - * - * @return the size of the table, or {@link #SIZE_UNCOALESCED} if there is no subscription and the table is - * uncoalesced. */ @JsProperty public double getSize() { @@ -487,6 +498,11 @@ public double getSize() { return size; } + /** + * The table description attribute. + * + * @return The description, or {@code null}. + */ @JsProperty @JsNullable public String getDescription() { @@ -497,8 +513,6 @@ public String getDescription() { * The total count of the rows in the table, excluding any filters. Unlike {@link #getSize()}, changes to this value * will not result in any event. If the table is unfiltered, this will return the same size as {@link #getSize()}. * If this table was uncoalesced before it was filtered, this will return {@link #SIZE_UNCOALESCED}. - * - * @return the size of the table before filters, or {@link #SIZE_UNCOALESCED} */ @JsProperty public double getTotalSize() { @@ -512,9 +526,7 @@ public double getTotalSize() { /** * An ordered list of {@link Sort}s to apply to the table. To update, call {@link #applySort(Sort[])}. Note that * this getter will return the new value immediately, even though it may take a little time to update on the server. - * You may listen for the sortchanged event to know when to update the UI. - * - * @return {@link Sort} array + * You may listen for the {@link #EVENT_SORTCHANGED} event to know when to update the UI. */ @JsProperty public JsArray getSort() { @@ -524,10 +536,8 @@ public JsArray getSort() { /** * An ordered list of filter conditions to apply to the table. To update, call * {@link #applyFilter(FilterCondition[])}. Note that this getter will return the new value immediately, even though - * it may take a little time to update on the server. You may listen for the {@code filterchanged} event to know - * when to update the UI. - * - * @return {@link FilterCondition} array + * it may take a little time to update on the server. You may listen for the {@link #EVENT_FILTERCHANGED} event to + * know when to update the UI. */ @JsProperty public JsArray getFilter() { @@ -537,12 +547,11 @@ public JsArray getFilter() { /** * Replace the currently set sort on this table. Returns the previously set value. Note that the sort property will * immediately return the new value, but you may receive update events using the old sort before the new sort is - * applied, and the {@code sortchanged} event fires. Reusing existing, applied sorts may enable this to perform - * better on the server. The {@code updated} event will also fire, but {@code rowadded} and {@code rowremoved} will - * not. + * applied, and the {@link #EVENT_SORTCHANGED} event fires. Reusing existing, applied sorts may enable this to + * perform better on the server. The {@link #EVENT_UPDATED} event will also fire, but {@link #EVENT_ROWADDED} and + * {@link #EVENT_ROWREMOVED} will not. * * @param sort - * @return {@link Sort} array */ @JsMethod @SuppressWarnings("unusable-by-js") @@ -576,12 +585,11 @@ public JsArray applySort(Sort[] sort) { /** * Replace the currently set filters on the table. Returns the previously set value. Note that the filter property * will immediately return the new value, but you may receive update events using the old filter before the new one - * is applied, and the {@code filterchanged} event fires. Reusing existing, applied filters may enable this to - * perform better on the server. The {@code updated} event will also fire, but {@code rowadded} and - * {@code rowremoved} will not. + * is applied, and the {@link #EVENT_FILTERCHANGED} event fires. Reusing existing, applied filters may enable this + * to perform better on the server. The {@link #EVENT_UPDATED} event will also fire, but {@link #EVENT_ROWADDED} and + * {@link #EVENT_ROWREMOVED} will not. * * @param filter - * @return {@link FilterCondition} array */ @JsMethod @SuppressWarnings("unusable-by-js") @@ -645,7 +653,6 @@ default CustomColumn asCustomColumn() { * Used when adding new filter and sort operations to the table, as long as they are present. * * @param customColumns - * @return {@link CustomColumn} array */ @JsMethod public JsArray applyCustomColumns(JsArray customColumns) { @@ -685,9 +692,6 @@ public JsArray applyCustomColumns(JsArray getCustomColumns() { @@ -719,7 +723,6 @@ public TableViewportSubscription setViewport(double firstRow, double lastRow, Js * @param lastRow * @param columns * @param updateIntervalMs - * @return {@link TableViewportSubscription} * @deprecated Use {@link #createViewportSubscription(Object)} instead. */ @JsMethod @@ -797,7 +800,6 @@ public TableSubscription subscribe(JsArray columns) { * * @param columns * @param updateIntervalMs - * @return {@link TableSubscription} * @deprecated Use {@link #createSubscription(Object)} with a {@link DataOptions.SubscriptionOptions} instead. */ @JsMethod @@ -815,9 +817,9 @@ public TableSubscription subscribe(JsArray columns, @JsOptional @JsNulla * Creates a subscription to the specified columns, across all rows in the table. Useful for charts or taking a * snapshot of the table atomically. The initial snapshot will arrive in a single event, but later changes will be * sent as updates. However, this may still be very expensive to run from a browser for very large tables. Each call - * to {@code createSubscription} creates a new subscription, which must have {@link TableSubscription#close()} - * called on it to stop it and release its resources, and all events are fired from the {@link TableSubscription} - * instance. + * to {@link #createSubscription(Object) createSubscription} creates a new subscription, which must have + * {@link TableSubscription#close()} called on it to stop it and release its resources, and all events are fired + * from the {@link TableSubscription} instance. * * @param options options for the subscription; see {@link DataOptions.SubscriptionOptions} for details * @return a new {@link TableSubscription} @@ -831,8 +833,8 @@ public TableSubscription createSubscription(@TsTypeRef(DataOptions.SubscriptionO * Creates a viewport subscription to the specified columns, across the specified rows in the table. The returned * {@link TableViewportSubscription} instance allows the viewport to be changed over time, and events are fired from * it when the data changes or when a viewport change has been applied. Each call to - * {@code createViewportSubscription} creates a new subscription, which must have - * {@link TableViewportSubscription#close()} called on it to stop it and release its resources + * {@link #createViewportSubscription(Object) createViewportSubscription} creates a new subscription, which must + * have {@link TableViewportSubscription#close()} called on it to stop it and release its resources * * @param options options for the viewport subscription; see {@link DataOptions.ViewportSubscriptionOptions} for * details @@ -860,7 +862,6 @@ public TableViewportSubscription createViewportSubscription( * the table, within the bounds of the specified rows and columns. * * @param options options for the snapshot; see {@link DataOptions.SnapshotOptions} for details - * @return Promise of {@link TableData} */ @JsMethod public Promise createSnapshot(@TsTypeRef(DataOptions.SnapshotOptions.class) Object options) { @@ -1025,8 +1026,8 @@ public Promise copy(boolean resolved) { /** * A promise that will resolve to a Totals Table of this table. This table will obey the configurations provided as * a parameter, or will use the table's default if no parameter is provided, and be updated once per second as - * necessary. Note that multiple calls to this method will each produce a new {@code TotalsTable} which must have - * {@code close} called on it when not in use. + * necessary. Note that multiple calls to this method will each produce a new {@link JsTotalsTable dh.TotalsTable} + * which must have {@link JsTotalsTable#close() close()} called on it when not in use. * * @param config * @return Promise of dh.TotalsTable @@ -1041,9 +1042,9 @@ public Promise getTotalsTable( } /** - * The default configuration to be used when building a {@code TotalsTable} for this table. + * The default configuration to be used when building a {@link JsTotalsTable dh.TotalsTable} for this table. * - * @return dh.TotalsTableConfig + * @return {@link JsTotalsTableConfig dh.TotalsTableConfig} */ @JsProperty public JsTotalsTableConfig getTotalsTableConfig() { @@ -1209,11 +1210,11 @@ private JsTotalsTableConfig getTotalsDirectiveFromOptionalConfig(Object config) } /** - * A promise that will resolve to a Totals Table of this table, ignoring any filters. See {@code getTotalsTable} - * above for more specifics. + * A promise that will resolve to a Totals Table of this table, ignoring any filters. See + * {@link #getTotalsTable(Object)} for more specifics. * * @param config - * @return promise of dh.TotalsTable + * @return promise of {@link JsTotalsTable dh.TotalsTable} */ @JsMethod public Promise getGrandTotalsTable( @@ -1232,11 +1233,12 @@ public Promise getGrandTotalsTable( } /** - * a promise that will resolve to a new roll-up {@code TreeTable} of this table. Multiple calls to this method will - * each produce a new {@code TreeTable} which must have {@code close} called on it when not in use. + * a promise that will resolve to a new roll-up {@link JsTreeTable dh.TreeTable} of this table. Multiple calls to + * this method will each produce a new {@link JsTreeTable dh.TreeTable} which must have {@link JsTreeTable#close() + * close()} called on it when not in use. * * @param configObject - * @return Promise of dh.TreeTable + * @return Promise of {@link JsTreeTable dh.TreeTable} */ @JsMethod public Promise rollup(@TsTypeRef(JsRollupConfig.class) Object configObject) { @@ -1269,11 +1271,12 @@ public Promise rollup(@TsTypeRef(JsRollupConfig.class) Object confi } /** - * A promise that will resolve to a new {@code TreeTable} of this table. Multiple calls to this method will each - * produce a new {@code TreeTable} which must have {@code close} called on it when not in use. + * A promise that will resolve to a new {@link JsTreeTable dh.TreeTable} of this table. Multiple calls to this + * method will each produce a new {@link JsTreeTable dh.TreeTable} which must have {@link JsTreeTable#close() + * close()} called on it when not in use. * * @param configObject - * @return Promise dh.TreeTable + * @return Promise of {@link JsTreeTable dh.TreeTable} */ @JsMethod public Promise treeTable(@TsTypeRef(JsTreeTableConfig.class) Object configObject) { @@ -1315,7 +1318,7 @@ public Promise treeTable(@TsTypeRef(JsTreeTableConfig.class) Object * table will not update. This does not change the original table, and the new table will not have any of the client * side sorts/filters/columns. New client side sorts/filters/columns can be added to the frozen copy. * - * @return Promise of dh.Table + * @return Promise of {@link JsTable dh.Table} */ @JsMethod public Promise freeze() { @@ -1478,19 +1481,26 @@ public Promise naturalJoin(JoinableTable rightTable, JsArray co .then(state -> Promise.resolve(new JsTable(workerConnection, state))); } + /** + * Alias for {@link #partitionBy(Object, Boolean)}. + * + * @param keys The partition key column name or names. + * @param dropKeys Whether to drop the key columns from the partitioned constituent tables. + * @return Promise of {@link JsPartitionedTable}. + */ @JsMethod public Promise byExternal(Object keys, @JsOptional @JsNullable Boolean dropKeys) { return partitionBy(keys, dropKeys); } /** - * Creates a new PartitionedTable from the contents of the current table, partitioning data based on the specified - * keys. + * Creates a new {@link JsPartitionedTable dh.PartitionedTable} from the contents of the current table, partitioning + * data based on the specified keys. * * @param keys * @param dropKeys * - * @return Promise dh.PartitionedTable + * @return Promise of {@link JsPartitionedTable dh.PartitionedTable} */ @JsMethod public Promise partitionBy(Object keys, @JsOptional @JsNullable Boolean dropKeys) { @@ -1533,10 +1543,10 @@ public Promise partitionBy(Object keys, @JsOptional @JsNulla } /** - * a promise that will resolve to ColumnStatistics for the column of this table. + * A promise that will resolve to {@link JsColumnStatistics ColumnStatistics} for the column of this table. * * @param column - * @return Promise of dh.ColumnStatistics + * @return Promise of {@link JsColumnStatistics ColumnStatistics} */ @JsMethod public Promise getColumnStatistics(Column column) { @@ -1729,6 +1739,11 @@ public boolean isUncoalesced() { return size == Long.MIN_VALUE; } + /** + * The plugin name attribute for this table. + * + * @return The plugin name, or {@code null}. + */ @JsProperty @JsNullable public String getPluginName() { @@ -1755,6 +1770,11 @@ private ClientTableState getHeadState() { return head; } + /** + * Gets a string representation of this table instance. + * + * @return A string representation of this table. + */ @JsMethod @Override public String toString() { diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/JsTotalsTable.java b/web/client-api/src/main/java/io/deephaven/web/client/api/JsTotalsTable.java index 983d9ffc316..aeeab9c1fa4 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/JsTotalsTable.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/JsTotalsTable.java @@ -29,21 +29,26 @@ import jsinterop.base.Js; /** - * Behaves like a {@code Table}, but doesn't expose all of its API for changing the internal state. Instead, state is - * driven by the upstream table - when it changes handle, this listens and updates its own handle accordingly. + * Behaves like a {@link JsTable dh.Table}, but doesn't expose all of its API for changing the internal state. Instead, + * state is driven by the upstream table - when its handle changes, this table listens and updates its own handle + * accordingly. * * Additionally, this is automatically subscribed to its one and only row, across all columns. * - * A new config is returned any time it is accessed, to prevent accidental mutation, and to allow it to be used as a - * template when fetching a new totals table, or changing the totals table in use. + * A new config is returned any time it is accessed. This is to: * - * A simplistic {@code Table}, providing access to aggregation of the table it is sourced from. This table is always - * automatically subscribed to its parent, and adopts changes automatically from it. This class has limited methods - * found on Table. Instances of this type always have a size of one when no groupBy is set on the config, but may - * potentially contain as few as zero rows, or as many as the parent table if each row gets its own group. + *

    + *
  1. Prevent accidental mutation.
  2. + *
  3. Allow it to be used as a template when fetching a new totals table or changing the totals table in use.
  4. + *
* - * When using the {@code groupBy} feature, it may be desireable to also provide a row to the user with all values across - * all rows. To achieve this, request the same Totals Table again, but remove the {@code groupBy} setting. + * This class has limited methods found on {@link JsTable dh.Table}. Instances of this type always have a size of one + * when no {@link JsTotalsTableConfig#groupBy groupBy} is set on the config, but may potentially contain as few as zero + * rows, or as many as the parent table if each row gets its own group. + * + * When using the {@link JsTotalsTableConfig#groupBy groupBy} feature, it may be desirable to also provide a row to the + * user with all values across all rows. To achieve this, request the same Totals Table again, but remove the + * {@link JsTotalsTableConfig#groupBy groupBy} setting. */ @TsInterface @TsName(namespace = "dh", name = "TotalsTable") @@ -60,10 +65,6 @@ public class JsTotalsTable implements JoinableTable, ServerObject { private Column[] columns; private Double updateIntervalMs; - /** - * Table is wrapped to let us delegate calls to it, the directive is a serialized string, and the {@code groupBy} is - * copied when passed in, as well as when it is accessed, to prevent accidental mutation of the array. - */ public JsTotalsTable(JsTable wrappedTable, String directive, JsArray groupBy) { this.wrappedTable = wrappedTable; this.directive = directive; @@ -75,6 +76,9 @@ public WorkerConnection getConnection() { return wrappedTable.getConnection(); } + /** + * Re-applies the most recently set viewport options, if any. + */ public void refreshViewport() { if (firstRow != null && lastRow != null) { setViewport(firstRow, lastRow, Js.uncheckedCast(columns), updateIntervalMs, null); @@ -100,6 +104,11 @@ public TypedTicket typedTicket() { .build(); } + /** + * Gets the configuration used when creating this Totals Table. + * + * @return {@link JsTotalsTableConfig dh.TotalsTableConfig} + */ @JsProperty public JsTotalsTableConfig getTotalsTableConfig() { JsTotalsTableConfig parsed = JsTotalsTableConfig.parse(directive); @@ -259,26 +268,58 @@ public double getSize() { return wrappedTable.getSize(); } + /** + * Gets a string representation of this Totals Table instance. + * + * @return A string representation of this totals table. + */ @Override public String toString() { return "JsTotalsTable { totalsTableConfig=" + getTotalsTableConfig() + " }"; } + /** + * Adds an event listener to this table. + * + * @param name The event name. + * @param callback The callback to invoke when the event fires. + * @return A function that removes this event listener when invoked. + */ @JsMethod public RemoverFn addEventListener(String name, EventFn callback) { return wrappedTable.addEventListener(name, callback); } + /** + * Removes an event listener from this table. + * + * @param name The event name. + * @param callback The callback to remove. + * @return {@code true} if a listener was removed; {@code false} otherwise. + */ @JsMethod public boolean removeEventListener(String name, EventFn callback) { return wrappedTable.removeEventListener(name, callback); } + /** + * Returns a promise that resolves with the next occurrence of the specified event. + * + * @param eventName The event name. + * @param timeoutInMillis Optional timeout in milliseconds. + * @return A promise that resolves to the next event. + */ @JsMethod - public Promise> nextEvent(String eventName, Double timeoutInMillis) { + public Promise> nextEvent(String eventName, @JsOptional Double timeoutInMillis) { return wrappedTable.nextEvent(eventName, timeoutInMillis); } + /** + * Checks whether this table has any listeners for the given event name. + * + * @param name The event name. + * @return {@code true} if there is at least one listener; {@code false} otherwise. + */ @JsMethod public boolean hasListeners(String name) { return wrappedTable.hasListeners(name); @@ -326,6 +367,11 @@ public JsArray applyFilter(FilterCondition[] filter) { return wrappedTable.applyFilter(filter); } + /** + * Gets the underlying {@link JsTable} instance used to back this Totals Table. + * + * @return The wrapped table. + */ public JsTable getWrappedTable() { return wrappedTable; } @@ -365,12 +411,27 @@ public JsArray getCustomColumns() { return wrappedTable.getCustomColumns(); } + /** + * A server-side snapshot of this table (a server-side snapshot of the entire source table). Subscriptions on the + * snapshot table will not update. This does not change the original table, and the new table will not have any of + * the client side sorts/filters/columns. New client side sorts/filters/columns can be added to the snapshot copy. + * + * @return A promise that resolves to the snapshot table. + */ @Override @JsMethod public Promise freeze() { return wrappedTable.freeze(); } + /** + * Creates a server-side snapshot of {@code baseTable} when this table updates. + * + * @param baseTable The table to snapshot. + * @param doInitialSnapshot Whether to create an initial snapshot immediately. + * @param stampColumns Optional list of column names to include in the result. + * @return A promise that resolves to the snapshot table. + */ @Override @JsMethod public Promise snapshot(JsTable baseTable, @JsOptional @JsNullable Boolean doInitialSnapshot, @@ -378,6 +439,16 @@ public Promise snapshot(JsTable baseTable, @JsOptional @JsNullable Bool return wrappedTable.snapshot(baseTable, doInitialSnapshot, stampColumns); } + /** + * Joins this table to the provided table. + * + * @param joinType The join type. + * @param rightTable The table to join to. + * @param columnsToMatch Columns that should match. + * @param columnsToAdd Columns from the right table to add to the result. + * @param asOfMatchRule If joinType is {@code AJ}/{@code RAJ}/{@code ReverseAJ}, the match rule to use. + * @return A promise that resolves to the joined table. + */ @Override @JsMethod public Promise join(String joinType, JoinableTable rightTable, JsArray columnsToMatch, @@ -385,6 +456,15 @@ public Promise join(String joinType, JoinableTable rightTable, JsArray< return wrappedTable.join(joinType, rightTable, columnsToMatch, columnsToAdd, asOfMatchRule); } + /** + * Performs an as-of join between this table and the provided table. + * + * @param rightTable The table to join to. + * @param columnsToMatch Columns that should match. + * @param columnsToAdd Columns from the right table to add to the result. + * @param asOfMatchRule The match rule to use. + * @return A promise that resolves to the joined table. + */ @Override @JsMethod public Promise asOfJoin(JoinableTable rightTable, JsArray columnsToMatch, @@ -392,6 +472,15 @@ public Promise asOfJoin(JoinableTable rightTable, JsArray colum return wrappedTable.asOfJoin(rightTable, columnsToMatch, columnsToAdd, asOfMatchRule); } + /** + * Performs a cross join between this table and the provided table. + * + * @param rightTable The table to join to. + * @param columnsToMatch Columns that should match. + * @param columnsToAdd Columns from the right table to add to the result. + * @param reserveBits Optional reserve bits for the join. + * @return A promise that resolves to the joined table. + */ @Override @JsMethod public Promise crossJoin(JoinableTable rightTable, JsArray columnsToMatch, @@ -399,6 +488,14 @@ public Promise crossJoin(JoinableTable rightTable, JsArray colu return wrappedTable.crossJoin(rightTable, columnsToMatch, columnsToAdd, reserveBits); } + /** + * Performs an exact join between this table and the provided table. + * + * @param rightTable The table to join to. + * @param columnsToMatch Columns that should match. + * @param columnsToAdd Columns from the right table to add to the result. + * @return A promise that resolves to the joined table. + */ @Override @JsMethod public Promise exactJoin(JoinableTable rightTable, JsArray columnsToMatch, @@ -406,6 +503,14 @@ public Promise exactJoin(JoinableTable rightTable, JsArray colu return wrappedTable.exactJoin(rightTable, columnsToMatch, columnsToAdd); } + /** + * Performs a natural join between this table and the provided table. + * + * @param rightTable The table to join to. + * @param columnsToMatch Columns that should match. + * @param columnsToAdd Columns from the right table to add to the result. + * @return A promise that resolves to the joined table. + */ @Override @JsMethod public Promise naturalJoin(JoinableTable rightTable, JsArray columnsToMatch, diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/WorkerConnection.java b/web/client-api/src/main/java/io/deephaven/web/client/api/WorkerConnection.java index 3ae41375b2b..d5817f0c9f9 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/WorkerConnection.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/WorkerConnection.java @@ -134,6 +134,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -559,7 +560,6 @@ public void exportedTableUpdateMessage(TableTicket clientId, long size) { public void connectionLost() { // notify all active tables and widgets that the connection is closed - // TODO(deephaven-core#3604) when a new session is created, refetch all widgets and use that to drive reconnect simpleReconnectableInstances.forEach((item, index, array) -> { try { item.disconnected(); @@ -730,7 +730,9 @@ public Promise getObject(JsVariableDefinition definition) { return getHierarchicalTable(definition); } else { warnLegacyTicketTypes(definition.getType()); - return getWidget(definition).then(JsWidget::refetch); + return getWidget(definition) + .then(JsWidget::refetch) + .then(JsWidget::markReconnectable); } } @@ -781,7 +783,9 @@ public Promise getObject(TypedTicket typedTicket) { return new JsWidget(this, typedTicket).refetch().then(w -> Promise.resolve(new JsTreeTable(this, w))); } else { warnLegacyTicketTypes(typedTicket.getType()); - return getWidget(typedTicket).then(JsWidget::refetch); + return getWidget(typedTicket) + .then(JsWidget::refetch) + .then(JsWidget::markReconnectable); } } @@ -1031,6 +1035,10 @@ public void unregisterSimpleReconnectable(HasLifecycle figure) { this.simpleReconnectableInstances.delete(figure); } + public boolean isConnected() { + return state == State.Connected; + } + public TableServiceGrpc.TableServiceStub tableServiceClient() { return tableServiceClient; diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/event/HasEventHandling.java b/web/client-api/src/main/java/io/deephaven/web/client/api/event/HasEventHandling.java index 9efdd29744e..326952555c7 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/event/HasEventHandling.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/event/HasEventHandling.java @@ -22,10 +22,14 @@ import jsinterop.base.JsPropertyMap; /** + * Base class providing an event listener API for Deephaven JS client objects. */ @TsInterface @TsName(namespace = "dh") public class HasEventHandling { + /** + * Internal event name used by some implementations to indicate that the underlying object has been released. + */ public static final String INTERNAL_EVENT_RELEASED = "released-internal"; private final JsPropertyMap>> map = Js.uncheckedCast(JsObject.create(null)); @@ -105,6 +109,16 @@ public void addEventListenerOneShot(EventPair... pairs) { } } + /** + * Returns a promise that resolves the next time the named event occurs, with the value of the event's detail. If a + * timeout is specified and occurs before the event takes place, the promise will reject, otherwise waits + * indefinitely. + * + * @param eventName The event name. + * @param timeoutInMillis Optional timeout in milliseconds. + * @param The type of the event detail. + * @return A promise that resolves with the next matching event. + */ @JsMethod public Promise> nextEvent(String eventName, @JsOptional @JsNullable Double timeoutInMillis) { LazyPromise> promise = new LazyPromise<>(); @@ -117,12 +131,25 @@ public Promise> nextEvent(String eventName, @JsOptional @JsNullable return promise.asPromise(); } + /** + * Checks whether any event listeners are registered for the given event name. + * + * @param name The event name. + * @return {@code true} if there is at least one listener registered for {@code name}; {@code false} otherwise. + */ @JsMethod public boolean hasListeners(String name) { final JsArray> listeners = map.get(name); return listeners != null && listeners.length > 0; } + /** + * Checks whether a specific event listener is registered for the given event name. + * + * @param name The event name. + * @param fn The event listener function. + * @return True if {@code fn} is currently registered for {@code name}. + */ public boolean hasListener(String name, EventFn fn) { return hasListeners(name) && map.get(name).indexOf(fn) != -1; } @@ -157,14 +184,30 @@ public boolean removeEventListener(String name, EventFn callback) { return true; } + /** + * Fires an event with no detail. + * + * @param type The event name. + */ public void fireEvent(String type) { fireEvent(new Event<>(type, null)); } + /** + * Fires an event with the given detail payload. + * + * @param type The event name. + * @param detail The event detail. + */ public void fireEvent(String type, @DoNotAutobox T detail) { fireEvent(new Event<>(type, detail)); } + /** + * Fires an event instance. + * + * @param e The event to fire. + */ public void fireEvent(Event e) { if (suppress) { JsLog.debug("Event suppressed", e.getType(), e); @@ -185,6 +228,14 @@ public void fireEvent(Event e) { } } + /** + * Fires a critical event with no detail. + * + *

+ * If no listeners are registered, a message is logged to the console. + * + * @param type The event type. + */ public void fireCriticalEvent(String type) { if (hasListeners(type)) { fireEvent(type); @@ -193,6 +244,15 @@ public void fireCriticalEvent(String type) { } } + /** + * Fires a critical event with the given detail. + * + *

+ * If no listeners are registered, a message is logged to the console. + * + * @param type The event type. + * @param detail The event detail. + */ public void fireCriticalEvent(String type, T detail) { if (hasListeners(type)) { fireEvent(type, detail); @@ -209,14 +269,25 @@ public void failureHandled(String failure) { fireCriticalEvent(CoreClient.EVENT_REQUEST_FAILED, failure); } + /** + * Suppresses delivery of fired events to listeners. + */ public void suppressEvents() { suppress = true; } + /** + * Re-enables delivery of fired events to listeners. + */ public void unsuppressEvents() { suppress = false; } + /** + * Checks whether events are currently suppressed. + * + * @return True if events are suppressed. + */ public boolean isSuppress() { return suppress; } diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/subscription/DataOptions.java b/web/client-api/src/main/java/io/deephaven/web/client/api/subscription/DataOptions.java index 946bbd268d4..c98a0ce6f62 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/subscription/DataOptions.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/subscription/DataOptions.java @@ -54,7 +54,8 @@ public static PreviewOptions of(JsPropertyMap previewOptions) { } /** - * If true, any array columns will be converted to strings for preview purposes. This is the legacy behavior. + * If {@code true}, any array columns will be converted to strings for preview purposes. This is the legacy + * behavior. */ @JsNullable @JsProperty diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/tree/TreeViewportData.java b/web/client-api/src/main/java/io/deephaven/web/client/api/tree/TreeViewportData.java index 510a486e9f9..f1b849d92ee 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/tree/TreeViewportData.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/tree/TreeViewportData.java @@ -35,14 +35,14 @@ default TableData.Row get(RowPositionUnion index) { double getOffset(); /** - * Row implementation that also provides additional read-only properties. Represents visible rows in the table, but - * with additional properties to reflect the tree structure. + * {@link TableData.Row} implementation that also provides additional read-only properties. Represents visible rows + * in the table, but with additional properties to reflect the tree structure. */ @JsType interface TreeRow extends TableData.Row { /** * {@code true} if this node is currently expanded to show its children; {@code false} otherwise. Those children - * will be the rows below this one with a greater depth than this one. + * are the rows below this one with a greater depth than this one. * * @return boolean */ diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/widget/JsWidget.java b/web/client-api/src/main/java/io/deephaven/web/client/api/widget/JsWidget.java index 275ec2c96e7..b7bb1c1efdd 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/widget/JsWidget.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/widget/JsWidget.java @@ -28,7 +28,7 @@ import io.deephaven.web.client.api.ServerObject; import io.deephaven.web.client.api.WorkerConnection; import io.deephaven.web.client.api.barrage.stream.BiDiStream; -import io.deephaven.web.client.api.event.HasEventHandling; +import io.deephaven.web.client.api.lifecycle.HasLifecycle; import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsOptional; import jsinterop.annotations.JsNullable; @@ -46,10 +46,10 @@ * A Widget represents a server side object that sends one or more responses to the client. The client can then * interpret these responses to see what to render, or how to respond. *

- * Most custom object types result in a single response being sent to the client, often with other exported objects, but - * some will have streamed responses, and allow the client to send follow-up requests of its own. This class's API is - * backwards compatible, but as such does not offer a way to tell the difference between a streaming or non-streaming - * object type, the client code that handles the payloads is expected to know what to expect. See + * Most custom object types result in a single response being sent to the client (often with other exported objects), + * but some will have streamed responses, and allow the client to send follow-up requests of its own. This class's API + * is backward compatible, and as such does not offer a way to tell the difference between a streaming or non-streaming + * object type. The client code that handles the payloads is expected to know what to expect. See * {@link WidgetMessageDetails} for more information. *

* When the promise that returns this object resolves, it will have the first response assigned to its fields. Later @@ -59,11 +59,11 @@ * remote messages are still pending - it is up to implementations of plugins to handle this case. *

* Also like WebSockets, the plugin API doesn't define how to serialize messages, and just handles any binary payloads. - * What it does handle however, is allowing those messages to include references to server-side objects with those + * What it does handle, however, is allowing those messages to include references to server-side objects with those * payloads. Those server side objects might be tables or other built-in types in the Deephaven JS API, or could be * objects usable through their own plugins. They also might have no plugin at all, allowing the client to hold a * reference to them and pass them back to the server, either to the current plugin instance, or through another API. - * The {@code Widget} type does not specify how those objects should be used or their lifecycle, but leaves that + * The {@link JsWidget} type does not specify how those objects should be used or their lifecycle, but leaves that * entirely to the plugin. Messages will arrive in the order they were sent. *

* This can suggest several patterns for how plugins operate: @@ -79,12 +79,12 @@ * before bidirectional plugins were implemented. Another example of this is plugins that serve as a "factory", giving * the user access to table manipulation/creation methods not supported by gRPC or the JS API. *

  • The plugin provides reference to Tables and other objects that only make sense within the context of the widget - * instance, so when the widget goes away, those objects should be released as well. This is also an example of + * instance. When the widget goes away, those objects should be released as well. This is also an example of * {@link io.deephaven.web.client.api.JsPartitionedTable}, as the partitioned table tracks creation of new keys through * an internal table instance.
  • * * - * Handling server objects in messages also has more than one potential pattern that can be used: + * There are also multiple potential patterns for handling server objects in messages: *
      *
    • One object per message - the message clearly is about that object, no other details required.
    • *
    • Objects indexed within their message - as each message comes with a list of objects, those objects can be @@ -97,11 +97,22 @@ * without the server somehow signaling that it will never reference that export again.
    • *
    */ -// TODO consider reconnect support? This is somewhat tricky without understanding the semantics of the widget @TsName(namespace = "dh", name = "Widget") -public class JsWidget extends HasEventHandling implements ServerObject, WidgetMessageDetails { +public class JsWidget extends HasLifecycle implements ServerObject, WidgetMessageDetails { + /** + * Fired when a new message is received from the server. + *

    + * {@code event.detail} is an {@link EventDetails} instance containing the message payload and any exported objects + * included with the message. + */ @JsProperty(namespace = "dh.Widget") public static final String EVENT_MESSAGE = "message"; + + /** + * Fired when the widget's message stream is closed, either because the server is finished sending messages, or + * because an error occurred, server shut down, session closed, etc. Plugins should specify their own close message + * if required. + */ @JsProperty(namespace = "dh.Widget") public static final String EVENT_CLOSE = "close"; @@ -110,6 +121,12 @@ public class JsWidget extends HasEventHandling implements ServerObject, WidgetMe private boolean hasFetched; + /** + * Set when the connection reports this widget as disconnected, cleared when a same-session revive (via + * {@link #reconnect()}) succeeds. While set, the next initial response re-announces the widget to consumers. + */ + private boolean awaitingRevive; + private final Supplier> streamFactory; private BiDiStream messageStream; @@ -134,6 +151,26 @@ public WorkerConnection getConnection() { return connection; } + /** + * Marks this as a standalone, independently-reconnectable widget and registers it with the connection so it is + * revived on reconnect. Called only for widgets handed directly to the caller - widgets wrapped by a figure / tree + * / partitioned-table are revived by their owner and must not be registered here (that would double-revive them). + */ + public Promise markReconnectable() { + connection.registerSimpleReconnectable(this); + return Promise.resolve(this); + } + + /** + * A failed revive means this widget will never reconnect, so stop tracking it as reconnectable (mirroring + * {@link #close()}); otherwise it keeps receiving disconnect/refetch calls on every future reconnect. + */ + @Override + public void die(Object error) { + connection.unregisterSimpleReconnectable(this); + super.die(error); + } + private void closeStream() { if (messageStream != null) { messageStream.end(); @@ -148,11 +185,63 @@ private void closeStream() { @JsMethod public void close() { suppressEvents(); + connection.unregisterSimpleReconnectable(this); closeStream(); connection.releaseTicket(getTicket()); } + /** + * Opens (or reopens) the message stream using the widget's current ticket. Used for the initial fetch. When invoked + * as the connection's new-session revive hook, the export ticket is no longer valid and the server-side object may + * differ, so we cannot safely reconnect - the revive fails. + */ + @Override public Promise refetch() { + if (!awaitingRevive) { + // initial fetch, or an internal caller deliberately rebinding the stream + return openStream(); + } + // A new session was created: the old export ticket is invalid and the object may differ. Fail the revive + // rather than silently reconnect to a different object. + IllegalStateException failure = new IllegalStateException("Cannot revive widget: a new session was created"); + die(failure); + return (Promise) (Promise) Promise.reject(failure); + } + + /** + * Same-session reconnect: the export ticket is still valid, so reopen the message stream with the same ticket and + * re-announce to consumers on success. If the stream cannot be reopened, fail the revive. + */ + @Override + public void reconnect() { + openStream().then(widget -> { + announceReconnect(); + return Promise.resolve(widget); + }, failure -> { + die(failure); + return (Promise) (Promise) Promise.reject(failure); + }).catch_(ignore -> { + // failure was already reported via die() + return null; + }); + } + + @Override + public void disconnected() { + awaitingRevive = true; + closeStream(); + super.disconnected(); + } + + private void announceReconnect() { + awaitingRevive = false; + // unsuppress events and fire the reconnect event first, then re-deliver the fresh initial response as a + // message so that consumers re-render from the server's current state + super.reconnect(); + fireEvent(EVENT_MESSAGE, new EventDetails(response.getData(), exportedObjects)); + } + + private Promise openStream() { closeStream(); return new Promise<>((resolve, reject) -> { exportedObjects = new JsArray<>(); @@ -182,7 +271,11 @@ public Promise refetch() { reject.onInvoke(status.getDescription()); } DomGlobal.setTimeout(ignore -> { - fireEvent(EVENT_CLOSE); + // Skip the close event on a transport failure while the whole connection is down - the + // connection's lifecycle (disconnected/reconnect/refetch) owns this widget's state instead. + if (status.isOk() || connection.isConnected()) { + fireEvent(EVENT_CLOSE); + } }, 0); closeStream(); }); diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/widget/plot/JsChart.java b/web/client-api/src/main/java/io/deephaven/web/client/api/widget/plot/JsChart.java index 55ae478071a..202003f32ca 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/widget/plot/JsChart.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/widget/plot/JsChart.java @@ -19,12 +19,13 @@ import java.util.Map; /** - * Provide the details for a chart. + * Provides the details for a chart. */ @JsType(name = "Chart", namespace = "dh.plot") public class JsChart extends HasEventHandling { /** - * A new series was added to this chart as part of a multi-series. The series instance is the detail for this event. + * Fired when a new series is added to this chart as part of a multi-series chart. The event detail is the added + * series instance. */ public static final String EVENT_SERIES_ADDED = "seriesadded"; @@ -52,30 +53,40 @@ public JsChart(FigureDescriptor.ChartDescriptor descriptor, JsFigure jsFigure) { JsObject.freeze(multiSeries); } + /** + * The column position of this chart in the figure layout. + */ @JsProperty public int getColumn() { return descriptor.getColumn(); } + /** + * The row position of this chart in the figure layout. + */ @JsProperty public int getRow() { return descriptor.getRow(); } + /** + * The number of columns this chart spans in the figure layout. + */ @JsProperty public int getColspan() { return descriptor.getColspan(); } + /** + * The number of rows this chart spans in the figure layout. + */ @JsProperty public int getRowspan() { return descriptor.getRowspan(); } /** - * The type of this chart, see {@code ChartType} enum for more details. - * - * @return int + * The type of this chart. See {@link JsChartType} for more details. */ @JsProperty @TsTypeRef(JsChartType.class) @@ -85,8 +96,6 @@ public int getChartType() { /** * The title of the chart. - * - * @return String */ @JsProperty @JsNullable @@ -97,42 +106,66 @@ public String getTitle() { return null; } + /** + * The font used to render the chart title. + */ @JsProperty public String getTitleFont() { return descriptor.getTitleFont(); } + /** + * The color used to render the chart title. + */ @JsProperty public String getTitleColor() { return descriptor.getTitleColor(); } + /** + * Whether the chart legend is shown. + */ @JsProperty public boolean isShowLegend() { return descriptor.getShowLegend(); } + /** + * The font used to render the chart legend. + */ @JsProperty public String getLegendFont() { return descriptor.getLegendFont(); } + /** + * The color used to render the chart legend. + */ @JsProperty public String getLegendColor() { return descriptor.getLegendColor(); } + /** + * Whether this chart is rendered in 3D. + */ @JsProperty(name = "is3d") public boolean isIs3d() { return descriptor.getIs3D(); } + /** + * Gets a copy of the chart series array. + */ // exposed for JS, do not use this from java methods @JsProperty(name = "series") public JsSeries[] getExportedSeriesArray() { return Js.uncheckedCast(Js.>uncheckedCast(series).slice()); } + /** + * Gets the chart multi-series array. + */ // exposed for JS, do not use this from java methods @JsProperty(name = "multiSeries") public JsMultiSeries[] getExportedMultiSeriesArray() { @@ -141,8 +174,6 @@ public JsMultiSeries[] getExportedMultiSeriesArray() { /** * The series data for display in this chart. - * - * @return dh.plot.Series */ @JsIgnore public JsSeries[] getSeries() { @@ -151,8 +182,6 @@ public JsSeries[] getSeries() { /** * The multi-series data for display in this chart. - * - * @return dh.plot.MultiSeries */ @JsIgnore public JsMultiSeries[] getMultiSeries() { @@ -161,8 +190,6 @@ public JsMultiSeries[] getMultiSeries() { /** * The axes used in this chart. - * - * @return dh.plot.Axis */ @JsProperty public JsAxis[] getAxes() { diff --git a/web/client-ui/Dockerfile b/web/client-ui/Dockerfile index f2b147d7b2f..32905c8bb53 100644 --- a/web/client-ui/Dockerfile +++ b/web/client-ui/Dockerfile @@ -2,10 +2,10 @@ FROM deephaven/node:local-build WORKDIR /usr/src/app # Most of the time, these versions are the same, except in cases where a patch only affects one of the packages -ARG WEB_VERSION=1.24.0 +ARG WEB_VERSION=1.26.0 ARG GRID_VERSION=1.1.0 ARG CHART_VERSION=1.1.0 -ARG WIDGET_VERSION=1.24.0 +ARG WIDGET_VERSION=1.26.0 # Pull in the published code-studio package from npmjs and extract is RUN set -eux; \