Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions crates/dbt-adapter/src/column/column_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl ColumnBuilder {
Redshift => Ok(Self::build_redshift(field, type_ops)),
Postgres | Salesforce | DuckDB | Alt => Ok(Self::build_postgres_like(field, type_ops)),
Fabric => Ok(Self::build_fabric(field, type_ops)),
SqlServer => Ok(Self::build_sqlserver(field, type_ops)),
ClickHouse => Self::build_clickhouse(field, type_ops),
Exasol => Ok(Self::build_postgres_like(field, type_ops)),
Starburst => todo!("Starburst"),
Expand Down Expand Up @@ -132,6 +133,14 @@ impl ColumnBuilder {
numeric_precision,
numeric_scale,
),
SqlServer => Column::new(
SqlServer,
name,
dtype,
char_size,
numeric_precision,
numeric_scale,
),
}
}

Expand Down Expand Up @@ -266,6 +275,39 @@ impl ColumnBuilder {
)
}

fn build_sqlserver(field: &FieldRef, type_ops: &dyn TypeOps) -> Column {
use AdapterType::SqlServer;
let data_type = field.data_type();
let char_size = sql_types::var_size(SqlServer, data_type);
let (numeric_precision, numeric_scale) = {
let precision_scale = sql_types::numeric_precision_scale(SqlServer, data_type)
.ok()
.flatten();
match precision_scale {
Some((p, Some(s))) => (Some(p), Some(s)),
Some((p, None)) => (Some(p), None),
None => (None, None),
}
};

let mut type_name_or_formatted = String::new();
if type_ops
.format_arrow_type_as_sql(data_type, &mut type_name_or_formatted)
.is_err()
{
type_name_or_formatted = data_type.to_string();
}

Column::new(
SqlServer,
field.name().to_string(),
type_name_or_formatted,
char_size.map(|p| p as u32),
numeric_precision.map(|p| p as u64),
numeric_scale.map(|s| s as u64),
)
}

fn build_snowflake(field: &FieldRef, type_ops: &dyn TypeOps) -> Column {
use AdapterType::Snowflake;
use sql_types::snowflake::*;
Expand Down
157 changes: 145 additions & 12 deletions crates/dbt-adapter/src/sql_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ impl TypeOps for DefaultTypeOps {
match adapter_type {
Postgres | Salesforce => postgres::try_format_type(data_type, true, out),
Fabric => fabric::try_format_type(data_type, true, out),
SqlServer => sqlserver::try_format_type(data_type, true, out),
ClickHouse => clickhouse::try_format_type(data_type, true, out),
_ => {
// sdf-specific distinct types are encoded as FixedSizeList(field, 1).
Expand Down Expand Up @@ -339,7 +340,7 @@ impl DefaultTypeOps {
(SqlType::Real | SqlType::HalfFloat, _) => match adapter_type {
Bigquery => "float64",
Databricks => "float",
Fabric => "real",
Fabric | SqlServer => "real",
_ => "float8",
},

Expand All @@ -350,7 +351,7 @@ impl DefaultTypeOps {
// Divergence: upstream has an implicit narrowing bug we fix
// see https://github.com/microsoft/dbt-fabric/blob/0de219082282724a789b0d1b18509d39899da8e1/dbt/adapters/fabric/fabric_adapter.py#L117
// https://learn.microsoft.com/en-us/sql/t-sql/data-types/float-and-real-transact-sql?view=fabric&preserve-view=true
Fabric => "float",
Fabric | SqlServer => "float",
_ => "float8",
},

Expand All @@ -369,6 +370,8 @@ impl DefaultTypeOps {
(Fabric, _) => "float",
(Databricks, 1..) => "double",
(Databricks, ..=0) => "bigint",
(SqlServer, 1..) => "float",
(SqlServer, ..=0) => "int",
(_, 1..) => "float8",
(_, ..=0) => "integer",
},
Expand All @@ -380,15 +383,15 @@ impl DefaultTypeOps {
// ## convert_boolean_type()
(SqlType::Boolean, _) => match adapter_type {
Bigquery => "bool",
Fabric => "bit",
Fabric | SqlServer => "bit",
_ => "boolean",
},

// ## convert_datetime_type()
(SqlType::Timestamp { .. }, _) => match adapter_type {
Bigquery => "datetime",
Databricks => "timestamp",
Fabric => "datetime2(6)",
Fabric | SqlServer => "datetime2(6)",
_ => "timestamp without time zone",
},

Expand All @@ -398,7 +401,7 @@ impl DefaultTypeOps {
// ## convert_time_type()
// Upstream maps Duration and Interval Arrow types to time.
(SqlType::Interval(_) | SqlType::Time { .. }, _) => match adapter_type {
Fabric => "time(6)",
Fabric | SqlServer => "time(6)",
_ => "time",
},

Expand All @@ -411,7 +414,7 @@ impl DefaultTypeOps {
// but that information isn't available here
// - N = 64 if column is empty
// - N = max(16, max_length) if column is not empty
Fabric => "varchar",
Fabric | SqlServer => "varchar",
_ => "text",
}
}
Expand Down Expand Up @@ -544,6 +547,7 @@ pub const fn get_field_sql_type_metadata_key(adapter_type: AdapterType) -> &'sta
AdapterType::DuckDB => todo!(),
AdapterType::Alt => todo!(),
AdapterType::Fabric => FABRIC_METADATA_SQL_TYPE_KEY,
AdapterType::SqlServer => todo!(),
AdapterType::ClickHouse => CLICKHOUSE_METADATA_SQL_TYPE_KEY,
AdapterType::Exasol => "DATA_TYPE",
AdapterType::Starburst => todo!(),
Expand Down Expand Up @@ -598,8 +602,8 @@ impl SdfSchemaBuilder {
metadata.get(ARROW_FIELD_COMMENT_METADATA_KEY)
}
// no evidence that these drivers store comments in metadata, but just in case
Postgres | Snowflake | Salesforce | Fabric | ClickHouse | Exasol | Starburst
| Athena | Trino | Dremio | Oracle | Datafusion => {
Postgres | Snowflake | Salesforce | Fabric | SqlServer | ClickHouse | Exasol
| Starburst | Athena | Trino | Dremio | Oracle | Datafusion => {
metadata.get(ARROW_FIELD_COMMENT_METADATA_KEY)
}
};
Expand Down Expand Up @@ -635,8 +639,8 @@ impl SdfSchemaBuilder {
pub fn build_sdf_schema(self, type_ops: &dyn TypeOps) -> AdapterResult<SdfSchema> {
use AdapterType::*;
match self.adapter_type {
Bigquery | Redshift | Databricks | Spark | DuckDB | Alt | Fabric | ClickHouse
| Exasol | Starburst | Athena | Trino | Dremio | Oracle | Datafusion => {
Bigquery | Redshift | Databricks | Spark | DuckDB | Alt | Fabric | SqlServer
| ClickHouse | Exasol | Starburst | Athena | Trino | Dremio | Oracle | Datafusion => {
let original_fields = self.original.fields();
let mut sdf_fields = Vec::with_capacity(original_fields.len());
for field in original_fields {
Expand Down Expand Up @@ -950,14 +954,87 @@ pub mod fabric {
}
}

pub mod sqlserver {

use arrow_schema::DataType;
use std::fmt::Write;

use crate::AdapterResult;
use crate::errors::{AdapterError, AdapterErrorKind};

const SQLSERVER_MAX_VARCHAR_TYPE: &str = "VARCHAR(MAX)";

pub fn try_format_type(
datatype: &DataType,
nullable: bool,
out: &mut String,
) -> AdapterResult<()> {
match datatype {
DataType::Null => out.push_str("INT"),
DataType::Boolean => out.push_str("BIT"),
DataType::Int8 => out.push_str("SMALLINT"),
DataType::Int16 => out.push_str("SMALLINT"),
DataType::Int32 => out.push_str("INT"),
DataType::Int64 => out.push_str("BIGINT"),

DataType::UInt8 => out.push_str("SMALLINT"),
DataType::UInt16 => out.push_str("INT"),
DataType::UInt32 => out.push_str("BIGINT"),
DataType::UInt64 => out.push_str("DECIMAL(20,0)"),
DataType::Float32 => out.push_str("REAL"),
DataType::Float64 => out.push_str("FLOAT"),

DataType::Timestamp(_, _) => out.push_str("DATETIME2(6)"),

DataType::Date32 => out.push_str("DATE"),

DataType::Time32(_) | DataType::Time64(_) => out.push_str("TIME(6)"),

DataType::Interval(_) => {
return Err(AdapterError::new(
AdapterErrorKind::UnsupportedType,
"INTERVAL is not supported in SQL Server",
));
}
DataType::Binary => out.push_str("VARBINARY(MAX)"),
DataType::Utf8 | DataType::Utf8View => out.push_str(SQLSERVER_MAX_VARCHAR_TYPE),

DataType::List(_) => {
return Err(AdapterError::new(
AdapterErrorKind::UnsupportedType,
"ARRAY is not supported in SQL Server",
));
}

DataType::Dictionary(_, value) if value.as_ref() == &DataType::Utf8 => {
out.push_str(SQLSERVER_MAX_VARCHAR_TYPE)
}
DataType::Decimal128(precision, scale) => {
write!(out, "DECIMAL({precision}, {scale})").unwrap()
}
_ => {
return Err(AdapterError::new(
AdapterErrorKind::UnsupportedType,
format!("{datatype} is not convertible to sqlserver sql type"),
));
}
};
if !nullable {
out.push_str(" NOT NULL");
}
Ok(())
}
}

pub const fn max_varchar_size(adapter_type: AdapterType) -> Option<usize> {
use AdapterType::*;
match adapter_type {
// FIXME: Actual MAX is 134_217_728 - 16_777_216 is the default value
Snowflake => Some(16_777_216),
Redshift => Some(256),
Postgres | Bigquery | Databricks | Salesforce | Spark | DuckDB | Alt | Fabric
| ClickHouse | Exasol | Starburst | Athena | Trino | Dremio | Oracle | Datafusion => None,
| SqlServer | ClickHouse | Exasol | Starburst | Athena | Trino | Dremio | Oracle
| Datafusion => None,
}
}

Expand All @@ -968,7 +1045,8 @@ pub const fn max_varbinary_size(adapter_type: AdapterType) -> Option<usize> {
Redshift => Some(65_535),
// TODO: define limits for more systems
Postgres | Bigquery | Databricks | Salesforce | Spark | DuckDB | Alt | Fabric
| ClickHouse | Exasol | Starburst | Athena | Trino | Dremio | Oracle | Datafusion => None,
| SqlServer | ClickHouse | Exasol | Starburst | Athena | Trino | Dremio | Oracle
| Datafusion => None,
}
}

Expand Down Expand Up @@ -1303,20 +1381,24 @@ mod tests {
assert_eq!(convert_floating_type(Postgres), "float8");
assert_eq!(convert_floating_type(Snowflake), "float8");
assert_eq!(convert_floating_type(Redshift), "float8");
assert_eq!(convert_floating_type(SqlServer), "float");
assert_eq!(convert_type(&DataType::Float32, SqlServer), "real");
let convert_decimal_type =
|adapter_type| convert_type(&DataType::Decimal32(10, 0), adapter_type);
assert_eq!(convert_decimal_type(Bigquery), "int64");
assert_eq!(convert_decimal_type(Databricks), "bigint");
assert_eq!(convert_decimal_type(Postgres), "integer");
assert_eq!(convert_decimal_type(Snowflake), "integer");
assert_eq!(convert_decimal_type(Redshift), "integer");
assert_eq!(convert_decimal_type(SqlServer), "int");
let convert_decimal_type =
|adapter_type| convert_type(&DataType::Decimal128(10, 2), adapter_type);
assert_eq!(convert_decimal_type(Bigquery), "float64");
assert_eq!(convert_decimal_type(Databricks), "double");
assert_eq!(convert_decimal_type(Postgres), "float8");
assert_eq!(convert_decimal_type(Snowflake), "float8");
assert_eq!(convert_decimal_type(Redshift), "float8");
assert_eq!(convert_decimal_type(SqlServer), "float");
}

#[test]
Expand All @@ -1327,6 +1409,7 @@ mod tests {
assert_eq!(convert_boolean_type(Postgres), "boolean");
assert_eq!(convert_boolean_type(Snowflake), "boolean");
assert_eq!(convert_boolean_type(Redshift), "boolean");
assert_eq!(convert_boolean_type(SqlServer), "bit");
}

#[test]
Expand All @@ -1351,6 +1434,7 @@ mod tests {
convert_datetime_type(Redshift),
"timestamp without time zone"
);
assert_eq!(convert_datetime_type(SqlServer), "datetime2(6)");
}
const ALL_ADAPTERS: [AdapterType; 5] = [Bigquery, Databricks, Postgres, Snowflake, Redshift];

Expand All @@ -1361,6 +1445,7 @@ mod tests {
for adapter_type in ALL_ADAPTERS {
assert_eq!(convert_date_type(adapter_type), "date");
}
assert_eq!(convert_date_type(SqlServer), "date");
}

#[test]
Expand All @@ -1371,6 +1456,7 @@ mod tests {
for adapter_type in ALL_ADAPTERS {
assert_eq!(convert_time_type(adapter_type), "time");
}
assert_eq!(convert_time_type(SqlServer), "time(6)");
}

#[test]
Expand All @@ -1381,5 +1467,52 @@ mod tests {
assert_eq!(convert_text_type(Postgres), "text");
assert_eq!(convert_text_type(Snowflake), "text");
assert_eq!(convert_text_type(Redshift), "text");
assert_eq!(convert_text_type(SqlServer), "varchar");
}

#[test]
fn sqlserver_try_format_type_formats_native_types() {
let mut out = String::new();
sqlserver::try_format_type(&DataType::Boolean, false, &mut out).unwrap();
assert_eq!(out, "BIT NOT NULL");

out.clear();
sqlserver::try_format_type(&DataType::Int32, true, &mut out).unwrap();
assert_eq!(out, "INT");

out.clear();
sqlserver::try_format_type(&DataType::Utf8, true, &mut out).unwrap();
assert_eq!(out, "VARCHAR(MAX)");

out.clear();
sqlserver::try_format_type(&DataType::Decimal128(18, 4), true, &mut out).unwrap();
assert_eq!(out, "DECIMAL(18, 4)");

out.clear();
sqlserver::try_format_type(
&DataType::Timestamp(TimeUnit::Microsecond, None),
true,
&mut out,
)
.unwrap();
assert_eq!(out, "DATETIME2(6)");
}

#[test]
fn sqlserver_try_format_type_rejects_unsupported_arrow_types() {
let mut out = String::new();
let err = sqlserver::try_format_type(
&DataType::Interval(arrow_schema::IntervalUnit::MonthDayNano),
true,
&mut out,
)
.expect_err("INTERVAL is not supported in SQL Server");
assert_eq!(err.kind(), AdapterErrorKind::UnsupportedType);

out.clear();
let item = Arc::new(Field::new("item", DataType::Int32, true));
let err = sqlserver::try_format_type(&DataType::List(item), true, &mut out)
.expect_err("ARRAY is not supported in SQL Server");
assert_eq!(err.kind(), AdapterErrorKind::UnsupportedType);
}
}