Changelog

2.2.0 — 2026-06-09

UX & UI Improvements

  • New (Load from DB / Save to DB): Cancel button. Long-running database operations (both table downloads and uploads) can now be aborted mid-flight. For uploads, SQLAlchemy automatically issues a ROLLBACK, preventing partial or corrupted data from being written.

  • New (Load from DB): Data Preview. When a dataset is selected from the dropdown, the widget now instantly fetches the first 50 rows (LIMIT 50) and displays them in an embedded QTableWidget. This provides immediate visual confirmation of the dataset contents without having to pull the entire table into memory first.

  • Improvement (Load from DB): Smooth progress reporting. The background reader now streams data in chunks (chunksize=1000) rather than running a single monolithic read_sql query. The progress bar updates continuously as rows arrive, preventing the UI from appearing frozen during large transfers.

  • UX: Improved button alignment and spacing in both database widgets to avoid visual overlap. In Save to DB, the Cancel button is stacked logically above the Save button for improved space usage.

2.1.1 — 2026-06-09

Load from DB (new widget)

  • Lists every dataset registered in the datasets metadata table via SQLAlchemy and pulls the selected one back into Orange as an Orange.data.Table using pandas.read_sql + Orange.data.pandas_compat.table_from_frame.

  • Offers a Class column combo populated from the dataset’s columns, pre-selected with the user’s persisted choice or the class_name recorded by Save to DB. The output Table already exposes the chosen column as domain.class_var, so no Select Columns widget is needed downstream.

  • Same dialect selector (PostgreSQL / MySQL), connection-status label and QThread worker pattern as Save to DB. Both the metadata listing and the actual table read happen off the GUI thread.

  • Workflow-persisted settings: selected_dataset and selected_class are Setting(..., schema_only=True), restored as soon as the connection comes back up on reload.

  • The Dataset combo is a ComboBoxSearch — type to filter when the registry gets large.

  • button next to the Dataset combo re-runs the listing without dropping the connection (handy when something else just published a new dataset). Auto-load is suppressed on Refresh so the user never gets a Load they didn’t ask for.

  • Delete button drops the selected dataset’s table and removes its row in datasets. Runs through a _DeleteDatasetWorker on a background thread, guarded by a confirmation dialog. Idempotent: re-deleting a missing table is a no-op thanks to DROP TABLE IF EXISTS.

  • Auto-load on workflow reopen: if the persisted selected_dataset is still on the server when the connection succeeds and the listing returns, Load fires automatically — the data flows out without a single click. The flag is cleared on Refresh and on Delete to avoid surprises.

Variable Dependency Graph

  • New: edges now carry numeric weights equal to the largest |argument| among the temporal calls (shift, sum, mean, count, min, max, sd) in the source expression that reference the dependency. Plain (non-temporal) references default to weight 1. The weights live in network.edges[0].edges.data and are consumable by any downstream Network widget.

  • New: the graph is now directed (DirectedEdges). Previously the sparse matrix was passed straight to Network which auto-wrapped it as UndirectedEdges — a latent bug since A→B is not the same as B→A in a dependency graph.

  • New: nodes carry an extra expression meta (literal expression text, empty for original variables) so the Network Explorer can show the formula as the node label.

  • New: Warning.no_derived fires when every input row is an original variable — usually a sign the user wired the data output of Time Features Constructor instead of the variable-definitions one.

  • Refactor: flattened the from_row_col / grafo decorator pattern into a single, documented build_dependency_network function.

  • Performance: O(n³) → O(n²) by precomputing a name index map and using a set to dedupe dependencies.

  • Bugfix: an IndexError was raised when the input domain had a single column. The widget now checks len(domain) >= 2 before reading the second column.

  • Removed 65 lines of dead commented-out code.

Time Features Constructor

  • New: chained descriptors. A descriptor can now reference another derived descriptor in its expression (e.g. X2 := shift(X1, -1) with X1 itself defined a few rows above). The widget topologically sorts the descriptors and cascades the transforms — each step runs against the table state produced by the previous one, so X2 sees X1 as a regular column. Cycles (e.g. X1 := X2 + 1 together with X2 := X1 + 1) raise a Circular dependency between descriptors: X1, X2 error. Errors during evaluation are reported per-descriptor so the failing row is obvious.

  • Fixed (critical): time-window functions used to lose context every 5 000 rows because Orange chunks tables during transform. The widget now caches the full source per FeatureFunc and returns the right slice for each chunk, so shift(x, -20) is correct across chunk boundaries on datasets of any size.

  • Fixed: “Variables to generate” was being cleared after every Send and on every input change, so workflow save / reload lost the definitions. Storage moved from ContextSetting to Setting(..., schema_only=True) (matching upstream Orange v4) and the editor is now restored from the persisted descriptors on every input. The legacy context handler stays around to migrate old workflows.

  • Each Send re-transforms the original input table instead of the cumulative output, removing the implicit “consume on Send” semantics and the duplicate-name errors that came with it.

  • eval hardened: __builtins__ replaced with an empty dict in the expression evaluator. Only the curated whitelist (safe builtins + math + selected random / numpy helpers) is exposed.

  • Refactor: modificar_expression collapsed from 7 near-identical loops to a single regex-driven pass.

  • Bugfix: FeatureEditor.editorData returned the variable name as the expression.

Save to DB

  • New: write mode selector with three options — Create new (default, fail if the target exists), Overwrite (drop and recreate the table and its datasets row), Append (keep existing rows and add the new ones). Re-running a workflow no longer breaks. The persisted write_mode Setting defaults to "create" so old workflows keep their previous behaviour. After the upload, the widget runs SELECT COUNT(*) and rewrites the datasets row with the actual total, so the registry stays accurate across appends.

  • New: MySQL support. The connection panel now exposes a database-type selector (PostgreSQL / MySQL). Per-dialect column types and identifier quoting ("name" vs \`name\`) live in a _Dialect abstraction.

  • Performance: uploads now go through Orange.data.pandas_compat.table_to_frame + DataFrame.to_sql(method='multi', chunksize=1000) over a SQLAlchemy engine. The old row-per-INSERT loop is gone — typical speedups are 50-100×, especially over the network. Identifier quoting and per-column DDL types are emitted by the SQLAlchemy dialect (DOUBLE_PRECISION on PostgreSQL, DOUBLE on MySQL, DATETIME instead of TIMESTAMP on MySQL to dodge the 2038 cap, VARCHAR(255) / TEXT everywhere else).

  • UX: the upload runs in a background QThread (_UploadWorker), so the canvas stays interactive on long writes. A status label under the connection box reports Not connected / Connected to … : … / Uploading rows N/M… / Upload completed in Xs / Upload failed: … with colour cues. Save, Connect and the form fields are disabled while the worker runs and re-enabled on success or failure; the widget aborts the thread cleanly on close.

  • Fixed (critical): SQL injection in the metadata INSERT. The query is now fully parametrised.

  • Added an identifier whitelist (^[A-Za-z_][A-Za-z0-9_]{0,62}$) and a quote_ident helper that wraps identifiers with PostgreSQL-standard double quotes (with any internal " doubled). Used in every CREATE TABLE / INSERT INTO that touches user-supplied names.

  • Replaced psycopg2 with psycopg2-binary in the install requirements so installation works on macOS, Linux and Windows without a C compiler.

  • setup.py now declares install_requires; pip install was previously not pulling in any runtime dependency.

Testing

The widget suite grew to 92 tests, covering:

  • Unit tests for modificar_expression, the time-window helpers, _sanitize_name / _expression_or_none and _temporal_weights.

  • A regression test for the 5 000-row chunk bug (shift(x, -20) over a 12 000-row table).

  • eval-safety tests asserting that __import__ / open raise NameError while sqrt / abs still resolve.

  • Widget-level tests (via Orange.widgets.tests.base.WidgetTest) for descriptor persistence, including an end-to-end settingsHandler.pack_data / stored_settings= round-trip that mirrors what Orange does when saving and reopening a .ows file.

  • Edge-weight tests covering single calls, three-argument families, mixed temporal / non-temporal references, max across multiple calls, and per-dependency independence.