Skip to content

Commit c511f6a

Browse files
author
James Robinson
authored
Fire off DELETE request to purge any stale introspection records after successful introspection pass. (#73)
1 parent caa4979 commit c511f6a

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

noteable_magics/sql/meta_commands.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
import pathlib
44
import re
5+
import sys
56
import time
67
from concurrent.futures import ThreadPoolExecutor, as_completed
7-
import sys
8+
from datetime import datetime
89
from typing import Any, Dict, Iterable, List, Optional, Tuple
910
from uuid import UUID
1011

@@ -541,6 +542,12 @@ def run(self, invoked_as: str, args: List[str]) -> None:
541542

542543
inspector = self.get_inspector()
543544

545+
# On successful completion, will tell Gate to delete any now-orphaned
546+
# relations from prior introspections older than this timestamp.
547+
introspection_started_at = datetime.utcnow()
548+
549+
# This and delta() just for development timing figures. Could become yet another
550+
# timer context manager implementation.
544551
start = time.monotonic()
545552

546553
def delta() -> float:
@@ -600,12 +607,15 @@ def delta() -> float:
600607
session.headers.update(auth_header)
601608

602609
if message_queue:
603-
# Clear out any prior known relations for this datasource.
604-
self.inform_gate_start(session, ds_id)
605-
606610
for message in message_queue:
607611
self.inform_gate_relation(session, ds_id, message)
608612

613+
# Clear out any prior known relations which may not exist anymore in this datasource.
614+
#
615+
# We do this at the tail end of things, and not the beginning, so as to not eagerly delete
616+
# prior known data if we happen to croak due to some unforseen exception while introspecting.
617+
self.inform_gate_completed(session, ds_id, introspection_started_at)
618+
609619
print(f'Done storing discovered table and view structures in {delta()}')
610620

611621
# run() contract: return what to bind to the SQL cell variable name, and if display() needs
@@ -821,14 +831,6 @@ def introspect_columns(
821831

822832
return retlist
823833

824-
def inform_gate_start(self, session: requests.Session, datasource_id: UUID):
825-
"""Tell gate to forget about any prior structures known for this datasource"""
826-
827-
# No route implemented for this yet, but need one, otherwise Gate-side will
828-
# never forget about dropped tables/views.
829-
830-
pass
831-
832834
def inform_gate_relation(
833835
self,
834836
session: requests.Session,
@@ -851,6 +853,16 @@ def inform_gate_relation(
851853
f'Failed storing structure of {relation_description.schema_name}.{relation_description.relation_name}: {resp.status_code}, {resp.text}'
852854
)
853855

856+
def inform_gate_completed(
857+
self, session: requests.Session, datasource_id: UUID, started_at: datetime
858+
):
859+
"""Tell gate to forget about any structures known for this datasource older than when we
860+
started this introspection run."""
861+
862+
session.delete(
863+
f"http://gate.default/api/v1/datasources/{datasource_id}/schema/relations?older_than={started_at.isoformat()}"
864+
)
865+
854866
def get_datasource_id(self) -> UUID:
855867
"""Convert a noteable_magics.sql.connection.Connection's name to the original
856868
UUID Gate knew it as.

tests/test_sql_magic_meta_commands.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import re
2+
import urllib.parse
3+
from datetime import datetime
24
from typing import List, Optional, Tuple
35
from uuid import uuid4
46

@@ -605,10 +607,20 @@ def patched_requests_mock(self, mocker, patched_jwt, requests_mock):
605607
status_code=204,
606608
)
607609

610+
# And deletes to this one.
611+
requests_mock.delete(
612+
f"http://gate.default/api/v1/datasources/{COCKROACH_UUID}/schema/relations",
613+
status_code=204,
614+
)
615+
608616
yield requests_mock
609617

610618
def test_full_introspection(self, sql_magic, capsys, patched_requests_mock):
619+
620+
introspection_start = datetime.utcnow()
611621
sql_magic.execute(rf'{COCKROACH_HANDLE} \introspect')
622+
introspection_end = datetime.utcnow()
623+
612624
out, err = capsys.readouterr()
613625

614626
assert 'Exception' not in out
@@ -620,9 +632,19 @@ def test_full_introspection(self, sql_magic, capsys, patched_requests_mock):
620632

621633
assert 'Done storing discovered table and view structures' in out
622634

623-
# One POST per relation discovered (as currently implemented)
624-
assert len(patched_requests_mock.request_history) == len(KNOWN_TABLES_AND_KINDS)
625-
assert all(req.method == 'POST' for req in patched_requests_mock.request_history)
635+
# One POST per relation discovered, and then a single DELETE (as currently implemented)
636+
assert len(patched_requests_mock.request_history) == len(KNOWN_TABLES_AND_KINDS) + 1
637+
# Remember, booleans are also integers because filthy historical C.
638+
assert sum(req.method == 'POST' for req in patched_requests_mock.request_history) == len(
639+
KNOWN_TABLES_AND_KINDS
640+
)
641+
642+
# The delete should be the last request, and should specify a since_when timestamp param
643+
# which should be between when introspection started and ended (in UTC).
644+
delete_request = patched_requests_mock.request_history[-1]
645+
assert delete_request.method == 'DELETE'
646+
provided_since_when: str = urllib.parse.parse_qs(delete_request.query)['older_than'][0]
647+
assert introspection_start < datetime.fromisoformat(provided_since_when) < introspection_end
626648

627649
# Each POST body should be a RelationStructureDescription, whose
628650
# relation_names should add up to be all the expected ones.

0 commit comments

Comments
 (0)