Skip to content

Add endpoints to admin in order to transfer data from a PF to another - #197

Open
f-necas wants to merge 2 commits into
mainfrom
add-data-import
Open

f-necas wants to merge 2 commits into
mainfrom
add-data-import

Conversation

@f-necas

@f-necas f-necas commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

No description provided.

@f-necas
f-necas requested a review from tonio October 21, 2026 13:00

@tonio tonio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few blocking issues in the transfer endpoints, see inline comments: the export holds a table lock for the whole download, export failures reach the client as a 200, and every restore failure is reported as a 400.

with data_engine.connect() as sa_connection:
conn = _driver_connection(sa_connection)
# A re-ingestion replacing the table waits for the end of the dump
conn.execute(sql.SQL("LOCK TABLE {} IN ACCESS SHARE MODE").format(table))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lock, along with a pooled data_engine connection, stays held for as long as the client takes to read the response, not just for as long as the DB takes to produce it. With a slow or stalled client, a re-ingestion that runs DROP TABLE queues for ACCESS EXCLUSIVE, and every later SELECT on the table (GeoServer WMS/WFS) queues behind it, so the published layer hangs until the download ends. A few concurrent exports can also exhaust the pool.

Suggestion: write the dump to a tempfile.SpooledTemporaryFile inside the locked section, release the connection, then stream the file.

"""Gzip stream of the table, read in a single snapshot."""
table = sql.Identifier(schema, table_name)
compressor = zlib.compressobj(wbits=GZIP_WBITS)
with data_engine.connect() as sa_connection:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is a generator passed to StreamingResponse, none of this code runs until after the 200 status and headers are sent. Any failure here (the table moved or dropped between _locate_final_table and LOCK TABLE, a lock or statement timeout, a dropped connection) gives the client an HTTP 200 with an empty or truncated body. The client only finds out later, when PUT rejects the file as invalid gzip.

Opening the connection, taking the lock and reading the definition before returning the response would allow a proper 404 or 500. Spooling the dump as suggested in the comment on line 91 fixes this as well.

except TableTransferError:
conn.rollback()
raise
except Exception as e:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This turns every exception into TableTransferError, so the endpoint returns 400 "stream is rejected" even for server-side problems:

  • Two concurrent PUT /data/{org}/{table} requests both pass the exists check, and the second CREATE TABLE fails with "relation already exists". The client gets 400 instead of the documented 409.
  • A dropped connection, a full disk or a statement timeout is reported as a bad file (400) instead of a server error (500).

_check_definition (line 152) does the same thing and reports these failures as "Invalid type".

Suggestion: map only data errors from the COPY (psycopg.errors.DataError, InvalidTextRepresentation, ...) to TableTransferError, map DuplicateTable to a 409, and let everything else propagate as a 500.

READ_SIZE = 1024 * 1024
MAX_DEFINITION_SIZE = 1024 * 1024
# Column of the final tables created with an id (see transform_staging_to_final)
DATAFEEDER_ID_COLUMN = "id_datafeeder"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplicates ID_COLUMN from data_manipulation.transformation.sql_transform, which is the constant that transform_staging_to_final actually uses. Importing it keeps the two from drifting apart, and the comment above becomes unnecessary.

@f-necas
f-necas requested a review from tonio October 30, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants