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
1 change: 1 addition & 0 deletions src/sqlbuild/cli/commands/_helpers/compile/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ def _analyze_compile_project(
dialect=adapter.sql_analysis_dialect() or "generic",
selected_keys=selected_keys if select or exclude else None,
prepared_sql=early_lint.preparation,
expansion_reuse=early_lint.expansion_reuse,
)
_ = complete_compile_phase(
status=status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ def build_compile_inputs(
diagnostics=diagnostics,
external_sql_reference_resolver=external_sql_reference_resolver,
scope_index=declaration_scope.index,
declaration_scope=declaration_scope,
)


Expand Down
21 changes: 14 additions & 7 deletions src/sqlbuild/compiler/compile/main/sql_expansion_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def build_sql_expansion_context(
value_renderer: TypedSqlValueRenderer,
cli_vars: dict[str, object] | None = None,
discovered_inputs: DiscoveredProjectInputs | None = None,
declaration_scope: DeclarationScopeBuild | None = None,
) -> SqlExpansionContext:
"""Assemble project vars, macros and declarations for SQL expansion."""

Expand All @@ -53,13 +54,10 @@ def build_sql_expansion_context(
target_config=None,
cli_vars={} if cli_vars is None else cli_vars,
)
loaded_macros: dict[str, LoadedMacro] = load_project_macros(
effective_discovered_inputs.macro_files
)
declaration_scope: DeclarationScopeBuild = build_declaration_scope(
discovered_inputs=effective_discovered_inputs,
loaded_macros=loaded_macros,
scope: DeclarationScopeBuild = declaration_scope or _build_declaration_scope(
discovered_inputs=effective_discovered_inputs
)
loaded_macros: dict[str, LoadedMacro] = scope.loaded_macros
enums: dict[str, EnumDeclaration]
constants: dict[str, ConstantDeclaration]
enums, constants = build_public_declaration_indexes(
Expand Down Expand Up @@ -99,5 +97,14 @@ def build_sql_expansion_context(
declaration_override=None,
),
local_declarations=local_declarations,
declaration_resolver=declaration_scope.resolver,
declaration_resolver=scope.resolver,
)


def _build_declaration_scope(
*, discovered_inputs: DiscoveredProjectInputs
) -> DeclarationScopeBuild:
return build_declaration_scope(
discovered_inputs=discovered_inputs,
loaded_macros=load_project_macros(discovered_inputs.macro_files),
)
1 change: 1 addition & 0 deletions src/sqlbuild/compiler/compile/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ class CompileProjectInputs:
diagnostics: tuple[CompilerDiagnostic, ...] = field(default_factory=tuple)
external_sql_reference_resolver: ExternalSqlReferenceResolver | None = None
scope_index: ScopeIndex = field(default_factory=ScopeIndex)
declaration_scope: DeclarationScopeBuild | None = field(default=None, repr=False, compare=False)


@dataclass(frozen=True)
Expand Down
2 changes: 2 additions & 0 deletions src/sqlbuild/compiler/planner/_helpers/sql_tests/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def uncommented_matches_by_pattern(
) -> tuple[tuple[re.Match[str], ...], ...]:
"""Find matches for multiple patterns with one protected-region scan."""

if all(pattern.search(sql) is None for pattern in patterns):
return tuple(() for _pattern in patterns)
protected_ranges: tuple[tuple[int, int], ...] = tuple(
(match.start(), match.end()) for match in _SQL_NON_CODE_PATTERN.finditer(sql)
)
Expand Down
3 changes: 3 additions & 0 deletions src/sqlbuild/lint/_helpers/expansion.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from sqlbuild.compiler.compile.main.sql_expansion_context import build_sql_expansion_context
from sqlbuild.compiler.compile.models import (
CompiledSqlExpansion,
DeclarationScopeBuild,
ExpansionSpan,
SqlExpansionContext,
)
Expand Down Expand Up @@ -85,6 +86,7 @@ def build_lint_expansion_context(
project_dir: Path,
value_renderer: TypedSqlValueRenderer | None = None,
discovered_inputs: DiscoveredProjectInputs | None = None,
declaration_scope: DeclarationScopeBuild | None = None,
) -> SqlExpansionContext:
"""Build the expansion context, reporting compile failures as lint failures."""

Expand All @@ -106,6 +108,7 @@ def build_lint_expansion_context(
project_dir=project_dir,
discovered_inputs=effective_discovered_inputs,
value_renderer=effective_renderer,
declaration_scope=declaration_scope,
)
except (AdapterUserError, CompileInputError, DiscoveryError) as error:
raise ProjectCompileError(
Expand Down
15 changes: 12 additions & 3 deletions src/sqlbuild/lint/_helpers/project_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@


def collect_project_files(
*, project_dir: Path, selected_paths: frozenset[Path] | None = None
*,
project_dir: Path,
selected_paths: frozenset[Path] | None = None,
source_files: dict[Path, str] | None = None,
) -> dict[Path, str]:
"""Return all authored SQL files in the project keyed by absolute path."""

"""Return authored SQL files keyed by path, reusing contents a caller already read."""

if source_files is not None:
return {
file_path: contents
for file_path, contents in source_files.items()
if selected_paths is None or file_path.resolve() in selected_paths
}
files: dict[Path, str] = {}
directory_name: str
for directory_name in LINT_DIRECTORY_NAMES:
Expand Down
11 changes: 8 additions & 3 deletions src/sqlbuild/lint/main/build_expansion_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

from pathlib import Path

from sqlbuild.compiler.compile.models import SqlExpansionContext
from sqlbuild.compiler.compile.models import DeclarationScopeBuild, SqlExpansionContext
from sqlbuild.compiler.discovery.models import DiscoveredProjectInputs
from sqlbuild.lint._helpers.expansion import build_lint_expansion_context


def build_expansion_context(
*, project_dir: Path, discovered_inputs: DiscoveredProjectInputs
*,
project_dir: Path,
discovered_inputs: DiscoveredProjectInputs,
declaration_scope: DeclarationScopeBuild | None = None,
) -> SqlExpansionContext:
return build_lint_expansion_context(
project_dir=project_dir, discovered_inputs=discovered_inputs
project_dir=project_dir,
discovered_inputs=discovered_inputs,
declaration_scope=declaration_scope,
)
3 changes: 2 additions & 1 deletion src/sqlbuild/lint/main/run_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ def run_lint(
dynamic_output_paths: frozenset[Path] = frozenset(),
compiled_expansions: dict[Path, CompiledSqlExpansion] | None = None,
expansion_context: SqlExpansionContext | None = None,
source_files: dict[Path, str] | None = None,
) -> LintRunResult:
"""Lint all DSL files in the project without modifying anything."""

files: dict[Path, str] = collect_project_files(
project_dir=project_dir, selected_paths=selected_paths
project_dir=project_dir, selected_paths=selected_paths, source_files=source_files
)
violations: list[LintViolation] = []
bodies: list[LintBody] = []
Expand Down
45 changes: 42 additions & 3 deletions src/sqlbuild/rule_engine/_helpers/run/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
CompiledProject,
CompiledSqlExpansion,
CompileProjectInputs,
DeclarationScopeBuild,
SqlExpansionContext,
)
from sqlbuild.compiler.compile.types import CompiledResourceType
Expand Down Expand Up @@ -47,6 +48,7 @@
RulesConfig,
RulesResult,
RulesRunResult,
SqlExpansionReuse,
)

_SQL_RULE_CACHE_VERSION: str = "sql-rules-v2"
Expand All @@ -70,6 +72,7 @@ def evaluate_rules(
dialect: str,
selected_keys: frozenset[CompiledObjectKey] | None = None,
prepared_sql: PreparedSqlLint | None = None,
expansion_reuse: SqlExpansionReuse | None = None,
) -> RulesRunResult:
"""Evaluate independent rule phases concurrently, then finalize their combined findings."""
effective_config: RulesConfig = resolve_rule_ignore_selectors(
Expand Down Expand Up @@ -123,6 +126,7 @@ def evaluate_rules(
selected_model_paths=model_paths,
cache_enabled=effective_config.cache.enabled,
prepared_sql=prepared_sql,
expansion_reuse=expansion_reuse,
)
sql_ms: int = round((time.monotonic() - sql_started) * 1000)
result: RulesResult = native_result.result()
Expand Down Expand Up @@ -272,6 +276,8 @@ def _run_prepared_lint(
compiled_expansions: dict[Path, CompiledSqlExpansion],
dynamic_output_paths: frozenset[Path],
prepared_sql: PreparedSqlLint | None,
expansion_reuse: SqlExpansionReuse | None,
source_files: dict[Path, str] | None,
) -> LintRunResult:
if prepared_sql is None or set(prepared_sql.codes) != set(config.enabled_native_rules or ()):
return run_lint(
Expand All @@ -281,6 +287,16 @@ def _run_prepared_lint(
discovered_inputs=discovered_inputs,
compiled_expansions=compiled_expansions,
dynamic_output_paths=dynamic_output_paths,
expansion_context=(
_lint_expansion_context(
project_dir=project_dir,
discovered_inputs=discovered_inputs,
expansion_reuse=expansion_reuse,
)
if config.native_enabled
else None
),
source_files=source_files,
)
completed: PreparedSqlLintResult = prepared_sql.future.result()
prepared: LintRunResult = completed.result
Expand Down Expand Up @@ -314,6 +330,7 @@ def _run_sql_rules(
selected_model_paths: frozenset[str] | None,
cache_enabled: bool,
prepared_sql: PreparedSqlLint | None = None,
expansion_reuse: SqlExpansionReuse | None = None,
) -> _SqlRulesEvaluation:
codes: tuple[str, ...] = tuple(rule.code for rule in rules if rule.code.startswith("SQBRSQL"))
if not codes:
Expand All @@ -340,13 +357,13 @@ def _run_sql_rules(
findings.extend(cached)
selected_paths: set[Path] = {project_dir / path for path in misses}
file_identities: dict[str, str] = {}
project_files: dict[Path, str] | None = None
if selected_model_paths is None:
model_paths: frozenset[Path] = frozenset(project_dir / path for path in models_by_path)
project_files = collect_project_files(project_dir=project_dir, selected_paths=None)
file_path: Path
contents: str
for file_path, contents in collect_project_files(
project_dir=project_dir, selected_paths=None
).items():
for file_path, contents in project_files.items():
if file_path in model_paths:
continue
relative_path: str = file_path.relative_to(project_dir).as_posix()
Expand Down Expand Up @@ -385,6 +402,8 @@ def _run_sql_rules(
and model.dynamic_column_contract.output_proven
),
prepared_sql=prepared_sql,
expansion_reuse=expansion_reuse,
source_files=project_files,
)
if selected_paths
else None
Expand Down Expand Up @@ -419,6 +438,26 @@ def _run_sql_rules(
)


def _lint_expansion_context(
*,
project_dir: Path,
discovered_inputs: DiscoveredProjectInputs,
expansion_reuse: SqlExpansionReuse | None,
) -> SqlExpansionContext:
"""Build lint expansion, reusing the compiler's scope only for the same discovery."""

declaration_scope: DeclarationScopeBuild | None = (
expansion_reuse.declaration_scope
if expansion_reuse is not None and expansion_reuse.discovered_inputs is discovered_inputs
else None
)
return build_expansion_context(
project_dir=project_dir,
discovered_inputs=discovered_inputs,
declaration_scope=declaration_scope,
)


def _lint_finding(*, violation: LintViolation, project_dir: Path) -> Finding:
path: Path = violation.file_path
if path.is_absolute() and path.is_relative_to(project_dir.resolve()):
Expand Down
11 changes: 10 additions & 1 deletion src/sqlbuild/rule_engine/classes/early_sql_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@

from sqlbuild.compiler.compile.models import CompileProjectInputs
from sqlbuild.rule_engine.main._prepare_sql_rules import prepare_sql_rules
from sqlbuild.rule_engine.models import PreparedSqlLint
from sqlbuild.rule_engine.models import PreparedSqlLint, SqlExpansionReuse


class EarlySqlLint:
def __init__(self, *, enabled: bool) -> None:
self.enabled: bool = enabled
self.preparation: PreparedSqlLint | None = None
self.expansion_reuse: SqlExpansionReuse | None = None
self.executor: ThreadPoolExecutor = ThreadPoolExecutor(
max_workers=1, thread_name_prefix="sqlbuild-lint"
)
Expand All @@ -34,3 +35,11 @@ def start(self, *, inputs: CompileProjectInputs, dialect: str) -> None:
self.preparation = prepare_sql_rules(
inputs=inputs, executor=self.executor, dialect=dialect
)
self.expansion_reuse = (
None
if inputs.declaration_scope is None
else SqlExpansionReuse(
discovered_inputs=inputs.discovered_inputs,
declaration_scope=inputs.declaration_scope,
)
)
9 changes: 8 additions & 1 deletion src/sqlbuild/rule_engine/main/run_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
from sqlbuild.compiler.discovery.models import DiscoveredProjectInputs
from sqlbuild.compiler.pipeline.models import ProjectGraph
from sqlbuild.rule_engine._helpers.run.rules import evaluate_rules
from sqlbuild.rule_engine.models import PreparedSqlLint, RulesConfig, RulesRunResult
from sqlbuild.rule_engine.models import (
PreparedSqlLint,
RulesConfig,
RulesRunResult,
SqlExpansionReuse,
)


def run_rules(
Expand All @@ -18,6 +23,7 @@ def run_rules(
dialect: str,
selected_keys: frozenset[CompiledObjectKey] | None = None,
prepared_sql: PreparedSqlLint | None = None,
expansion_reuse: SqlExpansionReuse | None = None,
) -> RulesRunResult:
"""Run selected native built-ins before selected custom Python rules."""

Expand All @@ -29,4 +35,5 @@ def run_rules(
dialect=dialect,
selected_keys=selected_keys,
prepared_sql=prepared_sql,
expansion_reuse=expansion_reuse,
)
11 changes: 10 additions & 1 deletion src/sqlbuild/rule_engine/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from pathlib import Path
from typing import Any

from sqlbuild.compiler.compile.models import SqlExpansionContext
from sqlbuild.compiler.compile.models import DeclarationScopeBuild, SqlExpansionContext
from sqlbuild.compiler.discovery.models import DiscoveredProjectInputs
from sqlbuild.lint.models import LintRunResult
from sqlbuild.rule_engine.types import RuleCheck, RuleOptionValue, RuleSubject

Expand Down Expand Up @@ -314,6 +315,14 @@ class PreparedSqlLintResult:
context: SqlExpansionContext


@dataclass(frozen=True)
class SqlExpansionReuse:
"""Compiler declaration scope for post-assembly SQL checks; assembly still runs its macros."""

discovered_inputs: DiscoveredProjectInputs
declaration_scope: DeclarationScopeBuild


@dataclass(frozen=True)
class PreparedSqlLint:
"""Process-local SQL checks started after compiler expansion has completed."""
Expand Down
Loading
Loading