Skip to main content

arrow_tiberius/write/
policy.rs

1//! Write-path options and conversion policies.
2
3/// Planning options for Arrow-to-SQL Server conversion.
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
5pub struct PlanOptions {
6    /// SQL Server text target policy.
7    pub string_policy: StringPolicy,
8    /// SQL Server binary target policy.
9    pub binary_policy: BinaryPolicy,
10    /// Timezone-aware timestamp policy.
11    pub timezone_policy: TimezonePolicy,
12    /// Nanosecond timestamp precision policy.
13    pub nanosecond_policy: NanosecondPolicy,
14    /// Unsigned 64-bit integer policy.
15    pub uint64_policy: UInt64Policy,
16    /// Decimal policy shared by decimal widths.
17    pub decimal_policy: DecimalPolicy,
18    /// Decimal256-specific policy.
19    pub decimal256_policy: Decimal256Policy,
20    /// Floating-point policy.
21    pub float_policy: FloatPolicy,
22    /// Date64-specific policy.
23    pub date64_policy: Date64Policy,
24}
25
26/// String conversion policy.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
28pub enum StringPolicy {
29    /// Use `nvarchar(max)`.
30    #[default]
31    NVarCharMax,
32    /// Use bounded `nvarchar(n)`.
33    NVarChar(usize),
34    /// Infer bounded `nvarchar(n)` from observed values.
35    ObservedNVarChar,
36}
37
38/// Binary conversion policy.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
40pub enum BinaryPolicy {
41    /// Use `varbinary(max)`.
42    #[default]
43    VarBinaryMax,
44    /// Use bounded `varbinary(n)`.
45    VarBinary(usize),
46    /// Infer bounded `varbinary(n)` from observed values.
47    ObservedVarBinary,
48}
49
50/// Timezone-aware timestamp conversion policy.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
52pub enum TimezonePolicy {
53    /// Reject timezone-aware timestamps.
54    #[default]
55    Reject,
56    /// Target SQL Server `datetimeoffset`.
57    DateTimeOffset,
58    /// Normalize to UTC and target timezone-free `datetime2`.
59    NormalizeUtcDateTime2,
60}
61
62/// Nanosecond timestamp precision policy.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
64pub enum NanosecondPolicy {
65    /// Reject nanosecond timestamps not divisible by 100.
66    #[default]
67    RejectNon100ns,
68    /// Round to SQL Server 100ns precision.
69    RoundTo100ns,
70    /// Truncate to SQL Server 100ns precision.
71    TruncateTo100ns,
72}
73
74/// Unsigned 64-bit integer conversion policy.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
76pub enum UInt64Policy {
77    /// Reject `UInt64` columns.
78    #[default]
79    Reject,
80    /// Target SQL Server `decimal(20,0)`.
81    Decimal20_0,
82    /// Target `bigint` after checking values fit signed 64-bit range.
83    CheckedBigInt,
84}
85
86/// Decimal conversion policy.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
88pub enum DecimalPolicy {
89    /// Reject Arrow decimals with negative scale.
90    #[default]
91    RejectNegativeScale,
92    /// Normalize Arrow decimals with negative scale.
93    NormalizeNegativeScale,
94}
95
96/// Decimal256 conversion policy.
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
98pub enum Decimal256Policy {
99    /// Checked downcast when precision, scale, and value fit SQL Server decimal.
100    #[default]
101    CheckedDowncast,
102    /// Reject all `Decimal256` columns.
103    Reject,
104}
105
106/// Floating-point conversion policy.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
108pub enum FloatPolicy {
109    /// Reject NaN and infinity values.
110    #[default]
111    RejectNonFinite,
112}
113
114/// Date64 conversion policy.
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
116pub enum Date64Policy {
117    /// Reject `Date64` values that are not midnight dates.
118    #[default]
119    RejectNonMidnight,
120    /// Remap `Date64` to SQL Server `datetime2`.
121    TimestampDateTime2,
122}
123
124/// Write-time batch schema compatibility policy.
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
126pub enum SchemaCheck {
127    /// Require exact schema equality with the planned schema.
128    #[default]
129    Strict,
130}
131
132#[cfg(test)]
133mod tests {
134    use super::{
135        BinaryPolicy, Date64Policy, Decimal256Policy, DecimalPolicy, FloatPolicy, NanosecondPolicy,
136        PlanOptions, SchemaCheck, StringPolicy, TimezonePolicy, UInt64Policy,
137    };
138
139    #[test]
140    fn defaults_match_v0_1_policy_decisions() {
141        let options = PlanOptions::default();
142
143        assert_eq!(options.string_policy, StringPolicy::NVarCharMax);
144        assert_eq!(options.binary_policy, BinaryPolicy::VarBinaryMax);
145        assert_eq!(options.timezone_policy, TimezonePolicy::Reject);
146        assert_eq!(options.nanosecond_policy, NanosecondPolicy::RejectNon100ns);
147        assert_eq!(options.uint64_policy, UInt64Policy::Reject);
148        assert_eq!(options.decimal_policy, DecimalPolicy::RejectNegativeScale);
149        assert_eq!(options.decimal256_policy, Decimal256Policy::CheckedDowncast);
150        assert_eq!(options.float_policy, FloatPolicy::RejectNonFinite);
151        assert_eq!(options.date64_policy, Date64Policy::RejectNonMidnight);
152    }
153
154    #[test]
155    fn individual_policy_defaults_match_plan_options() {
156        assert_eq!(StringPolicy::default(), StringPolicy::NVarCharMax);
157        assert_eq!(BinaryPolicy::default(), BinaryPolicy::VarBinaryMax);
158        assert_eq!(TimezonePolicy::default(), TimezonePolicy::Reject);
159        assert_eq!(
160            NanosecondPolicy::default(),
161            NanosecondPolicy::RejectNon100ns
162        );
163        assert_eq!(UInt64Policy::default(), UInt64Policy::Reject);
164        assert_eq!(DecimalPolicy::default(), DecimalPolicy::RejectNegativeScale);
165        assert_eq!(
166            Decimal256Policy::default(),
167            Decimal256Policy::CheckedDowncast
168        );
169        assert_eq!(FloatPolicy::default(), FloatPolicy::RejectNonFinite);
170        assert_eq!(Date64Policy::default(), Date64Policy::RejectNonMidnight);
171        assert_eq!(SchemaCheck::default(), SchemaCheck::Strict);
172    }
173
174    #[test]
175    fn supports_explicit_non_default_policy_overrides() {
176        let options = PlanOptions {
177            string_policy: StringPolicy::NVarChar(128),
178            binary_policy: BinaryPolicy::VarBinary(256),
179            timezone_policy: TimezonePolicy::DateTimeOffset,
180            nanosecond_policy: NanosecondPolicy::RoundTo100ns,
181            uint64_policy: UInt64Policy::Decimal20_0,
182            decimal_policy: DecimalPolicy::NormalizeNegativeScale,
183            decimal256_policy: Decimal256Policy::Reject,
184            float_policy: FloatPolicy::RejectNonFinite,
185            date64_policy: Date64Policy::TimestampDateTime2,
186        };
187
188        assert_eq!(options.string_policy, StringPolicy::NVarChar(128));
189        assert_eq!(options.binary_policy, BinaryPolicy::VarBinary(256));
190        assert_eq!(options.timezone_policy, TimezonePolicy::DateTimeOffset);
191        assert_eq!(options.nanosecond_policy, NanosecondPolicy::RoundTo100ns);
192        assert_eq!(options.uint64_policy, UInt64Policy::Decimal20_0);
193        assert_eq!(
194            options.decimal_policy,
195            DecimalPolicy::NormalizeNegativeScale
196        );
197        assert_eq!(options.decimal256_policy, Decimal256Policy::Reject);
198        assert_eq!(options.float_policy, FloatPolicy::RejectNonFinite);
199        assert_eq!(options.date64_policy, Date64Policy::TimestampDateTime2);
200    }
201}