diff --git a/workers/api/depth.js b/workers/api/depth.js index dd5a8ee..38e4c30 100644 --- a/workers/api/depth.js +++ b/workers/api/depth.js @@ -177,7 +177,7 @@ const THROUGH_SETS = { /** * Expands THROUGH_SETS for the parent rows: one query per set, joined from - * the link table to the target table, both restricted to status='ok'. + * the link table to the target table (D1 holds only status='ok' rows). * * @param {D1Session} db - The D1 database binding. * @param {EntityMeta} entity - The parent entity metadata. @@ -197,7 +197,7 @@ async function expandThroughSets(db, entity, rows, depth, authenticated, pdbfe) const target = ENTITIES[ts.targetTag]; const params = [JSON.stringify(parentIds)]; const from = ` FROM "${ts.link}" AS l JOIN "${target.table}" AS t ON t."id" = l."${ts.targetFk}"` + - ` WHERE l."${ts.parentFk}" ${IN_IDS} AND l."status" = 'ok' AND t."status" = 'ok'`; + ` WHERE l."${ts.parentFk}" ${IN_IDS}`; if (depth >= 2) { const cols = getColumns(target, pdbfe).map(c => selectColumn(target.table, c, 't.', authenticated)).join(', '); // ap-ok: SQL construction @@ -249,7 +249,7 @@ async function expandDepthOne(db, entity, rows, authenticated, pdbfe) { const childEntity = childTag ? ENTITIES[childTag] : null; const anonFilter = resolveAnonFilter(authenticated, childEntity); - let sql = `SELECT "id", "${rel.fk}" FROM "${rel.table}" WHERE "${rel.fk}" ${IN_IDS} AND "status" = 'ok'`; + let sql = `SELECT "id", "${rel.fk}" FROM "${rel.table}" WHERE "${rel.fk}" ${IN_IDS}`; /** @type {any[]} */ const params = [JSON.stringify(parentIds)]; @@ -346,16 +346,14 @@ async function expandDepthTwo(db, entity, rows, authenticated, pdbfe) { sql = `SELECT ${allCols} FROM "${rel.table}" AS t` + joinParts.join('') + - ` WHERE t."${rel.fk}" ${IN_IDS}` + - ` AND t."status" = 'ok'`; + ` WHERE t."${rel.fk}" ${IN_IDS}`; sql = appendFilterAndOrder(sql, params, anonFilter, 't.'); } else if (childColumns.length > 0) { // Standard path: no JOINs const colExpr = childColumns.map(c => selectColumn(rel.table, c, '', authenticated)).join(", "); // ap-ok: SQL construction sql = `SELECT "${rel.fk}", ${colExpr} FROM "${rel.table}"` + - ` WHERE "${rel.fk}" ${IN_IDS}` + - ` AND "status" = 'ok'`; + ` WHERE "${rel.fk}" ${IN_IDS}`; sql = appendFilterAndOrder(sql, params, anonFilter); } else if (GATED_TABLES.has(rel.table)) { @@ -364,8 +362,7 @@ async function expandDepthTwo(db, entity, rows, authenticated, pdbfe) { } else { // Fallback: unknown child entity, select everything sql = `SELECT * FROM "${rel.table}"` + - ` WHERE "${rel.fk}" ${IN_IDS}` + - ` AND "status" = 'ok'`; + ` WHERE "${rel.fk}" ${IN_IDS}`; sql = appendFilterAndOrder(sql, params, anonFilter); } @@ -426,7 +423,7 @@ async function expandParents(db, entity, rows, depth, authenticated, pdbfe, deta if (ids.size === 0) return; const cols = getColumns(parent, pdbfe).map(c => selectColumn(parent.table, c, '', authenticated)).join(', '); // ap-ok: SQL construction - const sql = `SELECT ${cols} FROM "${parent.table}" WHERE "id" ${IN_IDS} AND "status" = 'ok'`; + const sql = `SELECT ${cols} FROM "${parent.table}" WHERE "id" ${IN_IDS}`; const result = await db.prepare(sql).bind(JSON.stringify([...ids])).all(); // ap-ok: cold path behind cachedQuery const parents = result.results || []; for (const p of parents) parseJsonFields(parent, p); diff --git a/workers/api/handlers/compare.js b/workers/api/handlers/compare.js index cb28fc0..381a0a3 100644 --- a/workers/api/handlers/compare.js +++ b/workers/api/handlers/compare.js @@ -87,7 +87,7 @@ async function fetchEntityHeader(db, tag, id) { const meta = ENTITY_META[tag]; const cols = ['id', meta.nameCol, ...(meta.extraCols || [])]; // ap-ok: cold path, max 4 cols per entity header const colList = cols.map(c => '"' + c + '"').join(', '); // ap-ok: avoids nested template literal (sonar rule) - const sql = `SELECT ${colList} FROM "${meta.table}" WHERE "id" = ? AND "status" = 'ok'`; // ap-ok: cold path, builds SQL once per header lookup + const sql = `SELECT ${colList} FROM "${meta.table}" WHERE "id" = ?`; // ap-ok: cold path, builds SQL once per header lookup const row = await db.prepare(sql).bind(id).first(); if (!row) return null; return { tag, ...row }; @@ -131,8 +131,6 @@ async function overlapNetNet(db, idA, idB) { JOIN peeringdb_ixlan ixlan ON a.ixlan_id = ixlan.id JOIN peeringdb_ix ix ON ixlan.ix_id = ix.id WHERE a.net_id = ? AND b.net_id = ? - AND a.status = 'ok' AND b.status = 'ok' - AND ix.status = 'ok' ORDER BY ix.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; // Shared facilities: networks present at the same facility @@ -143,8 +141,6 @@ async function overlapNetNet(db, idA, idB) { JOIN peeringdb_network_facility b ON a.fac_id = b.fac_id JOIN peeringdb_facility f ON a.fac_id = f.id WHERE a.net_id = ? AND b.net_id = ? - AND a.status = 'ok' AND b.status = 'ok' - AND f.status = 'ok' ORDER BY f.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; // Only-side IXPs (present for the first bind but not the second). Run twice @@ -154,12 +150,12 @@ async function overlapNetNet(db, idA, idB) { FROM peeringdb_network_ixlan a JOIN peeringdb_ixlan ixlan ON a.ixlan_id = ixlan.id JOIN peeringdb_ix ix ON ixlan.ix_id = ix.id - WHERE a.net_id = ? AND a.status = 'ok' AND ix.status = 'ok' + WHERE a.net_id = ? AND ix.id NOT IN ( SELECT ix2.id FROM peeringdb_network_ixlan b JOIN peeringdb_ixlan ixlan2 ON b.ixlan_id = ixlan2.id JOIN peeringdb_ix ix2 ON ixlan2.ix_id = ix2.id - WHERE b.net_id = ? AND b.status = 'ok' + WHERE b.net_id = ? ) ORDER BY ix.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; @@ -169,10 +165,10 @@ async function overlapNetNet(db, idA, idB) { f.latitude, f.longitude FROM peeringdb_network_facility a JOIN peeringdb_facility f ON a.fac_id = f.id - WHERE a.net_id = ? AND a.status = 'ok' AND f.status = 'ok' + WHERE a.net_id = ? AND f.id NOT IN ( SELECT b.fac_id FROM peeringdb_network_facility b - WHERE b.net_id = ? AND b.status = 'ok' + WHERE b.net_id = ? ) ORDER BY f.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; @@ -214,8 +210,6 @@ async function overlapIxIx(db, idA, idB) { JOIN peeringdb_ix_facility b ON a.fac_id = b.fac_id JOIN peeringdb_facility f ON a.fac_id = f.id WHERE a.ix_id = ? AND b.ix_id = ? - AND a.status = 'ok' AND b.status = 'ok' - AND f.status = 'ok' ORDER BY f.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; // Shared member networks (present on both IXes via netixlan → ixlan → ix) @@ -227,8 +221,6 @@ async function overlapIxIx(db, idA, idB) { JOIN peeringdb_ixlan ixlanB ON b.ixlan_id = ixlanB.id JOIN peeringdb_network n ON a.net_id = n.id WHERE ixlanA.ix_id = ? AND ixlanB.ix_id = ? - AND a.status = 'ok' AND b.status = 'ok' - AND n.status = 'ok' ORDER BY n.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; // Only-side facilities — same SQL, swapped ids for only_a / only_b. @@ -237,10 +229,10 @@ async function overlapIxIx(db, idA, idB) { f.latitude, f.longitude FROM peeringdb_ix_facility a JOIN peeringdb_facility f ON a.fac_id = f.id - WHERE a.ix_id = ? AND a.status = 'ok' AND f.status = 'ok' + WHERE a.ix_id = ? AND f.id NOT IN ( SELECT b.fac_id FROM peeringdb_ix_facility b - WHERE b.ix_id = ? AND b.status = 'ok' + WHERE b.ix_id = ? ) ORDER BY f.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; @@ -250,11 +242,11 @@ async function overlapIxIx(db, idA, idB) { FROM peeringdb_network_ixlan a JOIN peeringdb_ixlan ixlan ON a.ixlan_id = ixlan.id JOIN peeringdb_network n ON a.net_id = n.id - WHERE ixlan.ix_id = ? AND a.status = 'ok' AND n.status = 'ok' + WHERE ixlan.ix_id = ? AND n.id NOT IN ( SELECT DISTINCT b.net_id FROM peeringdb_network_ixlan b JOIN peeringdb_ixlan ixlan2 ON b.ixlan_id = ixlan2.id - WHERE ixlan2.ix_id = ? AND b.status = 'ok' + WHERE ixlan2.ix_id = ? ) ORDER BY n.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; @@ -299,8 +291,6 @@ async function overlapIxNet(db, ixId, netId) { JOIN peeringdb_network_facility b ON a.fac_id = b.fac_id JOIN peeringdb_facility f ON a.fac_id = f.id WHERE a.ix_id = ? AND b.net_id = ? - AND a.status = 'ok' AND b.status = 'ok' - AND f.status = 'ok' ORDER BY f.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(ixId, netId).all(), @@ -309,10 +299,10 @@ async function overlapIxNet(db, ixId, netId) { SELECT f.id AS fac_id, f.name AS fac_name, f.city, f.country, f.latitude, f.longitude FROM peeringdb_ix_facility a JOIN peeringdb_facility f ON a.fac_id = f.id - WHERE a.ix_id = ? AND a.status = 'ok' AND f.status = 'ok' + WHERE a.ix_id = ? AND f.id NOT IN ( SELECT b.fac_id FROM peeringdb_network_facility b - WHERE b.net_id = ? AND b.status = 'ok' + WHERE b.net_id = ? ) ORDER BY f.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(ixId, netId).all(), @@ -322,10 +312,10 @@ async function overlapIxNet(db, ixId, netId) { SELECT f.id AS fac_id, f.name AS fac_name, f.city, f.country, f.latitude, f.longitude FROM peeringdb_network_facility b JOIN peeringdb_facility f ON b.fac_id = f.id - WHERE b.net_id = ? AND b.status = 'ok' AND f.status = 'ok' + WHERE b.net_id = ? AND f.id NOT IN ( SELECT a.fac_id FROM peeringdb_ix_facility a - WHERE a.ix_id = ? AND a.status = 'ok' + WHERE a.ix_id = ? ) ORDER BY f.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(netId, ixId).all(), @@ -336,7 +326,6 @@ async function overlapIxNet(db, ixId, netId) { FROM peeringdb_network_ixlan a JOIN peeringdb_ixlan ixlan ON a.ixlan_id = ixlan.id WHERE a.net_id = ? AND ixlan.ix_id = ? - AND a.status = 'ok' AND ixlan.status = 'ok' LIMIT ${COMPARE_ROW_LIMIT} `).bind(netId, ixId).all(), ]); @@ -368,7 +357,6 @@ async function overlapFacNet(db, facId, netId) { JOIN peeringdb_ixlan ixlan ON ixlan.ix_id = ix.id JOIN peeringdb_network_ixlan n ON n.ixlan_id = ixlan.id WHERE ixfac.fac_id = ? AND n.net_id = ? - AND ixfac.status = 'ok' AND ix.status = 'ok' AND ixlan.status = 'ok' AND n.status = 'ok' ORDER BY ix.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(facId, netId).all(), @@ -376,11 +364,11 @@ async function overlapFacNet(db, facId, netId) { SELECT ix.id AS ix_id, ix.name AS ix_name, ix.country, ix.city FROM peeringdb_ix_facility a JOIN peeringdb_ix ix ON a.ix_id = ix.id - WHERE a.fac_id = ? AND a.status = 'ok' AND ix.status = 'ok' + WHERE a.fac_id = ? AND ix.id NOT IN ( SELECT ixlan2.ix_id FROM peeringdb_network_ixlan b JOIN peeringdb_ixlan ixlan2 ON b.ixlan_id = ixlan2.id - WHERE b.net_id = ? AND b.status = 'ok' + WHERE b.net_id = ? ) ORDER BY ix.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(facId, netId).all(), @@ -391,10 +379,10 @@ async function overlapFacNet(db, facId, netId) { FROM peeringdb_network_ixlan b JOIN peeringdb_ixlan ixlan ON b.ixlan_id = ixlan.id JOIN peeringdb_ix ix ON ixlan.ix_id = ix.id - WHERE b.net_id = ? AND b.status = 'ok' AND ix.status = 'ok' AND ixlan.status = 'ok' + WHERE b.net_id = ? AND ix.id NOT IN ( SELECT a.ix_id FROM peeringdb_ix_facility a - WHERE a.fac_id = ? AND a.status = 'ok' + WHERE a.fac_id = ? ) ORDER BY ix.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(netId, facId).all(), @@ -425,7 +413,6 @@ async function overlapFacIx(db, facId, ixId) { JOIN peeringdb_network_ixlan b ON b.net_id = n.id JOIN peeringdb_ixlan ixlan ON b.ixlan_id = ixlan.id WHERE a.fac_id = ? AND ixlan.ix_id = ? - AND a.status = 'ok' AND n.status = 'ok' AND b.status = 'ok' AND ixlan.status = 'ok' ORDER BY n.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(facId, ixId).all(), @@ -433,11 +420,11 @@ async function overlapFacIx(db, facId, ixId) { SELECT DISTINCT n.id AS net_id, n.name AS net_name, n.asn FROM peeringdb_network_facility a JOIN peeringdb_network n ON a.net_id = n.id - WHERE a.fac_id = ? AND a.status = 'ok' AND n.status = 'ok' + WHERE a.fac_id = ? AND n.id NOT IN ( SELECT b.net_id FROM peeringdb_network_ixlan b JOIN peeringdb_ixlan ixlan ON b.ixlan_id = ixlan.id - WHERE ixlan.ix_id = ? AND b.status = 'ok' + WHERE ixlan.ix_id = ? ) ORDER BY n.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(facId, ixId).all(), @@ -447,10 +434,10 @@ async function overlapFacIx(db, facId, ixId) { FROM peeringdb_network_ixlan b JOIN peeringdb_ixlan ixlan ON b.ixlan_id = ixlan.id JOIN peeringdb_network n ON b.net_id = n.id - WHERE ixlan.ix_id = ? AND b.status = 'ok' AND ixlan.status = 'ok' AND n.status = 'ok' + WHERE ixlan.ix_id = ? AND n.id NOT IN ( SELECT a.net_id FROM peeringdb_network_facility a - WHERE a.fac_id = ? AND a.status = 'ok' + WHERE a.fac_id = ? ) ORDER BY n.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT} `).bind(ixId, facId).all(), @@ -478,7 +465,6 @@ async function overlapFacFac(db, idA, idB) { JOIN peeringdb_network_facility b ON a.net_id = b.net_id JOIN peeringdb_network n ON a.net_id = n.id WHERE a.fac_id = ? AND b.fac_id = ? - AND a.status = 'ok' AND b.status = 'ok' AND n.status = 'ok' ORDER BY n.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; const sharedIxpsSql = ` @@ -487,7 +473,6 @@ async function overlapFacFac(db, idA, idB) { JOIN peeringdb_ix_facility b ON a.ix_id = b.ix_id JOIN peeringdb_ix ix ON a.ix_id = ix.id WHERE a.fac_id = ? AND b.fac_id = ? - AND a.status = 'ok' AND b.status = 'ok' AND ix.status = 'ok' ORDER BY ix.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; // Only-side networks / ixps — same SQL, swapped ids for only_a / only_b. @@ -495,7 +480,7 @@ async function overlapFacFac(db, idA, idB) { SELECT DISTINCT n.id AS net_id, n.name AS net_name, n.asn FROM peeringdb_network_facility a JOIN peeringdb_network n ON a.net_id = n.id - WHERE a.fac_id = ? AND a.status = 'ok' AND n.status = 'ok' + WHERE a.fac_id = ? AND n.id NOT IN (SELECT net_id FROM peeringdb_network_facility WHERE fac_id = ? AND status='ok') ORDER BY n.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; @@ -503,7 +488,7 @@ async function overlapFacFac(db, idA, idB) { SELECT ix.id AS ix_id, ix.name AS ix_name, ix.country, ix.city FROM peeringdb_ix_facility a JOIN peeringdb_ix ix ON a.ix_id = ix.id - WHERE a.fac_id = ? AND a.status = 'ok' AND ix.status = 'ok' + WHERE a.fac_id = ? AND ix.id NOT IN (SELECT ix_id FROM peeringdb_ix_facility WHERE fac_id = ? AND status='ok') ORDER BY ix.name COLLATE NOCASE LIMIT ${COMPARE_ROW_LIMIT}`; diff --git a/workers/api/query.js b/workers/api/query.js index 998f457..4638028 100644 --- a/workers/api/query.js +++ b/workers/api/query.js @@ -475,13 +475,9 @@ function buildWherePagination(entity, filters, opts, singleId, tableAlias) { params.push(since); } - // Default to status=ok if no explicit status filter is present - // (matches upstream PeeringDB behaviour — deleted records excluded by default) - const hasStatusFilter = filters.some(f => f.field === 'status'); - if (!hasStatusFilter && getFilterType(entity, 'status')) { - clauses.push(`${pfx}"status" = ?`); - params.push('ok'); - } + // No default status filter: D1 holds only status='ok' rows (the sync + // removes any other status, sync/rows.js isListed), which is upstream's + // default listing. An explicit ?status= still filters like any field. // Restricted-entity visibility (e.g. poc). Anonymous callers may only ever // see the allowed value (visible=Public). Enforced HERE — the single WHERE diff --git a/workers/search/handlers/graph-search.js b/workers/search/handlers/graph-search.js index efba736..4e33320 100644 --- a/workers/search/handlers/graph-search.js +++ b/workers/search/handlers/graph-search.js @@ -77,7 +77,7 @@ function idsFromResult(result) { async function lookupByAsn(db, asn, entityTag, limit) { if (entityTag !== 'net') return null; const result = await db.prepare( - `SELECT id FROM peeringdb_network WHERE asn = ? AND status = 'ok' LIMIT ?` + `SELECT id FROM peeringdb_network WHERE asn = ? LIMIT ?` ).bind(asn, limit).all(); return idsFromResult(result); } @@ -101,7 +101,7 @@ async function lookupByAsn(db, asn, entityTag, limit) { */ async function filterByMetadata(db, entityTag, predicates, limit) { const table = tableFor(entityTag); - const conditions = [`status = 'ok'`]; + const conditions = []; const bindings = []; if (predicates.country) { @@ -122,7 +122,8 @@ async function filterByMetadata(db, entityTag, predicates, limit) { } bindings.push(limit); - const sql = `SELECT id FROM "${table}" WHERE ${conditions.join(' AND ')} LIMIT ?`; + const where = conditions.length ? ` WHERE ${conditions.join(' AND ')}` : ''; + const sql = `SELECT id FROM "${table}"${where} LIMIT ?`; const result = await db.prepare(sql).bind(...bindings).all(); return idsFromResult(result); } @@ -146,14 +147,14 @@ async function resolveNameToId(db, name, preferTag) { const table = tableFor(tag); // Exact match first. let result = await db.prepare( - `SELECT id FROM "${table}" WHERE name = ? AND status = 'ok' LIMIT 1` + `SELECT id FROM "${table}" WHERE name = ? LIMIT 1` ).bind(name).all(); if (result.success && result.results.length > 0) { return { id: result.results[0].id, tag }; } // Prefix / substring match. result = await db.prepare( - String.raw`SELECT id FROM "${table}" WHERE name LIKE ? ESCAPE '\' AND status = 'ok' LIMIT 1` + String.raw`SELECT id FROM "${table}" WHERE name LIKE ? ESCAPE '\' LIMIT 1` ).bind(`%${escapeLike(name)}%`).all(); if (result.success && result.results.length > 0) { return { id: result.results[0].id, tag }; @@ -189,7 +190,7 @@ async function traverseFromAnchor(db, anchorId, anchorTag, entityTag, traversalI if (anchorTag === 'ix' && entityTag === 'net') { // Networks at/members of an IX — via netixlan. sql = `SELECT DISTINCT net_id AS id FROM peeringdb_network_ixlan - WHERE ix_id = ? AND status = 'ok' LIMIT ?`; + WHERE ix_id = ? LIMIT ?`; } else if (anchorTag === 'fac' && entityTag === 'net') { // Networks present at a facility. sql = `SELECT DISTINCT net_id AS id FROM peeringdb_network_facility @@ -205,7 +206,7 @@ async function traverseFromAnchor(db, anchorId, anchorTag, entityTag, traversalI } else if (anchorTag === 'net' && entityTag === 'ix') { // IXes a network is present at. sql = `SELECT DISTINCT ix_id AS id FROM peeringdb_network_ixlan - WHERE net_id = ? AND status = 'ok' LIMIT ?`; + WHERE net_id = ? LIMIT ?`; } else if (anchorTag === 'net' && entityTag === 'fac') { // Facilities a network is present at. sql = `SELECT DISTINCT fac_id AS id FROM peeringdb_network_facility @@ -213,7 +214,7 @@ async function traverseFromAnchor(db, anchorId, anchorTag, entityTag, traversalI } else if (anchorTag === 'campus' && entityTag === 'fac') { // Facilities in a campus. sql = `SELECT DISTINCT id FROM peeringdb_facility - WHERE campus_id = ? AND status = 'ok' LIMIT ?`; + WHERE campus_id = ? LIMIT ?`; } if (!sql) return null; @@ -288,7 +289,7 @@ async function keywordFallback(db, entityTag, raw, limit) { const term = `%${escapeLike(raw)}%`; const result = await db.prepare( String.raw`SELECT id FROM "${table}" - WHERE status = 'ok' AND (name LIKE ? ESCAPE '\' OR aka LIKE ? ESCAPE '\') + WHERE (name LIKE ? ESCAPE '\' OR aka LIKE ? ESCAPE '\') LIMIT ?` ).bind(term, term, limit).all(); return idsFromResult(result); diff --git a/workers/search/handlers/keyword.js b/workers/search/handlers/keyword.js index cee1461..8582932 100644 --- a/workers/search/handlers/keyword.js +++ b/workers/search/handlers/keyword.js @@ -62,7 +62,7 @@ export async function handleKeyword(db, entityTag, q, limit, skip) { const sql = `SELECT id, ${primaryField} AS name${extraSelect}, status FROM ${table}` + - ` WHERE (${where}) AND status = 'ok'` + + ` WHERE (${where})` + ` ORDER BY name ASC LIMIT ? OFFSET ?`; const result = await db.prepare(sql).bind(...binds).all(); diff --git a/workers/search/handlers/query.js b/workers/search/handlers/query.js index cd577ce..73ad457 100644 --- a/workers/search/handlers/query.js +++ b/workers/search/handlers/query.js @@ -205,7 +205,7 @@ async function hydrateGraphIds(db, entityTag, idList, limit) { const inList = ids.join(','); const sql = `SELECT id, ${primaryField} AS name${extraSelect}, status FROM ${table}` + - ` WHERE id IN (${inList}) AND status = 'ok'` + + ` WHERE id IN (${inList})` + ` ORDER BY ${caseExpr} ASC LIMIT ?`; const result = await db.prepare(sql).bind(limit).all(); diff --git a/workers/tests/unit/api/query.test.js b/workers/tests/unit/api/query.test.js index 89deb6a..f179782 100644 --- a/workers/tests/unit/api/query.test.js +++ b/workers/tests/unit/api/query.test.js @@ -1,7 +1,7 @@ /** * @fileoverview Unit tests for the SQL query builder. * Tests filter translation, parameter binding, injection prevention, - * pagination, 'since' handling, and the default status=ok filter. + * pagination and 'since' handling (no default status filter: D1 holds only status='ok'). */ import { describe, it } from 'node:test'; @@ -101,28 +101,27 @@ const POC_RESTRICTED = { _anonFilter: { field: "visible", value: "Public" }, }; -// Because the default status=ok filter is now injected, tests that previously -// expected no WHERE clause will see one. Tests that provide an explicit -// status filter will not get the default injected. +// No default status filter is injected: D1 holds only status='ok' rows (the +// sync removes any other status). An explicit ?status= filters like any field. describe("buildRowQuery", () => { - it("should build a basic SELECT with default status filter", () => { + it("should build a basic SELECT without a status filter", () => { const result = buildRowQuery(NET_ENTITY, [], { depth: 0, limit: 0, skip: 0, since: 0 }); assert.ok(result.sql.includes('SELECT "id", "name", "asn", "org_id", "status", "updated"')); - assert.ok(result.sql.includes('WHERE "status" = ?')); + assert.ok(!result.sql.includes('"status" = ?')); assert.ok(result.sql.includes('ORDER BY "id" ASC')); - assert.deepEqual(result.params, ["ok", MAX_PAGE_LIMIT]); + assert.deepEqual(result.params, [MAX_PAGE_LIMIT]); }); - it("should apply equality filter alongside default status", () => { + it("should apply an equality filter on its own", () => { const filters = [{ field: "asn", op: "eq", value: "13335" }]; const result = buildRowQuery(NET_ENTITY, filters, { depth: 0, limit: 0, skip: 0, since: 0 }); assert.ok(result.sql.includes('"asn" = ? COLLATE NOCASE')); - assert.ok(result.sql.includes('"status" = ?')); - assert.deepEqual(result.params, ["ok", 13335, MAX_PAGE_LIMIT]); + assert.ok(!result.sql.includes('"status" = ?')); + assert.deepEqual(result.params, [13335, MAX_PAGE_LIMIT]); }); - it("should not inject default status when explicit status filter is provided", () => { + it("applies an explicit status filter like any field", () => { const filters = [{ field: "status", op: "eq", value: "deleted" }]; const result = buildRowQuery(NET_ENTITY, filters, { depth: 0, limit: 0, skip: 0, since: 0 }); // Should have exactly one status = ? in the WHERE clause @@ -169,16 +168,14 @@ describe("buildRowQuery", () => { it("should ignore unknown fields", () => { const filters = [{ field: "nonexistent", op: "eq", value: "foo" }]; const result = buildRowQuery(NET_ENTITY, filters, { depth: 0, limit: 0, skip: 0, since: 0 }); - // Still has the default status filter - assert.ok(result.sql.includes('"status" = ?')); - assert.deepEqual(result.params, ["ok", MAX_PAGE_LIMIT]); + assert.ok(!result.sql.includes('WHERE')); + assert.deepEqual(result.params, [MAX_PAGE_LIMIT]); }); it("should ignore unknown operators", () => { const filters = [{ field: "name", op: "regex", value: ".*" }]; const result = buildRowQuery(NET_ENTITY, filters, { depth: 0, limit: 0, skip: 0, since: 0 }); - // Only the default status filter - assert.deepEqual(result.params, ["ok", MAX_PAGE_LIMIT]); + assert.deepEqual(result.params, [MAX_PAGE_LIMIT]); }); it("should handle single ID fetch", () => { @@ -389,10 +386,10 @@ describe("buildJsonQuery omitempty", () => { }); describe("buildCountQuery", () => { - it("should generate COUNT(*) query with default status filter", () => { + it("should generate an unfiltered COUNT(*) query", () => { const result = buildCountQuery(NET_ENTITY, [], { depth: 0, limit: 0, skip: 0, since: 0 }); - assert.equal(result.sql, 'SELECT COUNT(*) AS cnt FROM "peeringdb_network" WHERE "status" = ?'); - assert.deepEqual(result.params, ["ok"]); + assert.equal(result.sql, 'SELECT COUNT(*) AS cnt FROM "peeringdb_network"'); + assert.deepEqual(result.params, []); }); it("should apply explicit status filter to COUNT query", () => { diff --git a/workers/tests/unit/api/upstream_compat_sqlite.test.js b/workers/tests/unit/api/upstream_compat_sqlite.test.js index 978eb84..45f91fe 100644 --- a/workers/tests/unit/api/upstream_compat_sqlite.test.js +++ b/workers/tests/unit/api/upstream_compat_sqlite.test.js @@ -12,8 +12,6 @@ * mirror's link-row sets. * 4. limit=0 means no limit (every row), as upstream; it used to be a * count mode upstream never had. - * 6. Expansions include only status='ok' rows, like the main queries and - * upstream (D1 also holds the pending rows ?since= returns). * 5. Detail views expand every `_id` parent into a `` object at * depth≥1, and at depth=2 serialise those parents at depth 1 (their * sets as ids, their own parents as objects), as upstream. Lists keep @@ -45,20 +43,16 @@ before(() => { net.run(102, 'Net 102', 64102, '2026-10-07T10:30:00Z'); net.run(103, 'Net 103', 64103, '2026-10-07T11:00:00Z'); - // One IX with three facilities linked (one link deleted) and a LAN with + // One IX with two of three facilities linked and a LAN with // net 100 present twice (two addresses) and net 101 once. sqlite.exec(`INSERT INTO "peeringdb_ix" (id, org_id, name, social_media, status, created, updated) VALUES (1, 1, 'IX One', '[]', 'ok', '${TS}', '${TS}')`); const fac = sqlite.prepare(`INSERT INTO "peeringdb_facility" (id, org_id, name, city, social_media, available_voltage_services, status, created, updated) VALUES (?, 1, ?, 'Amsterdam', '[]', '[]', 'ok', '${TS}', '${TS}')`); for (const id of [10, 11, 12]) fac.run(id, `Fac ${id}`); sqlite.exec(`INSERT INTO "peeringdb_campus" (id, org_id, name, social_media, status, created, updated) VALUES (5, 1, 'Campus Five', '[]', 'ok', '${TS}', '${TS}')`); sqlite.exec(`UPDATE "peeringdb_facility" SET campus_id = 5 WHERE id IN (10, 11)`); - // A pending campus (D1 keeps what ?since= returns; upstream lists only ok) - sqlite.exec(`INSERT INTO "peeringdb_campus" (id, org_id, name, social_media, status, created, updated) VALUES (6, 1, 'Campus Pending', '[]', 'pending', '${TS}', '${TS}')`); - sqlite.exec(`UPDATE "peeringdb_facility" SET campus_id = 6 WHERE id = 12`); const ixfac = sqlite.prepare(`INSERT INTO "peeringdb_ix_facility" (id, ix_id, fac_id, status, created, updated) VALUES (?, 1, ?, ?, '${TS}', '${TS}')`); ixfac.run(501, 10, 'ok'); ixfac.run(502, 11, 'ok'); - ixfac.run(503, 12, 'deleted'); sqlite.exec(`INSERT INTO "peeringdb_ixlan" (id, ix_id, name, ixf_ixp_member_list_url_visible, status, created, updated) VALUES (1, 1, '', 'Private', 'ok', '${TS}', '${TS}')`); const nix = sqlite.prepare(`INSERT INTO "peeringdb_network_ixlan" (id, net_id, ixlan_id, asn, speed, ipaddr4, status, created, updated) VALUES (?, ?, 1, ?, 10000, ?, 'ok', '${TS}', '${TS}')`); nix.run(901, 100, 64100, '192.0.2.1'); @@ -130,7 +124,7 @@ describe('unknown query parameters are ignored', () => { }); describe('ix.fac_set / ixlan.net_set (upstream sets through the link table)', () => { - it('ix depth=1: fac_set = facility ids (deleted link excluded), ixfac_set kept', async () => { + it('ix depth=1: fac_set = facility ids, ixfac_set kept', async () => { const ix = (await get(apiWorker, `${API}/ix/1?depth=1`)).body.data[0]; assert.deepEqual(ix.fac_set, [10, 11]); assert.deepEqual(ix.ixfac_set, [501, 502]); @@ -234,23 +228,3 @@ describe('detail views expand parent FKs (as upstream)', () => { assert.deepEqual(campus.fac_set.map((/** @type {any} */ f) => f.campus_id), [5, 5]); }); }); - -describe("expansions include only status='ok' rows", () => { - it('a pending parent is not expanded', async () => { - const fac = (await get(apiWorker, `${API}/fac/12?depth=1`)).body.data[0]; - assert.equal(fac.campus_id, 6); - assert.equal('campus' in fac, false); - }); - - it('a pending child is not in a set (ids or objects)', async () => { - assert.deepEqual((await get(apiWorker, `${API}/org/1?depth=1`)).body.data[0].campus_set, [5]); - const org2 = (await get(apiWorker, `${API}/org/1?depth=2`)).body.data[0]; - assert.deepEqual(org2.campus_set.map((/** @type {any} */ c) => c.id), [5]); - const fac2 = (await get(apiWorker, `${API}/fac/10?depth=2`)).body.data[0]; - assert.deepEqual(fac2.org.campus_set, [5], "parent's sets too"); - }); - - it('the pending row stays hidden from lists, as before', async () => { - assert.deepEqual(ids((await get(apiWorker, `${API}/campus`)).body), [5]); - }); -});