Changelog
=========

2.3.0
-----

**Fixes**

- *Fix (Time Features Constructor):* time-window functions accept spaces between arguments (``shift(x, -1)``, ``mean(x, -2, 0)``) and a leading ``+`` on offsets (``shift(x,+1)``). Previously these raised ``NameError`` or ``TypeError``. ``min(``, ``max(``, ``mean(`` and ``sum(`` are no longer matched inside longer names such as ``nanmin(``.
- *Fix (Time Features Constructor):* columns whose names contain punctuation other than spaces or hyphens (``a.b``, ``temp (C)``) or start with a digit (``1abc``) can be referenced by their sanitised name (``a_b``, ``temp__C_``, ``_1abc``), as the documentation already stated. Previously they raised ``NameError``.
- *Fix (Time Features Constructor):* expressions that reference no column (constants such as ``42``, and invalid expressions that the widget blanks out) yield one value per row instead of failing with *could not broadcast input array*.
- *Fix (Variable Dependency Graph):* variable names are sanitised with the constructor's ``sanitized_name``, so references to columns with punctuation or a leading digit now produce edges. Node names for those columns change accordingly (``temp (C)`` → ``temp__C_``).
- *Fix (Variable Dependency Graph):* references and temporal weights are read from the expression's syntax tree instead of searching its text. Names inside string literals, names bound in comprehensions or lambdas, and called function names (e.g. a column called ``sum``) no longer create edges, and a temporal call only counts in the form the constructor evaluates. Expressions that cannot be parsed get no edges and a new warning names them.
- *Fix (Save to DB):* **Overwrite** no longer drops the existing table before uploading. The data goes into a staging table that replaces the target only once every chunk is in (atomic ``RENAME TABLE`` on MySQL). Previously, on MySQL, a failed or cancelled Overwrite lost the original table because DDL auto-commits. The same staging path means a failed or cancelled **Create** (or **Append** into a missing table) no longer leaves a half-written table behind, and **Create** now also refuses a table that exists without a ``datasets`` row.
- *Fix (Load from DB):* each background operation keeps its own thread. Previously the listing thread, on finishing, cleared the references of the load it had just started (auto-load on workflow reopen, or Load clicked right after listing): **Cancel** stopped working, and closing the widget could destroy the still-running thread and abort Orange (*QThread: Destroyed while thread is still running*).
- *Fix (Load from DB / Save to DB):* removing the widget or closing the workflow mid-operation no longer freezes the canvas waiting for the query or upload to end; the operation is cancelled and its thread ends on its own. On exit, Orange waits for any such thread to finish.
- *Improvement (Load from DB):* loads read through a server-side cursor (``stream_results``), so rows really arrive in 1 000-row chunks: the progress bar is accurate and **Cancel** takes effect within one chunk instead of after the whole table has been fetched. **Cancel** is no longer enabled while a dataset is being deleted.
- *Tests (Save to DB):* the upload worker is tested end to end on SQLite and, when ``TIMEFEATURES_TEST_POSTGRES_URL`` / ``TIMEFEATURES_TEST_MYSQL_URL`` point to disposable databases, on real PostgreSQL and MySQL servers, including failures and cancellations mid-upload.

**Packaging & CI**

- ``setup.py`` now declares ``Orange3>=3.36.2`` and ``python_requires=">=3.10"``, the oldest combination the tests pass on (older Orange3 releases do not work with NumPy 2). Previously Orange3 only arrived indirectly through Orange3-Network, and the README advertised Python 3.8+.
- New GitHub Actions workflow (``.github/workflows/ci.yml``): the test suite on Python 3.10 with Orange3 3.36.2 and on Python 3.11 and 3.12 with the latest releases, with the Save to DB tests also running against PostgreSQL and MySQL service containers; a package job that builds the sdist and wheel, runs ``twine check`` and installs the wheel into an empty environment to check the entry points, icons and bundled help; and a docs job that builds Sphinx with warnings as errors.
- Removed ``setup.cfg``: its only key (``description-file``) is deprecated and will stop being accepted by setuptools; ``setup.py`` already sets the long description.
- The license is declared as the SPDX expression ``GPL-3.0-or-later`` instead of the deprecated license classifier, and packages are discovered with ``find_namespace_packages`` so setuptools keeps shipping the bundled help and icons without warnings. The built wheel has exactly the same files as before.
- Automatic releases: after a merge to ``main``, once the tests, package and docs jobs pass, the workflow publishes the version in ``timefeatures/__version__.py`` to PyPI if it is not there yet (PyPI trusted publishing, no stored token) and creates the matching ``vX.Y.Z`` tag and GitHub release. See ``RELEASING.md``.

**Documentation**

- The README and the constructor page no longer describe the restricted ``eval`` as a secure sandbox; they warn to open workflows only from trusted sources.
- Removed the completion-email references from the README, the index and the Save to DB page: notifications are currently disabled.
- Rebuilt the bundled in-app help (``timefeatures/help_html``) so the Orange Help window shows the updated pages.

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
-------------------

**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.
