diff --git a/ci/migration/README.md b/ci/migration/README.md new file mode 100644 index 0000000000..2765d1f2b1 --- /dev/null +++ b/ci/migration/README.md @@ -0,0 +1,261 @@ +# Cross‑Platform ODBC DSN Migration Scripts (Windows / Linux / macOS) + +This repository provides **cross‑platform migration scripts** to move existing +BigQuery ODBC DSNs from legacy **Simba drivers** to the new **ODBC Driver for +BigQuery**. + +The scripts support: + +- **Windows** (PowerShell) +- **Linux** (Bash) +- **macOS** (Bash) + +They follow the **same high‑level behavior and migration semantics** across all +platforms, with platform‑specific implementation details. + +______________________________________________________________________ + +## Goals of the Migration + +- Seamlessly transition DSNs from Simba BigQuery drivers to the Google‑provided + ODBC Driver for BigQuery +- Preserve existing DSN configuration wherever possible +- Support **safe testing (copy mode)** and **in‑place migration (replace mode)** +- Minimize manual registry / config file editing + +______________________________________________________________________ + +## What the Script Does + +At a high level, the script performs the following steps: + +1. **Installs the new ODBC Driver for BigQuery** . +2. **Searches for existing DSNs** that use the legacy Simba BigQuery driver. +3. **Prompts the user to select a DSN** if multiple Simba-based DSNs are found. +4. **Migrates the selected DSN** based on the chosen migration mode (`copy` or + `replace`). +5. **Updates registry entries for Windows** so the DSN points to the new Google + BigQuery ODBC driver. +6. **Decrypts and migrates encrypted credentials** (if present). +7. **Tests the migrated DSN connection** at the end. + +______________________________________________________________________ + +## Supported Drivers + +| Platform | Legacy Driver Detected | Target Driver | +| ------------- | ------------------------------------- | ------------------------ | +| Windows | Simba ODBC Driver for Google BigQuery | ODBC Driver for BigQuery | +| Linux / macOS | Simba Google BigQuery ODBC Connector | ODBC Driver for BigQuery | + +______________________________________________________________________ + +## Migration Modes (All Platforms) + +Both scripts support the same two explicit modes. + +### 1. `copy` Mode (Recommended for first run) + +**Behavior:** + +- Original Simba DSN is **left unchanged** +- A **new DSN is created** using the Google driver +- New DSN name is derived automatically + +| Original DSN | New DSN | +| ------------ | ------------------------------ | +| `MyBQDSN` | `MyBQDSN_Google` (Windows) | +| `MyBQDSN` | `MyBQDSN_google` (Linux/macOS) | + +**Use this when:** + +- You want side‑by‑side validation +- You need a rollback path +- Applications may still depend on the old DSN + +______________________________________________________________________ + +### 2. `replace` Mode (In‑place migration) + +**Behavior:** + +- DSN name remains **unchanged** +- Driver reference and configuration are updated **in place** +- Applications do not need configuration changes + +**Use this when:** + +- You are confident in the new driver +- DSN names must remain stable + +______________________________________________________________________ + +## When the Scripts Prompt for a DSN + +### Common Logic (All Platforms) + +The scripts first discover **all DSNs that use a Simba BigQuery driver**. + +There are three possible scenarios: + +______________________________________________________________________ + +### Scenario A: No Simba DSNs Found + +- Message is printed indicating no migration is needed +- Script exits safely without making changes + +______________________________________________________________________ + +### Scenario B: Exactly One Simba DSN Found + +- That DSN is **automatically selected** +- **No prompt** is shown +- Migration proceeds immediately + +______________________________________________________________________ + +### Scenario C: Multiple Simba DSNs Found + +- The script **lists all matching DSNs** +- The user is **prompted to select exactly one DSN** to migrate + +```text +Multiple Simba DSNs found: + [1] FinanceBQ + [2] AnalyticsBQ + +Enter the number of the DSN you want to migrate: +``` + +- Migration continues only after a valid selection +- Only the selected DSN is migrated. + +This prevents accidental bulk or unintended migrations. + +______________________________________________________________________ + +## Platform‑Specific Details + +## Windows (PowerShell) + +### Script + +``` +migrate.ps1 +``` + +### Usage + +``` +migrate.ps1 -Mode copy -DriverInstaller GoogleBigQueryODBC.msi +migrate.ps1 -Mode replace -DriverInstaller GoogleBigQueryODBC.msi +``` + +### DSN Discovery + +- System DSNs: `HKLM\\SOFTWARE\\ODBC\\ODBC.INI` +- User DSNs: `HKCU\\SOFTWARE\\ODBC\\ODBC.INI` + +### Windows‑Specific Behavior + +- Installs the driver via **silent MSI** (`msiexec /quiet`) +- Copies DSN registry trees +- Updates `Driver` and `TrustedCerts` paths +- Decrypts `KeyFilePath_Enc` using **Windows DPAPI** +- Automatically tests the migrated DSN connection + +> ⚠️ Run from an **elevated PowerShell prompt** when migrating system DSNs. + +______________________________________________________________________ + +## Linux / macOS (Bash) + +### Script + +``` +migrate.sh +``` + +### Usage + +``` +./migrate.sh copy +./migrate.sh replace +``` + +### Installer Input + +The installer argument may be: + +- A directory +- `.zip` +- `.tar.gz` / `.tgz` + +The script automatically extracts and locates the driver library. + +### DSN Discovery + +- Uses `odbc.ini` + +- Resolution order: + + 1. `$ODBCINI` environment variable (if set) + 2. `/etc/odbc.ini` + +### Linux/macOS‑Specific Behavior + +- Copies driver to: + + ``` + /usr/local/lib/google_odbc + ``` + +- Creates a timestamped backup of `odbc.ini` + +- Updates: + + - `[ODBC Data Sources]` + - Individual DSN sections + +- Uses `sudo` automatically when required + +______________________________________________________________________ + +## Configuration Files Modified + +| Platform | Configuration | +| ----------- | --------------------------------- | +| Windows | Registry (ODBC.INI, ODBCINST.INI) | +| Linux/macOS | `odbc.ini` | + +All scripts create backups before modifying configuration. + +______________________________________________________________________ + +## Safety & Best Practices + +- **Start with `copy` mode** on all platforms +- Validate applications against the new DSN +- Keep backups until rollout is complete +- Use `replace` only when DSN names must remain unchanged + +______________________________________________________________________ + +## Summary Table + +| Feature | Windows | Linux/macOS | +| ------------------- | ------------------- | --------------------------- | +| Script | PowerShell | Bash | +| DSN store | Registry | odbc.ini | +| Installer | MSI | dir / zip / tar.gz | +| Copy mode | `_Google` | `_google` | +| Replace mode | In‑place | In‑place | +| Multi‑DSN prompt | Yes | Yes | +| Backup created | Implicit (registry) | Explicit `.bak.` | +| Credential handling | DPAPI decrypt | Plain‑text only | + +______________________________________________________________________ + +This unified behavior ensures a **predictable, safe migration experience across +Windows, Linux, and macOS**. diff --git a/ci/migration/migrate_dsn.ps1 b/ci/migration/migrate_dsn.ps1 new file mode 100644 index 0000000000..a5477cb2b8 --- /dev/null +++ b/ci/migration/migrate_dsn.ps1 @@ -0,0 +1,225 @@ +# !/usr/bin/env powershell +# +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +<# +Migration script for Windows ODBC DSNs +- Migrates from "Simba ODBC Driver for Google Bigquery" +- to "ODBC Driver for BigQuery" + +Usage: + migrate.ps1 -Mode copy -DriverInstaller "GoogleBigQueryODBC.msi" + migrate.ps1 -Mode replace -DriverInstaller "GoogleBigQueryODBC.msi" +#> + +param( + [Parameter(Mandatory=$true)] + [ValidateSet("copy","replace")] + [string]$Mode, + + [Parameter(Mandatory=$true)] + [string]$DriverInstaller +) + +$SimbaDriverName = "Simba ODBC Driver for Google Bigquery" +$GoogleDriverName = "ODBC Driver for BigQuery" + +Write-Host "=== Installing ODBC Driver for BigQuery ===" +Start-Process "msiexec.exe" -ArgumentList "/i `"$DriverInstaller`" /quiet /norestart" -Wait +Write-Host "=== Driver installation complete ===`n" + +# Registry paths for DSNs +$DSNRoots = @( + "HKLM:\SOFTWARE\ODBC\ODBC.INI", + "HKCU:\SOFTWARE\ODBC\ODBC.INI" +) +$DSNListRoot = "ODBC Data Sources" + +function CopyRegistryTree($SourcePath, $TargetPath) { + if (!(Test-Path $TargetPath)) { + New-Item -Path $TargetPath | Out-Null + } + + $props = Get-ItemProperty -Path $SourcePath + foreach ($p in $props.PSObject.Properties) { + if ($p.Name -notlike "PS*") { + Set-ItemProperty -Path $TargetPath -Name $p.Name -Value $p.Value + } + } +} + +# === DPAPI HEX DECRYPTOR === +function Decode-DPAPIHex($hex) { + if ([string]::IsNullOrWhiteSpace($hex)) { return $null } + + Add-Type -AssemblyName System.Security + + # Convert hexadecimal → byte array + $bytes = for ($i = 0; $i -lt $hex.Length; $i += 2) { + [Convert]::ToByte($hex.Substring($i, 2), 16) + } + + # Try LocalMachine first + try { + return [System.Security.Cryptography.ProtectedData]::Unprotect( + $bytes, $null, + [System.Security.Cryptography.DataProtectionScope]::LocalMachine + ) + } + catch { + # Try CurrentUser if LocalMachine does not work + return [System.Security.Cryptography.ProtectedData]::Unprotect( + $bytes, $null, + [System.Security.Cryptography.DataProtectionScope]::CurrentUser + ) + } +} + +function TestODBCConnection($DSN) { + Write-Host " → Testing ODBC connection..." + try { + $conn = New-Object System.Data.Odbc.OdbcConnection("DSN=$DSN") + $conn.Open() + Write-Host " → Connection test: OK" -ForegroundColor Green + $conn.Close() + } catch { + Write-Host " → Connection test: FAILED" -ForegroundColor Red + Write-Host $_.Exception.Message + } +} +$SimbaDSNs = @() + +foreach ($root in $DSNRoots) { + + $dsnTablePath = Join-Path $root $DSNListRoot + if (!(Test-Path $dsnTablePath)) { continue } + + $dsns = Get-ItemProperty $dsnTablePath + foreach ($entry in $dsns.PSObject.Properties) { + + $dsnName = $entry.Name + $driverValue = $entry.Value + + if ($driverValue -eq $SimbaDriverName) { + $SimbaDSNs += [PSCustomObject]@{ + Root = $root + DsnName = $dsnName + DsnTablePath = $dsnTablePath + } + } + } +} + +if ($SimbaDSNs.Count -eq 0) { + Write-Host "No Simba DSNs found. Nothing to migrate." + exit +} +elseif ($SimbaDSNs.Count -eq 1) { + $Chosen = $SimbaDSNs[0] +} +else { + Write-Host "`nMultiple Simba DSNs found:" + for ($i=0; $i -lt $SimbaDSNs.Count; $i++) { + Write-Host " [$($i+1)] $($SimbaDSNs[$i].DsnName) ($($SimbaDSNs[$i].Root))" + } + + do { + $sel = Read-Host "Enter the number of the DSN you want to migrate" + } until ($sel -as [int] -and $sel -ge 1 -and $sel -le $SimbaDSNs.Count) + + $Chosen = $SimbaDSNs[$sel - 1] +} + +Write-Host "`nSelected DSN: $($Chosen.DsnName)" +Write-Host "Location: $($Chosen.Root)`n" + +# =============================================================== +# === Continue the original loop logic ONLY for chosen DSN === +# =============================================================== + +$root = $Chosen.Root +$dsnName = $Chosen.DsnName +$dsnTablePath = $Chosen.DsnTablePath + +Write-Host "`nFound Simba DSN: $dsnName" + +# Source DSN registry key +$srcDSNKey = Join-Path $root $dsnName +if (!(Test-Path $srcDSNKey)) { + Write-Warning "DSN entry exists in table but no registry key: $srcDSNKey" + exit +} + +# Determine target DSN values based on mode +if ($Mode -eq "replace") { + $newDSNName = $dsnName +} else { + $newDSNName = "${dsnName}_Google" +} + +Write-Host " → Target DSN: $newDSNName" +$dstDSNKey = Join-Path $root $newDSNName + +# 1. Update ODBC Data Sources table +Set-ItemProperty -Path $dsnTablePath -Name $newDSNName -Value $GoogleDriverName +if ($Mode -eq "replace" -and $newDSNName -eq $dsnName) { + Set-ItemProperty -Path $dsnTablePath -Name $dsnName -Value $GoogleDriverName +} + +# 2. Copy registry values +CopyRegistryTree -SourcePath $srcDSNKey -TargetPath $dstDSNKey + +# 3. Update Driver DLL +$googleDriverKey = Get-ChildItem "HKLM:\SOFTWARE\ODBC\ODBCINST.INI" | + Where-Object { $_.PSChildName -eq $GoogleDriverName } +if (!$googleDriverKey) { Write-Warning "Google driver not found in ODBCINST.INI"; exit } + +$googleDLL = (Get-ItemProperty $googleDriverKey.PSPath).Driver +Set-ItemProperty -Path $dstDSNKey -Name "Driver" -Value $googleDLL +Write-Host " → Set Driver DLL to: $googleDLL" + +# 3b. Fix TrustedCerts +$driverFolder = Split-Path $googleDLL -Parent +$rootsCert = Join-Path $driverFolder "Assets\roots.pem" +if (Test-Path $rootsCert) { + Set-ItemProperty -Path $dstDSNKey -Name "TrustedCerts" -Value $rootsCert + Write-Host " → Updated TrustedCerts to: $rootsCert" +} else { + Write-Warning " → roots.pem not found in $driverFolder\Assets" +} + +# 4. Decrypt KeyFilePath_Enc +$encKeyPath = (Get-ItemProperty $srcDSNKey -ErrorAction SilentlyContinue).KeyFilePath_Enc +if ($encKeyPath) { + + Write-Host " → Decrypting KeyFilePath_Enc..." + + $rawBytes = Decode-DPAPIHex $encKeyPath + if ($rawBytes) { + $plainPath = [System.Text.Encoding]::UTF8.GetString($rawBytes) + Write-Host " → Decrypted KeyFilePath: $plainPath" + + # Write plain KeyFilePath into new DSN + Set-ItemProperty -Path $dstDSNKey -Name "KeyFilePath" -Value $plainPath + } else { + Write-Warning " → Failed to decrypt KeyFilePath_Enc" + } + } + + # 5. Test DSN connectivity + TestODBCConnection -DSN $newDSNName + +Write-Host "`n=== Migration complete ===" diff --git a/ci/migration/migrate_dsn.sh b/ci/migration/migrate_dsn.sh new file mode 100644 index 0000000000..7a12a1aedd --- /dev/null +++ b/ci/migration/migrate_dsn.sh @@ -0,0 +1,326 @@ +#!/bin/bash +# +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Migration script for Linux/MacOS ODBC DSNs +# - Migrates from "Simba Google BigQuery ODBC Connector" +# - to "ODBC Driver for BigQuery" + +# Usage: +# migrate.sh copy +# migrate.sh replace + +set -euo pipefail + +usage() { + cat < + mode: copy - create new DSN(s) named _google (originals untouched) + replace - update existing Simba DSN(s) in-place (name preserved) + installer-path: directory OR .zip OR .tar.gz/.tgz +Environment: + ODBCINI - optional path to odbc.ini. If not set, /etc/odbc.ini is used. +EOF + exit 1 +} + +if [ $# -ne 2 ]; then + usage +fi + +MODE="$1" +INSTALLER="$2" + +if [ "$MODE" != "copy" ] && [ "$MODE" != "replace" ]; then + echo "ERROR: mode must be 'copy' or 'replace'" + exit 1 +fi + +# Resolve odbc.ini location (env first, then /etc/odbc.ini) +if [ -n "${ODBCINI-}" ]; then + ODBCINI_PATH="$ODBCINI" +elif [ -f "/etc/odbc.ini" ]; then + ODBCINI_PATH="/etc/odbc.ini" +else + echo "ERROR: No odbc.ini found. Set ODBCINI env var or ensure /etc/odbc.ini exists." + exit 1 +fi + +if [ ! -f "$ODBCINI_PATH" ]; then + echo "ERROR: odbc.ini not found at resolved path: $ODBCINI_PATH" + exit 1 +fi + +echo "Using odbc.ini: $ODBCINI_PATH" +echo "Mode: $MODE" +echo "Installer input: $INSTALLER" + +WORKDIR="" +CLEANUP=0 + +# Prepare installer extraction if necessary +if [ -d "$INSTALLER" ]; then + echo "Installer is a directory: $INSTALLER" + WORKDIR="$(realpath "$INSTALLER")" +else + WORKDIR="$(mktemp -d)" + CLEANUP=1 + case "$INSTALLER" in + *.zip) + if ! command -v unzip >/dev/null 2>&1; then + echo "ERROR: unzip is required to extract zip archives." + exit 1 + fi + unzip -q "$INSTALLER" -d "$WORKDIR" + ;; + *.tar.gz | *.tgz) + tar -xzf "$INSTALLER" -C "$WORKDIR" + ;; + *) + echo "ERROR: Installer must be a directory, .zip, or .tar.gz/.tgz" + exit 1 + ;; + esac +fi + +# Find lib/ folder in extracted tree +LIBDIR="" +if [ -d "$WORKDIR/lib" ]; then + LIBDIR="$WORKDIR/lib" +else + LIBDIR="$(find "$WORKDIR" -type d -name lib | head -n 1 || true)" +fi + +if [ -z "$LIBDIR" ] || [ ! -d "$LIBDIR" ]; then + echo "ERROR: Could not find lib/ folder inside installer content." + [ "$CLEANUP" -eq 1 ] && rm -rf "$WORKDIR" + exit 1 +fi + +# Find driver file (prefer .so, else .dylib) +NEW_DRIVER_SRC="$(find "$LIBDIR" -maxdepth 1 -type f -name "*.so" -print -quit || true)" +if [ -z "$NEW_DRIVER_SRC" ]; then + NEW_DRIVER_SRC="$(find "$LIBDIR" -maxdepth 1 -type f -name "*.dylib" -print-quit || true)" +fi + +if [ -z "$NEW_DRIVER_SRC" ]; then + echo "ERROR: No .so or .dylib found in $LIBDIR" + [ "$CLEANUP" -eq 1 ] && rm -rf "$WORKDIR" + exit 1 +fi + +echo "Found driver in installer: $NEW_DRIVER_SRC" + +DEST_DIR="/usr/local/lib/google_odbc" +mkdir -p "$DEST_DIR" +DRIVER_BASENAME="$(basename "$NEW_DRIVER_SRC")" +DEST_DRIVER_PATH="$DEST_DIR/$DRIVER_BASENAME" + +if [ ! -w "$DEST_DIR" ]; then + echo "Copying driver to $DEST_DIR with sudo..." + sudo cp -f "$NEW_DRIVER_SRC" "$DEST_DRIVER_PATH" + sudo chmod 644 "$DEST_DRIVER_PATH" +else + cp -f "$NEW_DRIVER_SRC" "$DEST_DRIVER_PATH" + chmod 644 "$DEST_DRIVER_PATH" +fi + +echo "Driver copied to: $DEST_DRIVER_PATH" + +TS="$(date +%Y%m%d%H%M%S)" +ODBC_BAK="${ODBCINI_PATH}.bak.${TS}" +cp -a "$ODBCINI_PATH" "$ODBC_BAK" +echo "Backup of odbc.ini saved to: $ODBC_BAK" + +# Detect Simba DSNs in [ODBC Data Sources] +SIMBA_DSNS=() +while IFS= read -r line; do + trimmed="$(echo "$line" | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//')" + if [[ "$trimmed" == *=*Simba\ Google\ BigQuery\ ODBC\ Connector* ]]; then + name="${trimmed%%=*}" + SIMBA_DSNS+=("$name") + fi +done < <(awk '/^\[ODBC Data Sources\]/{flag=1; next} /^\[/{flag=0} flag{print}' "$ODBCINI_PATH" || true) + +echo "Detected Simba DSN(s): ${SIMBA_DSNS[*]}" + +############################################################################## +# ★★★ MINIMAL CHANGE: Ask user which DSN to migrate if multiple exist ★★★ +############################################################################## +if [ ${#SIMBA_DSNS[@]} -eq 0 ]; then + echo "No Simba DSNs found. Exiting." + [ "$CLEANUP" -eq 1 ] && rm -rf "$WORKDIR" + exit 0 + +elif [ ${#SIMBA_DSNS[@]} -eq 1 ]; then + SELECTED_DSN="${SIMBA_DSNS[0]}" + +else + echo "Multiple Simba DSNs detected:" + i=1 + for d in "${SIMBA_DSNS[@]}"; do + echo " $i) $d" + i=$((i + 1)) + done + + while true; do + read -rp "Select the DSN to migrate: " choice + if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#SIMBA_DSNS[@]} ]; then + SELECTED_DSN="${SIMBA_DSNS[$((choice - 1))]}" + break + fi + echo "Invalid selection. Try again." + done +fi + +echo "Selected DSN: $SELECTED_DSN" + +# Override the array so downstream code behaves unchanged +SIMBA_DSNS=("$SELECTED_DSN") +############################################################################## + +TMP_ODBC="$(mktemp)" +cp "$ODBCINI_PATH" "$TMP_ODBC" + +# Replace driver & description inside a DSN section +update_dsn_inplace() { + target="$1" + newdrv="$2" + newdesc="$3" + awk -v section="$target" -v drv="$newdrv" -v desc="$newdesc" ' + BEGIN { in_section=0 } + { + if ($0 ~ ("^\\[" section "\\]$")) { print; in_section=1; next } + if ($0 ~ /^\[/ && in_section==1) { in_section=0 } + if (in_section==1) { + if ($0 ~ /^Description=/) { print "Description=" desc; next } + if ($0 ~ /^Driver=/) { print "Driver=" drv; next } + } + print + } + ' "$TMP_ODBC" >"${TMP_ODBC}.new" && mv "${TMP_ODBC}.new" "$TMP_ODBC" +} + +create_dsn_copy() { + orig="$1" + copyname="${orig}_google" + newdrv="$2" + newdesc="$3" + + if awk -v s="[$copyname]" 'BEGIN{found=0} $0==s{found=1} END{exit !found}' "$TMP_ODBC"; then + echo "Note: $copyname already exists; skipping creation." + return + fi + + awk -v s="[$orig]" 'BEGIN{in=0} { + if($0==s){in=1; print; next} + if(in==1 && $0~/^\[/){exit} + if(in==1) print + }' "$TMP_ODBC" >"${TMP_ODBC}.${orig}.section" || true + + if [ ! -s "${TMP_ODBC}.${orig}.section" ]; then + echo "Warning: Original section [$orig] not found; creating minimal section for $copyname" + { + echo "[$copyname]" + echo "Description=$newdesc" + echo "Driver=$newdrv" + } >>"$TMP_ODBC" + return + fi + + awk -v orig="$orig" -v copy="$copyname" -v drv="$newdrv" -v desc="$newdesc" ' + NR==1{ + if($0 ~ ("^\\[" orig "\\]$")){print "[" copy "]"} else {print} + next + } + { + if($0 ~ /^Description=/){ print "Description=" desc; next } + if($0 ~ /^Driver=/){ print "Driver=" drv; next } + print + }' "${TMP_ODBC}.${orig}.section" >>"$TMP_ODBC" + + rm -f "${TMP_ODBC}.${orig}.section" + echo "Created DSN copy: [$copyname]" +} + +NEW_DESC="ODBC Driver for BigQuery DSN" +NEW_DRV="$DEST_DRIVER_PATH" +NEW_DRIVER_NAME="ODBC Driver for BigQuery" + +# Update DSNs +for dsn in "${SIMBA_DSNS[@]}"; do + if [ "$MODE" = "replace" ]; then + awk -v dsn="$dsn" -v newname="$NEW_DRIVER_NAME" ' + BEGIN { in_ods=0 } + { + if ($0 ~ /^\[ODBC Data Sources\]/){ print; in_ods=1; next } + if ($0 ~ /^\[/ && $0 !~ /^\[ODBC Data Sources\]/){ in_ods=0 } + if(in_ods==1){ + if($0 ~ ("^"dsn"[ \t]*=")){ + print dsn "=" newname + next + } + } + print + }' "$TMP_ODBC" >"${TMP_ODBC}.tmp" && mv "${TMP_ODBC}.tmp" "$TMP_ODBC" + + update_dsn_inplace "$dsn" "$NEW_DRV" "$NEW_DESC" + echo "Replaced DSN: [$dsn]" + + else + create_dsn_copy "$dsn" "$NEW_DRV" "$NEW_DESC" + newdsn="${dsn}_google" + + awk -v original="$dsn" -v addeddsn="$newdsn" -v newname="$NEW_DRIVER_NAME" ' + BEGIN{ in_ods=0; printed=0 } + { + if($0 ~ /^\[ODBC Data Sources\]/){ print; in_ods=1; next } + if($0 ~ /^\[/ && $0 !~ /^\[ODBC Data Sources\]/){ + if(in_ods==1 && printed==0){ + print addeddsn "=" newname + printed=1 + } + in_ods=0 + } + if(in_ods==1){ print; next } + print + } + END{ + if(in_ods==1 && printed==0) print addeddsn "=" newname + } + ' "$TMP_ODBC" >"${TMP_ODBC}.tmp" && mv "${TMP_ODBC}.tmp" "$TMP_ODBC" + + echo "Created DSN copy: [$newdsn]" + fi +done + +# Write output back +if [ -w "$ODBCINI_PATH" ]; then + mv "$TMP_ODBC" "$ODBCINI_PATH" +else + echo "Updating $ODBCINI_PATH with sudo..." + sudo mv "$TMP_ODBC" "$ODBCINI_PATH" + sudo chown root:root "$ODBCINI_PATH" || true + sudo chmod 644 "$ODBCINI_PATH" || true +fi + +echo "Updated odbc.ini successfully: $ODBCINI_PATH" +echo "Backup retained at: $ODBC_BAK" +echo "Driver in use: $NEW_DRV" +echo "Mode finished: $MODE" + +[ "$CLEANUP" -eq 1 ] && rm -rf "$WORKDIR" + +exit 0