pg_orrery/test/sql/spgist_tle.sql
Ryan Malloy e1c22cb873 Fix GiST picksplit crash and SP-GiST operator argument order
GiST: entryvec->vector[] uses 1-based indexing (FirstOffsetNumber),
not 0-based. Reading vector[0] hit uninitialized memory, causing
SIGSEGV on large catalogs (14k+ satellites). Fixed in gist_tle_union
and gist_tle_picksplit.

SP-GiST: PostgreSQL requires the indexed column as the LEFT argument
of the operator to form a ScanKey (skey.h:23-26). Flipped &? from
(observer_window, tle) to (tle, observer_window) so inner_consistent
receives scankeys for tree-level pruning.

Removed L0 altitude pruning from inner_consistent — SMA bins don't
carry eccentricity, so HEO satellites (e.g. CLUSTER II, e=0.88,
SMA ~70000 km, perigee ~2000 km) were falsely pruned. L0 now only
narrows SMA range for L1 footprint computation.

All 15 regression tests pass. Consistency check on 14,376 satellites
confirms 0 false negatives, 0 false positives.
2026-02-17 21:30:28 -07:00

278 lines
9.0 KiB
SQL

-- Test SP-GiST orbital trie index and &? visibility cone operator
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS pg_orrery;
RESET client_min_messages;
-- ============================================================
-- Test table with mixed orbital regimes
-- ============================================================
CREATE TABLE test_spgist (
id serial,
name text,
tle tle
);
-- ISS (LEO, ~400km, 51.64 deg)
INSERT INTO test_spgist (name, tle) VALUES ('ISS',
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025
2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001');
-- Hubble (LEO, ~540km, 28.47 deg)
INSERT INTO test_spgist (name, tle) VALUES ('Hubble',
'1 20580U 90037B 24001.50000000 .00000790 00000+0 39573-4 0 9992
2 20580 28.4705 61.4398 0002797 317.3115 42.7577 15.09395228 00008');
-- GPS IIR-M (MEO, ~20200km, 55.44 deg)
INSERT INTO test_spgist (name, tle) VALUES ('GPS-IIR',
'1 28874U 05038A 24001.50000000 .00000012 00000+0 00000+0 0 9993
2 28874 55.4408 300.3467 0117034 51.6543 309.5420 2.00557079 00006');
-- Equatorial-LEO (same altitude as ISS, 5 deg inclination)
INSERT INTO test_spgist (name, tle) VALUES ('Equatorial-LEO',
'1 99901U 24999A 24001.50000000 .00016717 00000-0 10270-3 0 9990
2 99901 5.0000 208.9163 0006703 30.1694 61.7520 15.50100486 00001');
-- SSO-800 (Sun-synchronous, ~800km, 98.7 deg)
INSERT INTO test_spgist (name, tle) VALUES ('SSO-800',
'1 99902U 24999B 24001.50000000 .00000100 00000+0 50000-4 0 9991
2 99902 98.7000 120.0000 0001000 90.0000 270.0000 14.19553000 00001');
-- GEO-SAT (Geostationary, ~35786km, 0.04 deg)
INSERT INTO test_spgist (name, tle) VALUES ('GEO-SAT',
'1 99903U 24999C 24001.50000000 .00000000 00000+0 00000+0 0 9992
2 99903 0.0400 270.0000 0003000 0.0000 180.0000 1.00273791 00001');
-- ============================================================
-- Test 1: Operator standalone — ISS from Eagle Idaho (2h window)
-- Eagle Idaho: 43.6977N 116.3535W, 760m elevation
-- ISS passes altitude and inclination checks, but RAAN filter
-- rejects it — the orbital plane isn't overhead during this
-- specific 2-hour window (correct physics, see Test 5 for 24h).
-- ============================================================
SELECT name,
tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 02:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'ISS';
-- ============================================================
-- Test 2: Equatorial-LEO NOT visible from Eagle Idaho
-- 5 deg inc + ~12 deg footprint = 17 deg < 43.7 deg latitude
-- ============================================================
SELECT name,
tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 02:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'Equatorial-LEO';
-- ============================================================
-- Test 3: Create SP-GiST index, verify index scan with positive
-- results. Equatorial observer at 0E — SSO-800 RAAN (120 deg)
-- aligns with LST near 0E at this epoch, so it passes.
-- ============================================================
CREATE INDEX test_spgist_idx ON test_spgist USING spgist (tle tle_spgist_ops);
SET enable_seqscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('0.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 02:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
RESET enable_seqscan;
-- ============================================================
-- Test 4: Seqscan vs index scan consistency — same query must
-- return identical results regardless of scan method.
-- ============================================================
SET enable_indexscan = off;
SET enable_bitmapscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('0.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 02:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
RESET enable_indexscan;
RESET enable_bitmapscan;
-- ============================================================
-- Test 5: 24-hour window — RAAN filter bypassed (full Earth
-- rotation). Only ISS and SSO-800 pass inclination from Eagle
-- Idaho (43.7 deg). Hubble (28.5+14.8=43.3 deg) barely fails.
-- GPS-IIR and GEO-SAT filtered by altitude.
-- ============================================================
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
-- ============================================================
-- Test 6: High min_el (45 deg) changes footprint — wider
-- footprint lets more inclinations through. Same 24h window.
-- ============================================================
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
45.0
)::observer_window
ORDER BY name;
-- ============================================================
-- Test 7: GiST coexistence — both index types on same table
-- ============================================================
CREATE INDEX test_gist_idx ON test_spgist USING gist (tle);
-- GiST overlap query still works
SELECT a.name AS sat_a, b.name AS sat_b, a.tle && b.tle AS overlaps
FROM test_spgist a, test_spgist b
WHERE a.name = 'ISS' AND b.name = 'Hubble';
-- SP-GiST query still works alongside GiST
SET enable_seqscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
RESET enable_seqscan;
-- ============================================================
-- Test 8: NULL TLE handling — NULLs should be excluded
-- ============================================================
INSERT INTO test_spgist (name, tle) VALUES ('NULL-SAT', NULL);
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
-- ============================================================
-- Test 9: Degenerate TLE (mean_motion = 0) — rejected by filter
-- ============================================================
INSERT INTO test_spgist (name, tle) VALUES ('DECAYED',
'1 99904U 24999D 24001.50000000 .00000000 00000+0 00000+0 0 9993
2 99904 0.0000 0.0000 0000000 0.0000 0.0000 0.00000000 00001');
SELECT name,
tle &? ROW(
observer('0.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'DECAYED';
-- ============================================================
-- Test 10: Polar observer (90N) — only ISS and SSO-800 reach
-- the pole. ISS (51.6 + footprint) < 90, so only SSO-800
-- (retrograde, 98.7 deg inc > 90 deg) passes. 24h window.
-- ============================================================
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('90.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
-- ============================================================
-- Test 11: Zero-duration window — sees only what is directly
-- overhead at the instant. RAAN window = footprint only.
-- ============================================================
SELECT name,
tle &? ROW(
observer('0.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 00:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'ISS';
-- ============================================================
-- Test 12: Index-vs-seqscan consistency on 24h Eagle Idaho
-- (the primary correctness test, now after all inserts)
-- ============================================================
SET enable_seqscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
RESET enable_seqscan;
SET enable_indexscan = off;
SET enable_bitmapscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
RESET enable_indexscan;
RESET enable_bitmapscan;
-- ============================================================
-- Cleanup
-- ============================================================
DROP TABLE test_spgist;