├── .github └── workflows │ └── workflow.yml ├── .gitignore ├── CHANGELOG ├── Dockerfile ├── LICENSE ├── README.md ├── Setup.hs ├── app └── Main.hs ├── appveyor.yml ├── bench └── Space.hs ├── cbits └── odbc.c ├── doc ├── highlight.pack.js ├── init.js ├── patch.sh └── style.css ├── odbc.cabal ├── src └── Database │ └── ODBC │ ├── Conversion.hs │ ├── Internal.hs │ ├── SQLServer.hs │ └── TH.hs ├── stack.yaml ├── stack.yaml.lock ├── test ├── Main.hs └── test-ghcs └── upload-haddocks.sh /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | build: 9 | name: CI 10 | runs-on: ${{ matrix.os }} 11 | services: 12 | mysql: 13 | image: mcr.microsoft.com/mssql/server:2019-latest@sha256:a098c9ff6fbb8e1c9608ad7511fa42dba8d22e0d50b48302761717840ccc26af 14 | env: 15 | ACCEPT_EULA: "Y" 16 | SA_PASSWORD: "Passw0rd" 17 | ports: 18 | - 1433:1433 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | include: 23 | - os: ubuntu-20.04 24 | # can't run tests on Windows as "Container operations are only supported on Linux runners" 25 | #- os: windows-2019 26 | steps: 27 | - name: Clone project 28 | uses: actions/checkout@v2 29 | - name: Cache dependencies 30 | uses: actions/cache@v2 31 | with: 32 | path: ~/.stack 33 | key: ${{ runner.os }} 34 | - name: Build and run tests 35 | shell: bash 36 | run: | 37 | set -ex 38 | stack upgrade 39 | stack --version 40 | ODBC_TEST_CONNECTION_STRING='DRIVER={ODBC Driver 17 for SQL Server};SERVER=127.0.0.1;Uid=SA;Pwd=Passw0rd;Encrypt=no' stack test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work 2 | _release 3 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.3.0: 2 | * Add support for DATETIMEOFFSET 3 | * Add support for text-2.0 4 | 0.2.6: 5 | * Add support for SQLSTATE 6 | * Fix copying issues for error messages 7 | * Add support for GHC 9.0 8 | 0.2.5: 9 | * Add binding of parameters for text/binary 10 | * Fix null uniqueidentifier returning non-null value 11 | * Include column names in output 12 | * Support WCHAR 13 | 14 | 0.2.2: 15 | * Accept smaller int/floats in bigger types 16 | 17 | 0.2.1: 18 | * Expose rawUnescapedText 19 | 20 | 0.2.0: 21 | * Drop Maybes, use NullValue. 22 | 23 | 0.1.1: 24 | * Fix Smalldatetime ToSql instance to set seconds to 0. 25 | * Add support for numeric. 26 | 27 | 0.1.0: 28 | * Removed instance of ToSql for LocalTime, added two new 29 | newtypes: Datetime2 and Smalldatetime. 30 | 31 | 0.0.4: 32 | * Improved non-Unicode field support: varchar/text treated as 33 | actual binary, not "characters" in the SQL server sense. 34 | 35 | 0.0.3: 36 | * Handle multiple statements in exec call. 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:9-slim 2 | MAINTAINER Chris Done 3 | 4 | # Haskell system dependencies 5 | 6 | RUN apt-get update && apt-get install -yq --no-install-suggests --no-install-recommends --force-yes -y netbase git ca-certificates xz-utils build-essential curl && curl -sSL https://get.haskellstack.org/ | sh 7 | 8 | # ODBC system dependencies 9 | 10 | RUN apt-get install -y gnupg apt-transport-https 11 | RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - 12 | RUN curl https://packages.microsoft.com/config/debian/9/prod.list > /etc/apt/sources.list.d/mssql-release.list 13 | RUN apt-get update 14 | RUN ACCEPT_EULA=Y apt-get install msodbcsql17 -y 15 | RUN apt-get install -y unixodbc-dev freetds-dev locales 16 | RUN locale-gen en_US.UTF-8 17 | 18 | # Clone repo 19 | 20 | COPY travis_commit.txt travis_commit.txt 21 | RUN git clone https://github.com/fpco/odbc.git && \ 22 | cd odbc && \ 23 | echo Checking out $(cat ../travis_commit.txt) && \ 24 | git checkout $(cat ../travis_commit.txt) 25 | 26 | # Install GHC and Haskell build dependencies 27 | 28 | RUN cd odbc && stack setup && stack build --dependencies-only --test --no-run-tests 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright FP Complete (c) 2017 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of FP Complete nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # odbc [![Build Status](https://github.com/fpco/odbc/actions/workflows/workflow.yml/badge.svg?branch=master)](https://github.com/fpco/odbc/actions) [![Build status](https://ci.appveyor.com/api/projects/status/vpn6a1pme25upbux?svg=true)](https://ci.appveyor.com/project/chrisdone/odbc-0os0b) 2 | 3 | Haskell binding to the ODBC API, with a strong emphasis on stability, 4 | testing and simplicity. 5 | 6 | ## Platform and database support 7 | 8 | The following database drivers are tested against in CI: 9 | 10 | * Microsoft SQL Server 2019 11 | 12 | The following operating systems are tested against in CI: 13 | 14 | * Windows [![Build status](https://ci.appveyor.com/api/projects/status/vpn6a1pme25upbux?svg=true)](https://ci.appveyor.com/project/chrisdone/odbc-0os0b) 15 | * Linux [![Build Status](https://github.com/fpco/odbc/actions/workflows/workflow.yml/badge.svg?branch=master)](https://github.com/fpco/odbc/actions) 16 | 17 | ## How ODBC works 18 | 19 | ODBC is a C API that is split into a *manager* and a *driver*. 20 | 21 | On Windows, there is an ODBC manager that comes with the OS. On Linux 22 | and OS X, the unixODBC package provides the same functionality. 23 | 24 | Separately, for each database type, you have driver packages. When you 25 | provide a connection string, like this: 26 | 27 | ``` 28 | ODBC_TEST_CONNECTION_STRING='DRIVER={ODBC Driver 13 for SQL Server};SERVER=127.0.0.1;Uid=SA;Pwd=Passw0rd;Encrypt=no' 29 | ``` 30 | 31 | The `DRIVER` tells the ODBC API which library to use. In this case, 32 | it's the recent SQL Server driver provided by Microsoft. Then, ODBC 33 | functions like `SQLDriverConnectW` will call that library. 34 | 35 | ## How to connect to Microsoft SQL Server 36 | 37 | In recent years, Microsoft has released binary drivers for SQL Server 38 | for Windows, Linux and OS X, with a guide for each operating 39 | system. That guide for the latest and greatest official Microsoft 40 | driver is 41 | [here](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). 42 | 43 | You can use the [SQL Server docker image](https://hub.docker.com/_/microsoft-mssql-server) to easily run SQL Server anywhere in a few seconds. 44 | 45 | I have tested the OS X instructions on my own machine. 46 | [This project's Dockerfile](https://github.com/fpco/odbc/blob/master/Dockerfile) 47 | follows setup instructions for Linux, and 48 | [the AppVeyor file](https://github.com/fpco/odbc/blob/master/appveyor.yml) 49 | follows the setup instructions for Windows. 50 | 51 | There is a test program that comes with the package called `odbc` 52 | which accepts a connection string as its argument. You can use this to 53 | test your connection easily. 54 | 55 | (Use `17` instead of `13` if that's the driver you installed.) 56 | 57 | $ stack exec odbc 'DRIVER={ODBC Driver 13 for SQL Server};SERVER=192.168.99.101;Uid=SA;Pwd=Passw0rd;Encrypt=no' 58 | > create table foo (i int) 59 | Rows: 0 60 | > insert into foo values (123123123) 61 | Rows: 0 62 | > select * from foo 63 | 123123123 64 | Rows: 1 65 | 66 | ## Check your package is working 67 | 68 | You can spin up a SQL Server in docker and connect to it with the 69 | trivial binary `odbc` that comes with this package: 70 | 71 | ``` 72 | $ docker run --net=host -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Passw0rd' -d mcr.microsoft.com/mssql/server:2017-CU8-ubuntu 73 | Unable to find image 'mcr.microsoft.com/mssql/server:2017-CU8-ubuntu' locally 74 | 2017-CU8-ubuntu: Pulling from mssql/server 75 | 4fa80d7b805d: Pull complete 76 | 484dd0f2fbdc: Pull complete 77 | 47004b22ec62: Pull complete 78 | b70745c852a2: Pull complete 79 | 718060832ef2: Pull complete 80 | 5594e4e5950b: Pull complete 81 | 5b67719e2956: Pull complete 82 | 7d648891de3f: Pull complete 83 | e0d1b3db20c8: Pull complete 84 | ded313a21911: Pull complete 85 | Digest: sha256:e1708b7d3aaf4a693ef8785f15a8b4d082939681e373c4090fd0b294d1501e57 86 | Status: Downloaded newer image for mcr.microsoft.com/mssql/server:2017-CU8-ubuntu 87 | ba1ad8b726c7e958bad6d2f7b051514f218c3024984f388adab2d6bb7751ea90 88 | 89 | $ stack exec odbc 'DRIVER={ODBC Driver 17 for SQL Server};SERVER=127.0.0.1;Uid=SA;Pwd=Passw0rd;Encrypt=no' 90 | > select 2 * 3; 91 | 6 92 | Rows: 1 93 | > 94 | $ 95 | ``` 96 | 97 | ## Common issues 98 | 99 | 100 | ### Can't open lib 'ODBC Driver 13 for SQL Server' 101 | 102 | If you see an error like this: 103 | 104 | [unixODBC][Driver Manager]Can't open lib 'ODBC Driver 13 for SQL Server' : file not found 105 | 106 | Then you might be trying to use the wrong driver. You might have 107 | installed version `17`, so change the string to `ODBC Driver 17 for 108 | SQL Server`. 109 | 110 | If it still says this, you might have to configure an odbcinst.ini 111 | file: 112 | 113 | ``` yaml 114 | [ODBC Driver 17 for SQL Server] 115 | Driver = 116 | ``` 117 | 118 | In Nix, this might be where is the result of evaluating 119 | `${nixpkgs.unixODBCDrivers.msodbcsql17}/lib/libmsodbcsql-17.7.so.1.1"`. 120 | 121 | Which would need the following packages available: 122 | 123 | * nixpkgs.freetds 124 | * nixpklgs.unixODBC 125 | * nixpkgs.unixODBCDrivers.msodbcsql17 126 | 127 | ### Data source name not found and no default driver specified 128 | 129 | If you see an error like this: 130 | 131 | [unixODBC][Driver Manager]Data source name not found and no default driver specified 132 | 133 | This is a terrible error message. If passing your DSN via a shell 134 | environment variable or argument, check that your input string isn't 135 | quoted e.g. `"Driver=.."` instead of `Driver=..` due to silly shell 136 | scripting quoting issues. 137 | 138 | ## Terminating with uncaught exception of type 139 | 140 | If you see an error like this on OS X with driver version 17, 141 | 142 | ``` 143 | libc++abi.dylib: terminating with uncaught exception of type 144 | std::runtime_error: collate_byname::collate_byname failed to construct 145 | for C/en_AU.UTF-8/C/C/C/C 146 | ``` 147 | 148 | use driver 13 or [see here for more detail](https://github.com/fpco/odbc/issues/17). 149 | 150 | ## Contributors 151 | 152 | * Spencer Janssen 153 | * Yo Eight 154 | * Marco Z 155 | * Rakesh Emmadi 156 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | {-# LANGUAGE OverloadedStrings, LambdaCase #-} 3 | 4 | -- | A helpful client for debugging connections. 5 | 6 | module Main (main) where 7 | 8 | import Data.List 9 | import Data.Time.LocalTime (ZonedTime(..)) 10 | import qualified Data.Text as T 11 | import qualified Data.Text.IO as T 12 | import Control.Exception 13 | import qualified Data.Text as T 14 | import qualified Database.ODBC.Internal as ODBC 15 | import System.Environment 16 | import System.IO 17 | import Text.Printf 18 | 19 | -- | Main entry point. 20 | main :: IO () 21 | main = do 22 | args <- getArgs 23 | case args of 24 | [connStr] -> do 25 | conn <- ODBC.connect (T.pack connStr) 26 | term<- hIsTerminalDevice stdin 27 | if term 28 | then repl conn 29 | else piped conn 30 | _ -> error "usage: " 31 | 32 | -- | Accepts a query/command and prints any results. 33 | repl :: ODBC.Connection -> IO () 34 | repl c = do 35 | result <- prompt 36 | case result of 37 | Nothing -> pure () 38 | Just input -> do 39 | hSetBuffering stdout LineBuffering 40 | catch 41 | (catch 42 | (do (_, count) <- ODBC.stream c input output (False, 0 :: Int) 43 | putStrLn ("Rows: " ++ show count)) 44 | (\case 45 | UserInterrupt -> pure () 46 | e -> throwIO e)) 47 | (\(e :: ODBC.ODBCException) -> putStrLn (displayException e)) 48 | repl c 49 | 50 | -- | Accepts a single input and prints any results. 51 | piped :: ODBC.Connection -> IO () 52 | piped c = do 53 | input <- T.hGetContents stdin 54 | hSetBuffering stdout LineBuffering 55 | catch 56 | (catch 57 | (do (_, count) <- ODBC.stream c input output (False, 0 :: Int) 58 | putStrLn ("Rows: " ++ show count)) 59 | (\case 60 | UserInterrupt -> pure () 61 | e -> throwIO e)) 62 | (\(e :: ODBC.ODBCException) -> putStrLn (displayException e)) 63 | repl c 64 | 65 | prompt :: IO (Maybe T.Text) 66 | prompt = do 67 | hSetBuffering stdout NoBuffering 68 | putStr "> " 69 | catch (fmap Just T.getLine) (\(_ :: IOException) -> pure Nothing) 70 | 71 | output :: (Show b, Num b) => (a, b) -> [(ODBC.Column, ODBC.Value)] -> IO (ODBC.Step (Bool, b)) 72 | output (_printedHeaders, count) rowWithHeaders = do 73 | T.putStrLn 74 | ("[row " <> T.pack (show count) <> "]\n" <> 75 | T.unlines 76 | (map 77 | (\(name, value) -> 78 | ODBC.columnName name <> ": " <> T.pack (showColumn value)) 79 | rowWithHeaders)) 80 | pure (ODBC.Continue (True, count + 1)) 81 | where 82 | showColumn = 83 | \case 84 | ODBC.NullValue -> "NULL" 85 | ODBC.TextValue t -> show t 86 | ODBC.ByteStringValue bs -> show bs 87 | ODBC.BinaryValue bs -> show bs 88 | ODBC.BoolValue b -> show b 89 | ODBC.DoubleValue d -> printf "%f" d 90 | ODBC.FloatValue d -> printf "%f" d 91 | ODBC.IntValue i -> show i 92 | ODBC.DayValue d -> show d 93 | ODBC.ByteValue b -> show b 94 | ODBC.TimeOfDayValue v -> show v 95 | ODBC.LocalTimeValue v -> show v 96 | ODBC.ZonedTimeValue lt tz -> show $ ZonedTime lt tz 97 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | image: Visual Studio 2015 3 | 4 | build: off 5 | 6 | environment: 7 | global: 8 | # Avoid long paths on Windows 9 | STACK_ROOT: "c:\\s" 10 | STACK_WORK: ".w" 11 | WORK_DIR: "c:\\w" 12 | ODBC_TEST_CONNECTION_STRING: "DRIVER={ODBC Driver 13 for SQL Server};SERVER=127.0.0.1;Uid=sa;Pwd=Password12!" 13 | 14 | services: 15 | - mssql2017 16 | 17 | before_test: 18 | # Avoid long paths not to each MAX_PATH of 260 chars 19 | - xcopy /q /s /e /r /k /i /v /h /y "%APPVEYOR_BUILD_FOLDER%" "%WORK_DIR%" 20 | - cd "%WORK_DIR%" 21 | 22 | # Install stack 23 | - curl -L https://www.stackage.org/stack/windows-x86_64 -o stack.zip 24 | - 7z x stack.zip stack.exe 25 | 26 | # Install ODBC drivers 27 | - ps: Write-Host "Installing ODBC driver..." -ForegroundColor Cyan 28 | - ps: Write-Host "Downloading..." 29 | - ps: $msiPath = "$($env:USERPROFILE)\msodbcsql.msi" 30 | - ps: (New-Object Net.WebClient).DownloadFile('https://download.microsoft.com/download/D/5/E/D5EEF288-A277-45C8-855B-8E2CB7E25B96/x64/msodbcsql.msi', $msiPath) 31 | - ps: Write-Host "Installing..." 32 | - ps: cmd /c start /wait msiexec /i "$msiPath" /q 33 | - ps: del $msiPath 34 | 35 | test_script: 36 | - cd "%WORK_DIR%" 37 | - stack --verbosity warn setup --no-reinstall > nul 38 | - stack test 39 | -j 1 40 | --no-terminal 41 | --local-bin-path %SYSTEMROOT%\system32 42 | --extra-lib-dirs="%WORK_DIR%" 43 | 44 | # Uncomment these two lines if you need to RPD into the VM to debug something 45 | 46 | # init: 47 | # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) 48 | # on_finish: 49 | # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) 50 | -------------------------------------------------------------------------------- /bench/Space.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE BangPatterns #-} 3 | {-# LANGUAGE DeriveGeneric #-} 4 | 5 | -- | Measure space usage of the odbc library. 6 | 7 | module Main where 8 | 9 | import Control.Concurrent.Async 10 | import Data.Text (Text) 11 | import qualified Data.Text as T 12 | import qualified Database.ODBC.Internal as Internal 13 | import qualified Database.ODBC.SQLServer as SQLServer 14 | import System.Environment 15 | import Weigh 16 | 17 | -- | Weigh maps. 18 | main :: IO () 19 | main = do 20 | mlabels <- lookupEnv "ODBC_WEIGH_TESTS" 21 | case mlabels of 22 | Nothing -> 23 | mainWith 24 | (do setColumns [Case, Allocated, Max, Live, GCs] 25 | (mapM_ snd tests)) 26 | Just labels -> 27 | mainWith 28 | (do setColumns [Case, Allocated, Max, Live, GCs] 29 | (mapM_ snd (filter ((flip elem (words labels)) . fst) tests))) 30 | 31 | tests :: [(String, Weigh ())] 32 | tests = [("connection", connection) 33 | ,("querying", querying)] 34 | 35 | querying :: Weigh () 36 | querying = 37 | sequence_ 38 | [ action 39 | ("Query: " ++ show n ++ " times") 40 | (runs 41 | n 42 | (do c <- connectWithString 43 | _ <- 44 | SQLServer.query c "SELECT 12345678, N'Hello, World!'" :: IO [( Int 45 | , Text)] 46 | Internal.close c)) 47 | | n <- [1, 10, 20] 48 | ] 49 | 50 | connection :: Weigh () 51 | connection = do 52 | sequence_ 53 | [ action 54 | ("Connect/disconnect: " ++ show n ++ " times") 55 | (runs 56 | n 57 | (do c <- connectWithString 58 | Internal.close c)) 59 | | n <- [1, 10, 20] 60 | ] 61 | sequence_ 62 | [ action 63 | ("Connect/disconnect: " ++ show n ++ " n threads") 64 | (replicateConcurrently 65 | n 66 | (do c <- connectWithString 67 | Internal.close c)) 68 | | n <- [1, 10, 20] 69 | ] 70 | 71 | -- | Run n times. 72 | runs :: Int -> IO () -> IO () 73 | runs 0 _ = pure () 74 | runs !n m = m >> runs (n-1) m 75 | 76 | connectWithString :: IO Internal.Connection 77 | connectWithString = do 78 | mconnStr <- lookupEnvUnquote "ODBC_TEST_CONNECTION_STRING" 79 | case mconnStr of 80 | Nothing -> 81 | error 82 | "Need ODBC_TEST_CONNECTION_STRING environment variable.\n\ 83 | \Example:\n\ 84 | \ODBC_TEST_CONNECTION_STRING='DRIVER={ODBC Driver 13 for SQL Server};SERVER=127.0.0.1;Uid=SA;Pwd=Passw0rd;Encrypt=no'" 85 | Just connStr -> Internal.connect (T.pack connStr) 86 | 87 | -- | I had trouble passing in environment variables via Docker on 88 | -- Travis without the value coming in with quotes around it. 89 | lookupEnvUnquote :: String -> IO (Maybe [Char]) 90 | lookupEnvUnquote = fmap (fmap strip) . lookupEnv 91 | where strip = reverse . dropWhile (=='"') . reverse . dropWhile (=='"') 92 | -------------------------------------------------------------------------------- /cbits/odbc.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #endif 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define FALSE 0 15 | #define TRUE 1 16 | #define MAXBUFLEN 1024 17 | 18 | // Just a way of grouping together these two dependent resources. It's 19 | // probably not a good idea to free up an environment before freeing a 20 | // database. So, we put them together so that they can be allocated 21 | // and freed atomically. 22 | typedef struct EnvAndDbc { 23 | SQLHENV *env; 24 | SQLHDBC *dbc; 25 | char *error; 26 | char *sqlState; 27 | // Allocated once in odbc_AllocEnvAndDbc and freed once in odbc_FreeEnvAndDbc. 28 | } EnvAndDbc; 29 | 30 | char *odbc_error(EnvAndDbc *envAndDbc){ 31 | return envAndDbc->error; 32 | } 33 | 34 | char *odbc_sqlState(EnvAndDbc *envAndDbc){ 35 | return envAndDbc->sqlState; 36 | } 37 | 38 | void odbc_ProcessLogMessages(EnvAndDbc *envAndDbc, SQLSMALLINT plm_handle_type, SQLHANDLE plm_handle, char *logstring, int ConnInd); 39 | 40 | //////////////////////////////////////////////////////////////////////////////// 41 | // Alloc/dealloc env 42 | 43 | SQLHENV *odbc_SQLAllocEnv(){ 44 | SQLHENV *henv = malloc(sizeof *henv); 45 | *henv = SQL_NULL_HENV; 46 | RETCODE retcode = SQLAllocHandle (SQL_HANDLE_ENV, NULL, henv); 47 | if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) { 48 | free(henv); 49 | return NULL; 50 | } else { 51 | return henv; 52 | } 53 | } 54 | 55 | void odbc_SQLFreeEnv(SQLHENV *henv){ 56 | if(henv != NULL && *henv != SQL_NULL_HENV) { 57 | SQLFreeHandle(SQL_HANDLE_ENV, *henv); 58 | free(henv); 59 | } 60 | } 61 | 62 | RETCODE odbc_SetEnvAttr(SQLHENV *henv){ 63 | return 64 | SQLSetEnvAttr(*henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER); 65 | } 66 | 67 | //////////////////////////////////////////////////////////////////////////////// 68 | // Alloc/dealloc dbc 69 | 70 | SQLHDBC *odbc_SQLAllocDbc(SQLHENV *henv){ 71 | SQLHDBC *hdbc = malloc(sizeof *hdbc); 72 | *hdbc = SQL_NULL_HDBC; 73 | RETCODE retcode = SQLAllocHandle (SQL_HANDLE_DBC, *henv, hdbc); 74 | if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) { 75 | free(hdbc); 76 | return NULL; 77 | } else { 78 | return hdbc; 79 | } 80 | } 81 | 82 | void odbc_SQLFreeDbc(SQLHDBC *hdbc){ 83 | if(hdbc != NULL && *hdbc != SQL_NULL_HDBC) { 84 | SQLFreeHandle(SQL_HANDLE_DBC, *hdbc); 85 | free(hdbc); 86 | } 87 | } 88 | 89 | //////////////////////////////////////////////////////////////////////////////// 90 | // Allocate/dealloc env-and-database 91 | 92 | EnvAndDbc *odbc_AllocEnvAndDbc(){ 93 | SQLHENV *env = odbc_SQLAllocEnv(); 94 | if (env == NULL) { 95 | return NULL; 96 | } else { 97 | int retcode = odbc_SetEnvAttr(env); 98 | if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) { 99 | free(env); 100 | return NULL; 101 | } else { 102 | SQLHDBC *dbc = odbc_SQLAllocDbc(env); 103 | if (dbc == NULL) { 104 | return NULL; 105 | } else { 106 | EnvAndDbc *envAndDbc = malloc(sizeof *envAndDbc); 107 | envAndDbc->env = env; 108 | envAndDbc->dbc = dbc; 109 | envAndDbc->error = malloc(SQL_MAX_MESSAGE_LENGTH); 110 | // SQLSTATE is a five-character code, see https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/appendix-a-odbc-error-codes?view=sql-server-ver15 111 | envAndDbc->sqlState = malloc(6); 112 | return envAndDbc; 113 | } 114 | } 115 | } 116 | } 117 | 118 | void odbc_FreeEnvAndDbc(EnvAndDbc *envAndDbc){ 119 | free(envAndDbc->error); 120 | free(envAndDbc->sqlState); 121 | odbc_SQLFreeDbc(envAndDbc->dbc); 122 | odbc_SQLFreeEnv(envAndDbc->env); 123 | free(envAndDbc); 124 | } 125 | 126 | //////////////////////////////////////////////////////////////////////////////// 127 | // Connect/disconnect 128 | 129 | RETCODE odbc_SQLDriverConnect(EnvAndDbc *envAndDbc, SQLCHAR *connString, SQLSMALLINT len){ 130 | SQLSMALLINT ignored = 0; 131 | RETCODE r = SQLDriverConnect( 132 | *(envAndDbc->dbc), 133 | NULL, 134 | connString, 135 | len, 136 | NULL, 137 | 0, 138 | &ignored, 139 | SQL_DRIVER_NOPROMPT); 140 | if (r == SQL_ERROR) 141 | odbc_ProcessLogMessages(envAndDbc, SQL_HANDLE_DBC, *(envAndDbc->dbc), "odbc_SQLDriverConnect", FALSE); 142 | return r; 143 | } 144 | 145 | void odbc_SQLDisconnect(EnvAndDbc *envAndDbc){ 146 | SQLDisconnect(*(envAndDbc->dbc)); 147 | } 148 | 149 | //////////////////////////////////////////////////////////////////////////////// 150 | // Alloc/dealloc statement 151 | 152 | SQLHSTMT *odbc_SQLAllocStmt(EnvAndDbc *envAndDbc){ 153 | SQLHSTMT *hstmt = malloc(sizeof *hstmt); 154 | *hstmt = SQL_NULL_HSTMT; 155 | RETCODE retcode = SQLAllocHandle (SQL_HANDLE_STMT, *(envAndDbc->dbc), hstmt); 156 | if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) { 157 | free(hstmt); 158 | return NULL; 159 | } else { 160 | return hstmt; 161 | } 162 | } 163 | 164 | void odbc_SQLFreeStmt(SQLHSTMT *hstmt){ 165 | if(hstmt != NULL && *hstmt != SQL_NULL_HSTMT) { 166 | SQLFreeHandle(SQL_HANDLE_STMT, *hstmt); 167 | free(hstmt); 168 | } 169 | } 170 | 171 | //////////////////////////////////////////////////////////////////////////////// 172 | // Params 173 | 174 | SQLRETURN odbc_SQLBindParameter( 175 | EnvAndDbc* envAndDbc, 176 | SQLHSTMT* statement_handle, 177 | SQLUSMALLINT parameter_number, 178 | SQLSMALLINT value_type, 179 | SQLSMALLINT parameter_type, 180 | SQLULEN column_size, 181 | SQLPOINTER parameter_value_ptr, 182 | SQLLEN buffer_length, 183 | SQLLEN* buffer_length_ptr 184 | ) { 185 | RETCODE r = SQLBindParameter( 186 | *statement_handle, 187 | parameter_number, 188 | SQL_PARAM_INPUT, 189 | value_type, 190 | parameter_type, 191 | column_size, 192 | 0, 193 | parameter_value_ptr, 194 | buffer_length, 195 | buffer_length_ptr 196 | ); 197 | if (r == SQL_ERROR) 198 | odbc_ProcessLogMessages(envAndDbc, SQL_HANDLE_STMT, *statement_handle, "odbc_SQLBindParameter", FALSE); 199 | return r; 200 | } 201 | 202 | //////////////////////////////////////////////////////////////////////////////// 203 | // Execute 204 | 205 | RETCODE odbc_SQLExecDirectW(EnvAndDbc *envAndDbc, SQLHSTMT *hstmt, SQLWCHAR *stmt, SQLINTEGER len){ 206 | RETCODE r = SQLExecDirectW(*hstmt, stmt, len); 207 | if (r == SQL_ERROR) { 208 | odbc_ProcessLogMessages(envAndDbc, SQL_HANDLE_STMT, *hstmt, "odbc_SQLExecDirectW", FALSE); 209 | } 210 | return r; 211 | } 212 | 213 | //////////////////////////////////////////////////////////////////////////////// 214 | // Fetch row 215 | 216 | RETCODE odbc_SQLFetch(EnvAndDbc *envAndDbc, SQLHSTMT *hstmt){ 217 | RETCODE r = SQLFetch(*hstmt); 218 | if (r == SQL_ERROR) odbc_ProcessLogMessages(envAndDbc, SQL_HANDLE_STMT, *hstmt, "odbc_SQLFetch", FALSE); 219 | return r; 220 | } 221 | 222 | RETCODE odbc_SQLMoreResults(EnvAndDbc *envAndDbc, SQLHSTMT *hstmt){ 223 | RETCODE r = SQLMoreResults(*hstmt); 224 | if (r == SQL_ERROR) odbc_ProcessLogMessages(envAndDbc, SQL_HANDLE_STMT, *hstmt, "odbc_SQLMoreResults", FALSE); 225 | return r; 226 | } 227 | 228 | //////////////////////////////////////////////////////////////////////////////// 229 | // Get fields 230 | 231 | RETCODE odbc_SQLGetData(EnvAndDbc *envAndDbc, 232 | SQLHSTMT *hstmt, 233 | SQLUSMALLINT col, 234 | SQLSMALLINT targetType, 235 | SQLPOINTER buffer, 236 | SQLLEN bufferLen, 237 | SQLLEN * resultLen){ 238 | RETCODE r = SQLGetData(*hstmt, col, targetType, buffer, bufferLen, resultLen); 239 | if (r == SQL_ERROR) odbc_ProcessLogMessages(envAndDbc, SQL_HANDLE_STMT, *hstmt, "odbc_SQLGetData", FALSE); 240 | return r; 241 | } 242 | 243 | SQLRETURN odbc_SQLDescribeColW( 244 | SQLHSTMT *StatementHandle, 245 | SQLUSMALLINT ColumnNumber, 246 | SQLWCHAR * ColumnName, 247 | SQLSMALLINT BufferLength, 248 | SQLSMALLINT * NameLengthPtr, 249 | SQLSMALLINT * DataTypePtr, 250 | SQLULEN * ColumnSizePtr, 251 | SQLSMALLINT * DecimalDigitsPtr, 252 | SQLSMALLINT * NullablePtr){ 253 | return SQLDescribeColW( 254 | *StatementHandle, 255 | ColumnNumber, 256 | ColumnName, 257 | BufferLength, 258 | NameLengthPtr, 259 | DataTypePtr, 260 | ColumnSizePtr, 261 | DecimalDigitsPtr, 262 | NullablePtr); 263 | } 264 | 265 | //////////////////////////////////////////////////////////////////////////////// 266 | // Get columns 267 | 268 | RETCODE odbc_SQLNumResultCols(SQLHSTMT *hstmt, SQLSMALLINT *cols){ 269 | return SQLNumResultCols(*hstmt, cols); 270 | } 271 | 272 | //////////////////////////////////////////////////////////////////////////////// 273 | // Logs 274 | 275 | void odbc_ProcessLogMessages(EnvAndDbc *envAndDbc, SQLSMALLINT plm_handle_type, SQLHANDLE plm_handle, char *logstring, int ConnInd) { 276 | // It has been fully tested that trying anything beyond 1 produces 277 | // the same string. 278 | SQLSMALLINT plg_record_number = 1; 279 | // The subtract-1 leaves space for zero-termination. 280 | SQLSMALLINT copy_this_many_bytes = MAXBUFLEN - 1; 281 | 282 | // These are not interesting for our use-case, but needed by 283 | // SQLGetDiagRec: 284 | SDWORD plm_pfNativeError = 0L; 285 | SWORD plm_pcbErrorMsg = 0; 286 | 287 | // Copy the error into: envAndDbc->error 288 | // 289 | // This may fail, but in that case there's no useful error message. 290 | SQLGetDiagRec(plm_handle_type, 291 | plm_handle, 292 | plg_record_number, 293 | (SQLCHAR *)envAndDbc->sqlState, 294 | &plm_pfNativeError, 295 | (SQLCHAR *)envAndDbc->error, 296 | copy_this_many_bytes, 297 | &plm_pcbErrorMsg); 298 | } 299 | 300 | //////////////////////////////////////////////////////////////////////////////// 301 | // Accessors for DATE_STRUCT 302 | 303 | SQLSMALLINT DATE_STRUCT_year(DATE_STRUCT *d){ 304 | return d->year; 305 | } 306 | SQLUSMALLINT DATE_STRUCT_month(DATE_STRUCT *d){ 307 | return d->month; 308 | } 309 | SQLUSMALLINT DATE_STRUCT_day(DATE_STRUCT *d){ 310 | return d->day; 311 | } 312 | 313 | //////////////////////////////////////////////////////////////////////////////// 314 | // Accessors for TIME_STRUCT 315 | 316 | SQLUSMALLINT TIME_STRUCT_hour(TIME_STRUCT *t){ 317 | return t->hour; 318 | } 319 | SQLUSMALLINT TIME_STRUCT_minute(TIME_STRUCT *t){ 320 | return t->minute; 321 | } 322 | SQLUSMALLINT TIME_STRUCT_second(TIME_STRUCT *t){ 323 | return t->second; 324 | } 325 | 326 | //////////////////////////////////////////////////////////////////////////////// 327 | // Accessors for TIMESTAMP_STRUCT 328 | 329 | SQLSMALLINT TIMESTAMP_STRUCT_year(TIMESTAMP_STRUCT *t){ 330 | return t->year; 331 | } 332 | SQLUSMALLINT TIMESTAMP_STRUCT_month(TIMESTAMP_STRUCT *t){ 333 | return t->month; 334 | } 335 | SQLUSMALLINT TIMESTAMP_STRUCT_day(TIMESTAMP_STRUCT *t){ 336 | return t->day; 337 | } 338 | SQLUSMALLINT TIMESTAMP_STRUCT_hour(TIMESTAMP_STRUCT *t){ 339 | return t->hour; 340 | } 341 | SQLUSMALLINT TIMESTAMP_STRUCT_minute(TIMESTAMP_STRUCT *t){ 342 | return t->minute; 343 | } 344 | SQLUSMALLINT TIMESTAMP_STRUCT_second(TIMESTAMP_STRUCT *t){ 345 | return t->second; 346 | } 347 | SQLUINTEGER TIMESTAMP_STRUCT_fraction(TIMESTAMP_STRUCT *t){ 348 | return t->fraction; 349 | } 350 | 351 | //////////////////////////////////////////////////////////////////////////////// 352 | // Definition and accessors for SQL_SS_TIMESTAMPOFFSET_STRUCT 353 | // The strcut definition is from 354 | // https://docs.microsoft.com/en-us/sql/relational-databases/native-client-odbc-date-time/data-type-support-for-odbc-date-and-time-improvements 355 | typedef struct tagTIMESTAMPOFFSET_STRUCT { 356 | SQLSMALLINT year; 357 | SQLUSMALLINT month; 358 | SQLUSMALLINT day; 359 | SQLUSMALLINT hour; 360 | SQLUSMALLINT minute; 361 | SQLUSMALLINT second; 362 | SQLUINTEGER fraction; 363 | SQLSMALLINT timezone_hour; 364 | SQLSMALLINT timezone_minute; 365 | } TIMESTAMPOFFSET_STRUCT; 366 | 367 | #if (ODBCVER >= 0x0300) 368 | typedef TIMESTAMPOFFSET_STRUCT SQL_SS_TIMESTAMPOFFSET_STRUCT; 369 | #endif 370 | 371 | SQLSMALLINT TIMESTAMPOFFSET_STRUCT_year(TIMESTAMPOFFSET_STRUCT *t){ 372 | return t->year; 373 | } 374 | 375 | SQLUSMALLINT TIMESTAMPOFFSET_STRUCT_month(TIMESTAMPOFFSET_STRUCT *t){ 376 | return t->month; 377 | } 378 | 379 | SQLUSMALLINT TIMESTAMPOFFSET_STRUCT_day(TIMESTAMPOFFSET_STRUCT *t){ 380 | return t->day; 381 | } 382 | 383 | SQLUSMALLINT TIMESTAMPOFFSET_STRUCT_hour(TIMESTAMPOFFSET_STRUCT *t){ 384 | return t->hour; 385 | } 386 | 387 | SQLUSMALLINT TIMESTAMPOFFSET_STRUCT_minute(TIMESTAMPOFFSET_STRUCT *t){ 388 | return t->minute; 389 | } 390 | 391 | SQLUSMALLINT TIMESTAMPOFFSET_STRUCT_second(TIMESTAMPOFFSET_STRUCT *t){ 392 | return t->second; 393 | } 394 | 395 | SQLUINTEGER TIMESTAMPOFFSET_STRUCT_fraction(TIMESTAMPOFFSET_STRUCT *t){ 396 | return t->fraction; 397 | } 398 | 399 | SQLSMALLINT TIMESTAMPOFFSET_STRUCT_timezone_hour(TIMESTAMPOFFSET_STRUCT *t){ 400 | return t->timezone_hour; 401 | } 402 | 403 | SQLSMALLINT TIMESTAMPOFFSET_STRUCT_timezone_minute(TIMESTAMPOFFSET_STRUCT *t){ 404 | return t->timezone_minute; 405 | } -------------------------------------------------------------------------------- /doc/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("haskell",function(e){var i={v:[e.C("--","$"),e.C("{-","-}",{c:["self"]})]},a={cN:"meta",b:"{-#",e:"#-}"},l={cN:"meta",b:"^#",e:"$"},c={cN:"type",b:"\\b[A-Z][\\w']*",r:0},n={b:"\\(",e:"\\)",i:'"',c:[a,l,{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TM,{b:"[_a-z][\\w']*"}),i]},s={b:"{",e:"}",c:n.c};return{aliases:["hs"],k:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",c:[{bK:"module",e:"where",k:"module where",c:[n,i],i:"\\W\\.|;"},{b:"\\bimport\\b",e:"$",k:"import qualified as hiding",c:[n,i],i:"\\W\\.|;"},{cN:"class",b:"^(\\s*)?(class|instance)\\b",e:"where",k:"class family instance where",c:[c,n,i]},{cN:"class",b:"\\b(data|(new)?type)\\b",e:"$",k:"data family type newtype deriving",c:[a,c,n,s,i]},{bK:"default",e:"$",c:[c,n,i]},{bK:"infix infixl infixr",e:"$",c:[e.CNM,i]},{b:"\\bforeign\\b",e:"$",k:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",c:[c,e.QSM,i]},{cN:"meta",b:"#!\\/usr\\/bin\\/env runhaskell",e:"$"},a,l,e.QSM,e.CNM,c,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),i,{b:"->|<-"}]}}); -------------------------------------------------------------------------------- /doc/init.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('pre,code').each(function(i, block) { 3 | hljs.highlightBlock(block); 4 | }); 5 | $('.show').removeClass('show').addClass('hide'); 6 | }); 7 | -------------------------------------------------------------------------------- /doc/patch.sh: -------------------------------------------------------------------------------- 1 | for i in $(ls .stack-work/dist/x86_64-osx/Cabal-1.24.2.0/doc/html/odbc/*.html); do sed -i.bak 's//