use std::fmt;
use crate::{
DeltaSourceReport, LazyTableKind, LoadMode, MssqlDdlPlan, MssqlLifecyclePlan, MssqlSchemaPlan,
MssqlTargetTable, OutputStatus, PhaseTimingReport, PlannedMssqlOutput, ReportReasonCode,
RowCount, RunMode, SourceUsageStatus, ValidationStatus, WorkflowStatus,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MssqlDryRunOutputFieldReport {
index: u64,
name: String,
arrow_type: String,
nullable: bool,
}
impl MssqlDryRunOutputFieldReport {
pub(crate) fn from_mapping(mapping: &arrow_tiberius::SchemaMapping) -> Self {
Self {
index: crate::usize_to_u64_saturating(mapping.arrow().index()),
name: mapping.arrow().name().to_owned(),
arrow_type: mapping.arrow().data_type().to_string(),
nullable: mapping.arrow().nullable(),
}
}
#[must_use]
pub const fn index(&self) -> u64 {
self.index
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn arrow_type(&self) -> &str {
&self.arrow_type
}
#[must_use]
pub const fn nullable(&self) -> bool {
self.nullable
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MssqlDryRunSqlIdentityState {
Present,
Absent,
Unavailable,
}
impl MssqlDryRunSqlIdentityState {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Present => "present",
Self::Absent => "absent",
Self::Unavailable => "unavailable",
}
}
}
impl fmt::Display for MssqlDryRunSqlIdentityState {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MssqlDryRunSqlIdentityReport {
state: MssqlDryRunSqlIdentityState,
hash: Option<String>,
reason: Option<ReportReasonCode>,
}
impl MssqlDryRunSqlIdentityReport {
pub(crate) fn present(hash: String) -> Self {
Self {
state: MssqlDryRunSqlIdentityState::Present,
hash: Some(hash),
reason: None,
}
}
pub(crate) fn absent() -> Self {
Self {
state: MssqlDryRunSqlIdentityState::Absent,
hash: None,
reason: None,
}
}
pub(crate) fn unavailable(reason: ReportReasonCode) -> Self {
Self {
state: MssqlDryRunSqlIdentityState::Unavailable,
hash: None,
reason: Some(reason),
}
}
#[must_use]
pub const fn state(&self) -> MssqlDryRunSqlIdentityState {
self.state
}
#[must_use]
pub fn hash(&self) -> Option<&str> {
self.hash.as_deref()
}
#[must_use]
pub const fn reason(&self) -> Option<ReportReasonCode> {
self.reason
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MssqlDryRunOutputReport {
planned_output: PlannedMssqlOutput,
output_schema: Vec<MssqlDryRunOutputFieldReport>,
sql_identity: MssqlDryRunSqlIdentityReport,
source_usage_status: SourceUsageStatus,
used_source_names: Vec<String>,
output_row_count: RowCount,
output_row_count_reason: Option<ReportReasonCode>,
status: OutputStatus,
validation_status: ValidationStatus,
phase_timings: Vec<PhaseTimingReport>,
sql_server_contacted: bool,
row_production_started: bool,
table_lifecycle_started: bool,
bulk_writer_started: bool,
}
impl MssqlDryRunOutputReport {
pub(crate) fn new(
planned_output: PlannedMssqlOutput,
sql_identity: MssqlDryRunSqlIdentityReport,
source_usage_status: SourceUsageStatus,
used_source_names: Vec<String>,
phase_timings: Vec<PhaseTimingReport>,
) -> Self {
let output_schema = planned_output
.output_plan()
.schema_mappings()
.iter()
.map(MssqlDryRunOutputFieldReport::from_mapping)
.collect();
Self {
planned_output,
output_schema,
sql_identity,
source_usage_status,
used_source_names,
output_row_count: RowCount::unavailable(),
output_row_count_reason: Some(ReportReasonCode::NotExecuted),
status: OutputStatus::dry_run_planned(),
validation_status: ValidationStatus::skipped(ReportReasonCode::DryRun),
phase_timings,
sql_server_contacted: false,
row_production_started: false,
table_lifecycle_started: false,
bulk_writer_started: false,
}
}
#[must_use]
pub const fn planned_output(&self) -> &PlannedMssqlOutput {
&self.planned_output
}
#[must_use]
pub fn output_name(&self) -> &str {
self.planned_output.output_plan().output_name()
}
#[must_use]
pub const fn table_id(&self) -> u64 {
self.planned_output.table().id()
}
#[must_use]
pub const fn table_kind(&self) -> LazyTableKind {
self.planned_output.table().kind()
}
#[must_use]
pub fn table_name(&self) -> &str {
self.planned_output.table().name()
}
#[must_use]
pub fn output_schema(&self) -> &[MssqlDryRunOutputFieldReport] {
&self.output_schema
}
#[must_use]
pub fn target_table(&self) -> &MssqlTargetTable {
self.planned_output.output_plan().target_table()
}
#[must_use]
pub fn load_mode(&self) -> LoadMode {
self.planned_output.output_plan().load_mode()
}
#[must_use]
pub fn target_schema_plan(&self) -> &MssqlSchemaPlan {
self.planned_output.output_plan().schema_plan()
}
#[must_use]
pub fn target_ddl_plan(&self) -> &MssqlDdlPlan {
self.planned_output.output_plan().ddl_plan()
}
#[must_use]
pub fn target_lifecycle_plan(&self) -> &MssqlLifecyclePlan {
self.planned_output.output_plan().lifecycle_plan()
}
#[must_use]
pub const fn sql_identity(&self) -> &MssqlDryRunSqlIdentityReport {
&self.sql_identity
}
#[must_use]
pub const fn source_usage_status(&self) -> SourceUsageStatus {
self.source_usage_status
}
#[must_use]
pub fn used_source_names(&self) -> &[String] {
&self.used_source_names
}
#[must_use]
pub const fn output_row_count(&self) -> RowCount {
self.output_row_count
}
#[must_use]
pub const fn output_row_count_reason(&self) -> Option<ReportReasonCode> {
self.output_row_count_reason
}
#[must_use]
pub const fn status(&self) -> OutputStatus {
self.status
}
#[must_use]
pub const fn validation_status(&self) -> ValidationStatus {
self.validation_status
}
#[must_use]
pub fn phase_timings(&self) -> &[PhaseTimingReport] {
&self.phase_timings
}
#[must_use]
pub const fn run_mode(&self) -> RunMode {
RunMode::DryRun
}
#[must_use]
pub const fn sql_server_contacted(&self) -> bool {
self.sql_server_contacted
}
#[must_use]
pub const fn row_production_started(&self) -> bool {
self.row_production_started
}
#[must_use]
pub const fn table_lifecycle_started(&self) -> bool {
self.table_lifecycle_started
}
#[must_use]
pub const fn bulk_writer_started(&self) -> bool {
self.bulk_writer_started
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MssqlDryRunWorkflowReport {
outputs: Vec<MssqlDryRunOutputReport>,
sources: Vec<DeltaSourceReport>,
status: WorkflowStatus,
phase_timings: Vec<PhaseTimingReport>,
}
impl MssqlDryRunWorkflowReport {
pub(crate) fn new(
outputs: Vec<MssqlDryRunOutputReport>,
sources: Vec<DeltaSourceReport>,
) -> Self {
let status = if outputs.is_empty() {
WorkflowStatus::no_op(ReportReasonCode::NotExecuted)
} else {
WorkflowStatus::success()
};
Self {
outputs,
sources,
status,
phase_timings: Vec::new(),
}
}
pub(crate) fn with_phase_timings(mut self, phase_timings: Vec<PhaseTimingReport>) -> Self {
self.phase_timings = phase_timings;
self
}
#[must_use]
pub const fn run_mode(&self) -> RunMode {
RunMode::DryRun
}
#[must_use]
pub const fn status(&self) -> WorkflowStatus {
self.status
}
#[must_use]
pub fn len(&self) -> usize {
self.outputs.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.outputs.is_empty()
}
#[must_use]
pub fn outputs(&self) -> &[MssqlDryRunOutputReport] {
&self.outputs
}
#[must_use]
pub fn sources(&self) -> &[DeltaSourceReport] {
&self.sources
}
#[must_use]
pub fn phase_timings(&self) -> &[PhaseTimingReport] {
&self.phase_timings
}
#[must_use]
pub fn query_used_source_scan_metadata_exhausted(&self) -> bool {
let mut used_source_seen = false;
for source in &self.sources {
if source.usage_status() == SourceUsageStatus::Used {
used_source_seen = true;
if !source.scan_metadata_exhausted() {
return false;
}
}
}
used_source_seen
}
#[must_use]
pub fn sql_server_contacted(&self) -> bool {
self.outputs
.iter()
.any(MssqlDryRunOutputReport::sql_server_contacted)
}
#[must_use]
pub fn row_production_started(&self) -> bool {
self.outputs
.iter()
.any(MssqlDryRunOutputReport::row_production_started)
}
#[must_use]
pub fn table_lifecycle_started(&self) -> bool {
self.outputs
.iter()
.any(MssqlDryRunOutputReport::table_lifecycle_started)
}
#[must_use]
pub fn bulk_writer_started(&self) -> bool {
self.outputs
.iter()
.any(MssqlDryRunOutputReport::bulk_writer_started)
}
}
#[cfg(test)]
mod tests {
use super::MssqlDryRunSqlIdentityState;
#[test]
fn sql_identity_status_exposes_stable_codes() {
assert_eq!(MssqlDryRunSqlIdentityState::Present.as_str(), "present");
assert_eq!(MssqlDryRunSqlIdentityState::Absent.to_string(), "absent");
assert_eq!(
MssqlDryRunSqlIdentityState::Unavailable.as_str(),
"unavailable"
);
}
}