Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions packages/drivers/driver-sql/src/sql-driver-class-head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

/**
* [#29520 codemod] The "Class head: dialect probes, logger, configuration"
* section of `SqlDriver`: the bodies of `logger`, `emissionClients`,
* `clientSpelling`, `insertReturnsStoredRows`, `aliasIdentifierSql`,
* `dateGranularityCapabilities`, `dateBucketSql`, `knexConfigFor`,
* `withConnectBound`, `withUtcSession`, `withMysqlCalendarDayAsText`,
* `withPostgresCalendarDayAsText`, `installQueryTiming`, `assertSchemaMutable`,
* `assertDialectEmits` moved out of `sql-driver.ts` whole by
* `scripts/codemod-split-sql-driver.mjs`.
* section of `SqlDriver`: the bodies of `logger`, `logDurabilityFailure`,
* `emissionClients`, `clientSpelling`, `insertReturnsStoredRows`,
* `aliasIdentifierSql`, `dateGranularityCapabilities`, `dateBucketSql`,
* `knexConfigFor`, `withConnectBound`, `withUtcSession`,
* `withMysqlCalendarDayAsText`, `withPostgresCalendarDayAsText`,
* `installQueryTiming`, `assertSchemaMutable`, `assertDialectEmits` moved out
* of `sql-driver.ts` whole by `scripts/codemod-split-sql-driver.mjs`.
*
* A MOVE, not a redesign. {@link ClassHeadFacet} is a stateless facet of `SqlDriver`
* (ruling 6624345766): each static method of it is the body of the `SqlDriver`
Expand Down Expand Up @@ -65,6 +65,7 @@ export interface ClassHeadHost {
readonly isPostgres: SqlDriverClass['isPostgres'];
readonly isSqlite: SqlDriverClass['isSqlite'];
readonly knex: SqlDriverClass['knex'];
readonly logger: SqlDriverClass['logger'];
openedEmptyInMemory: SqlDriverClass['openedEmptyInMemory'];
readonly schemaMode: SqlDriverClass['schemaMode'];
}
Expand All @@ -79,6 +80,12 @@ export class ClassHeadFacet {
};
}

/** The body of `SqlDriver#logDurabilityFailure`; the method's docblock there is its contract. */
static logDurabilityFailure(this: Pick<ClassHeadHost, 'logger'>, msg: string, meta?: any): void {
if (this.logger.error) this.logger.error(msg, meta);
else this.logger.warn(msg, meta);
}

/** The body of `SqlDriver#emissionClients`; the method's docblock there is its contract. */
static emissionClients(): string[] {
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
* fault module `sql-driver-read-exit-fault.ts` (#25521 cut 15) and the
* predicate-emitter module `sql-driver-predicate-emitters.ts` (#25521 cut 16)
* and the generated top-level leaf `sql-driver-top-level.ts` (#29520 codemod),
* and, since #30147, every other `sql-driver-*.ts` module beside it (a
* facet member is named as the `SqlDriver` method whose body it is),
* and lists every function that builds an `INVALID_FILTER` error. Only three may call
* `unsupportedFilterError` directly (the seam itself, the `'author'`
* re-issue, and the column refusal raised from a dialect error AFTER the
Expand All @@ -48,7 +50,7 @@
*/

import { describe, it, expect, beforeEach } from 'vitest';
import { readFileSync } from 'node:fs';
import { readdirSync, readFileSync } from 'node:fs';
import ts from 'typescript';
import { SqlDriver } from './index.js';
import { markFilterSubtreeProvenance, type FilterCondition } from '@objectstack/spec/data';
Expand Down Expand Up @@ -275,12 +277,12 @@ const DOORS: readonly Door[] = [
// ── Half 1: the enumeration ───────────────────────────────────────────────────

/**
* Every function in `sql-driver.ts`, `sql-driver-cross-field-comparison.ts`,
* `sql-driver-read-exit-fault.ts` and `sql-driver-predicate-emitters.ts` that builds an `INVALID_FILTER` error, by the route it takes. Read with the TypeScript parser, not a regex, so a call
* Every function in `sql-driver.ts` and every `sql-driver-*.ts` module beside it that builds an `INVALID_FILTER` error, by the route it takes. Read with the TypeScript parser, not a regex, so a call
* split across lines, an inline `throw`, or a builder written as a method or an
* arrow cannot slip past the count.
*/
function enumerateRefusalBuilders(): {
files: string[];
directCallers: Set<string>;
seamCallers: Set<string>;
codeWriters: Set<string>;
Expand All @@ -293,13 +295,14 @@ function enumerateRefusalBuilders(): {
// `$empty` refusals; the scan reads it too.
// [#29520 codemod] The top-level declarations ahead of the class moved to a
// generated leaf, with the refusal builders among them; the scan reads it too.
const sources = [
['sql-driver.ts', readFileSync(new URL('./sql-driver.ts', import.meta.url), 'utf8')],
['sql-driver-cross-field-comparison.ts', readFileSync(new URL('./sql-driver-cross-field-comparison.ts', import.meta.url), 'utf8')],
['sql-driver-read-exit-fault.ts', readFileSync(new URL('./sql-driver-read-exit-fault.ts', import.meta.url), 'utf8')],
['sql-driver-predicate-emitters.ts', readFileSync(new URL('./sql-driver-predicate-emitters.ts', import.meta.url), 'utf8')],
['sql-driver-top-level.ts', readFileSync(new URL('./sql-driver-top-level.ts', import.meta.url), 'utf8')],
] as const;
// [#30147] The codemod moves a class method's body into a facet class
// (`export class <X>Facet`) in any section module, so the scan reads every
// `sql-driver-*.ts` module, not a list a move can outrun, and names a facet's
// static `x` as `SqlDriver.x`: it is that method's body.
const files = readdirSync(new URL('.', import.meta.url))
.filter((f) => /^sql-driver(-[a-z0-9-]+)?\.ts$/.test(f) && !f.endsWith('.test.ts'))
.sort();
const sources = files.map((f) => [f, readFileSync(new URL(`./${f}`, import.meta.url), 'utf8')] as const);
const directCallers = new Set<string>();
const seamCallers = new Set<string>();
const codeWriters = new Set<string>();
Expand All @@ -310,7 +313,7 @@ function enumerateRefusalBuilders(): {
if (ts.isMethodDeclaration(at) && ts.isIdentifier(at.name)) {
const owner = at.parent;
const cls = ts.isClassDeclaration(owner) && owner.name ? owner.name.text : '?';
return `${cls}.${at.name.text}`;
return `${/^[A-Z][A-Za-z0-9]*Facet$/.test(cls) ? 'SqlDriver' : cls}.${at.name.text}`;
}
if ((ts.isArrowFunction(at) || ts.isFunctionExpression(at)) && ts.isVariableDeclaration(at.parent)) {
const decl = at.parent;
Expand Down Expand Up @@ -357,7 +360,7 @@ function enumerateRefusalBuilders(): {
ts.forEachChild(node, visit);
};
for (const [name, source] of sources) visit(ts.createSourceFile(name, source, ts.ScriptTarget.Latest, true));
return { directCallers, seamCallers, codeWriters };
return { files, directCallers, seamCallers, codeWriters };
}

describe('[#20039] the filter-compile refusal class is closed — enumeration', () => {
Expand All @@ -376,6 +379,12 @@ describe('[#20039] the filter-compile refusal class is closed — enumeration',
expect(found.seamCallers, builder).toContain(builder);
}
expect(found.directCallers).toContain('unresolvableFilterColumnError');
// [#30147] The modules the builders were found in before the scan read
// every module, and one facet module, are among those it reads.
for (const f of ['sql-driver.ts', 'sql-driver-cross-field-comparison.ts', 'sql-driver-read-exit-fault.ts',
'sql-driver-predicate-emitters.ts', 'sql-driver-top-level.ts', 'sql-driver-filter-helpers.ts']) {
expect(found.files, `the scan lost ${f}`).toContain(f);
}
});

it('only three functions build the envelope WITHOUT the seam, none of them on the compile path', () => {
Expand Down
34 changes: 27 additions & 7 deletions packages/drivers/driver-sql/src/sql-driver-filter-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
* `temporalFilterColumnSql`, `isJsonColumn`, `jsonColumnFieldClass`,
* `isNonTextColumn`, `applyTextOperatorOverNonTextColumn`,
* `declaredValueShape`, `assertOperatorAppliesToColumn`, `applyFilters`,
* `logWithheldFilterDiagnostic`, `withWithheldFilterLog`, `compileFilters`,
* `applyContainsLike`, `declaredFieldsFor`, `applyFalseConstant` moved out of
* `sql-driver.ts` whole by `scripts/codemod-split-sql-driver.mjs`.
* `resolveWithheldFilterRefusal`, `logWithheldFilterDiagnostic`,
* `withWithheldFilterLog`, `compileFilters`, `applyContainsLike`,
* `declaredFieldsFor`, `applyFalseConstant` moved out of `sql-driver.ts` whole
* by `scripts/codemod-split-sql-driver.mjs`.
*
* A MOVE, not a redesign. {@link FilterHelpersFacet} is a stateless facet of `SqlDriver`
* (ruling 6624345766): each static method of it is the body of the `SqlDriver`
Expand All @@ -38,16 +39,18 @@
* evaluation cycle. `index.ts` re-exports nothing from this module.
*/

import { resolveFilterSubtreeProvenance } from '@objectstack/spec/data';
import type { DriverOptions, ValueShapeFieldDef } from '@objectstack/spec/data';
import { JSON_COLUMN_INCOMPATIBLE_OPERATORS, findMisplacedReadableIds, presentAsNumber, refuseReadableIdsOnce, temporalStorageForm } from '@objectstack/core';
import type { JsonColumnFieldClass } from '@objectstack/core';
import { StorageNameMapping } from '@objectstack/spec/system';
import type { Knex } from 'knex';
import {
AUDIT_TIMESTAMP_COLUMNS, SQLITE_TIME_EXPR_REFS, WITHHELD_FILTER_LOGGED, assertCompilableComparand,
assertDefinedComparands, canonicalTimeOfDay, canonicalUtcDatetime, emptyFieldConstraintError,
filterArrayReachedDriverError, isEmptyFieldConstraint, jsonColumnOperatorError, mysqlDatetimeLiteral,
normalizeSqliteDatetimeOutput, presentAuditTimestampOutput, refusalSubtree, withheldFilterDiagnosticOf,
AUDIT_TIMESTAMP_COLUMNS, SQLITE_TIME_EXPR_REFS, WITHHELD_FILTER_AUTHOR_TEXT, WITHHELD_FILTER_LOGGED,
WITHHELD_FILTER_SUBTREE, assertCompilableComparand, assertDefinedComparands, canonicalTimeOfDay,
canonicalUtcDatetime, emptyFieldConstraintError, filterArrayReachedDriverError, isEmptyFieldConstraint,
jsonColumnOperatorError, mysqlDatetimeLiteral, normalizeSqliteDatetimeOutput, presentAuditTimestampOutput,
refusalSubtree, unsupportedFilterError, withheldFilterDiagnosticOf,
} from './sql-driver-top-level.js';
import type { ReadPresentationKind } from './sql-driver-top-level.js';
import type { SqlDriver } from './sql-driver.js';
Expand Down Expand Up @@ -79,6 +82,7 @@ export interface FilterHelpersHost {
readonly isSqlite: SqlDriver['isSqlite'];
jsonColumnFieldClass: SqlDriver['jsonColumnFieldClass'];
readonly jsonFields: SqlDriver['jsonFields'];
logWithheldFilterDiagnostic: SqlDriver['logWithheldFilterDiagnostic'];
readonly logger: SqlDriver['logger'];
readonly managedObjectFields: SqlDriver['managedObjectFields'];
readonly mediaFields: SqlDriver['mediaFields'];
Expand Down Expand Up @@ -430,6 +434,22 @@ export class FilterHelpersFacet {
}
}

/** The body of `SqlDriver#resolveWithheldFilterRefusal`; the method's docblock there is its contract. */
static resolveWithheldFilterRefusal(this: Pick<FilterHelpersHost, 'logWithheldFilterDiagnostic'>, err: unknown, rootFilter: unknown): unknown {
const diagnostic = withheldFilterDiagnosticOf(err);
if (diagnostic === null) return err;
const carrier = err as Record<symbol, unknown>;
const subtree = carrier[WITHHELD_FILTER_SUBTREE];
const provenance =
subtree === undefined ? null : resolveFilterSubtreeProvenance(rootFilter, subtree);
if (provenance === 'author') {
const authorText = carrier[WITHHELD_FILTER_AUTHOR_TEXT];
return unsupportedFilterError(typeof authorText === 'string' ? authorText : diagnostic);
}
this.logWithheldFilterDiagnostic(err);
return err;
}

/** The body of `SqlDriver#logWithheldFilterDiagnostic`; the method's docblock there is its contract. */
static logWithheldFilterDiagnostic(this: Pick<FilterHelpersHost, 'logger'>, err: unknown): void {
const diagnostic = withheldFilterDiagnosticOf(err);
Expand Down
Loading
Loading