Skip to content

Commit a242c70

Browse files
Merge pull request #194 from lsst-sqre/tickets/DM-52588
DM-52588: Add initial implementation of timeseries join on `visit` and `detector`
2 parents fe44b6d + 1ae8f52 commit a242c70

File tree

4 files changed

+98
-1
lines changed

4 files changed

+98
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
### New features
2+
3+
- Added support for joining on ``visit`` and ``detector`` columns when
4+
constructing time series using ``join_style`` parameter, which can be
5+
either ``ccdVisit`` (the original scheme) or ``visit_detector`` for
6+
the new one.

src/datalinker/handlers/external.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ async def timeseries(
160160
pattern=ADQL_FOREIGN_COLUMN_REGEX,
161161
),
162162
] = None,
163+
join_style: Annotated[
164+
Literal["ccdVisit", "visit_detector"], Query(title="Join style")
165+
] = "ccdVisit",
163166
tap_metadata: Annotated[TAPMetadata, Depends(tap_metadata_dependency)],
164167
logger: Annotated[BoundLogger, Depends(logger_dependency)],
165168
) -> str:
@@ -169,9 +172,13 @@ async def timeseries(
169172
# In those cases we have to join with another table on ccdVisitId.
170173
if join_time_column:
171174
join_table, time_column = join_time_column.rsplit(".", 1)
175+
if join_style == "visit_detector":
176+
join_clause = "(s.visit = t.visitId AND s.detector = t.detector)"
177+
else:
178+
join_clause = "s.ccdVisitId = t.ccdVisitId"
172179
adql = (
173180
f"SELECT t.{time_column},{columns} FROM {table} AS s"
174-
f" JOIN {join_table} AS t ON s.ccdVisitId = t.ccdVisitId"
181+
f" JOIN {join_table} AS t ON {join_clause}"
175182
)
176183
else:
177184
adql = f"SELECT {columns} FROM {table} AS s"

tests/data/columns-principal.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,29 @@ tables:
306306
tap:principal: []
307307
dp02_test_PREOPS863_00.Visit:
308308
tap:principal: []
309+
dp1.CcdVisit:
310+
tap:principal:
311+
- band
312+
- dec
313+
- detector
314+
- expTime
315+
- magLim
316+
- psfSigma
317+
- ra
318+
- seeing
319+
- zenithDistance
320+
- visitId
321+
- expMidptMJD
322+
dp1.ForcedSource:
323+
tap:principal:
324+
- band
325+
- detector
326+
- objectId
327+
- psfDiffFlux
328+
- psfDiffFluxErr
329+
- psfFlux
330+
- psfFluxErr
331+
- visit
309332
ivoa.ObsCore:
310333
tap:principal:
311334
- dataproduct_type

tests/handlers/external_test.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,67 @@ async def test_timeseries_detail(client: AsyncClient) -> None:
189189
}
190190

191191

192+
@pytest.mark.asyncio
193+
async def test_timeseries_join_style(client: AsyncClient) -> None:
194+
r = await client.get(
195+
"/api/datalink/timeseries",
196+
params={
197+
"id": 18446744073709551617,
198+
"table": "dp1.ForcedSource",
199+
"id_column": "diaObjectId",
200+
"detail": "principal",
201+
"join_time_column": "dp1.CcdVisit.expMidptMJD",
202+
"join_style": "ccdVisit",
203+
},
204+
)
205+
assert r.status_code == 307
206+
url = urlparse(r.headers["Location"])
207+
assert url.path == "/api/tap/sync"
208+
query = parse_qs(url.query)
209+
assert query == {
210+
"LANG": ["ADQL"],
211+
"REQUEST": ["doQuery"],
212+
"QUERY": [
213+
(
214+
"SELECT t.expMidptMJD,s.band,s.detector,s.objectId,"
215+
"s.psfDiffFlux,s.psfDiffFluxErr,s.psfFlux,s.psfFluxErr,s.visit"
216+
" FROM dp1.ForcedSource"
217+
" AS s JOIN dp1.CcdVisit AS t ON s.ccdVisitId = t.ccdVisitId"
218+
" WHERE s.diaObjectId = 18446744073709551617"
219+
)
220+
],
221+
}
222+
223+
r = await client.get(
224+
"/api/datalink/timeseries",
225+
params={
226+
"id": 18446744073709551617,
227+
"table": "dp1.ForcedSource",
228+
"id_column": "diaObjectId",
229+
"detail": "principal",
230+
"join_time_column": "dp1.CcdVisit.expMidptMJD",
231+
"join_style": "visit_detector",
232+
},
233+
)
234+
assert r.status_code == 307
235+
url = urlparse(r.headers["Location"])
236+
assert url.path == "/api/tap/sync"
237+
query = parse_qs(url.query)
238+
assert query == {
239+
"LANG": ["ADQL"],
240+
"REQUEST": ["doQuery"],
241+
"QUERY": [
242+
(
243+
"SELECT t.expMidptMJD,s.band,s.detector,s.objectId,"
244+
"s.psfDiffFlux,s.psfDiffFluxErr,s.psfFlux,s.psfFluxErr,s.visit"
245+
" FROM dp1.ForcedSource AS s JOIN dp1.CcdVisit AS t"
246+
" ON (s.visit = t.visitId AND s.detector = t.detector)"
247+
" WHERE s.diaObjectId = 18446744073709551617"
248+
)
249+
],
250+
}
251+
252+
192253
@pytest.mark.asyncio
193254
async def test_links(
194255
client: AsyncClient, mock_butler: MockButler, mock_discovery: Discovery

0 commit comments

Comments
 (0)