diff --git a/.github/workflows/go.yml b/.github/workflows/acceptance.yml similarity index 60% rename from .github/workflows/go.yml rename to .github/workflows/acceptance.yml index fe73d8c..9dcba38 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/acceptance.yml @@ -1,27 +1,6 @@ -name: Go +name: Acceptance Tests on: [push] jobs: - build: - name: Unit Tests - runs-on: ubuntu-latest - steps: - - name: Setup go - uses: actions/setup-go@v4 - with: - go-version: '^1.20' - id: go - - - name: Check out code into the Go module directory - uses: actions/checkout@v4 - - - name: Get dependencies - run: | - go get -v -t -d ./... - - - name: Test - env: - GOPROXY: "https://proxy.golang.org" - run: go test -v . mariadb_test: name: MariaDB Acceptance Tests runs-on: ubuntu-latest diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d8738df --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: Build +on: [push] +jobs: + build: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - name: Setup go + uses: actions/setup-go@v4 + with: + go-version: '^1.20' + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v4 + + - name: Get dependencies + run: | + go get -v -t -d ./... + + - name: Test + env: + GOPROXY: "https://proxy.golang.org" + run: go test -v . \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..b2edef5 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,84 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '20 5 * * 4' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/Makefile b/Makefile index 4315a57..2eff84a 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,9 @@ build: docker compose down --remove-orphans docker compose build +fmt: + go fmt ./... + test: test-mariadb test-postgres test-mssql test-mariadb: diff --git a/README.md b/README.md new file mode 100644 index 0000000..06ad6dc --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +# Go unixodbc driver + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![Build](https://github.com/ninthclowd/unixodbc/actions/workflows/build.yml/badge.svg?branch=main) +![Acceptance Tests](https://github.com/ninthclowd/unixodbc/actions/workflows/acceptance.yml/badge.svg?branch=main) +![CodeQL](https://github.com/ninthclowd/unixodbc/actions/workflows/codeql.yml/badge.svg?branch=main) + + +This topic provides instructions for installing, running, and modifying the go unixodbc driver for connecting to ODBC databases +through [unixodbc](https://www.unixodbc.org/). The driver supports Go's [database/sql](https://golang.org/pkg/database/sql/) package. + +# Prerequisites + +The following software packages are required to use the go unixodbc driver. +## Go + +The latest driver requires the [Go language](https://golang.org/) 1.20 or higher. + +## Operating System +This driver was primarily developed with support of [Debian 12](https://www.debian.org/releases/bookworm/), however other +linux distributions may work correctly providing that [unixodbc](https://www.unixodbc.org/) 2.3.11 is installed. + + +## [unixodbc](https://www.unixodbc.org/) +[unixodbc](https://www.unixodbc.org/) 2.3.11 or greater must on the system your application is running on. For debian, the following packages must be installed in order for this +driver to connect: +- libssl-dev +- unixodbc-common +- unixodbc-dev +- unixodbc + +## Supported Databases and Database Drivers +The driver has been developed and tested for the following databases with the corresponding driver, but may work with other proprietary databases: + +| Database | Tested Version | Database Driver | +|-----------------------------------------|----------------|-------------------------------------------------------------------| +| [postgres](https://www.postgresql.org/) | 16 | [odbc-postgresql](https://odbc.postgresql.org/) | +| Microsoft SQL Server | 2022 | [tdsodbc](https://www.freetds.org/) | +| [mariadb](https://mariadb.org/) | 10.9 | [odbc-mariadb](https://mariadb.com/kb/en/mariadb-connector-odbc/) | + +# Configuration + +## Basic Connection +Typical connection to the database can be established by importing the go unixodbc driver and opening the connection with +[sql.Open](https://pkg.go.dev/database/sql#Open). +The connection string to use will be specific to the [database and database driver](#supported-databases-and-database-drivers) +that you are using: +```go +package main + +import ( + _ "github.com/ninthclowd/unixodbc" + "database/sql" +) + +func main(){ + db = sql.Open("unixodbc", "DSN=EXAMPLE") +} +``` + +## Prepared Statement Caching +The driver supports prepared statement caching on each connection using a LRU algorithm by connecting to the database +with [sql.OpenDB](https://pkg.go.dev/database/sql#OpenDB) and supplying an +[unixodbc.Connector](https://pkg.go.dev/github.com/ninthclowd/unixodbc#Connector): +```go +package main + +import ( + _ "github.com/ninthclowd/unixodbc" + "database/sql" +) + +func main(){ + db = sql.OpenDB(&unixodbc.Connector{ + ConnectionString: unixodbc.StaticConnStr("DSN=EXAMPLE"), + StatementCacheSize: 5, // number of prepared statements to cache per connection + }) +} +``` + +## Dynamic Connection Strings +The driver supports dynamic connection strings for use in databases that require token based authentication. This can +be accomplished by implementing [unixodbc.ConnectionStringFactory](https://pkg.go.dev/github.com/ninthclowd/unixodbc#ConnectionStringFactory) +and connecting with [sql.OpenDB](https://pkg.go.dev/database/sql#OpenDB): + +```go +package main + +import ( + "github.com/ninthclowd/unixodbc" + _ "github.com/ninthclowd/unixodbc" + "database/sql" +) + +type GetToken struct { +} + +// ConnectionString implements unixodbc.ConnectionStringFactory +func (d *GetToken) ConnectionString() (connStr string, err error) { + var token string + // insert code to pull token for each new connection + // ... + + // create a dynamic connection string and return it + connStr = "DSN=EXAMPLE;UID=User;PWD=" + token + return +} + +func main() { + db = sql.OpenDB(&unixodbc.Connector{ + ConnectionString: &GetToken{}, + }) +} +``` + + + + +# Development + +The developer notes are hosted with the source code on [GitHub](https://github.com/ninthclowd/unixodbc). + + +## Acceptance Testing + +At this time, most driver functionality is validated through the acceptance tests in the [test/acceptance](test/acceptance) +folder using the corresponding [workflow](.github/workflows/acceptance.yml) and [docker-compose.yml](docker-compose.yml). +To run these tests, ensure [Docker](https://www.docker.com/) is installed and run `make test`. + +## Submitting Pull Requests + +You may use your preferred editor to edit the driver code. If you are adding support for a new database, please include +new [Acceptance Tests](#acceptance-testing) for the database if possible. Please `make fmt` to format your code +and `make test` to validate your code before submitting. diff --git a/connector.go b/connector.go index 47c054f..c9773ed 100644 --- a/connector.go +++ b/connector.go @@ -16,6 +16,8 @@ func (s StaticConnStr) ConnectionString() (string, error) { return string(s), nil } +// ConnectionStringFactory can be implemented to provide dynamic connection strings for each new connection to the +// database, allowing for connections to systems that require token based authentication type ConnectionStringFactory interface { ConnectionString() (string, error) } @@ -25,17 +27,16 @@ var _ io.Closer = (*Connector)(nil) // Connector can be used with sql.OpenDB to allow more control of the unixodbc driver type Connector struct { - //ConnectionString is a factory that generates connection strings for each new connection that is opened. + // ConnectionString is a factory that generates connection strings for each new connection that is opened. //Use StaticConnStr if you have a static connection string that does not need to change with each new connection. //Ex: If you are connecting using a system DSN called "myDatabase", this could be: // StaticConnStr("DSN=myDatabase") - ConnectionString ConnectionStringFactory + ConnectionString ConnectionStringFactory + // StatementCacheSize is the number of prepared statements to cache for each connection. The driver will cache + // statements up to this limit and purge them using the least recently used algorithm. 0 will disable prepared + // statement caching. StatementCacheSize int - UseODBCCursor bool //SQL_ATTR_ODBC_CURSORS TODO - PacketSize uint32 //SQL_ATTR_PACKET_SIZE TODO - TraceFile string //SQL_ATTR_TRACEFILE TODO - odbcEnvironment *odbc.Environment } diff --git a/driver.go b/driver.go index b683c23..4102138 100644 --- a/driver.go +++ b/driver.go @@ -12,6 +12,7 @@ func init() { sql.Register("unixodbc", driverInstance) } +// OpenHandles reports the number of handles open to unixodbc. Useful in testing to ensure all handles are closed correctly. func OpenHandles() int64 { return odbc.OpenHandles() }