diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..35659c6 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,89 @@ +# Release Workflow + +This GitHub Actions workflow automates the creation of releases for the FastWrappers-TSQL project. + +## Trigger + +The workflow is automatically triggered when creating a new tag starting with `v` (e.g., `vx.y.z`). + +## Generated Artifacts + +The workflow generates 4 types of artifacts for different installation methods: + +1. **FastWrappers-TSQL.dacpac** - Data-tier Application Package + - Recommended for Visual Studio / SQL Server Data Tools + - Enables controlled deployment with drift detection + +2. **FastWrappers-TSQL.bacpac** - Binary Application Package + - For import/export between servers + - Contains the schema + compiled assembly + +3. **FastWrappers-TSQL.bak** - SQL Server Backup + - Compatible with SQL Server 2016+ (Compatibility Level 130) + - Direct restore via SSMS or T-SQL + +4. **FastWrappers-TSQL.sql** - Pure SQL Script + - Executable via sqlcmd or SSMS + - **Automatically generated from DACPAC with up-to-date binary** + - Contains the compiled assembly in inline hexadecimal format + +## Build Process + +1. **Checkout** source code +2. **Setup** MSBuild and NuGet +3. **Build** SQL project in Release mode +4. **Deploy** temporarily to SQL LocalDB +5. **Generate** artifacts: + - BACPAC via SqlPackage export + - BAK via BACKUP DATABASE (with compression) + - DACPAC copied from bin/Release + - **SQL script generated from DACPAC (contains up-to-date compiled binary)** +6. **Create** GitHub release with all artifacts + +## How to Create a New Release + +### 1. Update the Version + +Edit [Properties/AssemblyInfo.cs](../Properties/AssemblyInfo.cs): +```csharp +[assembly: AssemblyVersion("x.y.z.0")] +[assembly: AssemblyFileVersion("x.y.z.0")] +``` + +Optional: Update [FastWrappers_TSQL.sqlproj](../FastWrappers_TSQL.sqlproj): +```xml +x.y.z.0 +``` + +### 2. Commit and Tag + +```bash +git add Properties/AssemblyInfo.cs +git commit -m "Bump version to x.y.z" +git tag vx.y.z +git push origin main +git push origin vx.y.z +``` + +### 3. Verify the Release + +1. Go to https://github.com/aetperf/FastWrappers-TSQL/actions +2. Check that the "Create Release Artifacts" workflow is running +3. Once completed, verify the release at https://github.com/aetperf/FastWrappers-TSQL/releases + +## Troubleshooting + +### Workflow fails during build + +- Verify that the project compiles locally in Release mode +- Check NuGet dependencies + +### SqlPackage cannot find the assembly + +- Verify that the DACPAC is correctly generated in `bin/Release/` +- Verify that the file is signed (AetPCLRSign.pfx.snk) + +### Backup fails + +- SQL LocalDB may need more time to start +- Increase the `Start-Sleep` after LocalDB startup diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7ebee23 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,251 @@ +name: Create Release Artifacts + +on: + push: + tags: + - 'v*.*.*' + +jobs: + build-and-release: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Setup NuGet + uses: nuget/setup-nuget@v2 + + - name: Restore NuGet packages + run: nuget restore FastWrappers-TSQL.sln + + - name: Build SQL Project + run: msbuild FastWrappers_TSQL.sqlproj /p:Configuration=Release /p:Platform="Any CPU" /t:Build + + - name: Verify Build Artifacts + shell: powershell + run: | + Write-Host "Checking for build output..." + if (Test-Path "bin") { + Get-ChildItem -Path "bin" -Recurse -File | Format-Table FullName, Length + } else { + Write-Host "bin folder does not exist!" + } + + # Check for DACPAC in bin\Output (SQL Projects output here) + $dacpacPath = "bin\Output\FastWrappers_TSQL.dacpac" + if (Test-Path $dacpacPath) { + Write-Host "DACPAC found at: $dacpacPath" + } else { + Write-Host "DACPAC not found at: $dacpacPath" + Write-Host "Looking for .dacpac files..." + Get-ChildItem -Path . -Filter "*.dacpac" -Recurse -ErrorAction SilentlyContinue | Format-Table FullName + exit 1 + } + + - name: Setup SQL Server (LocalDB) + shell: powershell + run: | + # Start SQL LocalDB + sqllocaldb create MSSQLLocalDB + sqllocaldb start MSSQLLocalDB + + # Wait for SQL Server to be ready + Start-Sleep -Seconds 10 + + - name: Deploy Database and Create Artifacts + shell: powershell + run: | + # Variables + $serverInstance = "(localdb)\MSSQLLocalDB" + $dbName = "FastWrappers-TSQL" + $dacpacPath = "bin\Output\FastWrappers_TSQL.dacpac" + $outputDir = "bin\Release" + $bakPath = "$outputDir\FastWrappers-TSQL.bak" + $generatedSqlPath = "$outputDir\FastWrappers-TSQL.sql" + $templateSqlPath = "FastWrappers-TSQL.sql" + + # Ensure output directory exists + if (!(Test-Path $outputDir)) { + Write-Host "Creating $outputDir directory..." + New-Item -Path $outputDir -ItemType Directory -Force | Out-Null + } + + # Verify DACPAC exists + if (!(Test-Path $dacpacPath)) { + Write-Error "DACPAC not found at: $dacpacPath" + exit 1 + } + + # Download SqlPackage if needed + if (!(Test-Path "SqlPackage\SqlPackage.exe")) { + Write-Host "Downloading SqlPackage..." + Invoke-WebRequest -Uri "https://aka.ms/sqlpackage-windows" -OutFile "SqlPackage.zip" + Expand-Archive -Path "SqlPackage.zip" -DestinationPath "SqlPackage" -Force + Remove-Item "SqlPackage.zip" + } + + $sqlPackage = "SqlPackage\SqlPackage.exe" + + # Deploy the DACPAC to create the database + Write-Host "Deploying DACPAC to create database..." + & $sqlPackage /Action:Publish /SourceFile:$dacpacPath /TargetServerName:$serverInstance /TargetDatabaseName:$dbName /p:IncludeCompositeObjects=True + + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to deploy DACPAC" + exit 1 + } + + # Wait for deployment to complete + Start-Sleep -Seconds 5 + + # Test the assembly + Write-Host "Testing assembly..." + $testResult = sqlcmd -S $serverInstance -d $dbName -Q "SELECT dbo.EncryptString('test') AS Result" 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host "Assembly test successful!" + Write-Host $testResult + } else { + Write-Warning "Assembly test failed: $testResult" + Write-Warning "Continuing with artifact creation..." + } + + # Create backup file + Write-Host "Creating SQL Server backup..." + $bakFullPath = Join-Path $PWD $bakPath + sqlcmd -S $serverInstance -Q "BACKUP DATABASE [$dbName] TO DISK = N'$bakFullPath' WITH STATS = 10" + + if ($LASTEXITCODE -ne 0) { + Write-Warning "Failed to create BAK file, continuing..." + } + + # Generate SQL script with current assembly binary + Write-Host "Generating SQL script with assembly binary..." + + # Extract assembly binary from deployed database using SqlClient (no size limitations) + $connectionString = "Server=$serverInstance;Database=$dbName;Integrated Security=True;TrustServerCertificate=True;" + $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString) + $connection.Open() + + $command = $connection.CreateCommand() + $command.CommandText = "SELECT content FROM sys.assembly_files WHERE assembly_id = (SELECT assembly_id FROM sys.assemblies WHERE name = 'FastWrappers_TSQL')" + + $reader = $command.ExecuteReader() + $assemblyHex = $null + + if ($reader.Read()) { + $bytes = $reader.GetSqlBytes(0).Value + $hexString = [System.BitConverter]::ToString($bytes) -replace '-','' + $assemblyHex = "0x$hexString" + Write-Host "Assembly binary extracted (length: $($assemblyHex.Length) chars, $($bytes.Length) bytes)" + } + + $reader.Close() + $connection.Close() + + if ($assemblyHex) { + # Read template and inject assembly binary + $sqlTemplate = Get-Content $templateSqlPath -Raw + $finalSql = $sqlTemplate -replace '__ASSEMBLY_FROM_0X__', $assemblyHex + $finalSql | Out-File -FilePath $generatedSqlPath -Encoding UTF8 + Write-Host "SQL script generated successfully" + } else { + Write-Warning "Failed to extract assembly binary" + } + + # List all created artifacts + Write-Host "`nCreated artifacts:" + Get-ChildItem -Path $outputDir -File | Format-Table Name, Length + + Write-Host "All artifacts processing completed!" + + - name: Get version from tag + id: get_version + shell: bash + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + + - name: Prepare Release Files + shell: powershell + run: | + # Create a release directory with all available artifacts + New-Item -Path "release" -ItemType Directory -Force | Out-Null + + # Copy available files + $files = @( + @{Source="bin\Release\FastWrappers-TSQL.bak"; Name="FastWrappers-TSQL.bak"}, + @{Source="bin\Release\FastWrappers-TSQL.sql"; Name="FastWrappers-TSQL.sql"} + ) + + foreach ($file in $files) { + if (Test-Path $file.Source) { + Copy-Item $file.Source "release\$($file.Name)" -Force + Write-Host "Copied $($file.Name)" + } else { + Write-Warning "$($file.Name) not found, skipping" + } + } + + Write-Host "`nRelease files:" + Get-ChildItem -Path "release" | Format-Table Name, Length + + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v1 + with: + name: Release v${{ steps.get_version.outputs.VERSION }} + body: | + ## FastWrappers-TSQL v${{ steps.get_version.outputs.VERSION }} + + ### Installation Files + + - **FastWrappers-TSQL.bak** - SQL Server Backup (restore using SSMS) + - **FastWrappers-TSQL.sql** - SQL Script (execute using sqlcmd or SSMS) + + ### Installation + + **Option 1: Restore from backup (.bak file)** + ```sql + -- Restore database + RESTORE DATABASE [FastWrappers-TSQL] FROM DISK = 'FastWrappers-TSQL.bak'; + GO + + -- Enable TRUSTWORTHY and CLR + ALTER DATABASE [FastWrappers-TSQL] SET TRUSTWORTHY ON; + GO + + EXEC sp_configure 'show advanced options', 1; + RECONFIGURE; + EXEC sp_configure 'clr enabled', 1; + RECONFIGURE; + GO + + -- IMPORTANT: Change database owner to 'sa' + -- Required for signed UNSAFE assemblies to load properly + USE [FastWrappers-TSQL]; + GO + EXEC sp_changedbowner 'sa'; + GO + + -- Test + SELECT dbo.EncryptString('test'); + ``` + + **Option 2: Execute SQL Script (.sql file)** + ```sql + -- Simply execute the script in SSMS or using sqlcmd + sqlcmd -S YourServer -i FastWrappers-TSQL.sql + ``` + + ### Usage + + See [README.md](https://github.com/${{ github.repository }}/blob/main/README.md) for examples. + files: | + release/* + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/FastBCPWrapper_SP.cs.cs b/FastBCPWrapper_SP.cs.cs index eae5fca..03b6995 100644 --- a/FastBCPWrapper_SP.cs.cs +++ b/FastBCPWrapper_SP.cs.cs @@ -26,6 +26,7 @@ public static void RunFastBCP_Secure( SqlString sourcepasswordEnc, // chiffré (Base64) ; exclusif avec Trusted SqlBoolean trusted, // exclusif avec User/Password SqlString sourcedatabase, // obligatoire si pas de connectstring + SqlString applicationintent, // ReadOnly (default) | ReadWrite (SQL Server & OLEDB only) // Sources infos SqlString inputFile, // -F fileinput (optionnel) – exclusif avec query @@ -43,10 +44,11 @@ public static void RunFastBCP_Secure( SqlString dateformat, // -f (default "yyyy-MM-dd") SqlString encoding, // -e (default "UTF-8") SqlString decimalseparator, // -n (default ",") + SqlString parquetcompression, // zstd;none;snappy;gzip;lzo;lz4 (default zstd) // Parallel options SqlInt32 degree, // -p (Default -2) - SqlString method, // -m (Random;DataDriven;Ctid;Physloc;Rowid;RangeId;Ntile;None) (Default None) + SqlString method, // -m (Random;DataDriven;Ctid;Physloc;Rowid;RangeId;Ntile;Timepartition;None) (Default None) SqlString distributeKeyColumn, // -c SqlString datadrivenquery, // -Q (optionnel) SqlBoolean mergeDistributedFile, // -M (bool) default True @@ -57,9 +59,11 @@ public static void RunFastBCP_Secure( SqlString boolformat, // -b automatic;true/false;1/0;t/f; SqlString runid, // -R SqlString settingsfile, // -l (default FastBCP_Settings.json) + SqlString config, // --config (YAML config file) SqlString cloudprofile, // --cloudprofile SqlString license, // --license | --licence SqlString loglevel, // --loglevel Information|Debug|Verbose + SqlBoolean nobanner, // --nobanner (suppress banner) SqlBoolean debug // --debug (output stdout to SQL client) ) { @@ -88,6 +92,7 @@ public static void RunFastBCP_Secure( bool trustedVal = !trusted.IsNull && trusted.Value; string databaseVal = sourcedatabase.IsNull ? null : sourcedatabase.Value.Trim(); + string applicationintentVal = applicationintent.IsNull ? null : applicationintent.Value.Trim(); string inputFileVal = inputFile.IsNull ? null : inputFile.Value.Trim(); string queryVal = query.IsNull ? null : query.Value.Trim(); @@ -102,6 +107,7 @@ public static void RunFastBCP_Secure( string datefmtVal = dateformat.IsNull ? null : dateformat.Value.Trim(); string encodingVal = encoding.IsNull ? null : encoding.Value.Trim(); string decSepVal = decimalseparator.IsNull ? null : decimalseparator.Value.Trim(); + string parquetcompressionVal = parquetcompression.IsNull ? null : parquetcompression.Value.Trim(); int? degreeVal = degree.IsNull ? (int?)null : degree.Value; string methodVal = method.IsNull ? null : method.Value.Trim(); @@ -114,9 +120,11 @@ public static void RunFastBCP_Secure( string boolfmtVal = boolformat.IsNull ? null : boolformat.Value.Trim(); string runidVal = runid.IsNull ? null : runid.Value.Trim(); string settingsVal = settingsfile.IsNull ? null : settingsfile.Value.Trim(); + string configVal = config.IsNull ? null : config.Value.Trim(); string cloudProfVal = cloudprofile.IsNull ? null : cloudprofile.Value.Trim(); string licenseVal = license.IsNull ? null : license.Value.Trim(); string loglevelVal = loglevel.IsNull ? null : loglevel.Value.Trim(); + bool nobannerVal = !nobanner.IsNull && nobanner.Value; bool debugVal = !debug.IsNull && debug.Value ? true : false; // ----------------------------- @@ -226,6 +234,13 @@ public static void RunFastBCP_Secure( args.Append(" --sourcedatabase ").Append(Q(databaseVal)); } + // application intent (SQL Server & OLEDB only) + if (!string.IsNullOrEmpty(applicationintentVal) && + (connType == "mssql" || connType == "oledb" || connType == "msoledbsql")) + { + args.Append(" --applicationintent ").Append(applicationintentVal); + } + // decimal separator if (!string.IsNullOrEmpty(decSepVal)) args.Append(" --decimalseparator ").Append(Q(decSepVal)); @@ -253,6 +268,10 @@ public static void RunFastBCP_Secure( if (!string.IsNullOrEmpty(encodingVal)) args.Append(" --encoding ").Append(Q(encodingVal)); + // parquet compression + if (!string.IsNullOrEmpty(parquetcompressionVal)) + args.Append(" --parquetcompression ").Append(Q(parquetcompressionVal)); + // Parallel options if (degreeVal.HasValue) args.Append(" --paralleldegree ").Append(degreeVal.Value); @@ -274,12 +293,16 @@ public static void RunFastBCP_Secure( args.Append(" --runid ").Append(Q(runidVal)); if (!string.IsNullOrEmpty(settingsVal)) args.Append(" --settingsfile ").Append(Q(settingsVal)); + if (!string.IsNullOrEmpty(configVal)) + args.Append(" --config ").Append(Q(configVal)); if (!string.IsNullOrEmpty(cloudProfVal)) args.Append(" --cloudprofile ").Append(Q(cloudProfVal)); if (!string.IsNullOrEmpty(licenseVal)) args.Append(" --license ").Append(Q(licenseVal)); if (!string.IsNullOrEmpty(loglevelVal)) args.Append(" --loglevel ").Append(Q(loglevelVal)); + if (nobannerVal) + args.Append(" --nobanner"); string argLine = args.ToString().Trim(); diff --git a/FastTransferWrapper_SP.cs b/FastTransferWrapper_SP.cs index 3dacc91..1cfccc8 100644 --- a/FastTransferWrapper_SP.cs +++ b/FastTransferWrapper_SP.cs @@ -69,10 +69,12 @@ public static void RunFastTransfer_Secure( SqlString targetTable, SqlString loadMode, SqlInt32 batchSize, + SqlBoolean useWorkTables, // 6) Advanced SqlString method, SqlString distributeKeyColumn, + SqlString dataDrivenQuery, SqlInt32 degree, SqlString mapMethod, @@ -80,6 +82,7 @@ public static void RunFastTransfer_Secure( SqlString runId, SqlString settingsFile, SqlBoolean debug, + SqlBoolean noBanner, // 8) License (optional, can be null or empty if not required SqlString license, @@ -148,13 +151,16 @@ SqlString loglevel targetTable, loadMode, batchSize, + useWorkTables, method, distributeKeyColumn, + dataDrivenQuery, degree, mapMethod, runId, settingsFile, debug, + noBanner, license, loglevel ); @@ -186,13 +192,16 @@ private static void RunFastTransferInternal( SqlString targetTable, // Mandatory. eg "CopyTable" SqlString loadMode, // "Append" or "Truncate" SqlInt32 batchSize, // e.g. 130000 - SqlString method, // distribution method for parallelism :"None", "Random", "DataDriven", "RangeId", "Ntile", "Ctid", "Rowid" + SqlBoolean useWorkTables, // use staging work tables before final insert + SqlString method, // distribution method for parallelism :"None", "Random", "DataDriven", "RangeId", "Ntile", "Ctid", "Rowid", "Physloc", "NZDataSlice" SqlString distributeKeyColumn, // required if method in ["Random","DataDriven","RangeId","Ntile"] + SqlString dataDrivenQuery, // custom query for DataDriven method returning distinct values for distribution SqlInt32 degree, // concurrency degree if method != "None" useless if method = "None" should be != 1, can be less than 0 for dynamic degree (based on cpucount on the platform where FastTransfer is running. -2 = CpuCount/2) SqlString mapMethod, // "Position"(default) or "Name" (Automatic mapping of columns based on names (case insensitive) with tolerance on the order of columns. Non present columns in source or target are ignored. Name may mot be available for all target types (see doc)) SqlString runId, // a run identifier for logging (can be a string for grouping or a unique identifier). Guid is used if not provide SqlString settingsFile, // path for a custom FastTransfer_Settings.json file, for custom logging SqlBoolean debug, // for debugging purpose, if true, the FastTransfer_Settings.json file is created in the current directory with the default settings + SqlBoolean noBanner, // suppress FastTransfer banner and version information at startup SqlString license, // license key file or url for FastTransfer (optional, can be null or empty if not required) SqlString loglevel // optional, can be "error", "warning", "information", "debug" (default is "information") ) @@ -231,17 +240,20 @@ private static void RunFastTransferInternal( string tgtTableVal = targetTable.IsNull ? null : targetTable.Value.Trim(); string loadModeVal = loadMode.IsNull ? null : loadMode.Value.Trim(); int? batchSizeVal = batchSize.IsNull ? (int?)null : batchSize.Value; + bool useWorkTablesVal = !useWorkTables.IsNull && useWorkTables.Value; - string methodVal = method.IsNull ? null : method.Value.Trim(); - string distKeyColVal = distributeKeyColumn.IsNull ? null : distributeKeyColumn.Value.Trim(); - int? degreeVal = degree.IsNull ? (int?)null : degree.Value; - string mapMethodVal = mapMethod.IsNull ? null : mapMethod.Value.Trim(); + string methodVal = method.IsNull ? null : method.Value.Trim(); + string distKeyColVal = distributeKeyColumn.IsNull ? null : distributeKeyColumn.Value.Trim(); + string dataDrivenQueryVal = dataDrivenQuery.IsNull ? null : dataDrivenQuery.Value.Trim(); + int? degreeVal = degree.IsNull ? (int?)null : degree.Value; + string mapMethodVal = mapMethod.IsNull ? null : mapMethod.Value.Trim(); - string runIdVal = runId.IsNull ? null : runId.Value.Trim(); - string settingsFileVal = settingsFile.IsNull ? null : settingsFile.Value.Trim(); + string runIdVal = runId.IsNull ? null : runId.Value.Trim(); + string settingsFileVal = settingsFile.IsNull ? null : settingsFile.Value.Trim(); - bool debugVal = !debug.IsNull && debug.Value ? true : false; - // If license is provided, it can be null or empty if not required + bool debugVal = !debug.IsNull && debug.Value ? true : false; + bool noBannerVal = !noBanner.IsNull && noBanner.Value; + // If license is provided, it can be null or empty if not required string licenseVal = license.IsNull ? null : license.Value.Trim(); // Default loglevel to "information" @@ -382,10 +394,10 @@ private static void RunFastTransferInternal( bool hasMethod = !string.IsNullOrEmpty(methodVal); if (hasMethod) { - string[] validMethods = { "None", "Random", "DataDriven", "RangeId", "Ntile", "Ctid", "Rowid" , "NZDataSlice" }; + string[] validMethods = { "None", "Random", "DataDriven", "RangeId", "Ntile", "Ctid", "Rowid", "Physloc", "NZDataSlice" }; if (Array.IndexOf(validMethods, methodVal) < 0) { - throw new ArgumentException($"Invalid method: '{methodVal}'. use 'None', 'Random', 'DataDriven', 'RangeId', 'Ntile', 'NZDataSlice', 'Ctid' or 'Rowid'. WARNING the parameter is Case Sensitive"); + throw new ArgumentException($"Invalid method: '{methodVal}'. use 'None', 'Random', 'DataDriven', 'RangeId', 'Ntile', 'Ctid', 'Rowid', 'Physloc' or 'NZDataSlice'. WARNING the parameter is Case Sensitive"); } if (!methodVal.Equals("None")) @@ -551,6 +563,10 @@ private static void RunFastTransferInternal( { args += $" --batchsize {batchSizeVal.Value}"; } + if (useWorkTablesVal) + { + args += " --useworktables"; + } // Advanced Parameters if (hasMethod) @@ -571,52 +587,56 @@ private static void RunFastTransferInternal( args += $" --degree {degreeVal.Value}"; } } - if (hasMapMethod) - { - args += $" --mapmethod {Q(mapMethodVal)}"; - } - - // Log Parameters - if (!string.IsNullOrEmpty(runIdVal)) - { - args += $" --runid {Q(runIdVal)}"; - } - if (!string.IsNullOrEmpty(settingsFileVal)) - { - args += $" --settingsfile {Q(settingsFileVal)}"; - } - - // License (optional) - if (!string.IsNullOrEmpty(licenseVal)) - { - args += $" --license {Q(licenseVal)}"; - } + if (!string.IsNullOrEmpty(dataDrivenQueryVal)) + { + args += $" --datadrivenquery {Q(dataDrivenQueryVal)}"; + } + if (hasMapMethod) + { + args += $" --mapmethod {Q(mapMethodVal)}"; + } - // Loglevel - if (!string.IsNullOrEmpty(loglevelVal)) - { - args += $" --loglevel {Q(loglevelVal)}"; - } + // Log Parameters + if (!string.IsNullOrEmpty(runIdVal)) + { + args += $" --runid {Q(runIdVal)}"; + } + if (!string.IsNullOrEmpty(settingsFileVal)) + { + args += $" --settingsfile {Q(settingsFileVal)}"; + } - // Trim leading space - args = args.Trim(); + // License (optional) + if (!string.IsNullOrEmpty(licenseVal)) + { + args += $" --license {Q(licenseVal)}"; + } - //args4Log : for logging purpose, remove password or passwd info in the args string using regexp - string args4Log = args; - args4Log = System.Text.RegularExpressions.Regex.Replace(args4Log, @"--sourcepassword\s+""[^""]*""", "--sourcepassword \"\""); - args4Log = System.Text.RegularExpressions.Regex.Replace(args4Log, @"--targetpassword\s+""[^""]*""", "--targetpassword \"\""); - args4Log = System.Text.RegularExpressions.Regex.Replace(args4Log, @"--sourceconnectstring\s+""[^""]*""", "--sourceconnectstring \"\""); - args4Log = System.Text.RegularExpressions.Regex.Replace(args4Log, @"--targetconnectstring\s+""[^""]*""", "--targetconnectstring \"\""); + // Loglevel + if (!string.IsNullOrEmpty(loglevelVal)) + { + args += $" --loglevel {Q(loglevelVal)}"; + } + if (noBannerVal) + { + args += " --nobanner"; + } + // Trim leading space + args = args.Trim(); - if (debugVal) - { - //print the command line exe and args - SqlContext.Pipe.Send("FastTransfer Command " + exePath + " " + args4Log + Environment.NewLine); - } + //args4Log : for logging purpose, remove password or passwd info in the args string using regexp + string args4Log = args; + args4Log = System.Text.RegularExpressions.Regex.Replace(args4Log, @"--sourcepassword\s+""[^""]*""", "--sourcepassword \"\""); + args4Log = System.Text.RegularExpressions.Regex.Replace(args4Log, @"--targetpassword\s+""[^""]*""", "--targetpassword \"\""); + args4Log = System.Text.RegularExpressions.Regex.Replace(args4Log, @"--sourceconnectstring\s+""[^""]*""", "--sourceconnectstring \"\""); + args4Log = System.Text.RegularExpressions.Regex.Replace(args4Log, @"--targetconnectstring\s+""[^""]*""", "--targetconnectstring \"\""); - // -------------------------------------------------------------------- - // 4. Execute the CLI + if (debugVal) + { + //print the command line exe and args + SqlContext.Pipe.Send("FastTransfer Command " + exePath + " " + args4Log + Environment.NewLine); + } // -------------------------------------------------------------------- ProcessStartInfo psi = new ProcessStartInfo { diff --git a/FastWrappers-TSQL.sql b/FastWrappers-TSQL.sql new file mode 100644 index 0000000..6e58a72 --- /dev/null +++ b/FastWrappers-TSQL.sql @@ -0,0 +1,309 @@ +-- ===================================================================== +-- FastWrappers-TSQL - Complete Installation Script +-- ===================================================================== +-- This script creates the FastWrappers-TSQL database with all +-- required CLR assemblies, stored procedures, functions, and security roles. +-- ===================================================================== +-- Prerequisites: +-- 1. SQL Server 2016 or later +-- 2. CLR integration enabled (see instructions below) +-- 3. FastTransfer and FastBCP binaries available +-- ===================================================================== + +-- ===================================================================== +-- STEP 1: Enable CLR Integration (if not already enabled) +-- ===================================================================== +EXEC sp_configure 'show advanced options', 1; +RECONFIGURE; +GO + +EXEC sp_configure 'clr enabled', 1; +RECONFIGURE; +GO + +-- ===================================================================== +-- STEP 2: Create Database +-- ===================================================================== +USE [master] +GO + +IF EXISTS (SELECT name FROM sys.databases WHERE name = 'FastWrappers-TSQL') +BEGIN + PRINT 'Database FastWrappers-TSQL already exists. Dropping it...'; + ALTER DATABASE [FastWrappers-TSQL] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE [FastWrappers-TSQL]; +END +GO + +CREATE DATABASE [FastWrappers-TSQL] +GO + +ALTER DATABASE [FastWrappers-TSQL] SET RECOVERY SIMPLE; +GO + +ALTER DATABASE [FastWrappers-TSQL] SET PAGE_VERIFY CHECKSUM; +GO + +PRINT 'Database FastWrappers-TSQL created successfully.'; +GO + +-- ===================================================================== +-- STEP 3: Configure Database for CLR with sp_add_trusted_assembly +-- ===================================================================== +-- This method uses sp_add_trusted_assembly and is more secure +-- The assembly hex will be injected during the build process + +USE [FastWrappers-TSQL]; +GO + +-- Add assembly to trusted list BEFORE creating it +DECLARE @assemblyBinary VARBINARY(MAX) = __ASSEMBLY_FROM_0X__; +DECLARE @hash VARBINARY(64) = HASHBYTES('SHA2_512', @assemblyBinary); + +EXEC sys.sp_add_trusted_assembly + @hash = @hash, + @description = N'FastWrappers_TSQL Assembly'; +GO + +-- Now load the assembly (it's already trusted) +CREATE ASSEMBLY [FastWrappers_TSQL] +FROM __ASSEMBLY_FROM_0X__ +WITH PERMISSION_SET = UNSAFE; +GO + +PRINT 'Assembly loaded and added to trusted list.'; +GO + +-- ===================================================================== +-- STEP 4: Create CLR Function - EncryptString +-- ===================================================================== +USE [FastWrappers-TSQL]; +GO + +IF EXISTS (SELECT * FROM sys.objects WHERE name = 'EncryptString' AND type IN ('FN', 'TF', 'IF', 'FS')) +BEGIN + DROP FUNCTION [dbo].[EncryptString]; +END +GO + +CREATE FUNCTION [dbo].[EncryptString](@plainText [nvarchar](4000)) +RETURNS [nvarchar](4000) +WITH EXECUTE AS CALLER +AS EXTERNAL NAME [FastWrappers_TSQL].[FastWrapper.FastTransferCLR].[EncryptString]; +GO + +PRINT 'Function dbo.EncryptString created successfully.'; +GO + +-- ===================================================================== +-- STEP 5: Create CLR Stored Procedure - xp_RunFastTransfer_secure +-- ===================================================================== + +IF EXISTS (SELECT * FROM sys.objects WHERE name = 'xp_RunFastTransfer_secure' AND type = 'PC') +BEGIN + DROP PROCEDURE [dbo].[xp_RunFastTransfer_secure]; +END +GO + +CREATE PROCEDURE [dbo].[xp_RunFastTransfer_secure] + @fastTransferDir [nvarchar](max), + @sourceConnectionType [nvarchar](30), + @sourceConnectStringSecure [nvarchar](4000) = N'', + @sourceServer [nvarchar](255), + @sourceDSN [nvarchar](255) = N'', + @sourceProvider [nvarchar](1000) = N'', + @isSourceTrusted [bit] = 0, + @sourceUser [nvarchar](1000) = N'', + @sourcePasswordSecure [nvarchar](255) = N'', + @sourceDatabase [nvarchar](1000), + @fileInput [nvarchar](4000) = N'', + @query [nvarchar](4000) = N'', + @sourceSchema [nvarchar](255) = N'', + @sourceTable [nvarchar](255) = N'', + @targetConnectionType [nvarchar](30), + @targetConnectStringSecure [nvarchar](4000) = N'', + @targetServer [nvarchar](255), + @isTargetTrusted [bit] = 0, + @targetUser [nvarchar](1000) = N'', + @targetPasswordSecure [nvarchar](255) = N'', + @targetDatabase [nvarchar](255), + @targetSchema [nvarchar](255), + @targetTable [nvarchar](255), + @loadMode [nvarchar](50), + @batchSize [int] = 1048576, + @useWorkTables [bit] = 0, + @method [nvarchar](50) = N'None', + @distributeKeyColumn [nvarchar](255) = N'', + @dataDrivenQuery [nvarchar](4000) = N'', + @degree [int] = 4, + @mapmethod [nvarchar](50) = N'Position', + @runId [nvarchar](255) = N'', + @settingsFile [nvarchar](4000) = N'', + @debug [bit] = 0, + @noBanner [bit] = 0, + @license [nvarchar](4000) = N'', + @loglevel [nvarchar](50) = N'information' +WITH EXECUTE AS CALLER +AS EXTERNAL NAME [FastWrappers_TSQL].[FastWrapper.FastTransferCLR].[RunFastTransfer_Secure]; +GO + +PRINT 'Stored procedure dbo.xp_RunFastTransfer_secure created successfully.'; +GO + +-- ===================================================================== +-- STEP 6: Create CLR Stored Procedure - xp_RunFastBCP_secure +-- ===================================================================== + +IF EXISTS (SELECT * FROM sys.objects WHERE name = 'xp_RunFastBCP_secure' AND type = 'PC') +BEGIN + DROP PROCEDURE [dbo].[xp_RunFastBCP_secure]; +END +GO + +CREATE PROCEDURE [dbo].[xp_RunFastBCP_secure] + @fastBcpDir [nvarchar](max), + @connectionType [nvarchar](30), + @sourceConnectStringEnc [nvarchar](4000) = N'', + @sourcedsn [nvarchar](255) = N'', + @sourceprovider [nvarchar](1000) = N'', + @sourceserver [nvarchar](255) = N'', + @sourceuser [nvarchar](1000) = N'', + @sourcepasswordEnc [nvarchar](4000) = N'', + @trusted [bit] = 0, + @sourcedatabase [nvarchar](1000), + @applicationintent [nvarchar](20) = N'ReadOnly', + @inputFile [nvarchar](4000) = N'', + @query [nvarchar](4000) = N'', + @sourceschema [nvarchar](255) = N'', + @sourcetable [nvarchar](255) = N'', + @outputFile [nvarchar](4000) = N'', + @outputDirectory [nvarchar](4000) = N'', + @delimiter [nvarchar](10) = N'|', + @usequotes [bit] = 0, + @dateformat [nvarchar](50) = N'yyyy-MM-dd', + @encoding [nvarchar](50) = N'UTF-8', + @decimalseparator [nvarchar](2) = N',', + @parquetcompression [nvarchar](20) = N'zstd', + @degree [int] = -2, + @method [nvarchar](50) = N'None', + @distributeKeyColumn [nvarchar](255) = N'', + @datadrivenquery [nvarchar](4000) = N'', + @mergeDistributedFile [bit] = 1, + @timestamped [bit] = 0, + @noheader [bit] = 0, + @boolformat [nvarchar](50) = N'automatic', + @runid [nvarchar](255) = N'', + @settingsfile [nvarchar](4000) = N'', + @config [nvarchar](4000) = N'', + @cloudprofile [nvarchar](255) = N'', + @license [nvarchar](4000) = N'', + @loglevel [nvarchar](50) = N'information', + @nobanner [bit] = 0, + @debug [bit] = 0 +WITH EXECUTE AS CALLER +AS EXTERNAL NAME [FastWrappers_TSQL].[FastWrapper.FastBCPCLR].[RunFastBCP_Secure]; +GO + +PRINT 'Stored procedure dbo.xp_RunFastBCP_secure created successfully.'; +GO + +-- ===================================================================== +-- STEP 7: Create Security Roles +-- ===================================================================== + +IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = 'FastTransfer_Executor' AND type = 'R') +BEGIN + CREATE ROLE [FastTransfer_Executor]; + PRINT 'Role [FastTransfer_Executor] created successfully.'; +END +ELSE +BEGIN + PRINT 'Role [FastTransfer_Executor] already exists.'; +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = 'FastBCP_Executor' AND type = 'R') +BEGIN + CREATE ROLE [FastBCP_Executor]; + PRINT 'Role [FastBCP_Executor] created successfully.'; +END +ELSE +BEGIN + PRINT 'Role [FastBCP_Executor] already exists.'; +END +GO + +-- Grant EXECUTE permissions +GRANT EXECUTE ON dbo.xp_RunFastTransfer_secure TO [FastTransfer_Executor]; +GRANT EXECUTE ON dbo.xp_RunFastBCP_secure TO [FastBCP_Executor]; +GRANT EXECUTE ON dbo.EncryptString TO [FastTransfer_Executor]; +GRANT EXECUTE ON dbo.EncryptString TO [FastBCP_Executor]; +GO + +PRINT 'Permissions granted to [FastTransfer_Executor] and [FastBCP_Executor] roles.'; +GO + +-- ===================================================================== +-- STEP 8: Verification +-- ===================================================================== + +PRINT ''; +PRINT '====================================================================='; +PRINT 'Installation Summary'; +PRINT '====================================================================='; +PRINT ''; + +-- Check assembly +IF EXISTS (SELECT * FROM sys.assemblies WHERE name = 'FastWrappers_TSQL') + PRINT '[OK] Assembly [FastWrappers_TSQL] is loaded'; +ELSE + PRINT '[ERROR] Assembly [FastWrappers_TSQL] is NOT loaded!'; + +-- Check function +IF EXISTS (SELECT * FROM sys.objects WHERE name = 'EncryptString' AND type = 'FS') + PRINT '[OK] Function [dbo].[EncryptString] is created'; +ELSE + PRINT '[ERROR] Function [dbo].[EncryptString] is NOT created!'; + +-- Check FastTransfer stored procedure +IF EXISTS (SELECT * FROM sys.objects WHERE name = 'xp_RunFastTransfer_secure' AND type = 'PC') + PRINT '[OK] Stored procedure [dbo].[xp_RunFastTransfer_secure] is created'; +ELSE + PRINT '[ERROR] Stored procedure [dbo].[xp_RunFastTransfer_secure] is NOT created!'; + +-- Check FastBCP stored procedure +IF EXISTS (SELECT * FROM sys.objects WHERE name = 'xp_RunFastBCP_secure' AND type = 'PC') + PRINT '[OK] Stored procedure [dbo].[xp_RunFastBCP_secure] is created'; +ELSE + PRINT '[ERROR] Stored procedure [dbo].[xp_RunFastBCP_secure] is NOT created!'; + +-- Check roles +IF EXISTS (SELECT * FROM sys.database_principals WHERE name = 'FastTransfer_Executor' AND type = 'R') + PRINT '[OK] Role [FastTransfer_Executor] is created'; +ELSE + PRINT '[ERROR] Role [FastTransfer_Executor] is NOT created!'; + +IF EXISTS (SELECT * FROM sys.database_principals WHERE name = 'FastBCP_Executor' AND type = 'R') + PRINT '[OK] Role [FastBCP_Executor] is created'; +ELSE + PRINT '[ERROR] Role [FastBCP_Executor] is NOT created!'; + +PRINT ''; +PRINT '====================================================================='; +PRINT 'Next Steps'; +PRINT '====================================================================='; +PRINT '1. Add users to the executor roles:'; +PRINT ' ALTER ROLE [FastTransfer_Executor] ADD MEMBER [YourUserName];'; +PRINT ' ALTER ROLE [FastBCP_Executor] ADD MEMBER [YourUserName];'; +PRINT ''; +PRINT '2. Test the EncryptString function:'; +PRINT ' SELECT dbo.EncryptString(''TestPassword'');'; +PRINT ''; +PRINT '3. Run a test migration with FastTransfer:'; +PRINT ' EXEC dbo.xp_RunFastTransfer_secure @fastTransferDir = ''...'', ...'; +PRINT ''; +PRINT '4. Run a test export with FastBCP:'; +PRINT ' EXEC dbo.xp_RunFastBCP_secure @fastBCPDir = ''...'', ...'; +PRINT ''; +PRINT '====================================================================='; +GO diff --git a/FastWrappers_TSQL.sqlproj b/FastWrappers_TSQL.sqlproj index e78edfe..4de4e78 100644 --- a/FastWrappers_TSQL.sqlproj +++ b/FastWrappers_TSQL.sqlproj @@ -7,7 +7,7 @@ 2.0 4.1 {4b096d13-154a-472c-9882-f281334958f9} - Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider Database diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index e6de07d..843001f 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -19,5 +19,5 @@ // Numéro de build // Révision // -[assembly: AssemblyVersion("0.3.2.0")] -[assembly: AssemblyFileVersion("0.3.2.0")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] diff --git a/README.md b/README.md index 50a8fbf..68782e8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,269 @@ As a reminder : - **FastTransfer** is a CLI that allow import from file or transfer data between databases using streaming and parallel mecanism for high performance - **FastBCP** is a CLI that allow to export data from databases to files (csv, parquet, json,bson and excel) using streaming and parallel mecanism for high performance -Samples usage : +## Installation + +Download the latest release from the [Releases page](https://github.com/aetperf/FastWrappers-TSQL/releases). Each release provides 2 installation options: + +### Installation Methods + +#### 1. **FastWrappers-TSQL.bak** (Recommended) +SQL Server Backup file - **Requires SQL Server 2019 or higher** + +✅ **Fastest installation method** +✅ **Pre-configured database with all objects** + +⚠️ **Post-installation configuration required:** +- Enable CLR integration (`sp_configure 'clr enabled', 1`) +- Trust the assembly using `sp_add_trusted_assembly` (see [Post-Installation Configuration](#post-installation-configuration)) + +**Installation:** +```sql +-- Restore the database +RESTORE DATABASE [FastWrappers-TSQL] +FROM DISK = 'C:\path\to\FastWrappers-TSQL.bak' +WITH MOVE 'FastWrappers-TSQL' TO 'C:\path\to\FastWrappers-TSQL.mdf', + MOVE 'FastWrappers-TSQL_log' TO 'C:\path\to\FastWrappers-TSQL_log.ldf'; +GO +``` + +#### 2. **FastWrappers-TSQL.sql** (Alternative for older SQL Server versions) +Self-contained SQL script - **Compatible with SQL Server 2016 or higher** + +✅ **Complete installation in a single script** +✅ **Includes database creation, CLR activation, trusted assembly configuration, and all objects** +✅ **No post-installation configuration needed** + +**Installation:** +```sql +-- Simply execute the script in SSMS or using sqlcmd +sqlcmd -S YourServer -i FastWrappers-TSQL.sql +``` + +**What's included:** +- Database creation with proper settings +- CLR integration enabled +- Assembly registration with `sp_add_trusted_assembly` (secure method) +- All stored procedures and functions +- Security roles (FastTransfer_Executor, FastBCP_Executor) + +### Post-Installation Configuration + +After restoring the database (especially from .bak), you **must** configure CLR integration. Two options are available depending on your environment: + +#### Option 1: Using sp_add_trusted_assembly (Recommended for Production) + +This approach is **more secure** as it keeps TRUSTWORTHY OFF and doesn't require changing the database owner: + +```sql +-- Enable CLR integration +EXEC sp_configure 'show advanced options', 1; +RECONFIGURE; +GO + +EXEC sp_configure 'clr enabled', 1; +RECONFIGURE; +GO + +-- Extract the assembly hash and add it to trusted assemblies +DECLARE @hash VARBINARY(64); + +SELECT @hash = HASHBYTES('SHA2_512', content) +FROM sys.assembly_files +WHERE assembly_id = ( + SELECT assembly_id + FROM sys.assemblies + WHERE name = 'FastWrappers_TSQL' +); + +EXEC sys.sp_add_trusted_assembly + @hash = @hash, + @description = N'FastWrappers_TSQL Assembly v0.7.0'; +GO +``` + +**Advantages:** +- ✅ TRUSTWORTHY remains OFF (more secure) +- ✅ No need to change database owner +- ✅ Only this specific assembly is trusted + +**Note:** The assembly hash changes with each version. When upgrading, you must remove the old hash and add the new one: +```sql +-- Remove old version +EXEC sys.sp_drop_trusted_assembly @hash = ; +-- Then run the sp_add_trusted_assembly script above +``` + +#### Option 2: Using TRUSTWORTHY ON (Quick Setup for Dev/Test) + +This approach is simpler but **less secure**. Use only for development/testing environments: + +```sql +-- Enable TRUSTWORTHY for signed UNSAFE assemblies +ALTER DATABASE [FastWrappers-TSQL] SET TRUSTWORTHY ON; +GO + +-- Enable CLR integration +EXEC sp_configure 'show advanced options', 1; +RECONFIGURE; +GO + +EXEC sp_configure 'clr enabled', 1; +RECONFIGURE; +GO + +-- Set database owner to 'sa' (required for signed UNSAFE assemblies) +EXEC sp_changedbowner 'sa'; +GO +``` + +**Important:** With this method, the `sp_changedbowner 'sa'` command is **critical**. Without it, you will encounter error 0x80FC80F1 when trying to execute the stored procedures. + +## Security Roles + +The FastWrappers-TSQL database includes two predefined database roles to manage access to the stored procedures: + +### 1. **FastTransfer_Executor** + +This role grants `EXECUTE` permission on the **xp_RunFastTransfer_secure** stored procedure. + +**Usage:** +```sql +-- Add a user to the FastTransfer_Executor role +ALTER ROLE [FastTransfer_Executor] ADD MEMBER [YourUserName]; +GO +``` + +**Purpose:** Allows users to perform data transfers between databases without granting them broader permissions. + +### 2. **FastBCP_Executor** + +This role grants `EXECUTE` permission on the **xp_RunFastBCP_secure** stored procedure. + +**Usage:** +```sql +-- Add a user to the FastBCP_Executor role +ALTER ROLE [FastBCP_Executor] ADD MEMBER [YourUserName]; +GO +``` + +**Purpose:** Allows users to export data to files without granting them broader permissions. + +### Combined Access + +To grant a user access to both FastTransfer and FastBCP: + +```sql +-- Add user to both roles +ALTER ROLE [FastTransfer_Executor] ADD MEMBER [YourUserName]; +ALTER ROLE [FastBCP_Executor] ADD MEMBER [YourUserName]; +GO +``` + +**Note:** Users also need access to the **dbo.EncryptString** function to generate encrypted passwords. Consider granting `EXECUTE` permission explicitly if needed: + +```sql +GRANT EXECUTE ON dbo.EncryptString TO [YourUserName]; +GO +``` + +## Available Stored Procedures + +Once installed and configured, the FastWrappers-TSQL assembly provides the following stored procedures: + +### 1. **dbo.EncryptString** - Password Encryption Function +```sql +SELECT dbo.EncryptString('YourPassword') +``` +Encrypts passwords using AES-256 encryption. Use this function to generate encrypted passwords for the `@sourcePasswordSecure` and `@targetPasswordSecure` parameters. + +**Returns:** Base64-encoded encrypted string + +### 2. **dbo.xp_RunFastTransfer_secure** - Data Transfer Wrapper +```sql +EXEC dbo.xp_RunFastTransfer_secure @fastTransferDir = '...', ... +``` +Wraps the **FastTransfer** CLI to transfer data between databases with streaming and parallel processing for high performance. + +**Key Features:** +- Supports 13 source connection types (ClickHouse, DuckDB, HANA, SQL Server, MySQL, Netezza, Oracle, PostgreSQL, Teradata, ODBC, OLEDB) +- Supports 10 target connection types with bulk loading (clickhousebulk, duckdb, hanabulk, msbulk, mysqlbulk, nzbulk, orabulk, oradirect, pgcopy, teradata) +- Parallel methods: None, Random, DataDriven, RangeId, Ntile, Ctid (PostgreSQL), Physloc (SQL Server), Rowid (Oracle), NZDataSlice (Netezza) +- Automatic column mapping by position or name +- Encrypted connection strings and passwords using AES-256 +- Configurable batch sizes and parallelism degree +- Load modes: Append, Truncate +- Work tables support for staging data +- Custom data-driven distribution queries + +### 3. **dbo.xp_RunFastBCP_secure** - Data Export Wrapper +```sql +EXEC dbo.xp_RunFastBCP_secure @fastBCPDir = '...', ... +``` +Wraps the **FastBCP** CLI to export data from databases to files with streaming and parallel processing for high performance. + +**Key Features:** +- Supports 11 connection types (ClickHouse, HANA, SQL Server, MySQL, Netezza, ODBC, OLEDB, Oracle, PostgreSQL, Teradata) +- Multiple output formats: CSV, TSV, JSON, Parquet, BSON, Binary (PostgreSQL COPY), XLSX (Excel) +- Parquet compression codecs: Zstd (default), Snappy, Gzip, Lzo, Lz4, None +- Parallel methods: None, Random, DataDriven, RangeId, Ntile, Timepartition, Ctid (PostgreSQL), Physloc (SQL Server), Rowid (Oracle) +- Cloud storage support: AWS S3, Azure Blob Storage, Azure Data Lake Gen2, Google Cloud Storage, S3-Compatible, OneLake +- Configurable CSV/TSV formatting (delimiter, quotes, date format, decimal separator, boolean format, encoding) +- Encrypted connection strings and passwords using AES-256 +- File merge option for parallel exports +- Timestamped output files +- YAML configuration file support + +## Logging and Output + +### FastTransfer Output + +By default, **xp_RunFastTransfer_secure** returns a structured result set with transfer metrics: + +| Column | Type | Description | +|--------|------|-------------| +| targetdatabase | nvarchar(128) | Target database name | +| targetSchema | nvarchar(128) | Target schema name | +| targetTable | nvarchar(128) | Target table name | +| TotalRows | bigint | Number of rows transferred | +| TotalColumns | int | Number of columns transferred | +| TotalCells | bigint | Total cells transferred (rows × columns) | +| TotalTimeMs | bigint | Total execution time in milliseconds | +| Status | int | Exit code (0 = success, non-zero = error) | +| StdErr | nvarchar(max) | Error message if Status ≠ 0 | + +**Example output:** +``` +targetdatabase targetSchema targetTable TotalRows TotalColumns TotalCells TotalTimeMs Status StdErr +postgres public orders 15000000 9 135000000 27502 0 +``` + +#### Debug Mode (@debug = 1) + +When you set `@debug = 1`, the stored procedure will also output: + +1. **The complete command line** being executed (in the Messages tab): + ``` + FastTransfer Command .\FastTransfer.exe --sourceconnectiontype "mssql" --sourceserver "localhost" ... + ``` + *(Passwords and connection strings are automatically masked with `` for security)* + +2. **The full console output (stdout)** from FastTransfer (in the Messages tab): + - Real-time progress updates + - Detailed execution logs + - Performance metrics + - Any warnings or informational messages + +**Example with debug:** +```sql +EXEC dbo.xp_RunFastTransfer_secure + @fastTransferDir = 'C:\FastTransfer\latest', + @sourceConnectionType = 'mssql', + -- ... other parameters ... + @debug = 1 -- Enable verbose logging +``` + +## Usage Examples ### Copy one table using 12 threads between two MSSQL instances ```TSQL diff --git a/xp_RunFastBCP_secure.sql b/xp_RunFastBCP_secure.sql index b6c9279..36f106b 100644 --- a/xp_RunFastBCP_secure.sql +++ b/xp_RunFastBCP_secure.sql @@ -86,6 +86,7 @@ CREATE PROCEDURE [dbo].[xp_RunFastBCP_secure] @sourcepasswordSecure [nvarchar](255) = NULL, @isTrusted [bit] = 0, @sourcedatabase [nvarchar](1000), +@applicationintent [nvarchar](20) = 'ReadOnly', @inputFile [nvarchar](1000) = NULL, @query [nvarchar](4000) = NULL, @sourceschema [nvarchar](255) = NULL, @@ -97,6 +98,7 @@ CREATE PROCEDURE [dbo].[xp_RunFastBCP_secure] @dateformat [nvarchar](25) = NULL, @encoding [nvarchar](50) = NULL, @decimalSeparator [nvarchar](1) = NULL, +@parquetcompression [nvarchar](20) = 'zstd', @degree [int] = -2, @method [nvarchar](50) = 'None', @distributeKeyColumn [nvarchar](1000) = NULL, @@ -107,9 +109,11 @@ CREATE PROCEDURE [dbo].[xp_RunFastBCP_secure] @boolformat [nvarchar](10) = NULL, @runid [nvarchar](255) = NULL, @settingsfile [nvarchar](4000) = NULL, +@config [nvarchar](4000) = NULL, @cloudprofile [nvarchar](2000) = NULL, @license nvarchar(4000) = NULL, @loglevel nvarchar(50) = 'Information', +@nobanner [bit] = 0, @debug [bit] = 0 WITH EXECUTE AS CALLER AS diff --git a/xp_RunFastTransfer_secure.sql b/xp_RunFastTransfer_secure.sql index 6e0ed39..172a3ea 100644 --- a/xp_RunFastTransfer_secure.sql +++ b/xp_RunFastTransfer_secure.sql @@ -55,13 +55,16 @@ CREATE PROCEDURE [dbo].[xp_RunFastTransfer_secure] @targetTable [nvarchar](255), @loadMode [nvarchar](50), @batchSize [int] = 1048576, + @useWorkTables [bit] = 0, @method [nvarchar](50) = 'None', @distributeKeyColumn [nvarchar](255) = NULL, + @dataDrivenQuery [nvarchar](4000) = NULL, @degree [int] = 4, @mapmethod [nvarchar](50) = 'Position', @runId [nvarchar](255) = NULL, @settingsFile [nvarchar](4000) = NULL, @debug [bit] = 0, + @noBanner [bit] = 0, @license nvarchar(4000) = NULL, @loglevel nvarchar(50) = 'information' WITH EXECUTE AS CALLER