perf(transform): reuse compiled schema regexes and skip schemas absent from the content - #341
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| const combinedPattern = new RegExp( | ||
| const combinedPattern = cachedRegExp( | ||
| `(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)|` + | ||
| `(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` + |
There was a problem hiding this comment.
Why are we not using ASTs here, and why are we using regular expressions?
There was a problem hiding this comment.
Fair question — this PR doesn't introduce the regex approach, it only stops recompiling the same patterns, but the "why" is worth pinning down per call site:
validateNoUntransformedSchemas specifically cannot use the AST, because it's the safety net for the AST pass having missed a node. It runs on the deparsed output and asks "did any old schema name survive?" — checking the AST would only re-ask the question the AST pass already answered wrong, so it would pass exactly when it most needs to fail. Its own error message says as much ("indicates a missing visitor handler in create_sql_visitor").
The other three are text the SQL AST genuinely doesn't reach:
transformComments— pgpm-- Deploy:/-- requires:header paths; not SQL at all.transformVerifyCalls—verify_function('schema.fn'); the schema name lives inside a string literal, so to the parser it's opaque text, not a qualified name.transformJsonStringValues— same, schema names inside JSON string values.
So the regex isn't standing in for AST work here; it covers the non-AST residue plus a post-hoc assertion. Where it would be wrong is regex-rewriting actual SQL identifiers — that stays createSqlVisitor's job.
Happy to go further separately if you want: transformVerifyCalls could parse the literal's contents (known-shape qualified name) instead of pattern-matching, and the leftover check could be narrowed to string/comment tokens via the lexer rather than scanning the whole file.
Summary
The string-level passes in
transform.ts(transformSchemaRefsInString,transformVerifyCalls,transformJsonStringValues,transformComments,validateNoUntransformedSchemas) each rebuild aRegExpper (file × schema) and then scan the whole file with it — even though the pattern depends only on the schema name, and the schema usually does not appear in the file at all. On a real workload (transpiling a 24.5k-change pgpm module across ~40 schemas) that is ~1M identical compiles plus ~1M full-content scans; the CPU profile is dominated byescapeRegexp/ regex compilation, not by parsing.Two changes, no behavior change:
cachedRegExp(source, flags)/cachedEscapeRegexp(str)memoize by pattern text, so each distinct pattern compiles once per process. The cached instances are stateful (gcarrieslastIndex), which is safe here because every call site usesreplace/match(both reset it) or already resetslastIndexitself, as it had to with the throwaway instances.content.includes(schema)is a necessary condition. Where the pattern is case-insensitive (transformVerifyCalls,validateNoUntransformedSchemas) the guard is done on a lowercased copy, andtransformVerifyCallsrefreshes that copy after a replacement so a name introduced by an earlier substitution is still seen.Measured
Transpiling
application/constructive(24.5k changes, ~40 schemas) in the constructive-db codegen loop, same machine:Output is byte-identical across all 24.5k generated files.
@pgsql/transformtests: 236 passed, 4 snapshots; lint clean.Link to Devin session: https://app.devin.ai/sessions/b266e1e72e0a4182a53cdd048b59f740
Requested by: @pyramation