@open-mercato/core 0.10.1-develop.7776.1.3032ea0741 → 0.10.1-develop.7781.1.079f42c863
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/catalog/components/products/ProductsDataTable.js +6 -5
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +5 -4
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +6 -5
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +5 -4
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/query_index/cli.js +9 -1
- package/dist/modules/query_index/cli.js.map +2 -2
- package/dist/modules/query_index/lib/coverage.js +13 -2
- package/dist/modules/query_index/lib/coverage.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +28 -9
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +5 -1
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/reindexer.js +95 -32
- package/dist/modules/query_index/lib/reindexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-scope.js +101 -0
- package/dist/modules/query_index/lib/search-scope.js.map +7 -0
- package/dist/modules/query_index/lib/stale.js +41 -1
- package/dist/modules/query_index/lib/stale.js.map +2 -2
- package/dist/modules/query_index/lib/subscriber-scope.js +30 -7
- package/dist/modules/query_index/lib/subscriber-scope.js.map +3 -3
- package/dist/modules/query_index/lib/tenant-global.js +41 -0
- package/dist/modules/query_index/lib/tenant-global.js.map +7 -0
- package/dist/modules/query_index/subscribers/reindex.js +9 -2
- package/dist/modules/query_index/subscribers/reindex.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +4 -3
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +6 -5
- package/src/modules/customers/backend/customers/companies/page.tsx +5 -4
- package/src/modules/customers/backend/customers/deals/page.tsx +6 -5
- package/src/modules/customers/backend/customers/people/page.tsx +5 -4
- package/src/modules/query_index/cli.ts +19 -1
- package/src/modules/query_index/lib/coverage.ts +29 -3
- package/src/modules/query_index/lib/engine.ts +34 -9
- package/src/modules/query_index/lib/indexer.ts +6 -1
- package/src/modules/query_index/lib/reindexer.ts +205 -36
- package/src/modules/query_index/lib/search-scope.ts +144 -0
- package/src/modules/query_index/lib/stale.ts +73 -0
- package/src/modules/query_index/lib/subscriber-scope.ts +38 -8
- package/src/modules/query_index/lib/tenant-global.ts +93 -0
- package/src/modules/query_index/subscribers/reindex.ts +24 -1
- package/src/modules/sales/components/documents/ItemsSection.tsx +6 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/query_index/lib/coverage.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, type Transaction, sql } from 'kysely'\nimport { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('query_index').child({ component: 'coverage' })\n\ntype CoverageExecutor = Kysely<any> | Transaction<any>\n\nexport type CoverageScope = {\n entityType: string\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n}\n\ntype CoverageRow = {\n base_count: unknown\n indexed_count: unknown\n vector_indexed_count: unknown\n refreshed_at: Date | string | null\n}\n\nexport type CoverageSnapshot = CoverageRow & {\n baseCount: number\n indexedCount: number\n vectorIndexedCount: number\n}\n\nexport type CoverageBatchScope = {\n entityTypes: readonly string[]\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n}\n\nexport type CoverageAdjustment = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n withDeleted?: boolean\n deltaBase: number\n deltaIndex: number\n deltaVector?: number\n}\n\nexport type CoverageDeltaInput = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n withDeleted?: boolean\n baseDelta: number\n indexDelta: number\n vectorDelta?: number\n}\n\nconst COLUMN_CACHE = new Map<string, boolean>()\n// In-flight de-dup: without this, N concurrent `tableHasColumn` callers for the same\n// (table, column) \u2014 e.g. every entity type's `refreshCoverageSnapshot` asking about\n// `vector_search.entity_id` \u2014 would each see a cold cache and fire their own identical\n// `information_schema.columns` query, since the cache is only populated after a query\n// resolves. Tracking the in-flight promise lets late arrivals await the first one instead.\nconst COLUMN_CACHE_PENDING = new Map<string, Promise<boolean>>()\nconst GLOBAL_ORGANIZATION_PLACEHOLDER = '00000000-0000-0000-0000-000000000000'\nexport const COVERAGE_ORG_PLACEHOLDER = GLOBAL_ORGANIZATION_PLACEHOLDER\n\nfunction toCount(value: unknown): number {\n if (typeof value === 'number') return Number.isFinite(value) ? value : 0\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n }\n if (value != null && typeof (value as { valueOf: () => number }).valueOf === 'function') {\n const parsed = Number((value as { valueOf: () => number }).valueOf())\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n}\n\nfunction normalizeOrganizationForStore(orgId: string | null | undefined): string {\n return orgId ?? GLOBAL_ORGANIZATION_PLACEHOLDER\n}\n\nfunction applyOrganizationCondition<QB extends { where: (...args: any[]) => QB }>(\n qb: QB,\n column: string,\n organizationId: string | null | undefined,\n): QB {\n const stored = normalizeOrganizationForStore(organizationId ?? null)\n if (stored === GLOBAL_ORGANIZATION_PLACEHOLDER) {\n return qb.where((eb: any) => eb.or([\n eb(column as any, 'is', null),\n eb(column as any, '=', GLOBAL_ORGANIZATION_PLACEHOLDER),\n ]))\n }\n return qb.where(column as any, '=', stored)\n}\n\nasync function fetchCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope\n): Promise<(CoverageRow & { organization_id: string | null }) | null> {\n const { entityType, tenantId, organizationId, withDeleted } = scope\n let query = db\n .selectFrom('entity_index_coverage' as any)\n .select([\n 'base_count' as any,\n 'indexed_count' as any,\n 'vector_indexed_count' as any,\n 'refreshed_at' as any,\n 'organization_id' as any,\n ])\n .where('entity_type' as any, '=', entityType)\n .where('with_deleted' as any, '=', withDeleted === true)\n .orderBy('refreshed_at' as any, 'desc')\n query = tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', organizationId ?? null)\n const row = await query.executeTakeFirst() as (CoverageRow & { organization_id: string | null }) | undefined\n return row ?? null\n}\n\nasync function pruneDuplicateCoverageRows(\n db: CoverageExecutor,\n scope: CoverageScope,\n keepId: string | null\n): Promise<void> {\n let query = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n query = scope.tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', scope.tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', scope.organizationId ?? null)\n if (keepId) {\n query = query.where('id' as any, '!=', keepId)\n }\n await query.execute()\n}\n\nfunction coverageInitializationLockKey(scope: CoverageScope): string {\n return [\n scope.entityType,\n scope.tenantId ?? '__null_tenant__',\n normalizeOrganizationForStore(scope.organizationId ?? null),\n scope.withDeleted === true ? 'with_deleted' : 'active_only',\n ].join('|')\n}\n\nasync function lockCoverageInitialization(db: CoverageExecutor, scope: CoverageScope): Promise<void> {\n await sql`\n select pg_advisory_xact_lock(\n hashtextextended(${coverageInitializationLockKey(scope)}, 0)\n )\n `.execute(db)\n}\n\nasync function updateCoverageRows(\n db: CoverageExecutor,\n scope: CoverageScope,\n deltas: { deltaBase: number; deltaIndex: number; deltaVector: number },\n): Promise<Array<{ id: string }>> {\n let update = db\n .updateTable('entity_index_coverage' as any)\n .set({\n base_count: sql`greatest(${sql.ref('base_count')} + ${deltas.deltaBase}, 0)`,\n indexed_count: sql`greatest(${sql.ref('indexed_count')} + ${deltas.deltaIndex}, 0)`,\n vector_indexed_count: sql`greatest(${sql.ref('vector_indexed_count')} + ${deltas.deltaVector}, 0)`,\n refreshed_at: sql`now()`,\n } as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n update = scope.tenantId == null\n ? update.where('tenant_id' as any, 'is', null as any)\n : update.where('tenant_id' as any, '=', scope.tenantId)\n update = applyOrganizationCondition(update as any, 'organization_id', scope.organizationId ?? null)\n return update.returning(['id' as any]).execute() as Promise<Array<{ id: string }>>\n}\n\n// Applies a coverage delta by incrementing the stored columns in SQL (#5604). The obvious\n// alternative \u2014 read the row, add the delta in JavaScript, write the total back \u2014 is a\n// read-modify-write: two adjustments for the same scope that overlap both read the same row\n// and both write the same total, so one of them is silently lost. Incrementing in SQL makes\n// overlapping adjustments compose, and drops the `SELECT` (and the window between it and the\n// write) that made the coverage row a contention point in the first place.\n//\n// In steady state that is one statement. The extra statements are recovery paths that do not\n// fire once a scope has settled: the legacy NULL-organization fold below runs only for\n// global-organization scopes, the insert only until the scope's row exists, and the duplicate\n// prune only when the scope somehow holds more than one row.\n//\n// The statement is an `UPDATE` rather than an `INSERT \u2026 ON CONFLICT DO UPDATE`, because the\n// conflict target cannot see every scope this table stores: `entity_index_coverage_scope_idx`\n// is a plain `UNIQUE` constraint, and Postgres treats NULLs in one as distinct. For a scope\n// whose tenant is NULL \u2014 `resolveQueryIndexRecordScope`'s `global` branch, or any source row\n// with a NULL tenant column \u2014 the conflict branch would never fire, so every adjustment would\n// insert a fresh row carrying only its own delta and `pruneDuplicateCoverageRows` would then\n// delete the accumulated one. A NULL-aware `UPDATE` matches those rows, and it still adds the\n// delta to the column's own value, so it composes exactly the way the conflict branch does.\nasync function incrementCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope,\n deltas: { deltaBase: number; deltaIndex: number; deltaVector: number }\n): Promise<void> {\n const storedOrgId = normalizeOrganizationForStore(scope.organizationId ?? null)\n let deltaBase = deltas.deltaBase\n let deltaIndex = deltas.deltaIndex\n let deltaVector = deltas.deltaVector\n\n if (scope.organizationId == null) {\n // Legacy rows stored the global scope as a NULL organization; they are replaced by the\n // placeholder-organization row. `RETURNING` carries their counts into this delta so the\n // migration does not reset the counters (the read-modify-write this replaced folded them\n // in via its `SELECT`, which matched NULL and placeholder alike).\n let purge = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n .where('organization_id' as any, 'is', null as any)\n purge = scope.tenantId == null\n ? purge.where('tenant_id' as any, 'is', null as any)\n : purge.where('tenant_id' as any, '=', scope.tenantId)\n const purged = await purge\n .returning(['base_count' as any, 'indexed_count' as any, 'vector_indexed_count' as any])\n .execute() as Array<CoverageRow>\n for (const row of purged ?? []) {\n deltaBase += toCount(row.base_count)\n deltaIndex += toCount(row.indexed_count)\n deltaVector += toCount(row.vector_indexed_count)\n }\n }\n\n let updated = await updateCoverageRows(db, scope, { deltaBase, deltaIndex, deltaVector })\n\n if (updated.length === 0 && scope.tenantId == null) {\n // The unique constraint cannot serialize a first insert whose tenant is NULL. Take a\n // transaction-scoped lock only on this rowless branch, then repeat the UPDATE: a creator\n // that won while we waited has committed its row, so this adjustment composes into it.\n await lockCoverageInitialization(db, scope)\n updated = await updateCoverageRows(db, scope, { deltaBase, deltaIndex, deltaVector })\n }\n\n if (updated.length > 0) {\n // In steady state the scope holds exactly the row just incremented, so the adjustment is\n // the single statement it is described as. Only a scope that somehow accumulated more than\n // one row \u2014 a legacy import, or a concurrent first insert on a NULL-tenant scope, which the\n // unique constraint cannot prevent \u2014 pays for the collapse, and only until it is collapsed.\n if (updated.length > 1) await pruneDuplicateCoverageRows(db, scope, updated[0]!.id)\n return\n }\n\n // No row for this scope yet. `ON CONFLICT` closes the insert race for scopes the unique\n // constraint can see. NULL-tenant initialization reaches here only while holding the scoped\n // transaction advisory lock above, so it has the same single-creator guarantee.\n const rows = await db\n .insertInto('entity_index_coverage' as any)\n .values({\n entity_type: scope.entityType,\n tenant_id: scope.tenantId ?? null,\n organization_id: storedOrgId,\n with_deleted: scope.withDeleted === true,\n base_count: Math.max(deltaBase, 0),\n indexed_count: Math.max(deltaIndex, 0),\n vector_indexed_count: Math.max(deltaVector, 0),\n refreshed_at: sql`now()`,\n } as any)\n .onConflict((oc: any) => oc\n .columns(['entity_type', 'tenant_id', 'organization_id', 'with_deleted'])\n .doUpdateSet({\n base_count: sql`greatest(${sql.ref('entity_index_coverage.base_count')} + ${deltaBase}, 0)`,\n indexed_count: sql`greatest(${sql.ref('entity_index_coverage.indexed_count')} + ${deltaIndex}, 0)`,\n vector_indexed_count: sql`greatest(${sql.ref('entity_index_coverage.vector_indexed_count')} + ${deltaVector}, 0)`,\n refreshed_at: sql`now()`,\n } as any))\n .returning(['id' as any])\n .execute() as Array<{ id: string }>\n\n const keepId = rows?.[0]?.id ?? null\n await pruneDuplicateCoverageRows(db, scope, keepId)\n}\n\nasync function upsertCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope,\n counts: { baseCount: number; indexedCount: number; vectorIndexedCount: number }\n): Promise<void> {\n const storedOrgId = normalizeOrganizationForStore(scope.organizationId ?? null)\n if (scope.organizationId == null) {\n let purge = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n .where('organization_id' as any, 'is', null as any)\n purge = scope.tenantId == null\n ? purge.where('tenant_id' as any, 'is', null as any)\n : purge.where('tenant_id' as any, '=', scope.tenantId)\n await purge.execute()\n }\n\n const rows = await db\n .insertInto('entity_index_coverage' as any)\n .values({\n entity_type: scope.entityType,\n tenant_id: scope.tenantId ?? null,\n organization_id: storedOrgId,\n with_deleted: scope.withDeleted === true,\n base_count: counts.baseCount,\n indexed_count: counts.indexedCount,\n vector_indexed_count: counts.vectorIndexedCount,\n refreshed_at: sql`now()`,\n } as any)\n .onConflict((oc: any) => oc\n .columns(['entity_type', 'tenant_id', 'organization_id', 'with_deleted'])\n .doUpdateSet({\n base_count: counts.baseCount,\n indexed_count: counts.indexedCount,\n vector_indexed_count: counts.vectorIndexedCount,\n refreshed_at: sql`now()`,\n } as any))\n .returning(['id' as any])\n .execute() as Array<{ id: string }>\n\n const keepId = rows?.[0]?.id ?? null\n await pruneDuplicateCoverageRows(db, scope, keepId)\n}\n\nexport async function readCoverageSnapshot(\n db: Kysely<any>,\n scope: CoverageScope\n): Promise<(CoverageRow & { baseCount: number; indexedCount: number; vectorIndexedCount: number }) | null> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return null\n const row = await fetchCoverageRow(db, {\n entityType,\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId ?? null,\n withDeleted: scope.withDeleted === true,\n })\n if (!row) return null\n const refreshedAt = row.refreshed_at instanceof Date ? row.refreshed_at : (row.refreshed_at ? new Date(row.refreshed_at) : null)\n return {\n base_count: row.base_count,\n indexed_count: row.indexed_count,\n vector_indexed_count: row.vector_indexed_count,\n refreshed_at: refreshedAt ?? null,\n baseCount: toCount(row.base_count),\n indexedCount: toCount(row.indexed_count),\n vectorIndexedCount: toCount(row.vector_indexed_count),\n }\n}\n\nexport async function readCoverageSnapshots(\n db: Kysely<any>,\n batch: CoverageBatchScope\n): Promise<Map<string, CoverageSnapshot>> {\n const entityTypes = Array.from(\n new Set((batch.entityTypes ?? []).map((id) => String(id || '')).filter((id) => id.length > 0))\n )\n const result = new Map<string, CoverageSnapshot>()\n if (entityTypes.length === 0) return result\n\n const withDeleted = batch.withDeleted === true\n let query = db\n .selectFrom('entity_index_coverage' as any)\n .select([\n 'entity_type' as any,\n 'base_count' as any,\n 'indexed_count' as any,\n 'vector_indexed_count' as any,\n 'refreshed_at' as any,\n 'organization_id' as any,\n ])\n .where('entity_type' as any, 'in', entityTypes)\n .where('with_deleted' as any, '=', withDeleted)\n .orderBy('refreshed_at' as any, 'desc')\n query = batch.tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', batch.tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', batch.organizationId ?? null)\n\n const rows = await query.execute() as Array<CoverageRow & { entity_type: string }>\n for (const row of rows ?? []) {\n const entityType = String(row.entity_type || '')\n // Rows are ordered by refreshed_at desc, so the first row seen per entity is the latest.\n if (!entityType || result.has(entityType)) continue\n const refreshedAt = row.refreshed_at instanceof Date\n ? row.refreshed_at\n : (row.refreshed_at ? new Date(row.refreshed_at) : null)\n result.set(entityType, {\n base_count: row.base_count,\n indexed_count: row.indexed_count,\n vector_indexed_count: row.vector_indexed_count,\n refreshed_at: refreshedAt ?? null,\n baseCount: toCount(row.base_count),\n indexedCount: toCount(row.indexed_count),\n vectorIndexedCount: toCount(row.vector_indexed_count),\n })\n }\n return result\n}\n\nexport async function applyCoverageAdjustments(\n em: EntityManager,\n adjustments: CoverageAdjustment[],\n options?: { trx?: CoverageExecutor },\n): Promise<void> {\n if (!adjustments.length) return\n const db = (em as any).getKysely() as Kysely<any>\n // `em.getKysely()` hands back the EntityManager's transaction context whenever one is open,\n // and Kysely's `Transaction` throws rather than nesting a second one. A caller already inside\n // `em.transactional(...)` therefore gets the same guarantee the rowless nullable-tenant branch\n // opens a transaction for, so use the ambient one instead of asking for another.\n const ambientTrx = (db as { isTransaction?: boolean }).isTransaction === true\n ? (db as CoverageExecutor)\n : null\n // Keep scope initialization in a stable order. A caller-supplied transaction retains\n // advisory and unique-index locks until commit, so two batches that initialize the same\n // scopes in opposite orders could otherwise deadlock.\n const aggregated = aggregateAdjustments(adjustments).sort((left, right) => (\n scopeKey(left.scope).localeCompare(scopeKey(right.scope))\n ))\n const executor = options?.trx ?? ambientTrx\n for (const entry of aggregated) {\n const deltas = {\n deltaBase: entry.deltaBase,\n deltaIndex: entry.deltaIndex,\n deltaVector: entry.deltaVector,\n }\n if (executor) {\n await incrementCoverageRow(executor, entry.scope, deltas)\n } else if (entry.scope.tenantId == null) {\n await db.transaction().execute((trx) => incrementCoverageRow(trx, entry.scope, deltas))\n } else {\n await incrementCoverageRow(db, entry.scope, deltas)\n }\n }\n}\n\nexport async function deleteCoverageForEntity(db: Kysely<any>, entityType: string): Promise<void> {\n if (!entityType) return\n await db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', entityType)\n .execute()\n}\n\nasync function deleteCoverageScope(db: Kysely<any>, scope: CoverageScope): Promise<void> {\n const { entityType, tenantId, organizationId, withDeleted } = scope\n if (!entityType) return\n let query = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', entityType)\n .where('with_deleted' as any, '=', withDeleted === true)\n query = tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', tenantId)\n await applyOrganizationCondition(query as any, 'organization_id', organizationId ?? null).execute()\n}\n\nasync function tableHasColumn(db: Kysely<any>, table: string, column: string): Promise<boolean> {\n const key = `${table}.${column}`\n if (COLUMN_CACHE.has(key)) return COLUMN_CACHE.get(key)!\n const pending = COLUMN_CACHE_PENDING.get(key)\n if (pending) return pending\n const promise = (async () => {\n const exists = await db\n .selectFrom('information_schema.columns' as any)\n .select(sql<number>`1`.as('present'))\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, '=', table)\n .where('column_name' as any, '=', column)\n .executeTakeFirst()\n const present = !!exists\n COLUMN_CACHE.set(key, present)\n return present\n })()\n COLUMN_CACHE_PENDING.set(key, promise)\n try {\n return await promise\n } finally {\n COLUMN_CACHE_PENDING.delete(key)\n }\n}\n\nexport type ColumnCheck = { table: string; column: string }\n\n// Batches the `information_schema.columns` introspection used by `refreshCoverageSnapshot`\n// into a single query for a whole set of (table, column) pairs, and pre-populates\n// `COLUMN_CACHE_PENDING` for every pair before that query even runs. Callers of\n// `coverage_warmup.ts` use this so its many concurrently-dispatched `coverage.refresh`\n// subscribers hit an already-primed (or in-flight) cache instead of each doing their own\n// per-table introspection round trip.\nexport async function primeColumnCache(db: Kysely<any>, checks: ColumnCheck[]): Promise<void> {\n const missing: Array<{ table: string; column: string; key: string }> = []\n const seen = new Set<string>()\n for (const check of checks) {\n const table = String(check?.table || '')\n const column = String(check?.column || '')\n if (!table || !column) continue\n const key = `${table}.${column}`\n if (seen.has(key)) continue\n seen.add(key)\n if (COLUMN_CACHE.has(key) || COLUMN_CACHE_PENDING.has(key)) continue\n missing.push({ table, column, key })\n }\n if (!missing.length) return\n\n const tables = Array.from(new Set(missing.map((entry) => entry.table)))\n const columns = Array.from(new Set(missing.map((entry) => entry.column)))\n\n const batchPromise = (async (): Promise<Set<string>> => {\n const rows = await db\n .selectFrom('information_schema.columns' as any)\n .select(['table_name' as any, 'column_name' as any])\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, 'in', tables)\n .where('column_name' as any, 'in', columns)\n .execute() as Array<{ table_name: string; column_name: string }>\n return new Set(rows.map((row) => `${row.table_name}.${row.column_name}`))\n })()\n\n for (const entry of missing) {\n const entryPromise = batchPromise.then((present) => {\n const value = present.has(entry.key)\n COLUMN_CACHE.set(entry.key, value)\n return value\n })\n // Mark the stored promise as handled: when the batch query fails and no\n // `tableHasColumn` caller has adopted this entry yet (the common case \u2014 the warmup\n // awaits priming before dispatching any refresh), an orphaned rejection would\n // otherwise crash a plain-Node event worker via unhandledRejection. Awaiting\n // callers still observe the rejection through the stored reference.\n entryPromise.catch(() => undefined)\n COLUMN_CACHE_PENDING.set(entry.key, entryPromise)\n }\n\n try {\n await batchPromise\n } finally {\n for (const entry of missing) COLUMN_CACHE_PENDING.delete(entry.key)\n }\n}\n\nexport async function refreshCoverageSnapshot(\n em: EntityManager,\n scope: CoverageScope,\n): Promise<{ baseCount: number; indexedCount: number } | null> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return null\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n const withDeleted = scope.withDeleted === true\n\n const db = (em as any).getKysely() as Kysely<any>\n const baseTable = resolveEntityTableName(em, entityType)\n\n const hasOrg = await tableHasColumn(db, baseTable, 'organization_id')\n const hasTenant = await tableHasColumn(db, baseTable, 'tenant_id')\n const hasDeleted = await tableHasColumn(db, baseTable, 'deleted_at')\n\n // A scope the base table cannot express must not narrow the index side either. Index rows\n // can carry an organization the base table has no column for \u2014 `organizations` has no\n // `organization_id` yet its index rows derive one from the record id. Filtering only the\n // index side compares two different populations and reports a gap no reindex can close;\n // returning early instead of recounting left the previous snapshot frozen, which is what\n // surfaced as a permanent \"out of sync\" row for `directory:organization`.\n //\n // Tenant is different: a base table without `tenant_id` (`user_roles`) cannot be counted\n // per tenant at all, and writing the cross-tenant total into one tenant's row would leak\n // another tenant's volume into it. Drop the unusable scoped row instead, leaving only the\n // global row \u2014 which is the one that can be true \u2014 rather than freezing a stale count.\n const scopeOrg = organizationId !== null && hasOrg\n if (tenantId !== null && !hasTenant) {\n await deleteCoverageScope(db, { entityType, tenantId, organizationId, withDeleted })\n return null\n }\n const scopeTenant = tenantId !== null && hasTenant\n\n let baseQuery = db\n .selectFrom(`${baseTable} as b` as any)\n .select(sql`count(*)`.as('count'))\n if (scopeOrg) baseQuery = baseQuery.where('b.organization_id' as any, '=', organizationId)\n if (scopeTenant) baseQuery = baseQuery.where('b.tenant_id' as any, '=', tenantId)\n if (!withDeleted && hasDeleted) baseQuery = baseQuery.where('b.deleted_at' as any, 'is', null as any)\n\n let indexQuery = db\n .selectFrom('entity_indexes as ei' as any)\n .select(sql`count(*)`.as('count'))\n .where('ei.entity_type' as any, '=', entityType)\n if (scopeOrg) indexQuery = indexQuery.where('ei.organization_id' as any, '=', organizationId)\n if (scopeTenant) indexQuery = indexQuery.where('ei.tenant_id' as any, '=', tenantId)\n if (!withDeleted) indexQuery = indexQuery.where('ei.deleted_at' as any, 'is', null as any)\n\n const vectorCountPromise = (async (): Promise<number | undefined> => {\n const hasVectorTable = await tableHasColumn(db, 'vector_search', 'entity_id')\n if (!hasVectorTable || typeof tenantId !== 'string' || tenantId.length === 0) return undefined\n\n try {\n let vectorQuery = db\n .selectFrom('vector_search' as any)\n .select(sql`count(*)`.as('count'))\n .where('entity_id' as any, '=', entityType)\n .where('tenant_id' as any, '=', tenantId)\n if (organizationId !== null) {\n vectorQuery = vectorQuery.where('organization_id' as any, '=', organizationId)\n }\n const vectorRow = await vectorQuery.executeTakeFirst() as { count: unknown } | undefined\n return toCount(vectorRow?.count)\n } catch (err) {\n logger.warn('Failed to resolve vector count for coverage snapshot', {\n entityType,\n tenantId,\n organizationId,\n error: err instanceof Error ? err.message : err,\n })\n return undefined\n }\n })()\n\n const [baseRow, indexRow, vectorCount] = await Promise.all([\n baseQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,\n indexQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,\n vectorCountPromise,\n ])\n\n const baseCount = toCount(baseRow?.count)\n const indexCount = toCount(indexRow?.count)\n\n await writeCoverageCounts(em, { entityType, tenantId, organizationId, withDeleted }, {\n baseCount,\n indexedCount: indexCount,\n vectorCount,\n })\n\n return { baseCount, indexedCount: indexCount }\n}\n\nexport async function writeCoverageCounts(\n em: EntityManager,\n scope: CoverageScope,\n counts: { baseCount?: number; indexedCount?: number; vectorCount?: number }\n): Promise<void> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return\n const db = (em as any).getKysely() as Kysely<any>\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n const withDeleted = scope.withDeleted === true\n const existing = await fetchCoverageRow(db, {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n })\n const baseCount = counts.baseCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.baseCount)))\n : Math.max(0, Math.trunc(toCount(existing?.base_count)))\n const indexCount = counts.indexedCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.indexedCount)))\n : Math.max(0, Math.trunc(toCount(existing?.indexed_count)))\n const vectorCount = counts.vectorCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.vectorCount)))\n : Math.max(0, Math.trunc(toCount(existing?.vector_indexed_count)))\n await upsertCoverageRow(db, { entityType, tenantId, organizationId, withDeleted }, {\n baseCount,\n indexedCount: indexCount,\n vectorIndexedCount: vectorCount,\n })\n}\n\ntype AggregatedAdjustment = {\n scope: CoverageScope\n deltaBase: number\n deltaIndex: number\n deltaVector: number\n}\n\nfunction aggregateAdjustments(adjustments: CoverageAdjustment[]): AggregatedAdjustment[] {\n const map = new Map<string, AggregatedAdjustment>()\n for (const adj of adjustments) {\n if (!adj?.entityType) continue\n const deltaBase = Number.isFinite(adj.deltaBase) ? adj.deltaBase : 0\n const deltaIndex = Number.isFinite(adj.deltaIndex) ? adj.deltaIndex : 0\n const deltaVector = Number.isFinite(adj.deltaVector) ? adj.deltaVector! : 0\n if (deltaBase === 0 && deltaIndex === 0 && deltaVector === 0) continue\n const scope: CoverageScope = {\n entityType: adj.entityType,\n tenantId: adj.tenantId ?? null,\n organizationId: adj.organizationId ?? null,\n withDeleted: adj.withDeleted === true,\n }\n const key = scopeKey(scope)\n const existing = map.get(key)\n if (existing) {\n existing.deltaBase += deltaBase\n existing.deltaIndex += deltaIndex\n existing.deltaVector += deltaVector\n } else {\n map.set(key, { scope, deltaBase, deltaIndex, deltaVector })\n }\n }\n return Array.from(map.values())\n}\n\nfunction scopeKey(scope: CoverageScope): string {\n const tenant = scope.tenantId ?? '__tenant_null__'\n const org = normalizeOrganizationForStore(scope.organizationId ?? null)\n const deleted = scope.withDeleted === true ? '1' : '0'\n return `${scope.entityType}|${tenant}|${org}|${deleted}`\n}\n\nexport function createCoverageAdjustments(input: CoverageDeltaInput): CoverageAdjustment[] {\n const entityType = String(input.entityType || '')\n if (!entityType) return []\n const baseDelta = Number.isFinite(input.baseDelta) ? input.baseDelta : 0\n const indexDelta = Number.isFinite(input.indexDelta) ? input.indexDelta : 0\n const vectorDelta = Number.isFinite(input.vectorDelta) ? input.vectorDelta! : 0\n if (baseDelta === 0 && indexDelta === 0 && vectorDelta === 0) return []\n const withDeleted = input.withDeleted === true\n const tenantId = input.tenantId ?? null\n const organizationId = input.organizationId ?? null\n return [\n {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n deltaBase: baseDelta,\n deltaIndex: indexDelta,\n deltaVector: vectorDelta,\n },\n ]\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { resolveQueryIndexSourceTableMetadata } from './subscriber-scope'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, type Transaction, sql } from 'kysely'\nimport { resolveEntityTableName as resolveFallbackEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nfunction resolveEntityTableName(em: EntityManager, entity: string): string {\n return resolveQueryIndexSourceTableMetadata(em, entity)?.table ?? resolveFallbackEntityTableName(em, entity)\n}\n\nconst logger = createLogger('query_index').child({ component: 'coverage' })\n\ntype CoverageExecutor = Kysely<any> | Transaction<any>\n\nexport type CoverageScope = {\n entityType: string\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n}\n\ntype CoverageRow = {\n base_count: unknown\n indexed_count: unknown\n vector_indexed_count: unknown\n refreshed_at: Date | string | null\n}\n\nexport type CoverageSnapshot = CoverageRow & {\n baseCount: number\n indexedCount: number\n vectorIndexedCount: number\n}\n\nexport type CoverageBatchScope = {\n entityTypes: readonly string[]\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n}\n\nexport type CoverageAdjustment = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n withDeleted?: boolean\n deltaBase: number\n deltaIndex: number\n deltaVector?: number\n}\n\nexport type CoverageDeltaInput = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n withDeleted?: boolean\n baseDelta: number\n indexDelta: number\n vectorDelta?: number\n}\n\nconst COLUMN_CACHE = new Map<string, boolean>()\n// In-flight de-dup: without this, N concurrent `tableHasColumn` callers for the same\n// (table, column) \u2014 e.g. every entity type's `refreshCoverageSnapshot` asking about\n// `vector_search.entity_id` \u2014 would each see a cold cache and fire their own identical\n// `information_schema.columns` query, since the cache is only populated after a query\n// resolves. Tracking the in-flight promise lets late arrivals await the first one instead.\nconst COLUMN_CACHE_PENDING = new Map<string, Promise<boolean>>()\nconst GLOBAL_ORGANIZATION_PLACEHOLDER = '00000000-0000-0000-0000-000000000000'\nexport const COVERAGE_ORG_PLACEHOLDER = GLOBAL_ORGANIZATION_PLACEHOLDER\n\nfunction toCount(value: unknown): number {\n if (typeof value === 'number') return Number.isFinite(value) ? value : 0\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n }\n if (value != null && typeof (value as { valueOf: () => number }).valueOf === 'function') {\n const parsed = Number((value as { valueOf: () => number }).valueOf())\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n}\n\nfunction normalizeOrganizationForStore(orgId: string | null | undefined): string {\n return orgId ?? GLOBAL_ORGANIZATION_PLACEHOLDER\n}\n\nfunction applyOrganizationCondition<QB extends { where: (...args: any[]) => QB }>(\n qb: QB,\n column: string,\n organizationId: string | null | undefined,\n): QB {\n const stored = normalizeOrganizationForStore(organizationId ?? null)\n if (stored === GLOBAL_ORGANIZATION_PLACEHOLDER) {\n return qb.where((eb: any) => eb.or([\n eb(column as any, 'is', null),\n eb(column as any, '=', GLOBAL_ORGANIZATION_PLACEHOLDER),\n ]))\n }\n return qb.where(column as any, '=', stored)\n}\n\nasync function fetchCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope\n): Promise<(CoverageRow & { organization_id: string | null }) | null> {\n const { entityType, tenantId, organizationId, withDeleted } = scope\n let query = db\n .selectFrom('entity_index_coverage' as any)\n .select([\n 'base_count' as any,\n 'indexed_count' as any,\n 'vector_indexed_count' as any,\n 'refreshed_at' as any,\n 'organization_id' as any,\n ])\n .where('entity_type' as any, '=', entityType)\n .where('with_deleted' as any, '=', withDeleted === true)\n .orderBy('refreshed_at' as any, 'desc')\n query = tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', organizationId ?? null)\n const row = await query.executeTakeFirst() as (CoverageRow & { organization_id: string | null }) | undefined\n return row ?? null\n}\n\nasync function pruneDuplicateCoverageRows(\n db: CoverageExecutor,\n scope: CoverageScope,\n keepId: string | null\n): Promise<void> {\n let query = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n query = scope.tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', scope.tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', scope.organizationId ?? null)\n if (keepId) {\n query = query.where('id' as any, '!=', keepId)\n }\n await query.execute()\n}\n\nfunction coverageInitializationLockKey(scope: CoverageScope): string {\n return [\n scope.entityType,\n scope.tenantId ?? '__null_tenant__',\n normalizeOrganizationForStore(scope.organizationId ?? null),\n scope.withDeleted === true ? 'with_deleted' : 'active_only',\n ].join('|')\n}\n\nasync function lockCoverageInitialization(db: CoverageExecutor, scope: CoverageScope): Promise<void> {\n await sql`\n select pg_advisory_xact_lock(\n hashtextextended(${coverageInitializationLockKey(scope)}, 0)\n )\n `.execute(db)\n}\n\nasync function updateCoverageRows(\n db: CoverageExecutor,\n scope: CoverageScope,\n deltas: { deltaBase: number; deltaIndex: number; deltaVector: number },\n): Promise<Array<{ id: string }>> {\n let update = db\n .updateTable('entity_index_coverage' as any)\n .set({\n base_count: sql`greatest(${sql.ref('base_count')} + ${deltas.deltaBase}, 0)`,\n indexed_count: sql`greatest(${sql.ref('indexed_count')} + ${deltas.deltaIndex}, 0)`,\n vector_indexed_count: sql`greatest(${sql.ref('vector_indexed_count')} + ${deltas.deltaVector}, 0)`,\n refreshed_at: sql`now()`,\n } as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n update = scope.tenantId == null\n ? update.where('tenant_id' as any, 'is', null as any)\n : update.where('tenant_id' as any, '=', scope.tenantId)\n update = applyOrganizationCondition(update as any, 'organization_id', scope.organizationId ?? null)\n return update.returning(['id' as any]).execute() as Promise<Array<{ id: string }>>\n}\n\n// Applies a coverage delta by incrementing the stored columns in SQL (#5604). The obvious\n// alternative \u2014 read the row, add the delta in JavaScript, write the total back \u2014 is a\n// read-modify-write: two adjustments for the same scope that overlap both read the same row\n// and both write the same total, so one of them is silently lost. Incrementing in SQL makes\n// overlapping adjustments compose, and drops the `SELECT` (and the window between it and the\n// write) that made the coverage row a contention point in the first place.\n//\n// In steady state that is one statement. The extra statements are recovery paths that do not\n// fire once a scope has settled: the legacy NULL-organization fold below runs only for\n// global-organization scopes, the insert only until the scope's row exists, and the duplicate\n// prune only when the scope somehow holds more than one row.\n//\n// The statement is an `UPDATE` rather than an `INSERT \u2026 ON CONFLICT DO UPDATE`, because the\n// conflict target cannot see every scope this table stores: `entity_index_coverage_scope_idx`\n// is a plain `UNIQUE` constraint, and Postgres treats NULLs in one as distinct. For a scope\n// whose tenant is NULL \u2014 `resolveQueryIndexRecordScope`'s `global` branch, or any source row\n// with a NULL tenant column \u2014 the conflict branch would never fire, so every adjustment would\n// insert a fresh row carrying only its own delta and `pruneDuplicateCoverageRows` would then\n// delete the accumulated one. A NULL-aware `UPDATE` matches those rows, and it still adds the\n// delta to the column's own value, so it composes exactly the way the conflict branch does.\nasync function incrementCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope,\n deltas: { deltaBase: number; deltaIndex: number; deltaVector: number }\n): Promise<void> {\n const storedOrgId = normalizeOrganizationForStore(scope.organizationId ?? null)\n let deltaBase = deltas.deltaBase\n let deltaIndex = deltas.deltaIndex\n let deltaVector = deltas.deltaVector\n\n if (scope.organizationId == null) {\n // Legacy rows stored the global scope as a NULL organization; they are replaced by the\n // placeholder-organization row. `RETURNING` carries their counts into this delta so the\n // migration does not reset the counters (the read-modify-write this replaced folded them\n // in via its `SELECT`, which matched NULL and placeholder alike).\n let purge = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n .where('organization_id' as any, 'is', null as any)\n purge = scope.tenantId == null\n ? purge.where('tenant_id' as any, 'is', null as any)\n : purge.where('tenant_id' as any, '=', scope.tenantId)\n const purged = await purge\n .returning(['base_count' as any, 'indexed_count' as any, 'vector_indexed_count' as any])\n .execute() as Array<CoverageRow>\n for (const row of purged ?? []) {\n deltaBase += toCount(row.base_count)\n deltaIndex += toCount(row.indexed_count)\n deltaVector += toCount(row.vector_indexed_count)\n }\n }\n\n let updated = await updateCoverageRows(db, scope, { deltaBase, deltaIndex, deltaVector })\n\n if (updated.length === 0 && scope.tenantId == null) {\n // The unique constraint cannot serialize a first insert whose tenant is NULL. Take a\n // transaction-scoped lock only on this rowless branch, then repeat the UPDATE: a creator\n // that won while we waited has committed its row, so this adjustment composes into it.\n await lockCoverageInitialization(db, scope)\n updated = await updateCoverageRows(db, scope, { deltaBase, deltaIndex, deltaVector })\n }\n\n if (updated.length > 0) {\n // In steady state the scope holds exactly the row just incremented, so the adjustment is\n // the single statement it is described as. Only a scope that somehow accumulated more than\n // one row \u2014 a legacy import, or a concurrent first insert on a NULL-tenant scope, which the\n // unique constraint cannot prevent \u2014 pays for the collapse, and only until it is collapsed.\n if (updated.length > 1) await pruneDuplicateCoverageRows(db, scope, updated[0]!.id)\n return\n }\n\n // No row for this scope yet. `ON CONFLICT` closes the insert race for scopes the unique\n // constraint can see. NULL-tenant initialization reaches here only while holding the scoped\n // transaction advisory lock above, so it has the same single-creator guarantee.\n const rows = await db\n .insertInto('entity_index_coverage' as any)\n .values({\n entity_type: scope.entityType,\n tenant_id: scope.tenantId ?? null,\n organization_id: storedOrgId,\n with_deleted: scope.withDeleted === true,\n base_count: Math.max(deltaBase, 0),\n indexed_count: Math.max(deltaIndex, 0),\n vector_indexed_count: Math.max(deltaVector, 0),\n refreshed_at: sql`now()`,\n } as any)\n .onConflict((oc: any) => oc\n .columns(['entity_type', 'tenant_id', 'organization_id', 'with_deleted'])\n .doUpdateSet({\n base_count: sql`greatest(${sql.ref('entity_index_coverage.base_count')} + ${deltaBase}, 0)`,\n indexed_count: sql`greatest(${sql.ref('entity_index_coverage.indexed_count')} + ${deltaIndex}, 0)`,\n vector_indexed_count: sql`greatest(${sql.ref('entity_index_coverage.vector_indexed_count')} + ${deltaVector}, 0)`,\n refreshed_at: sql`now()`,\n } as any))\n .returning(['id' as any])\n .execute() as Array<{ id: string }>\n\n const keepId = rows?.[0]?.id ?? null\n await pruneDuplicateCoverageRows(db, scope, keepId)\n}\n\nasync function upsertCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope,\n counts: { baseCount: number; indexedCount: number; vectorIndexedCount: number }\n): Promise<void> {\n const storedOrgId = normalizeOrganizationForStore(scope.organizationId ?? null)\n if (scope.organizationId == null) {\n let purge = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n .where('organization_id' as any, 'is', null as any)\n purge = scope.tenantId == null\n ? purge.where('tenant_id' as any, 'is', null as any)\n : purge.where('tenant_id' as any, '=', scope.tenantId)\n await purge.execute()\n }\n\n const rows = await db\n .insertInto('entity_index_coverage' as any)\n .values({\n entity_type: scope.entityType,\n tenant_id: scope.tenantId ?? null,\n organization_id: storedOrgId,\n with_deleted: scope.withDeleted === true,\n base_count: counts.baseCount,\n indexed_count: counts.indexedCount,\n vector_indexed_count: counts.vectorIndexedCount,\n refreshed_at: sql`now()`,\n } as any)\n .onConflict((oc: any) => oc\n .columns(['entity_type', 'tenant_id', 'organization_id', 'with_deleted'])\n .doUpdateSet({\n base_count: counts.baseCount,\n indexed_count: counts.indexedCount,\n vector_indexed_count: counts.vectorIndexedCount,\n refreshed_at: sql`now()`,\n } as any))\n .returning(['id' as any])\n .execute() as Array<{ id: string }>\n\n const keepId = rows?.[0]?.id ?? null\n await pruneDuplicateCoverageRows(db, scope, keepId)\n}\n\nexport async function readCoverageSnapshot(\n db: Kysely<any>,\n scope: CoverageScope\n): Promise<(CoverageRow & { baseCount: number; indexedCount: number; vectorIndexedCount: number }) | null> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return null\n const row = await fetchCoverageRow(db, {\n entityType,\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId ?? null,\n withDeleted: scope.withDeleted === true,\n })\n if (!row) return null\n const refreshedAt = row.refreshed_at instanceof Date ? row.refreshed_at : (row.refreshed_at ? new Date(row.refreshed_at) : null)\n return {\n base_count: row.base_count,\n indexed_count: row.indexed_count,\n vector_indexed_count: row.vector_indexed_count,\n refreshed_at: refreshedAt ?? null,\n baseCount: toCount(row.base_count),\n indexedCount: toCount(row.indexed_count),\n vectorIndexedCount: toCount(row.vector_indexed_count),\n }\n}\n\nexport async function readCoverageSnapshots(\n db: Kysely<any>,\n batch: CoverageBatchScope\n): Promise<Map<string, CoverageSnapshot>> {\n const entityTypes = Array.from(\n new Set((batch.entityTypes ?? []).map((id) => String(id || '')).filter((id) => id.length > 0))\n )\n const result = new Map<string, CoverageSnapshot>()\n if (entityTypes.length === 0) return result\n\n const withDeleted = batch.withDeleted === true\n let query = db\n .selectFrom('entity_index_coverage' as any)\n .select([\n 'entity_type' as any,\n 'base_count' as any,\n 'indexed_count' as any,\n 'vector_indexed_count' as any,\n 'refreshed_at' as any,\n 'organization_id' as any,\n ])\n .where('entity_type' as any, 'in', entityTypes)\n .where('with_deleted' as any, '=', withDeleted)\n .orderBy('refreshed_at' as any, 'desc')\n query = batch.tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', batch.tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', batch.organizationId ?? null)\n\n const rows = await query.execute() as Array<CoverageRow & { entity_type: string }>\n for (const row of rows ?? []) {\n const entityType = String(row.entity_type || '')\n // Rows are ordered by refreshed_at desc, so the first row seen per entity is the latest.\n if (!entityType || result.has(entityType)) continue\n const refreshedAt = row.refreshed_at instanceof Date\n ? row.refreshed_at\n : (row.refreshed_at ? new Date(row.refreshed_at) : null)\n result.set(entityType, {\n base_count: row.base_count,\n indexed_count: row.indexed_count,\n vector_indexed_count: row.vector_indexed_count,\n refreshed_at: refreshedAt ?? null,\n baseCount: toCount(row.base_count),\n indexedCount: toCount(row.indexed_count),\n vectorIndexedCount: toCount(row.vector_indexed_count),\n })\n }\n return result\n}\n\nexport async function applyCoverageAdjustments(\n em: EntityManager,\n adjustments: CoverageAdjustment[],\n options?: { trx?: CoverageExecutor },\n): Promise<void> {\n if (!adjustments.length) return\n const db = (em as any).getKysely() as Kysely<any>\n // `em.getKysely()` hands back the EntityManager's transaction context whenever one is open,\n // and Kysely's `Transaction` throws rather than nesting a second one. A caller already inside\n // `em.transactional(...)` therefore gets the same guarantee the rowless nullable-tenant branch\n // opens a transaction for, so use the ambient one instead of asking for another.\n const ambientTrx = (db as { isTransaction?: boolean }).isTransaction === true\n ? (db as CoverageExecutor)\n : null\n // Keep scope initialization in a stable order. A caller-supplied transaction retains\n // advisory and unique-index locks until commit, so two batches that initialize the same\n // scopes in opposite orders could otherwise deadlock.\n const aggregated = aggregateAdjustments(adjustments).sort((left, right) => (\n scopeKey(left.scope).localeCompare(scopeKey(right.scope))\n ))\n const executor = options?.trx ?? ambientTrx\n for (const entry of aggregated) {\n const deltas = {\n deltaBase: entry.deltaBase,\n deltaIndex: entry.deltaIndex,\n deltaVector: entry.deltaVector,\n }\n if (executor) {\n await incrementCoverageRow(executor, entry.scope, deltas)\n } else if (entry.scope.tenantId == null) {\n await db.transaction().execute((trx) => incrementCoverageRow(trx, entry.scope, deltas))\n } else {\n await incrementCoverageRow(db, entry.scope, deltas)\n }\n }\n}\n\nexport async function deleteCoverageForEntity(db: Kysely<any>, entityType: string): Promise<void> {\n if (!entityType) return\n await db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', entityType)\n .execute()\n}\n\ntype CatalogueCoverageDatabase = {\n entity_index_coverage: { entity_type: string; tenant_id: string | null; organization_id: string | null }\n}\n\n/**\n * Drop every coverage row of `entityType` outside the global scope (no tenant, and the\n * organization placeholder or no organization). A declared platform-wide catalogue keeps its\n * coverage there only; rows an earlier sweep wrote under the caller's scope are never\n * refreshed again and would keep reporting stale counts.\n */\nexport async function deleteScopedCoverageForEntity(db: Kysely<CatalogueCoverageDatabase>, entityType: string): Promise<void> {\n if (!entityType) return\n await db\n .deleteFrom('entity_index_coverage')\n .where('entity_type', '=', entityType)\n .where(sql<boolean>`(tenant_id is not null or (organization_id is not null and organization_id <> ${GLOBAL_ORGANIZATION_PLACEHOLDER}))`)\n .execute()\n}\n\nasync function deleteCoverageScope(db: Kysely<any>, scope: CoverageScope): Promise<void> {\n const { entityType, tenantId, organizationId, withDeleted } = scope\n if (!entityType) return\n let query = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', entityType)\n .where('with_deleted' as any, '=', withDeleted === true)\n query = tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', tenantId)\n await applyOrganizationCondition(query as any, 'organization_id', organizationId ?? null).execute()\n}\n\nasync function tableHasColumn(db: Kysely<any>, table: string, column: string): Promise<boolean> {\n const key = `${table}.${column}`\n if (COLUMN_CACHE.has(key)) return COLUMN_CACHE.get(key)!\n const pending = COLUMN_CACHE_PENDING.get(key)\n if (pending) return pending\n const promise = (async () => {\n const exists = await db\n .selectFrom('information_schema.columns' as any)\n .select(sql<number>`1`.as('present'))\n .where(table.includes('.') ? sql<boolean>`table_schema = ${table.split('.')[0]}` : sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, '=', table.split('.').pop())\n .where('column_name' as any, '=', column)\n .executeTakeFirst()\n const present = !!exists\n COLUMN_CACHE.set(key, present)\n return present\n })()\n COLUMN_CACHE_PENDING.set(key, promise)\n try {\n return await promise\n } finally {\n COLUMN_CACHE_PENDING.delete(key)\n }\n}\n\nexport type ColumnCheck = { table: string; column: string }\n\n// Batches the `information_schema.columns` introspection used by `refreshCoverageSnapshot`\n// into a single query for a whole set of (table, column) pairs, and pre-populates\n// `COLUMN_CACHE_PENDING` for every pair before that query even runs. Callers of\n// `coverage_warmup.ts` use this so its many concurrently-dispatched `coverage.refresh`\n// subscribers hit an already-primed (or in-flight) cache instead of each doing their own\n// per-table introspection round trip.\nexport async function primeColumnCache(db: Kysely<any>, checks: ColumnCheck[]): Promise<void> {\n for (const check of checks.filter((entry) => typeof entry?.table === 'string' && entry.table.includes('.'))) await tableHasColumn(db, check.table, check.column)\n checks = checks.filter((entry) => !(typeof entry?.table === 'string' && entry.table.includes('.')))\n const missing: Array<{ table: string; column: string; key: string }> = []\n const seen = new Set<string>()\n for (const check of checks) {\n const table = String(check?.table || '')\n const column = String(check?.column || '')\n if (!table || !column) continue\n const key = `${table}.${column}`\n if (seen.has(key)) continue\n seen.add(key)\n if (COLUMN_CACHE.has(key) || COLUMN_CACHE_PENDING.has(key)) continue\n missing.push({ table, column, key })\n }\n if (!missing.length) return\n\n const tables = Array.from(new Set(missing.map((entry) => entry.table)))\n const columns = Array.from(new Set(missing.map((entry) => entry.column)))\n\n const batchPromise = (async (): Promise<Set<string>> => {\n const rows = await db\n .selectFrom('information_schema.columns' as any)\n .select(['table_name' as any, 'column_name' as any])\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, 'in', tables)\n .where('column_name' as any, 'in', columns)\n .execute() as Array<{ table_name: string; column_name: string }>\n return new Set(rows.map((row) => `${row.table_name}.${row.column_name}`))\n })()\n\n for (const entry of missing) {\n const entryPromise = batchPromise.then((present) => {\n const value = present.has(entry.key)\n COLUMN_CACHE.set(entry.key, value)\n return value\n })\n // Mark the stored promise as handled: when the batch query fails and no\n // `tableHasColumn` caller has adopted this entry yet (the common case \u2014 the warmup\n // awaits priming before dispatching any refresh), an orphaned rejection would\n // otherwise crash a plain-Node event worker via unhandledRejection. Awaiting\n // callers still observe the rejection through the stored reference.\n entryPromise.catch(() => undefined)\n COLUMN_CACHE_PENDING.set(entry.key, entryPromise)\n }\n\n try {\n await batchPromise\n } finally {\n for (const entry of missing) COLUMN_CACHE_PENDING.delete(entry.key)\n }\n}\n\nexport async function refreshCoverageSnapshot(\n em: EntityManager,\n scope: CoverageScope,\n): Promise<{ baseCount: number; indexedCount: number } | null> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return null\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n const withDeleted = scope.withDeleted === true\n\n const db = (em as any).getKysely() as Kysely<any>\n const baseTable = resolveEntityTableName(em, entityType)\n\n const hasOrg = await tableHasColumn(db, baseTable, 'organization_id')\n const hasTenant = await tableHasColumn(db, baseTable, 'tenant_id')\n const hasDeleted = await tableHasColumn(db, baseTable, 'deleted_at')\n\n // A scope the base table cannot express must not narrow the index side either. Index rows\n // can carry an organization the base table has no column for \u2014 `organizations` has no\n // `organization_id` yet its index rows derive one from the record id. Filtering only the\n // index side compares two different populations and reports a gap no reindex can close;\n // returning early instead of recounting left the previous snapshot frozen, which is what\n // surfaced as a permanent \"out of sync\" row for `directory:organization`.\n //\n // Tenant is different: a base table without `tenant_id` (`user_roles`) cannot be counted\n // per tenant at all, and writing the cross-tenant total into one tenant's row would leak\n // another tenant's volume into it. Drop the unusable scoped row instead, leaving only the\n // global row \u2014 which is the one that can be true \u2014 rather than freezing a stale count.\n const scopeOrg = organizationId !== null && hasOrg\n if (tenantId !== null && !hasTenant) {\n await deleteCoverageScope(db, { entityType, tenantId, organizationId, withDeleted })\n return null\n }\n const scopeTenant = tenantId !== null && hasTenant\n\n let baseQuery = db\n .selectFrom(`${baseTable} as b` as any)\n .select(sql`count(*)`.as('count'))\n if (scopeOrg) baseQuery = baseQuery.where('b.organization_id' as any, '=', organizationId)\n if (scopeTenant) baseQuery = baseQuery.where('b.tenant_id' as any, '=', tenantId)\n if (!withDeleted && hasDeleted) baseQuery = baseQuery.where('b.deleted_at' as any, 'is', null as any)\n\n let indexQuery = db\n .selectFrom('entity_indexes as ei' as any)\n .select(sql`count(*)`.as('count'))\n .where('ei.entity_type' as any, '=', entityType)\n if (scopeOrg) indexQuery = indexQuery.where('ei.organization_id' as any, '=', organizationId)\n if (scopeTenant) indexQuery = indexQuery.where('ei.tenant_id' as any, '=', tenantId)\n if (!withDeleted) indexQuery = indexQuery.where('ei.deleted_at' as any, 'is', null as any)\n\n const vectorCountPromise = (async (): Promise<number | undefined> => {\n const hasVectorTable = await tableHasColumn(db, 'vector_search', 'entity_id')\n if (!hasVectorTable || typeof tenantId !== 'string' || tenantId.length === 0) return undefined\n\n try {\n let vectorQuery = db\n .selectFrom('vector_search' as any)\n .select(sql`count(*)`.as('count'))\n .where('entity_id' as any, '=', entityType)\n .where('tenant_id' as any, '=', tenantId)\n if (organizationId !== null) {\n vectorQuery = vectorQuery.where('organization_id' as any, '=', organizationId)\n }\n const vectorRow = await vectorQuery.executeTakeFirst() as { count: unknown } | undefined\n return toCount(vectorRow?.count)\n } catch (err) {\n logger.warn('Failed to resolve vector count for coverage snapshot', {\n entityType,\n tenantId,\n organizationId,\n error: err instanceof Error ? err.message : err,\n })\n return undefined\n }\n })()\n\n const [baseRow, indexRow, vectorCount] = await Promise.all([\n baseQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,\n indexQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,\n vectorCountPromise,\n ])\n\n const baseCount = toCount(baseRow?.count)\n const indexCount = toCount(indexRow?.count)\n\n await writeCoverageCounts(em, { entityType, tenantId, organizationId, withDeleted }, {\n baseCount,\n indexedCount: indexCount,\n vectorCount,\n })\n\n return { baseCount, indexedCount: indexCount }\n}\n\nexport async function writeCoverageCounts(\n em: EntityManager,\n scope: CoverageScope,\n counts: { baseCount?: number; indexedCount?: number; vectorCount?: number }\n): Promise<void> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return\n const db = (em as any).getKysely() as Kysely<any>\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n const withDeleted = scope.withDeleted === true\n const existing = await fetchCoverageRow(db, {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n })\n const baseCount = counts.baseCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.baseCount)))\n : Math.max(0, Math.trunc(toCount(existing?.base_count)))\n const indexCount = counts.indexedCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.indexedCount)))\n : Math.max(0, Math.trunc(toCount(existing?.indexed_count)))\n const vectorCount = counts.vectorCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.vectorCount)))\n : Math.max(0, Math.trunc(toCount(existing?.vector_indexed_count)))\n await upsertCoverageRow(db, { entityType, tenantId, organizationId, withDeleted }, {\n baseCount,\n indexedCount: indexCount,\n vectorIndexedCount: vectorCount,\n })\n}\n\ntype AggregatedAdjustment = {\n scope: CoverageScope\n deltaBase: number\n deltaIndex: number\n deltaVector: number\n}\n\nfunction aggregateAdjustments(adjustments: CoverageAdjustment[]): AggregatedAdjustment[] {\n const map = new Map<string, AggregatedAdjustment>()\n for (const adj of adjustments) {\n if (!adj?.entityType) continue\n const deltaBase = Number.isFinite(adj.deltaBase) ? adj.deltaBase : 0\n const deltaIndex = Number.isFinite(adj.deltaIndex) ? adj.deltaIndex : 0\n const deltaVector = Number.isFinite(adj.deltaVector) ? adj.deltaVector! : 0\n if (deltaBase === 0 && deltaIndex === 0 && deltaVector === 0) continue\n const scope: CoverageScope = {\n entityType: adj.entityType,\n tenantId: adj.tenantId ?? null,\n organizationId: adj.organizationId ?? null,\n withDeleted: adj.withDeleted === true,\n }\n const key = scopeKey(scope)\n const existing = map.get(key)\n if (existing) {\n existing.deltaBase += deltaBase\n existing.deltaIndex += deltaIndex\n existing.deltaVector += deltaVector\n } else {\n map.set(key, { scope, deltaBase, deltaIndex, deltaVector })\n }\n }\n return Array.from(map.values())\n}\n\nfunction scopeKey(scope: CoverageScope): string {\n const tenant = scope.tenantId ?? '__tenant_null__'\n const org = normalizeOrganizationForStore(scope.organizationId ?? null)\n const deleted = scope.withDeleted === true ? '1' : '0'\n return `${scope.entityType}|${tenant}|${org}|${deleted}`\n}\n\nexport function createCoverageAdjustments(input: CoverageDeltaInput): CoverageAdjustment[] {\n const entityType = String(input.entityType || '')\n if (!entityType) return []\n const baseDelta = Number.isFinite(input.baseDelta) ? input.baseDelta : 0\n const indexDelta = Number.isFinite(input.indexDelta) ? input.indexDelta : 0\n const vectorDelta = Number.isFinite(input.vectorDelta) ? input.vectorDelta! : 0\n if (baseDelta === 0 && indexDelta === 0 && vectorDelta === 0) return []\n const withDeleted = input.withDeleted === true\n const tenantId = input.tenantId ?? null\n const organizationId = input.organizationId ?? null\n return [\n {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n deltaBase: baseDelta,\n deltaIndex: indexDelta,\n deltaVector: vectorDelta,\n },\n ]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { resolveQueryIndexSourceTableMetadata } from "./subscriber-scope.js";
|
|
1
2
|
import { SortDir } from "@open-mercato/shared/lib/query/types";
|
|
2
|
-
import { resolveEntityTableName, resolveRegisteredEntityTableName } from "@open-mercato/shared/lib/query/engine";
|
|
3
|
+
import { resolveEntityTableName as resolveFallbackEntityTableName, resolveRegisteredEntityTableName } from "@open-mercato/shared/lib/query/engine";
|
|
3
4
|
import { isOrmBackedSystemEntityId } from "@open-mercato/shared/lib/data/engine";
|
|
4
5
|
import {
|
|
5
6
|
buildArrayColumnNoOverlapPredicate,
|
|
@@ -58,6 +59,9 @@ import { mapWithConcurrency } from "@open-mercato/shared/lib/query/bounded-decry
|
|
|
58
59
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
59
60
|
import { parseNumberWithDefault } from "@open-mercato/shared/lib/number";
|
|
60
61
|
import { createBoundedTtlMemo } from "@open-mercato/shared/lib/query/bounded-ttl-memo";
|
|
62
|
+
function resolveEntityTableName(em, entity) {
|
|
63
|
+
return resolveQueryIndexSourceTableMetadata(em, entity)?.table ?? resolveFallbackEntityTableName(em, entity);
|
|
64
|
+
}
|
|
61
65
|
const logger = createLogger("query_index").child({ component: "engine" });
|
|
62
66
|
function customDocumentValue(row, field) {
|
|
63
67
|
const bare = field.replace(/^cf[:_]/, "");
|
|
@@ -374,13 +378,13 @@ class HybridQueryEngine {
|
|
|
374
378
|
const baseExists = await profiler.measure("base_table_exists", () => this.tableExists(baseTable));
|
|
375
379
|
if (!baseExists) {
|
|
376
380
|
if (debugEnabled) this.debug("query:fallback:missing-base", { entity, baseTable });
|
|
377
|
-
const fallbackResult = await this.
|
|
381
|
+
const fallbackResult = await this.queryFallback(entity, opts);
|
|
378
382
|
finishProfile({ result: "fallback", reason: "missing_base" });
|
|
379
383
|
return await applyAfterExtensions(fallbackResult);
|
|
380
384
|
}
|
|
381
385
|
if (opts.omitAutomaticTenantOrgScope === true) {
|
|
382
386
|
if (debugEnabled) this.debug("query:fallback:omit-automatic-scope", { entity });
|
|
383
|
-
const fallbackResult = await this.
|
|
387
|
+
const fallbackResult = await this.queryFallback(entity, opts);
|
|
384
388
|
finishProfile({ result: "fallback", reason: "omit_automatic_tenant_org_scope" });
|
|
385
389
|
return await applyAfterExtensions(fallbackResult);
|
|
386
390
|
}
|
|
@@ -408,7 +412,7 @@ class HybridQueryEngine {
|
|
|
408
412
|
);
|
|
409
413
|
if (!hasIndexRows) {
|
|
410
414
|
if (debugEnabled) this.debug("query:fallback:no-index", { entity });
|
|
411
|
-
const fallbackResult = await this.
|
|
415
|
+
const fallbackResult = await this.queryFallback(entity, opts);
|
|
412
416
|
finishProfile({ result: "fallback", reason: "no_index_rows" });
|
|
413
417
|
return await applyAfterExtensions(fallbackResult);
|
|
414
418
|
}
|
|
@@ -435,7 +439,7 @@ class HybridQueryEngine {
|
|
|
435
439
|
logger.warn("Partial index coverage detected; falling back to basic engine", { entity });
|
|
436
440
|
if (debugEnabled) this.debug("query:fallback:partial-coverage", { entity });
|
|
437
441
|
}
|
|
438
|
-
const fallbackResult = await this.
|
|
442
|
+
const fallbackResult = await this.queryFallback(entity, opts);
|
|
439
443
|
const resultWithWarning = {
|
|
440
444
|
...fallbackResult,
|
|
441
445
|
meta: {
|
|
@@ -514,6 +518,10 @@ class HybridQueryEngine {
|
|
|
514
518
|
indexSources.push({ alias: source.indexAlias, entityId: source.entityId, recordIdColumn: `${source.alias}.${source.recordIdColumn}` });
|
|
515
519
|
}
|
|
516
520
|
}
|
|
521
|
+
const tenantlessIndexSources = /* @__PURE__ */ new Set();
|
|
522
|
+
for (const source of preparedCfSources) {
|
|
523
|
+
if (!source.tenantField && !await this.columnExists(source.table, "tenant_id")) tenantlessIndexSources.add(source.indexAlias);
|
|
524
|
+
}
|
|
517
525
|
const searchSources = indexSources.map((src) => ({ entity: String(src.entityId), recordIdColumn: src.recordIdColumn })).filter((src) => src.recordIdColumn && src.entity);
|
|
518
526
|
const searchFilters = normalizedFilters.filter((filter) => isSearchFilterOp(filter.op));
|
|
519
527
|
const sourceSearchFilters = [
|
|
@@ -751,6 +759,8 @@ class HybridQueryEngine {
|
|
|
751
759
|
}
|
|
752
760
|
if (hasTenantColumn) {
|
|
753
761
|
jc = jc.onRef("ei.tenant_id", "=", qualify("tenant_id")).on("ei.tenant_id", "is not", null);
|
|
762
|
+
} else {
|
|
763
|
+
jc = jc.on("ei.tenant_id", "is", null);
|
|
754
764
|
}
|
|
755
765
|
if (!opts.withDeleted) {
|
|
756
766
|
jc = jc.on("ei.deleted_at", "is", null);
|
|
@@ -772,7 +782,9 @@ class HybridQueryEngine {
|
|
|
772
782
|
jc = jc.onRef(`${source.indexAlias}.organization_id`, "=", orgRef).on(`${source.indexAlias}.organization_id`, "is not", null);
|
|
773
783
|
}
|
|
774
784
|
const tenantRef = source.tenantField ? `${source.alias}.${source.tenantField}` : columns.has("tenant_id") ? qualify("tenant_id") : null;
|
|
775
|
-
if (
|
|
785
|
+
if (tenantlessIndexSources.has(source.indexAlias)) {
|
|
786
|
+
jc = jc.on(`${source.indexAlias}.tenant_id`, "is", null);
|
|
787
|
+
} else if (tenantRef) {
|
|
776
788
|
jc = jc.onRef(`${source.indexAlias}.tenant_id`, "=", tenantRef).on(`${source.indexAlias}.tenant_id`, "is not", null);
|
|
777
789
|
}
|
|
778
790
|
if (!opts.withDeleted) jc = jc.on(`${source.indexAlias}.deleted_at`, "is", null);
|
|
@@ -2055,10 +2067,17 @@ class HybridQueryEngine {
|
|
|
2055
2067
|
if (listCountCapWarning) result.meta = { listCountCapWarning };
|
|
2056
2068
|
return result;
|
|
2057
2069
|
}
|
|
2070
|
+
async queryFallback(entity, opts) {
|
|
2071
|
+
const source = resolveQueryIndexSourceTableMetadata(this.em, entity);
|
|
2072
|
+
if (source?.schema || source?.table.includes(".")) {
|
|
2073
|
+
throw new Error("[internal] Query fallback cannot preserve a schema-qualified source identity");
|
|
2074
|
+
}
|
|
2075
|
+
return this.fallback.query(entity, opts);
|
|
2076
|
+
}
|
|
2058
2077
|
async tableExists(table) {
|
|
2059
2078
|
if (baseTableExistsCache.get(table)) return true;
|
|
2060
2079
|
const db = this.getDb();
|
|
2061
|
-
const exists = await db.selectFrom("information_schema.tables").select(sql`1`.as("one")).where("table_name", "=", table).executeTakeFirst();
|
|
2080
|
+
const exists = await db.selectFrom("information_schema.tables").select(sql`1`.as("one")).where(table.includes(".") ? sql`table_schema = ${table.split(".")[0]}` : sql`table_schema = current_schema()`).where("table_name", "=", table.split(".").pop()).executeTakeFirst();
|
|
2062
2081
|
const present = !!exists;
|
|
2063
2082
|
if (present) baseTableExistsCache.set(table, true);
|
|
2064
2083
|
return present;
|
|
@@ -2360,7 +2379,7 @@ class HybridQueryEngine {
|
|
|
2360
2379
|
this.columnCache.delete(key);
|
|
2361
2380
|
}
|
|
2362
2381
|
const db = this.getDb();
|
|
2363
|
-
const exists = await db.selectFrom("information_schema.columns").select(sql`1`.as("one")).where("table_name", "=", table).where("column_name", "=", column).executeTakeFirst();
|
|
2382
|
+
const exists = await db.selectFrom("information_schema.columns").select(sql`1`.as("one")).where(table.includes(".") ? sql`table_schema = ${table.split(".")[0]}` : sql`table_schema = current_schema()`).where("table_name", "=", table.split(".").pop()).where("column_name", "=", column).executeTakeFirst();
|
|
2364
2383
|
const present = !!exists;
|
|
2365
2384
|
if (present) this.columnCache.set(key, true);
|
|
2366
2385
|
else this.columnCache.delete(key);
|
|
@@ -2369,7 +2388,7 @@ class HybridQueryEngine {
|
|
|
2369
2388
|
async getBaseColumnsForEntity(entity) {
|
|
2370
2389
|
const db = this.getDb();
|
|
2371
2390
|
const table = resolveEntityTableName(this.em, entity);
|
|
2372
|
-
const rows = await db.selectFrom("information_schema.columns").select(["column_name", "data_type"]).where("table_name", "=", table).execute();
|
|
2391
|
+
const rows = await db.selectFrom("information_schema.columns").select(["column_name", "data_type"]).where(table.includes(".") ? sql`table_schema = ${table.split(".")[0]}` : sql`table_schema = current_schema()`).where("table_name", "=", table.split(".").pop()).execute();
|
|
2373
2392
|
const map = /* @__PURE__ */ new Map();
|
|
2374
2393
|
for (const r of rows) map.set(r.column_name, r.data_type);
|
|
2375
2394
|
return map;
|