ddl-manager
Version:
store postgres procedures and triggers in files
166 lines (161 loc) • 6.62 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheScanner = void 0;
const ast_1 = require("../ast");
class CacheScanner {
constructor(driver, database, graph) {
this.driver = driver;
this.database = database;
this.graph = graph;
}
async scan(params = {}) {
let allCacheColumns = this.graph.findCacheColumnsForTablesOrColumns(params.concreteTables);
const brokenColumns = [];
for (const column of allCacheColumns) {
const hasWrongValues = await this.tryScanColumnOnWrongValues(params, column);
if (hasWrongValues) {
brokenColumns.push(column);
}
}
if (params.onFinish) {
await params.onFinish();
}
return brokenColumns;
}
async tryScanColumnOnWrongValues(params, column) {
const timeStart = new Date();
if (params.onStartScanColumn) {
await params.onStartScanColumn(column.toString());
}
try {
const wrongExample = await this.tryFindWrongRowExampleForColumn(params, column);
if (params.onScanColumn) {
const timeEnd = new Date();
await params.onScanColumn({
column: column.getId(),
hasWrongValues: !!wrongExample,
wrongExample,
time: {
start: timeStart,
end: timeEnd,
duration: +timeEnd - +timeStart
}
});
}
return wrongExample;
}
catch (error) {
if (params.onScanError) {
const timeEnd = new Date();
await params.onScanError({
column: column.toString(),
error: error,
time: {
start: timeStart,
end: timeEnd,
duration: +timeEnd - +timeStart
}
});
}
}
}
async tryFindWrongRowExampleForColumn(params, column) {
var _a;
const columnRef = `${column.for.getIdentifier()}.${column.name}`;
let details = "null::json";
if (column.select.from.length === 1) {
const selectSql = column.select.toString();
const fromAlias = column.select.getFromTable().getIdentifier();
const sourceRowJson = `row_to_json(${fromAlias}.*)`;
if (this.database.aggregators.some(aggName => selectSql.includes(aggName + "("))) {
details = `array_agg(${sourceRowJson})`;
}
else if (column.select.orderBy) {
details = `(${column.select.clone({
columns: [new ast_1.SelectColumn({
expression: ast_1.Expression.unknown(`array_agg(${sourceRowJson})`),
name: "source_row"
})],
orderBy: undefined,
limit: undefined
})})`;
}
else {
details = `ARRAY[${sourceRowJson}]`;
}
}
const selectExpected = column.select.clone({
columns: [
...column.select.columns,
new ast_1.SelectColumn({
expression: ast_1.Expression.unknown(details),
name: "__cache_source_row_details__"
})
]
});
let whereBroken = `${columnRef} is distinct from tmp.${column.name}`;
const expression = column.getColumnExpression();
if (expression.isFuncCall()) {
const [call] = expression.getFuncCalls();
if (call.name === "array_agg") {
whereBroken = `
${columnRef} is distinct from tmp.${column.name} and
not(${columnRef} @> tmp.${column.name} and
${columnRef} <@ tmp.${column.name})
`;
}
if (call.name === "sum") {
whereBroken = `coalesce(${columnRef}, 0) is distinct from coalesce(tmp.${column.name}, 0)`;
}
if (call.name === "string_agg" && !call.orderBy) {
const actual = `string_to_array(coalesce(${columnRef}, ''), ${call.args[1]})`;
const expected = `string_to_array(coalesce(tmp.${column.name}, ''), ${call.args[1]})`;
whereBroken = `
${actual} is distinct from ${expected} and
not(${actual} @> ${expected} and
${actual} <@ ${expected})
`;
}
}
if (column.name == ast_1.Cache.generateJsonHelperColumnName(column.cache.name)) {
whereBroken = `coalesce(${columnRef}, '{}'::jsonb) is distinct from coalesce(tmp.${column.name}, '{}'::jsonb)`;
}
const selectHasBroken = `
select
${columnRef} as "actual",
tmp.${column.name} as "expected",
'${column.getTableId()}' as "table",
row_to_json(${column.for.getIdentifier()}.*) as "cacheRow",
tmp.__cache_source_row_details__ as "sourceRows"
from ${column.for}
left join lateral (
${selectExpected.toSQL()}
) as tmp on true
where
${whereBroken}
order by ${column.for.getIdentifier()}.id
limit 1
`;
const { rows } = await this.driver.queryWithTimeout(selectHasBroken, (_a = params.timeout) !== null && _a !== void 0 ? _a : 0);
const wrongExample = rows[0];
if (wrongExample) {
wrongExample.table = column.getTableId();
wrongExample.selectExpectedForThatRow = `
select
${column.for.getIdentifier()}.id,
${columnRef} as "actual",
tmp.${column.name} as "expected"
from ${column.for}
left join lateral (
${selectExpected.toSQL()}
) as tmp on true
where
${column.for.getIdentifier()}.id = ${wrongExample.cacheRow.id} and
${columnRef} is distinct from tmp.${column.name}
`;
}
return wrongExample;
}
}
exports.CacheScanner = CacheScanner;
//# sourceMappingURL=CacheScanner.js.map