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.
