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
42 changes: 42 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,48 @@ endif
build:
go build -o ${OUTPUT_PATH} ./cmd/baton-sql

# DB2 support requires the native IBM CLI driver at build and run time (see docs/db2.md).
# Flags are scoped to this target only — do not export CGO_CFLAGS/CGO_LDFLAGS in your shell.
# -ldb2 is omitted on purpose: go_ibm_db's cgo directives already add it.
#
# The binary resolves libdb2 relative to itself first (./clidriver/lib next to the
# executable), then at the build-time DB2HOME, so one artifact works both untarred
# alongside a bundled driver (see package-db2) and on hosts with a system-wide install.
# On macOS, libdb2.dylib's install name is the bare filename, so the darwin steps rewrite
# the load command to @rpath and re-sign (ad-hoc). No LD_LIBRARY_PATH/DYLD_LIBRARY_PATH
# is needed at run time in either layout.
DB2HOME ?= /usr/local/clidriver

ifeq ($(GOOS),darwin)
DB2_RPATH_FLAGS = -Wl,-rpath,@executable_path/clidriver/lib -Wl,-rpath,$(DB2HOME)/lib
else
DB2_RPATH_FLAGS = -Wl,-rpath,$$ORIGIN/clidriver/lib -Wl,-z,origin -Wl,-rpath,$(DB2HOME)/lib
endif

.PHONY: build-db2
build-db2:
CGO_CFLAGS='-I$(DB2HOME)/include' CGO_LDFLAGS='-L$(DB2HOME)/lib $(DB2_RPATH_FLAGS)' \
go build -tags db2 -o ${OUTPUT_PATH} ./cmd/baton-sql
ifeq ($(GOOS),darwin)
install_name_tool -change libdb2.dylib @rpath/libdb2.dylib ${OUTPUT_PATH}
codesign -f -s - ${OUTPUT_PATH}
endif

# Self-contained DB2 distribution: binary + clidriver (including its license/ directory,
# which the IBM redistribution terms require shipping) in one archive. Untar and run —
# no installation, no environment variables.
BUNDLE_NAME = baton-sql-db2-${GOOS}-${GOARCH}
BUNDLE_DIR = dist/${BUNDLE_NAME}

.PHONY: package-db2
package-db2: build-db2
rm -rf ${BUNDLE_DIR} ${BUNDLE_DIR}.tar.gz
mkdir -p ${BUNDLE_DIR}
cp ${OUTPUT_PATH} ${BUNDLE_DIR}/
cp -R $(DB2HOME) ${BUNDLE_DIR}/clidriver
tar -czf ${BUNDLE_DIR}.tar.gz -C dist ${BUNDLE_NAME}
@echo "Created ${BUNDLE_DIR}.tar.gz"

.PHONY: update-deps
update-deps:
go get -d -u ./...
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- PostgreSQL
- Vertica
- Amazon Redshift
- IBM DB2 — opt-in: requires a binary built with the `db2` tag and IBM's native CLI driver; see [docs/db2.md](docs/db2.md)

## Configuration

Expand Down
181 changes: 181 additions & 0 deletions docs/db2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# DB2 Support

IBM DB2 support is **opt-in** and built with `make build-db2`. Unlike every other engine in
baton-sql (all pure Go), DB2 uses the cgo driver `github.com/ibmdb/go_ibm_db`, which links
against IBM's native ODBC/CLI driver ("clidriver"). No pure-Go DB2 driver exists — the native
dependency cannot be eliminated, so it is isolated behind the `db2` build tag instead.

What this means in practice:

- `make build` (and CI, releases, cross-compilation) needs **no** IBM software, no CGO flags,
and no environment variables. DB2 code is excluded from default builds.
- A default binary given a `db2://` DSN fails with a clear error:
`baton-sql: DB2 support not compiled into this binary; rebuild with -tags db2 (see docs/db2.md)`.
- `make build-db2` produces a DB2-capable binary. The CGO flags are scoped to that one make
target — **do not export `CGO_CFLAGS`/`CGO_LDFLAGS` in your shell profile**. Global CGO flags
leak into every cgo link on your machine and break unrelated Go builds.
- The built binary needs no `LD_LIBRARY_PATH`/`DYLD_LIBRARY_PATH` at run time: the Makefile
bakes the lookup into the binary. It resolves the clidriver **relative to itself first**
(`./clidriver` next to the executable), then falls back to the build-time `DB2HOME` path —
so `make package-db2` produces a self-contained archive customers untar and run anywhere.

## Prerequisites

- Go 1.24 or later and a C compiler (clang/gcc)
- IBM DB2 ODBC/CLI driver (clidriver) for your platform — free download, no license key

## Step 1: Install the clidriver

Download the archive for your platform from
`https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/`:

| Platform | Archive |
|----------|---------|
| macOS ARM64 (Apple Silicon) | `macarm64_odbc_cli.tar.gz` |
| macOS x86_64 (Intel) | `macos64_odbc_cli.tar.gz` |
| Linux x86_64 | `linuxx64_odbc_cli.tar.gz` |
| Linux ARM64 | **Not available** — IBM does not ship a Linux ARM64 clidriver |
| Linux ppc64le | `linuxppc64le_odbc_cli.tar.gz` |

```bash
curl -O https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/macarm64_odbc_cli.tar.gz
tar -xzf macarm64_odbc_cli.tar.gz
sudo mv clidriver /usr/local/
rm macarm64_odbc_cli.tar.gz
```

Any location works; `/usr/local/clidriver` is the default the Makefile assumes.

## Step 2: Build

```bash
make build-db2 # clidriver at /usr/local/clidriver
DB2HOME=/opt/clidriver make build-db2 # clidriver elsewhere
```

That's it — no shell configuration. The target runs
`go build -tags db2` with `CGO_CFLAGS`/`CGO_LDFLAGS` set inline for that command only
(`-ldb2` is deliberately omitted; the driver's own cgo directives add it), then on macOS
rewrites the dylib load command with `install_name_tool` and re-signs the binary.

```bash
# Verify — runs without any library-path environment variables:
./dist/darwin_arm64/baton-sql -h
```

## Distributing to customers (binary bundle)

Most deployments run the bare binary, not Docker. `make package-db2` produces a single
self-contained archive:

```bash
make package-db2
# -> dist/baton-sql-db2-<os>-<arch>.tar.gz (~55 MB)
```

The archive holds the binary with the clidriver beside it (including the `license/`
directory IBM's redistribution terms require):

```
baton-sql-db2-linux-amd64/
├── baton-sql
└── clidriver/
```

The customer untars it anywhere and runs `./baton-sql` — no installation, no root, no
environment variables. The binary finds the driver via its baked-in relative path
(`$ORIGIN/clidriver/lib` on Linux, `@executable_path/clidriver/lib` on macOS); if a
system-wide clidriver exists at the build-time `DB2HOME` it serves as fallback. The two
directories must stay side by side — moving the binary out of the bundle alone breaks the
relative lookup (the fallback still applies if present).

Platform notes: Linux bundles are glibc-only (no Alpine/musl) and x86_64 only — IBM ships
no Linux ARM64 clidriver. The clidriver needs `libxml2` from the OS (`apt-get install
libxml2` / `yum install libxml2`) — preinstalled on virtually every full server distro,
absent in minimal container images. A Linux bundle must be built on Linux (cgo — use the
Docker builder below or any amd64 Linux host).

## DSN Format

Standard URL form (recommended) — converted to DB2's native format automatically:

```
db2://username:password@hostname:port/database
db2://db2inst1:pass123@localhost:50000/TESTDB
```

Query parameters are forwarded as additional DB2 connection keywords
(e.g. `db2://user:pass@host:50000/DB?Security=SSL` adds `SECURITY=SSL`). Values containing
`;` are brace-quoted automatically; parameters that would override the URL-derived keywords
(`HOSTNAME`, `DATABASE`, `PORT`, `PROTOCOL`, `UID`, `PWD`) are rejected — use the native
form below for full control.

DB2's native form is also accepted as-is:

```
HOSTNAME=localhost;PORT=50000;DATABASE=TESTDB;UID=db2inst1;PWD=pass123;PROTOCOL=TCPIP
```

## Troubleshooting

**`'sqlcli1.h' file not found`** — clidriver missing or `DB2HOME` wrong. Check that
`$DB2HOME/include/sqlcli1.h` exists. If you see this from `make build` (not `build-db2`),
something reintroduced the driver into the default build — the `db2` tag gate is broken.

**`found architecture 'x86_64', required architecture 'arm64'`** — wrong clidriver archive
for your CPU; see the table above.

**`Library not loaded: libdb2.dylib` / `libdb2.so: cannot open shared object file`** — the
binary can't find the clidriver in either of its baked-in locations: `./clidriver/lib` next
to the executable, then the build-time `DB2HOME` path. Restore the bundle layout (binary and
`clidriver/` side by side), rebuild with `make build-db2`, or set
`LD_LIBRARY_PATH`/`DYLD_LIBRARY_PATH` to the clidriver `lib` dir as a stopgap. This also
happens when `go build -tags db2` is invoked directly, skipping the Makefile's
rpath/install_name steps. Note macOS SIP strips `DYLD_*` variables across protected
binaries — the baked paths are the reliable option.

**`error while loading shared libraries: libxml2.so.2`** (Linux) — the clidriver depends on
the OS libxml2 package: `apt-get install libxml2` / `yum install libxml2`.

**`go vet` / `golangci-lint` with `-tags db2` fails** — type-checking the tagged path needs
the clidriver headers too. Default-tag lint and vet need nothing.

## Docker

- The default release pipeline (goreleaser, `CGO_ENABLED=0`) is unaffected — DB2 does not
ride the standard release. Ship DB2 as the binary bundle above or as a separate Docker
image.
- Use a **glibc** base (Debian/Ubuntu/UBI). The clidriver does not support musl, so Alpine
is out. **linux/amd64 only** (no ARM64 clidriver).
- Bake the rpath at build time and copy the clidriver into the runtime image at the same
path — no `LD_LIBRARY_PATH` needed.

```dockerfile
FROM golang:1.24-bookworm AS builder
RUN curl -sO https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/linuxx64_odbc_cli.tar.gz && \
tar -xzf linuxx64_odbc_cli.tar.gz && mv clidriver /opt/clidriver
WORKDIR /src
COPY . .
RUN CGO_CFLAGS="-I/opt/clidriver/include" \
CGO_LDFLAGS="-L/opt/clidriver/lib -Wl,-rpath,/opt/clidriver/lib" \
go build -tags db2 -o /baton-sql ./cmd/baton-sql

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends libxml2 && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /opt/clidriver /opt/clidriver
COPY --from=builder /baton-sql /baton-sql
ENTRYPOINT ["/baton-sql"]
```

### Redistribution licensing

Bundling the clidriver in a distributed image is expressly permitted by the IBM license's
"Redistributables" clause (IPLA + License Information document, shipped inside the tarball
at `clidriver/license/`), with conditions: distribute only the files enumerated in
`odbc_REDIST.txt`, keep the `license/` directory and copyright notices, and your end-user
terms must be at least as protective of IBM as IBM's own. Two caveats before shipping to
customers: the GSKit TLS libraries (`libgsk8*`) are not in the enumerated redistributables
list — get legal/IBM sign-off if TLS connections to DB2 are needed — and the Db2 Connect
license file (`db2consv_*.lic`, required for direct z/OS or IBM i connections) is **not**
redistributable; customers must supply their own.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/go-sql-driver/mysql v1.9.2
github.com/google/cel-go v0.28.1
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/ibmdb/go_ibm_db v0.5.2
github.com/jackc/pgx/v5 v5.10.0
github.com/microsoft/go-mssqldb v1.8.0
github.com/quasilyte/go-ruleguard/dsl v0.3.23
Expand Down Expand Up @@ -88,6 +89,7 @@ require (
github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLW
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ibmdb/go_ibm_db v0.5.2 h1:g5bHeJdy4SXhw6c9PX1I3Tn4KrCbAzl2faX1BfTTR/8=
github.com/ibmdb/go_ibm_db v0.5.2/go.mod h1:BA12Alfe+h5BMGZGE+b0pqP4leILZkpoxe5qr/iMoHw=
github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70 h1:muF5XqVkHnMdbMDXusPdKtuT8qWzefBgSuLH1JVHcC4=
github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70/go.mod h1:NSpUK0x9IyEoM1EjTp2/S8ErxZfRHoA2DfwiYobFSkc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
Expand Down
8 changes: 8 additions & 0 deletions pkg/bsql/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ func parseTimeWithEngine(value string, dbEngine database.DbEngine) (*time.Time,
"2006-01-02 15:04:05",
time.RFC3339,
}
case database.DB2:
// DB2 common formats, including its native dash/dot TIMESTAMP string form
prioritizedFormats = []string{
"2006-01-02 15:04:05.000000",
"2006-01-02-15.04.05.000000",
"2006-01-02 15:04:05",
time.RFC3339,
}
default:
// Try the generic time parser for unknown engines
return parseTime(value)
Expand Down
2 changes: 2 additions & 0 deletions pkg/bsql/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ func (s *SQLSyncer) getNextPlaceholder(qArgs []interface{}) string {
return fmt.Sprintf(":%d", len(qArgs))
case database.Vertica:
return "?"
case database.DB2:
return "?"
default:
return "?"
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"regexp"
"strings"

"github.com/conductorone/baton-sql/pkg/database/db2"
"github.com/conductorone/baton-sql/pkg/database/hdb"
"github.com/conductorone/baton-sql/pkg/database/mysql"
"github.com/conductorone/baton-sql/pkg/database/oracle"
Expand All @@ -32,6 +33,7 @@ const (
Oracle
HDB
Vertica
DB2
)

// ConnectOptions represents the structured configuration used to build a DSN.
Expand Down Expand Up @@ -425,6 +427,13 @@ func Connect(ctx context.Context, opts ConnectOptions) (*sql.DB, DbEngine, error
}
return db, Vertica, nil

case "db2":
db, err := db2.Connect(ctx, parsedDsn.String())
if err != nil {
return nil, Unknown, err
}
return db, DB2, nil

default:
return nil, Unknown, fmt.Errorf("unsupported database scheme: %s", parsedDsn.Scheme)
}
Expand Down
37 changes: 37 additions & 0 deletions pkg/database/db2/db2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build db2

package db2

import (
"context"
"database/sql"
"time"

_ "github.com/ibmdb/go_ibm_db"
)

// Connect establishes a connection to DB2 database.
func Connect(ctx context.Context, dsn string) (*sql.DB, error) {
// Convert URL format to DB2 DSN format if needed
db2DSN, err := convertToDB2DSN(dsn)
if err != nil {
return nil, err
}

db, err := sql.Open("go_ibm_db", db2DSN)
if err != nil {
return nil, err
}

db.SetConnMaxLifetime(time.Minute * 5)
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)

// Test the connection
if err := db.PingContext(ctx); err != nil {
db.Close()
return nil, err
}

return db, nil
}
16 changes: 16 additions & 0 deletions pkg/database/db2/db2_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build !db2

package db2

import (
"context"
"database/sql"
"errors"
)

// Connect is a stub used when DB2 support is not compiled in. The go_ibm_db
// driver requires cgo and the IBM clidriver at build time, so it is only
// included when building with -tags db2.
func Connect(_ context.Context, _ string) (*sql.DB, error) {
return nil, errors.New("baton-sql: DB2 support not compiled into this binary; rebuild with -tags db2 (see docs/db2.md)")
}
Loading
Loading