Skip to content
Open
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
19 changes: 14 additions & 5 deletions language-extensions/dotnet-core-CSharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ Language Extensions is a feature of SQL Server used for executing external code.

For more information about SQL Server Language Extensions, refer to this [documentation](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sql-server-ver15).

The dotnet-core-CSharp-extension version in this repository is compatible with SQL Server 2019 CU3 onwards. It integrates .NET core in SQL Server and works with .NET 8.0 in **Windows only**.
The dotnet-core-CSharp-extension version in this repository is compatible with SQL Server 2019 CU3 onwards. It integrates .NET Core in SQL Server and works with .NET 8.0 and up on Windows and Linux.

Currently, the extension supports the following data types: SQL_C_SLONG, SQL_C_ULONG, SQL_C_SSHORT, SQL_C_USHORT, SQL_C_SBIGINT, SQL_C_UBIGINT, SQL_C_STINYINT, SQL_C_UTINYINT, SQL_C_BIT, SQL_C_FLOAT, SQL_C_DOUBLE, SQL_C_CHAR, SQL_C_WCHAR, and SQL_C_NUMERIC. It supports the following SQL data types: int, bigint, smallint, tinyint, real, float, bit, char(n), varchar(n), nchar(n), nvarchar(n), decimal(p,s), and numeric(p,s).

To use this dotnet-core-CSharp-lang-extension.zip package, follow [this tutorial](./sample/regex/README.md). For any fixes or enhancements, you are welcome to modify, rebuild and use the binaries using the following instructions.
To use this `dotnet-core-CSharp-lang-extension.zip` (Windows) or `dotnet-core-CSharp-lang-extension.tar.gz` (Linux) package, follow [this tutorial](./sample/regex/README.md). For any fixes or enhancements, you are welcome to modify, rebuild and use the binaries using the following instructions.

## Building

Expand All @@ -24,15 +24,24 @@ To use this dotnet-core-CSharp-lang-extension.zip package, follow [this tutorial
- PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\nativecsharpextension.dll \
- PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\hostfxr.dll \
- PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.dll \
- PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.runtimeconfig.json\
- PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.runtimeconfig.json \
- PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.deps.json

5. Run [create-dotnet-core-CSharp-extension-zip.cmd](./build/windows/create-dotnet-core-CSharp-extension-zip.cmd) which will generate: \
- PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\target\debug\dotnet-core-CSharp-lang-extension.zip
- PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\packages\dotnet-core-CSharp-lang-extension.zip
This zip can be used in CREATE EXTERNAL LANGUAGE, as detailed in the tutorial in the Usage section below.

### Linux
Not Supported.

1. Run [build-dotnet-core-CSharp-extension.ps1](./build/linux/build-dotnet-core-CSharp-extension.ps1) which will generate: \
- PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/libnativecsharpextension.so \
- PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.dll \
- PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.runtimeconfig.json \
- PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.deps.json

2. Run [create-dotnet-core-CSharp-extension-zip.ps1](./build/linux/create-dotnet-core-CSharp-extension-zip.ps1) which will generate: \
- PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/packages/dotnet-core-CSharp-lang-extension.tar.gz
This tarball can be used in CREATE EXTERNAL LANGUAGE, as detailed in the tutorial in the Usage section below.

Comment thread
MarkpageBxl marked this conversation as resolved.
## Testing (Optional)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -euo pipefail

# Set root and working directories
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
DOTNET_EXTENSION_HOME="$ENL_ROOT/language-extensions/dotnet-core-CSharp"
DOTNET_EXTENSION_WORKING_DIR="$ENL_ROOT/build-output/dotnet-core-CSharp-extension/linux"

# Clean and create working directory
rm -rf "$DOTNET_EXTENSION_WORKING_DIR"
mkdir -p "$DOTNET_EXTENSION_WORKING_DIR"

# Default to release if no arguments provided
if [ $# -eq 0 ]; then
set -- "release"
fi

# Process each build configuration
for BUILD_CONFIGURATION in "$@"; do
BUILD_CONFIGURATION="$(echo "$BUILD_CONFIGURATION" | tr '[:upper:]' '[:lower:]')"

# Default to release if not debug
if [ "$BUILD_CONFIGURATION" != "debug" ]; then
BUILD_CONFIGURATION="release"
fi

echo "[Info] Building dotnet-core-CSharp-extension libnativecsharpextension.so..."

# Set build output directory
BUILD_OUTPUT="$DOTNET_EXTENSION_WORKING_DIR/$BUILD_CONFIGURATION"
rm -rf "$BUILD_OUTPUT"
mkdir -p "$BUILD_OUTPUT"
pushd "$BUILD_OUTPUT" > /dev/null

# Set source and include paths
DOTNET_NATIVE_SRC="$DOTNET_EXTENSION_HOME/src/native"
DOTNET_NATIVE_INCLUDE="$DOTNET_EXTENSION_HOME/include"
EXTENSION_HOST_INCLUDE="$ENL_ROOT/extension-host/include"
DOTNET_NATIVE_LIB="$DOTNET_EXTENSION_HOME/lib"

# Determine C++ compiler
CXX="${CXX:-c++}"

# Build compiler arguments
CC_ARGS=(
-shared
-fPIC
-fshort-wchar
-std=c++17
-o libnativecsharpextension.so
"-I$DOTNET_NATIVE_INCLUDE"
"-I$EXTENSION_HOST_INCLUDE"
)

if [ "$BUILD_CONFIGURATION" = "debug" ]; then
CC_ARGS+=(-DDEBUG -g)
fi

# Add all .cpp source files
for src in "$DOTNET_NATIVE_SRC"/*.cpp; do
CC_ARGS+=("$src")
done

# Link with static nethost library (must run restore-packages.sh first)
if [ ! -f "$DOTNET_NATIVE_LIB/libnethost.a" ]; then
echo "Error: libnethost.a not found at $DOTNET_NATIVE_LIB/libnethost.a" >&2
echo " Run restore-packages.sh first to install the .NET SDK and copy libnethost.a." >&2
exit 1
fi
CC_ARGS+=("$DOTNET_NATIVE_LIB/libnethost.a")
# Link with libdl for dlopen/dlsym
CC_ARGS+=(-ldl)

echo "[Info] Compiling with: $CXX ${CC_ARGS[*]}"
"$CXX" "${CC_ARGS[@]}"

popd > /dev/null

# Build managed code
echo "[Info] Building Microsoft.SqlServer.CSharpExtension dll..."
DOTNET_MANAGED_SRC="$DOTNET_EXTENSION_HOME/src/managed"
dotnet build \
"$DOTNET_MANAGED_SRC/Microsoft.SqlServer.CSharpExtension.csproj" \
-m \
-c "$BUILD_CONFIGURATION" \
-o "$BUILD_OUTPUT" \
--no-restore \
--no-dependencies

echo "Success: Built dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration."
done

exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
DOTNET_EXTENSION_WORKING_DIR="$ENL_ROOT/build-output/dotnet-core-CSharp-extension/linux"

check_error() {
local error_level=$1
local error_message=$2
if [ "$error_level" -ne 0 ]; then
echo "Error: $error_message" >&2
exit "$error_level"
fi
}

# Default to release if no arguments provided
if [ $# -eq 0 ]; then
set -- "release"
fi

# Process each build configuration
for BUILD_CONFIGURATION in "$@"; do
BUILD_CONFIGURATION="$(echo "$BUILD_CONFIGURATION" | tr '[:upper:]' '[:lower:]')"

# Default to release if not debug
if [ "$BUILD_CONFIGURATION" != "debug" ]; then
BUILD_CONFIGURATION="release"
fi

BUILD_OUTPUT="$DOTNET_EXTENSION_WORKING_DIR/$BUILD_CONFIGURATION"
mkdir -p "$BUILD_OUTPUT/packages"

# Delete the ref folder so that the tarball can be loaded by the SPEES
rm -rf "$BUILD_OUTPUT/ref"

# Collect files to compress
FILES_TO_COMPRESS=()
FILES_TO_COMPRESS+=("Microsoft.SqlServer.CSharpExtension.runtimeconfig.json")
FILES_TO_COMPRESS+=("Microsoft.SqlServer.CSharpExtension.deps.json")

# Add all .dll and .so files
for f in "$BUILD_OUTPUT"/*.dll "$BUILD_OUTPUT"/*.so; do
[ -e "$f" ] && FILES_TO_COMPRESS+=("$(basename "$f")")
done

# Include .pdb files for debug builds
if [ "$BUILD_CONFIGURATION" = "debug" ]; then
for f in "$BUILD_OUTPUT"/*.pdb; do
[ -e "$f" ] && FILES_TO_COMPRESS+=("$(basename "$f")")
done
fi

# Package the binaries
pushd "$BUILD_OUTPUT" > /dev/null
tar -czvf "$BUILD_OUTPUT/packages/dotnet-core-CSharp-lang-extension.tar.gz" "${FILES_TO_COMPRESS[@]}"
check_error $? "Failed to create tarball for dotnet-core-CSharp-extension for configuration=$BUILD_CONFIGURATION"
popd > /dev/null

echo "Success: Compressed dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration."
done
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Note: errexit (-e) is intentionally NOT set. Each significant command is
# followed by check_exit_code, which prints a descriptive message and exits
# with the failing command's status. Enabling -e would abort before those
# messages could be emitted, making failures harder to diagnose.
set -uo pipefail

check_exit_code() {
Comment thread
SicongLiu2000 marked this conversation as resolved.
local exit_code=$?
if [ ${exit_code} -eq 0 ]; then
echo "$1"
else
echo "$2"
exit ${exit_code}
fi
}

# Install .NET SDK 8.0 for building the C# extension managed code
# and for obtaining libnethost.a (used by the native host to discover hostfxr).
#

# Check if dotnet is already installed and is version 8.x
if command -v dotnet &>/dev/null && dotnet --version 2>/dev/null | grep -q "^8\."; then
echo "Info: .NET SDK 8.x already installed ($(dotnet --version))"
else
echo "Info: Installing .NET SDK 8.0..."
apt-get update
apt-get install -y --no-install-recommends wget apt-transport-https
wget https://dot.net/v1/dotnet-install.sh -O /tmp/dotnet-install.sh
chmod +x /tmp/dotnet-install.sh
/tmp/dotnet-install.sh --channel 8.0 --install-dir /usr/share/dotnet
ln -sf /usr/share/dotnet/dotnet /usr/bin/dotnet
check_exit_code "Success: Installed .NET SDK 8.0" "Error: Failed to install .NET SDK 8.0"
fi

# Copy libnethost.a from the .NET SDK runtime packs into the extension's lib directory.
# The SDK ships this as part of the AppHost pack.
#
SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
ENL_ROOT=${SCRIPTDIR}/../../../..
DOTNET_EXTENSION_HOME=${ENL_ROOT}/language-extensions/dotnet-core-CSharp

NETHOST_DIR=$(find /usr/share/dotnet -path "*/runtimes/linux-x64/native/libnethost.a" -print -quit 2>/dev/null)
if [ -z "$NETHOST_DIR" ]; then
echo "Error: libnethost.a not found in .NET SDK. Ensure .NET SDK 8.0 is installed."
exit 1
fi

echo "Info: Found libnethost.a at: $NETHOST_DIR"
mkdir -p "${DOTNET_EXTENSION_HOME}/lib"
cp "$NETHOST_DIR" "${DOTNET_EXTENSION_HOME}/lib/libnethost.a"
check_exit_code "Success: Copied libnethost.a to extension lib directory" "Error: Failed to copy libnethost.a"

# Pre-restore NuGet packages while network is still available.
# OneBranch enables network isolation after package-restore phases,
# so dotnet restore must happen here rather than during the build step.
#
# Use the parent repo's NuGet.Config which points to the ADO artifact feed
# (with nuget.org as upstream) rather than the submodule's NuGet.Config
# which points directly to nuget.org (unreachable from OneBranch containers).
#
MANAGED_PROJ="${DOTNET_EXTENSION_HOME}/src/managed/Microsoft.SqlServer.CSharpExtension.csproj"
MANAGED_TEST_PROJ="${DOTNET_EXTENSION_HOME}/test/src/managed/Microsoft.SqlServer.CSharpExtensionTest.csproj"
PARENT_NUGET_CONFIG="${ENL_ROOT}/../NuGet.Config"

echo "Info: Restoring NuGet packages for Microsoft.SqlServer.CSharpExtension..."
if [ -f "$PARENT_NUGET_CONFIG" ]; then
echo "Info: Using NuGet.Config from parent repo: $PARENT_NUGET_CONFIG"
dotnet restore "$MANAGED_PROJ" --configfile "$PARENT_NUGET_CONFIG"
check_exit_code "Success: NuGet packages restored (extension)" "Error: Failed to restore NuGet packages (extension)"

echo "Info: Restoring NuGet packages for Microsoft.SqlServer.CSharpExtensionTest..."
dotnet restore "$MANAGED_TEST_PROJ" --configfile "$PARENT_NUGET_CONFIG"
check_exit_code "Success: NuGet packages restored (test)" "Error: Failed to restore NuGet packages (test)"
else
echo "Info: Parent NuGet.Config not found, using default"
dotnet restore "$MANAGED_PROJ"
check_exit_code "Success: NuGet packages restored (extension)" "Error: Failed to restore NuGet packages (extension)"

dotnet restore "$MANAGED_TEST_PROJ"
check_exit_code "Success: NuGet packages restored (test)" "Error: Failed to restore NuGet packages (test)"
fi

exit 0
35 changes: 31 additions & 4 deletions language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,33 @@
//*********************************************************************
#pragma once

#ifdef _WIN32
#include "Windows.h"
#else
// Provide the minimal HRESULT-style codes used by this extension on non-Windows
// platforms. Guard each definition so we never clash with a value supplied by
// another header, and parenthesize the values for safe use in expressions.
#ifndef S_OK
#define S_OK (0)
#endif
#ifndef E_FAIL
#define E_FAIL (-1)
#endif
#endif

#include <string>
#include <coreclr_delegates.h>
#include <hostfxr.h>

#ifdef _WIN32
#define STR(s) L ## s
#define CH(c) L ## c
#define PATH_SEPARATOR CH('\\')
#else
#define STR(s) s
#define CH(c) c
#define PATH_SEPARATOR CH('/')
#endif

using namespace std;
using string_t = std::basic_string<char_t>;
Expand Down Expand Up @@ -46,10 +66,10 @@ class DotnetEnvironment
// Load managed assembly and get function pointer to a managed method
//
const string_t ManagedExtensionName = STR("Microsoft.SqlServer.CSharpExtension");
const string_t ManagedExtensionPath = m_root_path + STR("\\") + ManagedExtensionName + STR(".dll");
const string_t ManagedExtensionPath = m_root_path + PATH_SEPARATOR + ManagedExtensionName + STR(".dll");
Comment thread
SicongLiu2000 marked this conversation as resolved.
const string_t ManagedExtensionType = ManagedExtensionName + STR(".CSharpExtension, ") + ManagedExtensionName;
const string_t ManagedExtensionMethod = to_utf16_str(method_name);
const string_t DelegateTypeName = ManagedExtensionName + STR(".CSharpExtension+") + to_utf16_str(method_name) + STR("Delegate, ") + ManagedExtensionName;
const string_t ManagedExtensionMethod = convert_string(method_name);
const string_t DelegateTypeName = ManagedExtensionName + STR(".CSharpExtension+") + ManagedExtensionMethod + STR("Delegate, ") + ManagedExtensionName;
int rc = m_load_assembly_and_get_function_pointer(
ManagedExtensionPath.c_str(),
ManagedExtensionType.c_str(),
Expand All @@ -72,9 +92,16 @@ class DotnetEnvironment
load_assembly_and_get_function_pointer_fn m_load_assembly_and_get_function_pointer;
string_t m_root_path;

// Convert utf8_str to utf16_str
// Convert a std::string to the platform string_t type.
// On Windows this is UTF-8 -> UTF-16; on Linux it is a no-op.
//
static string_t convert_string(const std::string& str);

#ifdef _WIN32
// Convert utf8_str to utf16_str (Windows only)
//
static string_t to_utf16_str(const std::string& utf8str);
#endif

// Convert an int to string in hex.
//
Expand Down
1 change: 0 additions & 1 deletion language-extensions/dotnet-core-CSharp/include/Logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
//*********************************************************************
#include <string>
#include <iostream>
#include <stdio.h>

using namespace std;
#define LOG(msg) Logger::Log(msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
#include <coreclr_delegates.h>
#include <hostfxr.h>

#if defined(_WIN32) || defined(WINDOWS)
#include <windows.h>
#endif

#include <sql.h>
#include "sqlexternallanguage.h"
#include "sqlexternallibrary.h"
Expand Down
Loading