fix(edp): resolve managed reader source scope

This commit is contained in:
Codex
2026-07-19 12:28:00 +03:00
parent 0611a88971
commit 95446bdc24
10 changed files with 273 additions and 19 deletions
@@ -187,6 +187,7 @@ export async function persistDataProductPublish(pool, batch, definition, {
}
export async function readDataProductSnapshot(pool, binding, definition, { limit = 5000 } = {}) {
const sourceConnectionId = readerSourceConnectionId(binding);
const client = await pool.connect();
try {
await client.query("begin isolation level repeatable read read only");
@@ -202,7 +203,7 @@ export async function readDataProductSnapshot(pool, binding, definition, { limit
where tenant_id = $1 and connection_id = $2 and provider_id = $3 and data_product_id = $4
order by source_id asc, semantic_type asc
limit $5`,
[binding.tenantId, binding.connectionId, binding.providerId, definition.id, limit + 1],
[binding.tenantId, sourceConnectionId, binding.providerId, definition.id, limit + 1],
);
if (rows.rowCount > limit) throw deliveryError("data_product_snapshot_limit_exceeded", 413);
await client.query("commit");
@@ -266,6 +267,7 @@ export function normalizeHistoryReadOptions(value, definition) {
}
export async function readDataProductHistory(pool, binding, definition, options) {
const sourceConnectionId = readerSourceConnectionId(binding);
const query = normalizeHistoryReadOptions(options, definition);
const cursor = query.cursor || {};
const rows = await pool.query(
@@ -302,7 +304,7 @@ export async function readDataProductHistory(pool, binding, definition, options)
order by query_bucket asc, source_id asc, semantic_type asc
limit $13`,
[
binding.tenantId, binding.connectionId, binding.providerId, definition.id,
binding.tenantId, sourceConnectionId, binding.providerId, definition.id,
query.from, query.to, query.sourceIds, query.resolutionMs,
Boolean(query.cursor), cursor.bucketStart || query.from, cursor.sourceId || "", cursor.semanticType || "",
query.limit + 1,
@@ -337,6 +339,7 @@ export async function readDataProductHistory(pool, binding, definition, options)
}
export async function readPatchEvents(pool, binding, definition, after, { limit = 100 } = {}) {
const sourceConnectionId = readerSourceConnectionId(binding);
const client = await pool.connect();
try {
await client.query("begin isolation level repeatable read read only");
@@ -351,7 +354,7 @@ export async function readPatchEvents(pool, binding, definition, after, { limit
where tenant_id = $1 and connection_id = $2 and provider_id = $3
and data_product_id = $4 and cursor > $5
order by cursor asc limit $6`,
[binding.tenantId, binding.connectionId, binding.providerId, definition.id, after.toString(), limit],
[binding.tenantId, sourceConnectionId, binding.providerId, definition.id, after.toString(), limit],
);
await client.query("commit");
return rows.rows.map((row) => ({
@@ -518,26 +521,40 @@ async function persistPatchChunks(client, batch, definition, batchId, chunks) {
async function currentCursor(db, scope, explicitProductId) {
const dataProductId = explicitProductId || scope.contract.dataProductId;
const connectionId = scope.source
? scope.source.connectionId
: readerSourceConnectionId(scope);
const result = await db.query(
`select current_cursor from external_data_plane_delivery_state
where tenant_id = $1 and connection_id = $2 and provider_id = $3 and data_product_id = $4`,
[scope.tenantId || scope.source.tenantId, scope.connectionId || scope.source.connectionId,
[scope.tenantId || scope.source.tenantId, connectionId,
scope.providerId || scope.source.providerId, dataProductId],
);
return result.rowCount ? BigInt(result.rows[0].current_cursor) : 0n;
}
async function patchFloor(db, binding, dataProductId) {
const sourceConnectionId = readerSourceConnectionId(binding);
const result = await db.query(
`select min(cursor) as floor from external_data_plane_patch_outbox
where tenant_id = $1 and connection_id = $2 and provider_id = $3 and data_product_id = $4`,
[binding.tenantId, binding.connectionId, binding.providerId, dataProductId],
[binding.tenantId, sourceConnectionId, binding.providerId, dataProductId],
);
if (result.rows[0]?.floor !== null && result.rows[0]?.floor !== undefined) return BigInt(result.rows[0].floor);
const current = await currentCursor(db, binding, dataProductId);
return current + 1n;
}
function readerSourceConnectionId(binding) {
const sourceConnectionId = String(binding?.sourceConnectionId || "").trim();
if (sourceConnectionId) return sourceConnectionId;
if (!binding?.bindingKey) {
const legacyConnectionId = String(binding?.connectionId || "").trim();
if (legacyConnectionId) return legacyConnectionId;
}
throw deliveryError("reader_source_scope_unresolved", 409);
}
function scopeValues(batch, tail) {
return [
batch.source.tenantId, batch.source.connectionId, batch.source.providerId,