Skip to main content

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