-
Notifications
You must be signed in to change notification settings - Fork 47
Add NVARCHAR (SQL_C_WCHAR) Output Support to .NET Core C# Language Extension #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,3 +50,59 @@ Not Supported. | |
| After downloading or building the dotnet-core-CSharp-lang-extension.zip, use [CREATE EXTERNAL LANGUAGE](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-external-language-transact-sql?view=sql-server-ver15) to register the language with SQL Server 2019 CU3+. | ||
|
|
||
| This [tutorial](./sample/regex/README.md) will walk you through an end to end sample using the .NET Core C# language extension. | ||
|
|
||
| ## Output Schema Support | ||
|
|
||
| By default, output column types are inferred from the .NET DataFrame column types. For string columns, you can explicitly specify the SQL data type using the `OutputColumnDataTypes` property. | ||
|
|
||
| ### Specifying Output Column Types | ||
|
|
||
| Use `OutputColumnDataTypes` to specify the SQL data type for output columns by name: | ||
|
|
||
| ```csharp | ||
| using Microsoft.SqlServer.CSharpExtension.SDK; | ||
| using Microsoft.Data.Analysis; | ||
| using static Microsoft.SqlServer.CSharpExtension.Sql; | ||
|
|
||
| public class MyExecutor : AbstractSqlServerExtensionExecutor | ||
| { | ||
| public override DataFrame Execute(DataFrame input, Dictionary<string, dynamic> sqlParams) | ||
| { | ||
| // Specify NVARCHAR (UTF-16) output for a string column | ||
| OutputColumnDataTypes["unicode_column"] = SqlDataType.DotNetWChar; | ||
|
|
||
| // Process and return data | ||
| return resultDataFrame; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Supported String Types | ||
|
|
||
| | SqlDataType | SQL Type | Encoding | Description | | ||
| |-------------|----------|----------|-------------| | ||
| | `SqlDataType.DotNetChar` | VARCHAR | UTF-8 | Default for string columns | | ||
| | `SqlDataType.DotNetWChar` | NVARCHAR | UTF-16 | Use for Unicode data | | ||
|
|
||
|
Comment on lines
+82
to
+86
|
||
| ### Example: Mixed VARCHAR and NVARCHAR Output | ||
|
|
||
| ```csharp | ||
| public class MixedOutputExecutor : AbstractSqlServerExtensionExecutor | ||
| { | ||
| public override DataFrame Execute(DataFrame input, Dictionary<string, dynamic> sqlParams) | ||
| { | ||
| // "ascii_col" will default to VARCHAR (no configuration needed) | ||
|
|
||
| // "unicode_col" should be NVARCHAR | ||
| OutputColumnDataTypes["unicode_col"] = SqlDataType.DotNetWChar; | ||
|
|
||
| return input; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Default Behavior | ||
|
|
||
| If no explicit type is specified for a string column: | ||
| - String columns default to `DotNetChar` (VARCHAR/UTF-8) | ||
| - Numeric and other types are automatically mapped from their .NET types | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -45,19 +45,42 @@ public class CSharpOutputDataSet: CSharpDataSet | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// by extracting data and information from every DataFrameColumn. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="dataFrame">The DataFrame containing the output data.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public unsafe void ExtractColumns(DataFrame dataFrame) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="outputColumnDataTypes">Optional user-specified column data types by name.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public unsafe void ExtractColumns( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DataFrame dataFrame, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dictionary<string, SqlDataType> outputColumnDataTypes = null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Logging.Trace("CSharpOutputDataSet::ExtractColumns"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _strLenOrNullMapPtrs = new IntPtr[ColumnsNumber]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _dataPtrs = new IntPtr[ColumnsNumber]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for(ushort columnNumber = 0; columnNumber < ColumnsNumber; ++columnNumber) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DataFrameColumn column = dataFrame.Columns[columnNumber]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Determine the SQL data type for this column. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // All .NET strings are output as DotNetChar (varchar/UTF-8). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Default behavior: map .NET types to SQL types (strings -> DotNetChar/varchar). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SqlDataType dataType = DataTypeMap[column.DataType]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For string columns, check for user-specified type override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (column.DataType == typeof(string) && outputColumnDataTypes != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (outputColumnDataTypes.TryGetValue(column.Name, out var userType)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (userType != SqlDataType.DotNetChar && userType != SqlDataType.DotNetWChar) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new ArgumentException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $"Invalid type override '{userType}' for string column '{column.Name}'. " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $"Only DotNetChar and DotNetWChar are supported for string columns."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dataType = userType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Logging.Trace($"ExtractColumns: Column '{column.Name}' using user-specified type: {dataType}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+68
to
+81
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (column.DataType == typeof(string) && outputColumnDataTypes != null) | |
| { | |
| if (outputColumnDataTypes.TryGetValue(column.Name, out var userType)) | |
| { | |
| if (userType != SqlDataType.DotNetChar && userType != SqlDataType.DotNetWChar) | |
| { | |
| throw new ArgumentException( | |
| $"Invalid type override '{userType}' for string column '{column.Name}'. " + | |
| $"Only DotNetChar and DotNetWChar are supported for string columns."); | |
| } | |
| dataType = userType; | |
| Logging.Trace($"ExtractColumns: Column '{column.Name}' using user-specified type: {dataType}"); | |
| } | |
| if (column.DataType == typeof(string) | |
| && outputColumnDataTypes != null | |
| && outputColumnDataTypes.TryGetValue(column.Name, out var userType)) | |
| { | |
| if (userType != SqlDataType.DotNetChar && userType != SqlDataType.DotNetWChar) | |
| { | |
| throw new ArgumentException( | |
| $"Invalid type override '{userType}' for string column '{column.Name}'. " + | |
| $"Only DotNetChar and DotNetWChar are supported for string columns."); | |
| } | |
| dataType = userType; | |
| Logging.Trace($"ExtractColumns: Column '{column.Name}' using user-specified type: {dataType}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code sample returns
resultDataFrame, but that variable isn’t defined in the example, so the snippet won’t compile if copied as-is. Consider returninginput(as in other examples) or declare/buildresultDataFramein the snippet to keep the documentation self-contained.