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