123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- # Copyright Materialize, Inc. and contributors. All rights reserved.
- #
- # Use of this software is governed by the Business Source License
- # included in the LICENSE file at the root of this repository.
- #
- # As of the Change Date specified in that file, in accordance with
- # the Business Source License, use of this software will be governed
- # by the Apache License, Version 2.0.
- from enum import Enum
- from typing import Any
- from materialize.output_consistency.execution.evaluation_strategy import (
- EvaluationStrategy,
- EvaluationStrategyKey,
- )
- from materialize.output_consistency.execution.sql_dialect_adjuster import (
- SqlDialectAdjuster,
- )
- from materialize.output_consistency.expression.expression import Expression
- from materialize.output_consistency.query.query_result import QueryExecution
- class ValidationErrorType(Enum):
- SUCCESS_MISMATCH = 1
- """Different outcome (success vs. error)"""
- ROW_COUNT_MISMATCH = 2
- """Different number of rows"""
- CONTENT_TYPE_MISMATCH = 3
- """Different data types in successful queries"""
- CONTENT_MISMATCH = 4
- """Different data in successful queries"""
- ERROR_MISMATCH = 5
- """Different error messages"""
- EXPLAIN_PLAN_MISMATCH = 6
- """Different explain plans"""
- def __str__(self) -> str:
- return self.name
- class ValidationMessage:
- """Either a `ValidationRemark`, `ValidationWarning`, or `ValidationError`"""
- def __init__(
- self,
- message: str,
- description: str | None = None,
- ):
- self.message = message
- self.description = description
- class ValidationRemark(ValidationMessage):
- def __init__(
- self,
- message: str,
- description: str | None = None,
- sql: str | None = None,
- ):
- super().__init__(message, description)
- self.sql = sql
- def __str__(self) -> str:
- remark_desc = f" ({self.description})" if self.description else ""
- query_desc = f"\n Query: {self.sql}" if self.sql else ""
- return f"{self.message}{remark_desc}{query_desc}"
- class ValidationWarning(ValidationMessage):
- def __init__(
- self,
- message: str,
- description: str | None = None,
- strategy: EvaluationStrategy | None = None,
- sql: str | None = None,
- ):
- super().__init__(message, description)
- self.strategy = strategy
- self.sql = sql
- def __str__(self) -> str:
- warning_desc = f": {self.description}" if self.description else ""
- strategy_desc = f" with strategy '{self.strategy}'" if self.strategy else ""
- query_desc = f"\n Query: {self.sql}" if self.sql else ""
- return f"{self.message}{strategy_desc}{warning_desc}{query_desc}"
- class ValidationErrorDetails:
- def __init__(
- self,
- strategy: EvaluationStrategy,
- value: Any,
- sql: str | None = None,
- sql_error: str | None = None,
- ):
- self.strategy = strategy
- self.value = value
- self.sql = sql
- self.sql_error = sql_error
- class ValidationError(ValidationMessage):
- def __init__(
- self,
- query_execution: QueryExecution,
- error_type: ValidationErrorType,
- message: str,
- details1: ValidationErrorDetails,
- details2: ValidationErrorDetails,
- description: str | None = None,
- col_index: int | None = None,
- concerned_expression: Expression | None = None,
- location: str | None = None,
- ):
- super().__init__(message, description)
- self.query_execution = query_execution
- self.error_type = error_type
- self.details1 = details1
- self.details2 = details2
- self.col_index = col_index
- if concerned_expression is not None:
- self.concerned_expression_str = concerned_expression.to_sql(
- SqlDialectAdjuster(), query_execution.query_template.uses_join(), True
- )
- self.concerned_expression_hash = concerned_expression.hash()
- else:
- self.concerned_expression_str = None
- self.concerned_expression_hash = None
- self.location = location
- def get_details_by_strategy_key(
- self,
- ) -> dict[EvaluationStrategyKey, ValidationErrorDetails]:
- return {
- details.strategy.identifier: details
- for details in [self.details1, self.details2]
- }
- def __str__(self) -> str:
- error_desc = f" ({self.description})" if self.description else ""
- location_desc = f" at {self.location}" if self.location is not None else ""
- expression_desc = (
- f"\nExpression: {self.concerned_expression_str}"
- if self.concerned_expression_str is not None
- else ""
- )
- strategy1_desc = f" ({self.details1.strategy})"
- strategy2_desc = f" ({self.details2.strategy})"
- value_and_strategy_desc = (
- f"\n Value 1{strategy1_desc}: '{self.details1.value}' (type: {type(self.details1.value)})"
- f"\n Value 2{strategy2_desc}: '{self.details2.value}' (type: {type(self.details2.value)})"
- )
- if self.error_type == ValidationErrorType.SUCCESS_MISMATCH:
- if self.details1.sql_error is not None:
- value_and_strategy_desc = value_and_strategy_desc + (
- f"\n Error 1: '{self.details1.sql_error}'"
- )
- if self.details2.sql_error is not None:
- value_and_strategy_desc = value_and_strategy_desc + (
- f"\n Error 2: '{self.details2.sql_error}'"
- )
- sql_desc = f"\n Query 1: {self.details1.sql}\n Query 2: {self.details2.sql}"
- expression_hash = (
- f"\n Expression hash: {self.concerned_expression_hash}"
- if self.concerned_expression_hash is not None
- else ""
- )
- return f"{self.error_type}: {self.message}{location_desc}{error_desc}.{expression_desc}{value_and_strategy_desc}{sql_desc}{expression_hash}"
|