├── register_driver.reg ├── .gitignore ├── test ├── test.d └── testclient.d ├── connection-parameters.md ├── Makefile ├── NOTICE ├── odbc-conformance.md ├── client ├── mockcurl.d ├── prestoerrors.d ├── util.d ├── statementclient.d └── queryresults.d ├── driver ├── tableinfo.d ├── prestoresults.d ├── bindings.d ├── handles.d ├── typeinfo.d ├── util.d ├── columnresults.d └── getinfo.d ├── installer └── win │ └── installer.nsi ├── README.md ├── odbc ├── sqltypes.d ├── odbcinst.d └── sqlucode.d └── LICENSE /register_driver.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/presto-odbc/HEAD/register_driver.reg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Compiled Dynamic libraries 8 | *.so 9 | *.dylib 10 | *.dll 11 | *.exp 12 | *.ilk 13 | *.pdb 14 | 15 | # Compiled Static libraries 16 | *.lai 17 | *.la 18 | *.a 19 | *.lib 20 | 21 | # Executables 22 | *.exe 23 | *.out 24 | *.app 25 | unittests 26 | -------------------------------------------------------------------------------- /test/test.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.client.test; 15 | 16 | import std.stdio : writeln; 17 | 18 | version(unittest) void main() { writeln("Tests completed."); } 19 | -------------------------------------------------------------------------------- /connection-parameters.md: -------------------------------------------------------------------------------- 1 | 2 | # Connection Parameters 3 | 4 | This file provides a brief overview of the various connection parameters you can use to connect to 5 | your Presto instance via the Presto ODBC driver. 6 | 7 | Connection parameter names are *not* case sensitive, the values you put into them *are*. 8 | 9 | Connection Parameter | Description 10 | ------------- | ------------- 11 | Endpoint (required) | host:port of the server you would like to connect to 12 | PrestoCatalog (required) | Presto Catalog 13 | PrestoSchema | Presto Schema 14 | Username | Sets the Presto user 15 | ProxyEndpoint | host:port of your socks5 proxy machine 16 | 17 | A connection parameter string is a string of the form `key=value;key=value` up to any number of 18 | key/value pairs. Leading/trailing semicolons are optional. 19 | 20 | An example would be: 21 | `endpoint=localhost:8080;prestoCatalog=tpch;prestoSchema=tiny;` 22 | -------------------------------------------------------------------------------- /test/testclient.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.client.testclient; 15 | 16 | //This file exists to facilitate "live testing" and trying simple queries 17 | 18 | import std.stdio : writeln; 19 | import std.net.curl; 20 | 21 | import presto.client.queryresults : QueryResults; 22 | import presto.client.statementclient : ClientSession, StatementClient; 23 | 24 | version(unittest) {} 25 | else void main() { 26 | auto session = ClientSession("localhost:8080", "test"); 27 | session.catalog = "tpch"; 28 | session.schema = "tiny"; 29 | 30 | auto client = StatementClient(session, "SELECT * FROM orders"); 31 | foreach (resultBatch; client) { 32 | writeln("Starting a new batch"); 33 | 34 | foreach (row; resultBatch.byRow!(string, "comment")()) { 35 | writeln(row); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VS_HOME = C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC 2 | LIBCURL = /cygdrive/c/D/dmd2/windows/bin64/libcurl.dll 3 | TEMP = /cygdrive/c/temp 4 | LOGFILE = $(TEMP)/presto_odbc.log 5 | 6 | MAKE_NSISW_CMD = "/cygdrive/c/Program Files (x86)/NSIS/makensis.exe" 7 | MAKE_NSIS_FLAGS = /V4 8 | 9 | DC = LINKCMD64="$(VS_HOME)\bin\link.exe" dmd 10 | CFLAGS = -c 11 | FLAGS = -g -w -version=UNICODE 12 | 13 | ifeq ($(OS),Windows_NT) 14 | INSTALLER_DIR = installer/win 15 | INSTALLER_SCRIPT = $(INSTALLER_DIR)/installer.nsi 16 | INSTALLER_OUTPUT = $(INSTALLER_DIR)/*.exe 17 | 18 | FLAGS += -m64 -Luser32.lib 19 | 20 | PROGRAM = presto.dll 21 | else 22 | FLAGS += -m32 -fPIC -L-lcurl 23 | 24 | PROGRAM = presto.dylib 25 | endif 26 | 27 | SOURCES = client/*.d odbc/*.d driver/*.d 28 | TEST_SOURCES = $(SOURCES) test/*.d 29 | TEST_PROGRAM = unittests 30 | 31 | .PHONY: all driver tests copy logdiff clean 32 | 33 | all: driver tests 34 | 35 | driver: 36 | $(DC) $(FLAGS) $(SOURCES) -shared -of$(PROGRAM) 37 | 38 | tests: 39 | $(DC) -unittest $(FLAGS) $(TEST_SOURCES) -of$(TEST_PROGRAM) 40 | 41 | check: tests 42 | cp $(LIBCURL) . 43 | chmod 555 $(notdir $(LIBCURL)) 44 | ./$(TEST_PROGRAM) 45 | 46 | install: driver check 47 | mkdir -p $(TEMP) 48 | cp $(PROGRAM) $(TEMP)/$(PROGRAM) 49 | cp $(LIBCURL) $(TEMP)/ 50 | chmod 555 $(TEMP)/$(notdir $(LIBCURL)) 51 | rm -f $(TEMP)/SQL.LOG 52 | if [ -f $(LOGFILE) ]; then mv $(LOGFILE) $(LOGFILE).old; fi 53 | @echo "Install complete" 54 | 55 | logdiff: 56 | sdiff --text $(LOGFILE) $(LOGFILE).old 57 | 58 | clean: 59 | rm -f *.obj *.exp *.lib *.ilk *.pdb $(PROGRAM) $(TEST_PROGRAM) 60 | rm -f $(INSTALLER_OUTPUT) 61 | 62 | installer: driver check 63 | $(MAKE_NSISW_CMD) $(MAKE_NSIS_FLAGS) $(INSTALLER_SCRIPT) 64 | 65 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | THE FOLLOWING IS SOFTWARE LICENSED BY THIRD PARTIES UNDER OPEN SOURCE LICENSES THAT MAY BE USED BY THIS PRODUCT. 2 | 3 | ----- 4 | 5 | The following software may be included in this product: iODBC. This software contains the following license and notice below: 6 | 7 | Copyright (C) 1995-2006, OpenLink Software Inc and Ke Jin. 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions 12 | are met: 13 | 14 | 1. Redistributions of source code must retain the above copyright notice, 15 | this list of conditions and the following disclaimer. 16 | 2. Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 3. Neither the name of OpenLink Software Inc. nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OPENLINK OR CONTRIBUTORS 27 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 28 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 33 | THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /odbc-conformance.md: -------------------------------------------------------------------------------- 1 | # Driver Conformance to the ODBC Standard 2 | 3 | ## Near-fully implemented functions: 4 | * SQLBindCol 5 | * SQLColumns 6 | * SQLExecDirect 7 | * SQLExecute 8 | * SQLFetch 9 | * SQLGetInfo 10 | * SQLGetTypeInfo 11 | * SQLNumResultCols 12 | * SQLPrepare 13 | * SQLTables 14 | * SQLBindParameter 15 | * SQLColAttribute 16 | * SQLSpecialColumns 17 | * SQLDescribeCol 18 | * SQLRowCount 19 | * SQLGetData 20 | * SQLForeignKeys 21 | * SQLPrimaryKeys 22 | * SQLNativeSql 23 | 24 | ## Partially-implemented or mocked functions: 25 | * SQLAllocHandle 26 | * SQLConnect 27 | * SQLDisconnect 28 | * SQLDriverConnect 29 | * SQLFreeHandle 30 | * SQLFreeStmt 31 | * SQLGetDiagField 32 | * SQLGetDiagRec 33 | * SQLGetConnectAttr 34 | * SQLGetStmtAttr 35 | * SQLGetEnvAttr 36 | * SQLSetConnectAttr 37 | * SQLSetEnvAttr 38 | * SQLSetStmtAttr 39 | * SQLEndTran 40 | 41 | ## Unimplemented Functions: 42 | 43 | These functions are unimplemented because our target ODBC applications have not been proven to exercise them. 44 | 45 | ### Necessary for the "Core" conformance level of ODBC 46 | 47 | Data Fetching: 48 | * SQLStatistics 49 | 50 | Cursors: 51 | * SQLCloseCursor 52 | * SQLGetCursorName 53 | * SQLSetCursorName 54 | * SQLFetchScroll (Tableau's data sheet says it might need this) 55 | 56 | Parameters: 57 | * SQLNumParams 58 | * SQLParamData 59 | * SQLPutData 60 | * SQLCancel (Tableau's data sheet says it might need this) 61 | 62 | Descriptor Fields (see [handles.d](driver/handles.d)): 63 | * SQLCopyDesc 64 | * SQLGetDescField 65 | * SQLGetDescRec 66 | * SQLSetDescRec 67 | * SQLSetDescField 68 | 69 | ### Necessary for "Level 1" conformance: 70 | 71 | * SQLBrowseConnect 72 | * SQLBulkOperations 73 | * SQLMoreResults 74 | * SQLProcedureColumns 75 | * SQLProcedures 76 | * SQLSetPos 77 | 78 | ### Necessary for "Level 2" conformance: 79 | 80 | * SQLColumnPrivileges 81 | * SQLDescribeParam 82 | * SQLTablePrivileges 83 | -------------------------------------------------------------------------------- /client/mockcurl.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.client.mockcurl; 15 | 16 | import std.array : empty, front, popFront; 17 | import std.net.curl : HTTP; 18 | 19 | //Exports post and get methods, either from std.net.curl or mock versions 20 | //depending on whether or not we are testing. 21 | 22 | version(unittest) { 23 | char[][] mockCurlResults; 24 | 25 | void enqueueCurlResult(char[] result) { 26 | mockCurlResults ~= result; 27 | } 28 | 29 | char[] get(const(char)[] url, HTTP conn = HTTP()) { 30 | assert(!mockCurlResults.empty); 31 | auto result = mockCurlResults.front; 32 | mockCurlResults.popFront; 33 | return result; 34 | } 35 | 36 | char[] post(PostUnit)(const(char)[] url, const(PostUnit)[] postData, HTTP conn = HTTP()) { 37 | return get(url); 38 | } 39 | 40 | void del(const(char)[] url, HTTP conn = HTTP()) { 41 | //No-op 42 | } 43 | 44 | } else { 45 | public import std.net.curl : post, get, del; 46 | } 47 | 48 | unittest { 49 | /* Broken when running with other tests, see: 50 | * http://forum.dlang.org/thread/fqbjamocnvvxpuzgmjid@forum.dlang.org#post-fqbjamocnvvxpuzgmjid 51 | enqueueCurlResult("test1".dup); 52 | enqueueCurlResult("test2".dup); 53 | enqueueCurlResult("test3".dup); 54 | 55 | assert(get("localhost") == "test1"); 56 | assert(get("localhost") == "test2"); 57 | assert(post("localhost", "post data") == "test3"); 58 | del("No-op"); 59 | */ 60 | } 61 | -------------------------------------------------------------------------------- /client/prestoerrors.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | module presto.client.prestoerrors; 16 | 17 | import facebook.json : JSON_TYPE, JSONValue; 18 | import std.typecons : Nullable; 19 | 20 | import presto.client.util; 21 | 22 | class QueryException : PrestoClientException { 23 | this (JSONValue rawResult) immutable { 24 | message = getOptionalProperty!(string, "message")(rawResult); 25 | super(message.isNull ? "Unknown Presto Server Exception" : message.get); 26 | 27 | sqlState = getOptionalProperty!(string, "sqlState")(rawResult); 28 | errorCode = getOptionalProperty!(long, "errorCode")(rawResult); 29 | if ("errorLocation" in rawResult) { 30 | errorLocation = immutable(ErrorLocation)(rawResult["errorLocation"]); 31 | } 32 | if ("failureInfo" in rawResult) { 33 | failureInfo = immutable(FailureInfo)(rawResult["failureInfo"]); 34 | } 35 | } 36 | 37 | immutable Nullable!string message; 38 | immutable Nullable!string sqlState; 39 | immutable Nullable!long errorCode; 40 | immutable Nullable!ErrorLocation errorLocation; 41 | immutable Nullable!FailureInfo failureInfo; 42 | } 43 | 44 | struct FailureInfo { 45 | this (JSONValue rawResult) immutable { 46 | message = getOptionalProperty!(string, "message")(rawResult); 47 | type = getOptionalProperty!(string, "type")(rawResult); 48 | if ("errorLocation" in rawResult) { 49 | errorLocation = immutable(ErrorLocation)(rawResult["errorLocation"]); 50 | } 51 | if ("stack" in rawResult) { 52 | stack = parseStack(rawResult["stack"].array); 53 | } 54 | } 55 | 56 | immutable Nullable!string message; 57 | immutable Nullable!string type; 58 | immutable Nullable!ErrorLocation errorLocation; 59 | immutable(string[]) stack; 60 | } 61 | 62 | private immutable(string[]) parseStack(JSONValue[] stack) { 63 | string[] result; 64 | foreach (line; stack) { 65 | result ~= line.str; 66 | } 67 | return result.idup; 68 | } 69 | 70 | struct ErrorLocation { 71 | this (JSONValue rawResult) immutable { 72 | lineNumber = getOptionalProperty!(long, "lineNumber")(rawResult); 73 | columnNumber = getOptionalProperty!(long, "columnNumber")(rawResult); 74 | } 75 | 76 | immutable Nullable!long lineNumber; 77 | immutable Nullable!long columnNumber; 78 | } 79 | -------------------------------------------------------------------------------- /driver/tableinfo.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.odbcdriver.tableinfo; 15 | 16 | import std.array : front, empty, popFront; 17 | import std.conv : text, to; 18 | import std.variant : Variant; 19 | 20 | import odbc.sqlext; 21 | import odbc.odbcinst; 22 | 23 | import presto.odbcdriver.handles : OdbcStatement; 24 | import presto.odbcdriver.bindings : OdbcResult, OdbcResultRow; 25 | import presto.odbcdriver.util : dllEnforce, logMessage; 26 | 27 | final class TableInfoResult : OdbcResult { 28 | this(OdbcStatement statementHandle) { 29 | this.statementHandle = statementHandle; 30 | } 31 | 32 | void addTable(string name) { 33 | with (statementHandle) with (connection) with (session) { 34 | results ~= new TableInfoResultRow(catalog, schema, name); 35 | } 36 | } 37 | 38 | override bool empty() const { 39 | return results.empty; 40 | } 41 | 42 | override inout(TableInfoResultRow) front() inout { 43 | assert(!empty); 44 | return results.front; 45 | } 46 | 47 | override void popFront() { 48 | results.popFront; 49 | } 50 | 51 | override size_t numberOfColumns() { 52 | return TableInfoResultColumns.max; 53 | } 54 | 55 | private: 56 | OdbcStatement statementHandle; 57 | TableInfoResultRow[] results; 58 | } 59 | 60 | 61 | // http://msdn.microsoft.com/en-us/library/ms711831%28v=vs.85%29.aspx 62 | final class TableInfoResultRow : OdbcResultRow { 63 | this(string catalogName, string schemaName, string tableName) { 64 | this.catalogName = catalogName; 65 | this.schemaName = schemaName; 66 | this.tableName = tableName; 67 | } 68 | 69 | override Variant dataAt(int column) { 70 | with (TableInfoResultColumns) { 71 | switch (column) { 72 | case TABLE_CATALOG: 73 | return Variant(catalogName); 74 | case TABLE_SCHEMA: 75 | return Variant(schemaName); 76 | case TABLE_NAME: 77 | return Variant(tableName); 78 | case TABLE_TYPE: 79 | return Variant("TABLE"); 80 | case REMARKS: 81 | return Variant("A faux table for testing"); 82 | default: 83 | dllEnforce(false, "Non-existant column " ~ text(cast(TableInfoResultColumns) column)); 84 | assert(false, "Silence compiler errors about not returning"); 85 | } 86 | } 87 | } 88 | private: 89 | string catalogName; 90 | string schemaName; 91 | string tableName; 92 | } 93 | 94 | enum TableInfoResultColumns { 95 | TABLE_CATALOG = 1, 96 | TABLE_SCHEMA, 97 | TABLE_NAME, 98 | TABLE_TYPE, 99 | REMARKS 100 | } 101 | -------------------------------------------------------------------------------- /installer/win/installer.nsi: -------------------------------------------------------------------------------- 1 | !define APPNAME "Presto ODBC Driver (64-bit)" 2 | !define DESCRIPTION "Presto ODBC Driver (64-bit)" 3 | 4 | #installer files 5 | !define LICENSE_FILE "..\..\LICENSE" 6 | !define PRESTO_DLL "..\..\presto.dll" 7 | !define LIBCURL_DLL "..\..\libcurl.dll" 8 | 9 | # These three must be integers 10 | !define VERSIONMAJOR 1 11 | !define VERSIONMINOR 1 12 | !define VERSIONBUILD 1 13 | 14 | # These will be displayed by the "Click here for support information" link in "Add/Remove Programs" 15 | # It is possible to use "mailto:" links in here to open the email client 16 | !define HELPURL "https://github.com/prestodb/presto-odbc" # "Support Information" link 17 | !define UPDATEURL "https://github.com/prestodb/presto-odbc" # "Product Updates" link 18 | !define ABOUTURL "https://github.com/prestodb/presto-odbc" # "Publisher" link 19 | 20 | # This is the size (in kB) of all the files copied 21 | !define INSTALLSIZE 3370 22 | 23 | RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on) 24 | 25 | InstallDir "C:\temp" 26 | 27 | # rtf or txt file - remember if it is txt, it must be in the DOS text format (\r\n) 28 | LicenseData "${LICENSE_FILE}" 29 | 30 | # This will be in the installer/uninstaller's title bar 31 | Name "${APPNAME}" 32 | outFile "presto-installer-64.exe" 33 | 34 | !include LogicLib.nsh 35 | 36 | # Just three pages - license agreement, install location, and installation 37 | page license 38 | page directory 39 | Page instfiles 40 | 41 | !macro VerifyUserIsAdmin 42 | UserInfo::GetAccountType 43 | pop $0 44 | ${If} $0 != "admin" ;Require admin rights on NT4+ 45 | messageBox mb_iconstop "Administrator rights required!" 46 | setErrorLevel 740 ;ERROR_ELEVATION_REQUIRED 47 | quit 48 | ${EndIf} 49 | !macroend 50 | 51 | function .onInit 52 | setShellVarContext all 53 | SetRegView 64 54 | !insertmacro VerifyUserIsAdmin 55 | functionEnd 56 | 57 | section "install" 58 | setOutPath $INSTDIR 59 | 60 | # Files added here should be removed by the uninstaller (see section "uninstall") 61 | file "${PRESTO_DLL}" 62 | file "${LIBCURL_DLL}" 63 | 64 | # Uninstaller - See function un.onInit and section "uninstall" for configuration 65 | writeUninstaller "$INSTDIR\uninstall.exe" 66 | 67 | # Registry keys for the driver 68 | WriteRegStr HKEY_LOCAL_MACHINE "Software\ODBC\ODBCINST.INI\ODBC Drivers" "Presto ODBC Driver" "Installed" 69 | WriteRegStr HKEY_LOCAL_MACHINE "Software\ODBC\ODBCINST.INI\Presto ODBC Driver" "Setup" "$INSTDIR\presto.dll" 70 | WriteRegStr HKEY_LOCAL_MACHINE "Software\ODBC\ODBCINST.INI\Presto ODBC Driver" "Driver" "$INSTDIR\presto.dll" 71 | WriteRegDWORD HKEY_LOCAL_MACHINE "Software\ODBC\ODBCINST.INI\Presto ODBC Driver" "UsageCount" 1 72 | sectionEnd 73 | 74 | # Uninstaller 75 | function un.onInit 76 | SetShellVarContext all 77 | SetRegView 64 78 | #Verify the uninstaller - last chance to back out 79 | MessageBox MB_OKCANCEL "Permanently remove ${APPNAME}?" IDOK next 80 | Abort 81 | next: 82 | !insertmacro VerifyUserIsAdmin 83 | functionEnd 84 | 85 | section "uninstall" 86 | # Remove files 87 | delete $INSTDIR\"${PRESTO_DLL}" 88 | delete $INSTDIR\"${LIBCURL_DLL}" 89 | delete $INSTDIR\*.log 90 | delete $INSTDIR\uninstall.exe 91 | 92 | # Try to remove the install directory - this will only happen if it is empty 93 | rmDir $INSTDIR 94 | 95 | # Remove driver info from the registry 96 | DeleteRegValue HKLM "Software\ODBC\ODBCINST.INI\ODBC Drivers" "Presto ODBC Driver" 97 | DeleteRegKey HKLM "Software\ODBC\ODBCINST.INI\Presto ODBC Driver" 98 | 99 | sectionEnd -------------------------------------------------------------------------------- /driver/prestoresults.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.odbcdriver.prestoresults; 15 | 16 | import std.array : front, empty, popFront; 17 | import std.conv : text, to; 18 | import std.variant : Variant; 19 | import facebook.json : JSONValue, JSON_TYPE; 20 | 21 | import odbc.sqlext; 22 | import odbc.odbcinst; 23 | 24 | import presto.client.queryresults : ColumnMetadata; 25 | 26 | import presto.odbcdriver.bindings : OdbcResult, OdbcResultRow; 27 | import presto.odbcdriver.util : dllEnforce, logMessage; 28 | 29 | void addToPrestoResultRow(JSONValue columnData, PrestoResultRow result, string resultDataType) { 30 | final switch (columnData.type) { 31 | case JSON_TYPE.STRING: 32 | if (resultDataType == "varchar") { 33 | result.addNextValue(Variant(columnData.str)); 34 | } else { 35 | if (columnData.str != "NaN") { 36 | dllEnforce(false, "A non-Nan double is not expected in the result"); 37 | } 38 | result.addNextValue(Variant(double.nan)); 39 | } 40 | break; 41 | case JSON_TYPE.INTEGER: 42 | result.addNextValue(Variant(columnData.integer)); 43 | break; 44 | case JSON_TYPE.FLOAT: 45 | result.addNextValue(Variant(columnData.floating)); 46 | break; 47 | case JSON_TYPE.TRUE: 48 | result.addNextValue(Variant(true)); 49 | break; 50 | case JSON_TYPE.FALSE: 51 | result.addNextValue(Variant(false)); 52 | break; 53 | case JSON_TYPE.NULL: 54 | result.addNextValue(Variant(null)); 55 | break; 56 | case JSON_TYPE.OBJECT: 57 | case JSON_TYPE.ARRAY: 58 | case JSON_TYPE.UINTEGER: 59 | dllEnforce(false, "Unexpected JSON type: " ~ text(columnData.type)); 60 | break; 61 | } 62 | } 63 | 64 | unittest { 65 | JSONValue v = "NaN"; 66 | auto r = new PrestoResultRow(); 67 | addToPrestoResultRow(v, r, "double"); 68 | assert(r.data[0].type == typeid(double));//double NaN 69 | addToPrestoResultRow(v, r, "varchar");//string "NaN" 70 | assert(r.data[1].type == typeid(string)); 71 | v = "{\"x\":\"y\"}"; 72 | addToPrestoResultRow(v, r, "varchar"); 73 | assert(r.data[2].type == typeid(string)); 74 | } 75 | 76 | final class PrestoResult : OdbcResult { 77 | void addRow(PrestoResultRow r) { 78 | dllEnforce(r.numberOfColumns() != 0, "Row has at least 1 column"); 79 | results_ ~= r; 80 | numberOfColumns_ = r.numberOfColumns(); 81 | } 82 | 83 | override bool empty() const { 84 | return results_.empty; 85 | } 86 | 87 | override inout(PrestoResultRow) front() inout { 88 | assert(!empty); 89 | return results_.front; 90 | } 91 | 92 | override void popFront() { 93 | results_.popFront(); 94 | } 95 | 96 | override size_t numberOfColumns() { 97 | return numberOfColumns_; 98 | } 99 | 100 | void columnMetadata(immutable(ColumnMetadata)[] data) { 101 | if (!columnMetadata_) { 102 | columnMetadata_ = data; 103 | } 104 | } 105 | 106 | auto columnMetadata() const { 107 | return columnMetadata_; 108 | } 109 | 110 | private: 111 | PrestoResultRow[] results_; 112 | immutable(ColumnMetadata)[] columnMetadata_ = null; 113 | size_t numberOfColumns_ = 0; 114 | } 115 | 116 | final class PrestoResultRow : OdbcResultRow { 117 | void addNextValue(Variant v) { 118 | data ~= v; 119 | } 120 | 121 | override Variant dataAt(int column) { 122 | dllEnforce(column >= 1); 123 | return data[column - 1]; 124 | } 125 | 126 | size_t numberOfColumns() { 127 | dllEnforce(!data.empty, "Row has 0 columns"); 128 | return cast(uint) data.length; 129 | } 130 | private: 131 | Variant[] data; 132 | } 133 | -------------------------------------------------------------------------------- /client/util.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | module presto.client.util; 16 | 17 | import std.algorithm : find, max; 18 | import std.array : array, front, empty, popFront; 19 | import std.conv : text; 20 | import std.range : ElementType, isBidirectionalRange, retro; 21 | import std.typecons : Nullable, tuple, Unqual; 22 | 23 | import facebook.json : JSONValue, JSON_TYPE; 24 | 25 | version(unittest) { 26 | import facebook.json : parseJSON; 27 | import std.exception : assertThrown; 28 | } 29 | 30 | class PrestoClientException : Exception { 31 | this(string msg, string file = __FILE__, int line = __LINE__) { 32 | super(msg, file, line); 33 | } 34 | this(string msg, string file = __FILE__, int line = __LINE__) immutable { 35 | super(msg, file, line); 36 | } 37 | 38 | } 39 | 40 | bool asBool(const(JSONValue) v) { 41 | if (v.type == JSON_TYPE.TRUE) { 42 | return true; 43 | } 44 | if (v.type == JSON_TYPE.FALSE) { 45 | return false; 46 | } 47 | throw new PrestoClientException("Expected a JSON bool, got: " ~ text(v.type)); 48 | } 49 | 50 | unittest { 51 | assert(asBool(parseJSON("true")) == true); 52 | assert(asBool(parseJSON("false")) == false); 53 | assertThrown!PrestoClientException(asBool(parseJSON(`"blahblah"`))); 54 | } 55 | 56 | T jsonValueAs(T)(JSONValue elt) { 57 | static if (is(T == bool)) { 58 | return asBool(elt); 59 | } else static if (is(T == long)) { 60 | return elt.integer; 61 | } else static if (is(T == double)) { 62 | return elt.floating; 63 | } else { 64 | return elt.str; 65 | } 66 | } 67 | 68 | unittest { 69 | assert(jsonValueAs!long(parseJSON("5")) == 5); 70 | assert(jsonValueAs!long(parseJSON("4")) != 5); 71 | assertThrown!Exception(jsonValueAs!long(parseJSON("\"str\""))); 72 | } 73 | 74 | immutable(Nullable!T) getOptionalProperty(T, string propertyName)(JSONValue src) { 75 | if (propertyName !in src) { 76 | return immutable(Nullable!T)(); 77 | } 78 | return immutable(Nullable!T)(jsonValueAs!T(src[propertyName])); 79 | } 80 | 81 | unittest { 82 | auto js = parseJSON(`{"test" : "value"}`); 83 | assert(getOptionalProperty!(string, "test")(js).get == "value"); 84 | assert(getOptionalProperty!(string, "meep")(js).isNull); 85 | } 86 | 87 | T getPropertyOrDefault(T, string propertyName)(JSONValue src, lazy T default_ = T.init) { 88 | if (propertyName !in src) { 89 | return default_; 90 | } 91 | return jsonValueAs!T(src[propertyName]); 92 | } 93 | 94 | unittest { 95 | auto js = parseJSON(`{"test" : "value"}`); 96 | assert(getPropertyOrDefault!(string, "test")(js) == "value"); 97 | assert(getPropertyOrDefault!(string, "meep")(js) == ""); 98 | } 99 | 100 | auto findLastSplit(alias pred = "a==b")(string haystack, char needle) { 101 | auto result = haystack.retro.array.find!(pred)(needle); 102 | if (result.empty && haystack != text(needle)) { 103 | return tuple(haystack, haystack[0 .. 0], haystack[0 .. 0]); 104 | } 105 | 106 | auto lengthBeforeNeedle = !result.empty ? result.length - 1 : 0; 107 | auto beforeNeedle = haystack[0 .. lengthBeforeNeedle]; 108 | 109 | haystack = haystack[lengthBeforeNeedle .. $]; 110 | auto outNeedle = haystack[0 .. 1]; 111 | 112 | haystack = haystack[1 .. $]; 113 | return tuple(beforeNeedle, outNeedle, haystack); 114 | } 115 | 116 | unittest { 117 | auto test = "meep:blop"; 118 | auto result = test.findLastSplit(':'); 119 | assert(result[0] == "meep"); 120 | assert(result[1] == ":"); 121 | assert(result[2] == "blop"); 122 | 123 | test = ":"; 124 | result = test.findLastSplit(':'); 125 | assert(result[0] == ""); 126 | assert(result[1] == ":"); 127 | assert(result[2] == ""); 128 | 129 | test = ""; 130 | result = test.findLastSplit(':'); 131 | assert(result[0] == ""); 132 | assert(result[1] == ""); 133 | assert(result[2] == ""); 134 | 135 | test = "meep"; 136 | result = test.findLastSplit(':'); 137 | assert(result[0] == "meep"); 138 | assert(result[1] == ""); 139 | assert(result[2] == ""); 140 | 141 | test = "a:"; 142 | result = test.findLastSplit(':'); 143 | assert(result[0] == "a"); 144 | assert(result[1] == ":"); 145 | assert(result[2] == ""); 146 | 147 | test = ":b"; 148 | result = test.findLastSplit(':'); 149 | assert(result[0] == ""); 150 | assert(result[1] == ":"); 151 | assert(result[2] == "b"); 152 | 153 | test = "a:b:c"; 154 | result = test.findLastSplit(':'); 155 | assert(result[0] == "a:b"); 156 | assert(result[1] == ":"); 157 | assert(result[2] == "c"); 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Presto](http://prestodb.io) ODBC Driver 2 | 3 | ODBC is a C API that provides a standard way of communicating with anything that accepts SQL-like input; the Presto ODBC Driver lets you communicate with Presto via this standard. The high-level goal is to be able to communicate with Presto from MS Query, MS Excel, and Tableau. 4 | 5 | This driver is written in the [D Programming Language](http://dlang.org) 6 | 7 | ## Current Status 8 | 9 | * Only works on Windows 10 | * Many functions are not implemented, the driver does *not* meet the "Core" level of [ODBC conformance](odbc-conformance.md) 11 | * The only error handling in place is a "catch and log" strategy 12 | * Most queries will work as expected 13 | * Tableau works correctly for the cases we have tried 14 | * MS Query is partially tested and may work if you create a Presto File DSN 15 | * The driver is single-threaded 16 | * You cannot run multiple instances of the driver on the same machine due to limitations in D's support for DLLs 17 | 18 | ## Goals 19 | 20 | * Full ODBC 3.51 conformance 21 | * Full support on Windows/Mac/Linux 22 | * Seamless integration with Tableau 23 | 24 | ## Development Environment Setup 25 | 26 | ### Installation Prerequisites 27 | 28 | 1. Cygwin with the GNU make package 29 | 1. [MSVC 64-bit linker](http://www.visualstudio.com) (download and install the free Express 2013 edition for Windows Desktop) 30 | 1. dmd (D Language Compiler), tested with [dmd 2.065](http://dlang.org/download) (note: this must be the installer version and it must be installed *after* Visual Studio) 31 | 1. Access to a running [Presto](http://prestodb.io) instance 32 | 33 | ### Building and Registering the Driver 34 | 35 | 1. Build the Presto ODBC Driver 36 | 1. Launch the Cygwin terminal 37 | 1. Navigate to your checkout of this repo (e.g. `cd /cygdrive/c/presto-odbc`) 38 | 1. `make clean install` -- builds the driver, runs tests, copies the driver and libcurl to `C:\temp` and backs up the log files 39 | 1. Register the Presto ODBC Driver by double clicking the `register_driver.reg` file in the main directory of this repo 40 | 1. Setup a data source for the driver 41 | 1. Open Control Panel and choose `Set up ODBC data sources (64-bit)` 42 | 1. Sanity Check: Look at the `Drivers` tab, make sure you see `Presto ODBC Driver` 43 | 1. Enable the Driver Manager Logfile (from the ODBC Data Sources window) 44 | 1. Go to the `Tracing` tab 45 | 1. Set the `Log File Path` to `C:\temp\SQL.LOG` 46 | 1. Click `Start Tracing Now` 47 | 1. Click Ok to close the program 48 | 49 | ### Using the driver with Tableau 50 | 51 | 1. Open Tableau 52 | 1. Click `Connect to data` 53 | 1. At the bottom, select `Other Database (ODBC)` 54 | 1. Click the radio button for `Driver` 55 | 1. Select `Presto ODBC Driver` 56 | 1. Click `Connect` 57 | 1. Enter a connection parameters string of the form `key=value;key=value` to describe your connection. A description of the supported keys and values can be found in the [Connection Parameters](connection-parameters.md) file 58 | 1. Tableau will then partially load the driver and redundantly ask for the same information in Tableau's UI: 59 | 1. Replace the contents of the `String Extras` box with the string you entered in notepad. Ignore the rest. 60 | 1. Click `OK` 61 | 1. Tableau will perform a bunch of fake queries to analyze the ODBC driver, this may take a while to complete but only needs to happen once 62 | 1. On the new screen: 63 | 1. Click `Select Schema`, then press the search icon, then select the schema you entered on the previous screen 64 | 1. Search for `tables` (or press enter to see all of them) and drag any table you wish to use to the right 65 | 1. Click `Go to Worksheet` 66 | 1. Click `OK` to go past the warning dialog 67 | 1. Analyze! 68 | 69 | ## Coding Conventions 70 | 71 | Not all of the conventions have been applied to the source yet. 72 | 73 | * 4 space indentation 74 | * Try to limit to 120 columns 75 | * Prefer `myHttpFunction` to `myHTTPFunction` 76 | * All ODBC functions must have their contents wrapped with a call to `exceptionBoundary` 77 | * As appropriate, change integer types to enum types (use `StatementAttribute` instead of `SQLSMALLINT`, etc) 78 | * Always wrap C types with safer D abstractions (see `toDString` and `OutputWChar`); prefix the C-style variables with an underscore 79 | * Also prefix variables with an underscore to express that the variable should be cast/converted to or encapsulated in another type before use 80 | * Use `dllEnforce` instead of `assert` 81 | * Avoid fully qualifying enum values (`MyEnumType.MyEnumValue`); use a `with` statement instead 82 | * Always use a `with` statement when accessing ODBC handles 83 | * Always specify whether lengths are in bytes or characters as part of a variable's name 84 | * At the top of the file, separate `import`s from different packages with a blank line. In particular, there should be a blank line between imports from `std` and `presto.client` as well as between `presto.client` and `presto.driver` 85 | * Always group imports from the same packages together 86 | 87 | ## References 88 | 89 | * [ODBC 3.x Requirements](http://msdn.microsoft.com/en-us/library/ms713848%28v=vs.85%29.aspx) 90 | * [ODBC Function Summary](http://msdn.microsoft.com/en-us/library/ms712628%28v=vs.85%29.aspx) 91 | * [ODBC C Data Types Table](http://msdn.microsoft.com/en-us/library/ms714556%28v=vs.85%29.aspx) 92 | * [ODBC SQL Data Types Table](http://msdn.microsoft.com/en-us/library/ms710150%28v=vs.85%29.aspx) 93 | * [Interfacing from D to C](http://dlang.org/interfaceToC) 94 | -------------------------------------------------------------------------------- /odbc/sqltypes.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 1995 by Ke Jin 3 | * Copyright (C) 1996-2014 by OpenLink Software 4 | * All Rights Reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in 14 | * the documentation and/or other materials provided with the 15 | * distribution. 16 | * 3. Neither the name of OpenLink Software Inc. nor the names of its 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 OPENLINK OR 24 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | module odbc.sqltypes; 33 | 34 | version(Windows) { 35 | public import std.c.windows.windows; 36 | } else { 37 | alias VOID = void; 38 | enum TRUE = 1; 39 | enum FALSE = 0; 40 | 41 | alias BYTE = ubyte; 42 | alias CHAR = char; 43 | alias WORD = ushort; 44 | alias DWORD = uint; 45 | alias LPVOID = void*; 46 | alias LPSTR = SQLCHAR*; 47 | alias LPWSTR = SQLWCHAR*; 48 | alias LPCSTR = const(SQLCHAR)*; 49 | alias LPCWSTR = const(SQLWCHAR)*; 50 | alias LPTSTR = TCHAR*; 51 | alias LPDWORD = DWORD*; 52 | alias BOOL = int; 53 | } 54 | 55 | nothrow: 56 | 57 | //API declaration data types 58 | alias SQLCHAR = ubyte; 59 | alias SQLSMALLINT = short; 60 | alias SQLUSMALLINT = ushort; 61 | alias SQLINTEGER = int; 62 | alias SQLUINTEGER = uint; 63 | alias SQLPOINTER = void*; 64 | alias SQLSCHAR = byte; 65 | alias SQLDATE = ubyte; 66 | alias SQLDECIMAL = ubyte; 67 | alias SQLNUMERIC = ubyte; 68 | alias SQLDOUBLE = double; 69 | alias SQLFLOAT = double; 70 | alias SQLREAL = float; 71 | alias SQLTIME = ubyte; 72 | alias SQLTIMESTAMP = ubyte; 73 | alias SQLVARCHAR = ubyte; 74 | 75 | version(Win64) { 76 | alias SQLLEN = long; 77 | alias SQLULEN = ulong; 78 | alias SQLSETPOSIROW = ulong; 79 | } else { 80 | alias SQLLEN = int; 81 | alias SQLULEN = uint; 82 | alias SQLSETPOSIROW = ushort; 83 | } 84 | 85 | //Backward compatibility with older platform sdks 86 | alias SQLROWCOUNT = SQLULEN; 87 | alias SQLROWSETSIZE = SQLULEN; 88 | alias SQLTRANSID = SQLULEN; 89 | alias SQLROWOFFSET = SQLLEN; 90 | 91 | //Generic pointer types 92 | alias PTR = void*; 93 | alias SQLHANDLE = void*; 94 | 95 | //Handles 96 | 97 | alias HENV = void*; 98 | alias HDBC = void*; 99 | alias HSTMT = void*; 100 | alias SQLHENV = SQLHANDLE; 101 | alias SQLHDBC = SQLHANDLE; 102 | alias SQLHSTMT = SQLHANDLE; 103 | alias SQLHDESC = SQLHANDLE; 104 | 105 | //Window Handle 106 | version(Win32) { 107 | alias SQLHWND = HWND; 108 | } else version(OSX) { 109 | alias HWND = SQLPOINTER; //WindowPtr; 110 | alias SQLHWND = HWND; 111 | } else { 112 | alias SQLHWND = SQLPOINTER; 113 | } 114 | 115 | //SQL portable types for C 116 | alias UCHAR = ubyte; 117 | alias SCHAR = byte; 118 | alias SWORD = short; 119 | alias UWORD = ushort; 120 | alias SDWORD = int; 121 | alias UDWORD = uint; 122 | 123 | alias SSHORT = short; 124 | alias USHORT = ushort; 125 | alias SLONG = int; 126 | alias ULONG = uint; 127 | alias SFLOAT = float; 128 | alias SDOUBLE = double; 129 | alias LDOUBLE = double; 130 | 131 | //Return type for functions 132 | alias RETCODE = short; 133 | alias SQLRETURN = SQLSMALLINT; 134 | 135 | //SQL portable types for C - DATA, TIME, TIMESTAMP, and BOOKMARK 136 | alias BOOKMARK = SQLULEN; 137 | 138 | struct DATE_STRUCT { 139 | SQLSMALLINT year; 140 | SQLUSMALLINT month; 141 | SQLUSMALLINT day; 142 | } 143 | alias SQL_DATE_STRUCT = DATE_STRUCT; 144 | 145 | struct TIME_STRUCT { 146 | SQLUSMALLINT hour; 147 | SQLUSMALLINT minute; 148 | SQLUSMALLINT second; 149 | } 150 | alias SQL_TIME_STRUCT = TIME_STRUCT; 151 | 152 | struct TIMESTAMP_STRUCT { 153 | SQLSMALLINT year; 154 | SQLUSMALLINT month; 155 | SQLUSMALLINT day; 156 | SQLUSMALLINT hour; 157 | SQLUSMALLINT minute; 158 | SQLUSMALLINT second; 159 | SQLUINTEGER fraction; 160 | } 161 | alias SQL_TIMESTAMP_STRUCT = TIMESTAMP_STRUCT; 162 | 163 | /* 164 | * Enumeration for DATETIME_INTERVAL_SUBCODE values for interval data types 165 | * 166 | * These values are from SQL-92 167 | */ 168 | 169 | enum SQLINTERVAL { 170 | SQL_IS_YEAR = 1, 171 | SQL_IS_MONTH, 172 | SQL_IS_DAY, 173 | SQL_IS_HOUR, 174 | SQL_IS_MINUTE, 175 | SQL_IS_SECOND, 176 | SQL_IS_YEAR_TO_MONTH, 177 | SQL_IS_DAY_TO_HOUR, 178 | SQL_IS_DAY_TO_MINUTE, 179 | SQL_IS_DAY_TO_SECOND, 180 | SQL_IS_HOUR_TO_MINUTE, 181 | SQL_IS_HOUR_TO_SECOND, 182 | SQL_IS_MINUTE_TO_SECOND 183 | } 184 | 185 | struct SQL_YEAR_MONTH_STRUCT { 186 | SQLUINTEGER year; 187 | SQLUINTEGER month; 188 | } 189 | 190 | struct SQL_DAY_SECOND_STRUCT { 191 | SQLUINTEGER day; 192 | SQLUINTEGER our; 193 | SQLUINTEGER minute; 194 | SQLUINTEGER second; 195 | SQLUINTEGER fraction; 196 | } 197 | 198 | struct SQL_INTERVAL_STRUCT { 199 | SQLINTERVAL interval_type; 200 | SQLSMALLINT interval_sign; 201 | TimeRep intval; 202 | 203 | union TimeRep { 204 | SQL_YEAR_MONTH_STRUCT year_month; 205 | SQL_DAY_SECOND_STRUCT day_second; 206 | } 207 | } 208 | 209 | //The ODBC C types for SQL_C_SBIGINT and SQL_C_UBIGINT 210 | alias ODBCINT64 = long; 211 | alias SQLBIGINT = ODBCINT64; 212 | alias SQLUBIGINT = ulong; 213 | 214 | //The internal representation of the numeric data type 215 | enum SQL_MAX_NUMERIC_LEN = 16; 216 | struct SQL_NUMERIC_STRUCT { 217 | SQLCHAR precision; 218 | SQLSCHAR scale; 219 | SQLCHAR sign; //0 for negative, 1 for positive 220 | SQLCHAR val[SQL_MAX_NUMERIC_LEN]; 221 | } 222 | 223 | struct SQLGUID { 224 | DWORD Data1; 225 | WORD Data2; 226 | WORD Data3; 227 | BYTE Data4[8]; //BYTE 228 | } 229 | 230 | alias SQLWCHAR = wchar; 231 | 232 | version(UNICODE) { 233 | alias TCHAR = SQLWCHAR; 234 | } else { 235 | alias TCHAR = SQLCHAR; 236 | } 237 | alias SQLTCHAR = TCHAR; 238 | -------------------------------------------------------------------------------- /driver/bindings.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.odbcdriver.bindings; 15 | 16 | import std.stdio : writeln; 17 | import std.array : empty, front, popFront; 18 | import std.conv : to, text, wtext; 19 | import std.variant : Variant; 20 | import std.typetuple : TypeTuple; 21 | import std.traits : isSomeString, Unqual; 22 | 23 | import odbc.sqlext; 24 | import odbc.odbcinst; 25 | 26 | import presto.odbcdriver.util; 27 | 28 | unittest { 29 | enum testSqlTypeId = SQL_C_TYPE_ID.SQL_C_LONG; 30 | alias testSqlType = int; 31 | auto binding = ColumnBinding(testSqlTypeId, [], new SQLLEN); 32 | binding.outputBuffer.length = testSqlType.sizeof; 33 | binding.numberOfBytesWritten = -1; 34 | 35 | copyToOutput!(testSqlType)(Variant(5), binding); 36 | assert(binding.numberOfBytesWritten == testSqlType.sizeof); 37 | assert(*(cast(testSqlType*) binding.outputBuffer) == 5); 38 | } 39 | 40 | unittest { 41 | enum testSqlTypeId = SQL_C_TYPE_ID.SQL_C_CHAR; 42 | alias testSqlType = string; 43 | auto binding = ColumnBinding(testSqlTypeId, [], new SQLLEN); 44 | binding.outputBuffer.length = 10; 45 | binding.numberOfBytesWritten = -1; 46 | 47 | copyToOutput!(testSqlType)(Variant("Hello world, my name is Fred"), binding); 48 | assert(binding.numberOfBytesWritten == 9); 49 | assert(cast(char[]) binding.outputBuffer == "Hello wor\0"); 50 | } 51 | 52 | //Writes the value inside the Variant into the buffer specified by the binding 53 | void copyToOutput(SQL_C_TYPE)(Variant value, ref ColumnBinding binding) { 54 | 55 | static void copyToOutputImpl(VARIANT_TYPE)(Variant value, ref ColumnBinding binding) { 56 | alias ResultType = firstNonVoidType!(SQL_C_TYPE, VARIANT_TYPE); 57 | 58 | with (binding) { 59 | static if (is(VARIANT_TYPE == typeof(null))) { 60 | numberOfBytesWritten = SQL_NULL_DATA; 61 | } else static if (is(ResultType == string)) { 62 | static if (is(VARIANT_TYPE == string)) { 63 | auto srcString = value.get!VARIANT_TYPE; 64 | } else { 65 | logMessage("Converting a non-string", VARIANT_TYPE.stringof, "to a string for output"); 66 | auto srcString = to!ResultType(value.get!VARIANT_TYPE); 67 | } 68 | numberOfBytesWritten = copyToNarrowBuffer(srcString, cast(char[]) outputBuffer); 69 | } else static if (is(ResultType == wstring)) { 70 | static if (is(VARIANT_TYPE == wstring)) { 71 | auto srcString = value.get!VARIANT_TYPE; 72 | } else { 73 | static if (!is(VARIANT_TYPE == string)) { 74 | logMessage("Converting a non-string type", VARIANT_TYPE.stringof, "to a wstring for output"); 75 | } 76 | auto srcString = to!ResultType(value.get!VARIANT_TYPE); 77 | } 78 | auto outputWCharBuffer = outputWChar(outputBuffer, indicator); 79 | if (!outputWCharBuffer) { 80 | numberOfBytesWritten = SQL_NO_TOTAL; 81 | return; 82 | } 83 | copyToBuffer(srcString, outputWCharBuffer); 84 | } else static if (isSomeString!VARIANT_TYPE) { 85 | assert(false, ResultType.stringof ~ " " ~ VARIANT_TYPE.stringof); 86 | } else { 87 | auto resultPtr = cast(ResultType*) outputBuffer.ptr; 88 | *resultPtr = to!ResultType(value.get!VARIANT_TYPE); 89 | numberOfBytesWritten = ResultType.sizeof; 90 | } 91 | } 92 | } //with 93 | 94 | dispatchOnVariantType!(copyToOutputImpl)(value, binding); 95 | } 96 | 97 | unittest { 98 | dispatchOnSqlCType!(requireIntType)(SQL_C_TYPE_ID.SQL_C_LONG); 99 | } 100 | 101 | auto dispatchOnSqlCType(alias fun, TList...)(SQL_C_TYPE_ID type, auto ref TList vs) { 102 | auto impl(SqlTList...)() { 103 | static if (SqlTList.length <= 1) { 104 | assert(false, "Bad SQL_TYPE_ID passed: " ~ text(type)); 105 | } else { 106 | if (type == SqlTList[0]) { 107 | return fun!(SqlTList[1])(vs); 108 | } else { 109 | return impl!(SqlTList[2 .. $])(); 110 | } 111 | } 112 | } 113 | return impl!SQL_C_TYPES(); 114 | } 115 | 116 | version(unittest) { 117 | static void requireIntType(T, TList...)(TList) { 118 | static if (!is(T == int)) { 119 | assert(false, "Wrong type dispatched"); 120 | } 121 | } 122 | } 123 | 124 | unittest { 125 | int testValue = 5; 126 | dispatchOnVariantType!(requireIntType)(Variant(testValue)); 127 | } 128 | 129 | auto dispatchOnVariantType(alias fun, TList...)(Variant value, auto ref TList vs) { 130 | auto type = value.type(); 131 | foreach (T; TypeTuple!(string, short, ushort, int, uint, long, ulong, bool, 132 | double, Nullability, SQL_TYPE_ID, typeof(null))) { 133 | if(type == typeid(T)) { 134 | return fun!T(value, vs); 135 | } 136 | } 137 | assert(false, "Unexpected type in variant: " ~ text(value.type())); 138 | } 139 | 140 | unittest { 141 | static assert(is(firstNonVoidType!(int, void, double) == int)); 142 | static assert(is(firstNonVoidType!(void, double) == double)); 143 | static assert(is(firstNonVoidType!(void, void, double) == double)); 144 | } 145 | 146 | template firstNonVoidType(TList...) { 147 | static assert(TList.length != 0, "No non-void types in the list"); 148 | 149 | static if (is(TList[0] == void)) { 150 | alias firstNonVoidType = .firstNonVoidType!(TList[1 .. $]); 151 | } else { 152 | alias firstNonVoidType = TList[0]; 153 | } 154 | } 155 | 156 | /** 157 | * Stores information about how to return results to the user for a particular column. 158 | */ 159 | struct ColumnBinding { 160 | this(SQL_C_TYPE_ID columnType, void[] outputBuffer, SQLLEN* indicator) { 161 | this.columnType = columnType; 162 | this.outputBuffer = outputBuffer; 163 | this.indicator = indicator; 164 | } 165 | 166 | SQL_C_TYPE_ID columnType; 167 | void[] outputBuffer; 168 | SQLLEN* indicator; 169 | 170 | SQLLEN numberOfBytesWritten() { 171 | assert(indicator != null); 172 | return *indicator; 173 | } 174 | 175 | void numberOfBytesWritten(SQLLEN value) { 176 | if (indicator != null) { 177 | *indicator = value; 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * A range that allows retrieving one row at a time from the result set of a query. 184 | */ 185 | interface OdbcResult { 186 | bool empty() const; 187 | inout(OdbcResultRow) front() inout; 188 | void popFront(); 189 | 190 | size_t numberOfColumns(); 191 | } 192 | 193 | final class EmptyOdbcResult : OdbcResult { 194 | override bool empty() const { return true; } 195 | override inout(OdbcResultRow) front() inout { return null; } 196 | override void popFront() {} 197 | 198 | override size_t numberOfColumns() { return 0; } 199 | } 200 | 201 | interface OdbcResultRow { 202 | Variant dataAt(int column); 203 | } 204 | -------------------------------------------------------------------------------- /client/statementclient.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.client.statementclient; 15 | 16 | import core.time : dur, Duration; 17 | 18 | import std.algorithm : findSplit; 19 | import std.conv : text, to; 20 | import std.datetime : SysTime, Clock; 21 | import std.string : toLower; 22 | import std.traits : EnumMembers; 23 | import std.typecons : Rebindable, rebindable; 24 | import std.net.curl : HTTP; 25 | 26 | import facebook.json : parseJSON; 27 | 28 | import presto.client.mockcurl : get, post, del; 29 | import presto.client.queryresults : QueryResults, queryResults; 30 | import presto.client.util; 31 | 32 | version(unittest) { 33 | import presto.client.mockcurl : enqueueCurlResult; 34 | import presto.client.queryresults : JSBuilder; 35 | } 36 | 37 | struct ClientSession { 38 | this(string endpoint) { 39 | this.endpoint = endpoint; 40 | this.time_zone = getThisTimeZoneId(); 41 | } 42 | 43 | this(string endpoint, string source) { 44 | this(endpoint); 45 | this.source = source; 46 | } 47 | 48 | string endpoint = null; 49 | string user = null; 50 | string source = null; 51 | string schema = null; 52 | string catalog = null; 53 | Duration timeout = dur!"seconds"(5); 54 | string proxyEndpoint = null; 55 | 56 | //Obeys format from http://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html 57 | string time_zone; 58 | 59 | //TODO: This is non-trivial. 60 | //Obeys format from Java Locale 61 | string language = null; 62 | 63 | //NOTE: Do *NOT* attempt to cache this work - even though HTTP is a struct 64 | // it has surprising class-like copying semantics. 65 | HTTP connection() const { 66 | import etc.c.curl; 67 | 68 | auto http = HTTP(); 69 | http.connectTimeout(timeout); 70 | if (proxyEndpoint && !proxyEndpoint.empty) { 71 | auto hostPort = proxyEndpoint.findLastSplit(':'); 72 | if (hostPort[2].empty) { 73 | throw new PrestoClientException("Must specify a proxy port"); 74 | } 75 | http.proxy = hostPort[0]; 76 | http.proxyPort = to!ushort(hostPort[2]); 77 | http.proxyType = CurlProxy.socks5_hostname; 78 | } 79 | 80 | return addHeaders(http); 81 | } 82 | 83 | private HTTP addHeaders(HTTP http) const { 84 | foreach (header; EnumMembers!PRESTO_HEADER) { 85 | enum memberName = mapHeaderToMemberName!header; 86 | http.addHeaderIfNotNull!(header)(mixin(memberName)); 87 | } 88 | 89 | return http; 90 | } 91 | } 92 | 93 | unittest { 94 | auto cs = ClientSession("localhost"); 95 | cs.schema = "tiny"; 96 | cs.catalog = "tpch"; 97 | cs.connection; 98 | } 99 | 100 | string getThisTimeZoneId() { 101 | auto utcOffset = SysTime(0).timezone.utcOffsetAt(Clock.currTime.stdTime); 102 | auto hoursOffset = utcOffset.hours; 103 | auto offsetSign = hoursOffset > 0 ? "+" : "-"; 104 | return "GMT" ~ offsetSign ~ text(hoursOffset); 105 | } 106 | 107 | struct StatementClient { 108 | @disable this(); 109 | 110 | this(ClientSession session, string query) { 111 | this.session_ = session; 112 | this.query_ = query; 113 | 114 | auto fullEndpoint = session.endpoint ~ "/v1/statement"; 115 | auto response = fullEndpoint.post(query, session.connection); 116 | 117 | parseAndSetResults(response); 118 | } 119 | 120 | ~this() { 121 | terminateQuery(); 122 | } 123 | 124 | inout(ClientSession) session() inout { 125 | return session_; 126 | } 127 | string query() const { 128 | return query_; 129 | } 130 | bool queryTerminated() { 131 | return queryTerminated_; 132 | } 133 | 134 | immutable(QueryResults) front() const nothrow pure { 135 | return results_; 136 | } 137 | 138 | void popFront() { 139 | assert(!empty); 140 | auto response = get(results_.nextURI, session.connection); 141 | parseAndSetResults(response); 142 | 143 | if (!results_.succeeded || results_.nextURI == "") { 144 | processedAll_ = true; 145 | } 146 | } 147 | 148 | bool empty() const nothrow { 149 | return processedAll_ || queryTerminated_; 150 | } 151 | 152 | void terminateQuery() { 153 | if (results_.nextURI != "") { 154 | del(results_.nextURI, session.connection); 155 | queryTerminated_ = true; 156 | } 157 | } 158 | 159 | private: 160 | void parseAndSetResults(char[] response) { 161 | results_ = Rebindable!(immutable(QueryResults))(queryResults(parseJSON(response))); 162 | } 163 | 164 | bool queryTerminated_ = false; 165 | bool processedAll_ = false; 166 | ClientSession session_; 167 | string query_; 168 | Rebindable!(immutable(QueryResults)) results_; 169 | } 170 | 171 | enum PRESTO_HEADER { 172 | USER = "X-Presto-User", 173 | SOURCE = "X-Presto-Source", 174 | CATALOG = "X-Presto-Catalog", 175 | SCHEMA = "X-Presto-Schema", 176 | //TIME_ZONE = "X-Presto-Time-Zone", 177 | //LANGUAGE = "X-Presto-Language", 178 | } 179 | 180 | unittest { 181 | enqueueCurlResult(new JSBuilder().withNext().toString().dup); 182 | enqueueCurlResult(new JSBuilder().withNext().withColumns().toString().dup); 183 | enqueueCurlResult(new JSBuilder().withNext().withColumns().withData().toString().dup); 184 | enqueueCurlResult(new JSBuilder().withColumns().withData().toString().dup); 185 | 186 | auto session = ClientSession("localhost"); 187 | auto query = "SELECT lemons FROM life"; 188 | auto client = StatementClient(session, query); 189 | assert(client.query == query); 190 | assert(!client.empty); 191 | assert(client.front.byRow().empty); 192 | 193 | client.popFront; 194 | assert(!client.empty); 195 | assert(client.front.byRow().empty); 196 | 197 | client.popFront; 198 | assert(!client.empty); 199 | assert(!client.front.byRow().empty); 200 | 201 | client.popFront; 202 | assert(client.empty); 203 | assert(!client.front.byRow().empty); 204 | } 205 | 206 | unittest { 207 | enqueueCurlResult(new JSBuilder().withNext().toString().dup); 208 | enqueueCurlResult(new JSBuilder().withNext().withColumns().toString().dup); 209 | enqueueCurlResult(new JSBuilder().withNext().withColumns().withData().toString().dup); 210 | enqueueCurlResult(new JSBuilder().withColumns().withData().toString().dup); 211 | 212 | auto session = ClientSession("localhost"); 213 | auto query = "SELECT lemons FROM life"; 214 | auto client = StatementClient(session, query); 215 | assert(client.query == query); 216 | assert(!client.empty); 217 | assert(client.front.byRow().empty); 218 | 219 | client.popFront; 220 | assert(!client.empty); 221 | assert(client.front.byRow().empty); 222 | 223 | client.popFront; 224 | assert(!client.empty); 225 | assert(!client.front.byRow().empty); 226 | 227 | client.popFront; 228 | assert(client.empty); 229 | assert(!client.front.byRow().empty); 230 | } 231 | 232 | unittest { 233 | import std.concurrency : Tid, spawn, receiveOnly; 234 | 235 | enqueueCurlResult(new JSBuilder().withNext().withColumns().withData().toString().dup); 236 | enqueueCurlResult(new JSBuilder().withNext().withColumns().withData().toString().dup); 237 | enqueueCurlResult(new JSBuilder().withNext().withColumns().withData().toString().dup); 238 | enqueueCurlResult(new JSBuilder().withNext().withColumns().withData().toString().dup); 239 | enqueueCurlResult(new JSBuilder().withColumns().withData().toString().dup); 240 | 241 | auto session = ClientSession("localhost"); 242 | auto client = StatementClient(session, ""); 243 | 244 | Tid[] workers; 245 | foreach (resultBatch; client) { 246 | workers ~= spawn(&resultProcessorThread, resultBatch); 247 | } 248 | 249 | foreach (workerTid; workers) { 250 | auto result = receiveOnly!long(); 251 | assert(result == 48); 252 | } 253 | } 254 | 255 | version(unittest) { 256 | import std.concurrency : ownerTid, send; 257 | 258 | void resultProcessorThread(immutable(QueryResults) resultBatch) { 259 | long reduceVal = 0; 260 | foreach (row; resultBatch.byRow!(long, "col2")()) { 261 | reduceVal += row[0]; 262 | } 263 | ownerTid.send(reduceVal); 264 | } 265 | } 266 | 267 | 268 | unittest { 269 | enqueueCurlResult(new JSBuilder().withNext().toString().dup); 270 | 271 | auto session = ClientSession("localhost"); 272 | auto client = StatementClient(session, "query"); 273 | assert(!client.empty); 274 | client.terminateQuery(); 275 | assert(client.empty); 276 | } 277 | 278 | private void addHeaderIfNotNull(PRESTO_HEADER header)(ref HTTP http, string value) { 279 | if (value != null) { 280 | string headerValue = header; 281 | http.addRequestHeader(text(headerValue), value); 282 | } 283 | } 284 | 285 | private string mapHeaderToMemberName(PRESTO_HEADER header)() { 286 | return toLower(text(header)); 287 | } 288 | 289 | unittest { 290 | static assert(mapHeaderToMemberName!(PRESTO_HEADER.USER) == "user"); 291 | } 292 | -------------------------------------------------------------------------------- /driver/handles.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.odbcdriver.handles; 15 | 16 | import odbc.sqlext; 17 | 18 | import presto.odbcdriver.bindings : ColumnBinding, OdbcResult, EmptyOdbcResult; 19 | import presto.odbcdriver.util; 20 | 21 | /** 22 | * About Descriptor Handles: 23 | * 1) There are 4 "sub-handle-types" that fall into this category. 24 | * They seem to have very little in common other than they must all produce 25 | * answers to the same set of queries from SQLGetDescField and SQLGetDescRec 26 | * (and must also work with the setter variants of those functions). 27 | * 2) Because these sub-handles are queried using the same enum values, but expected 28 | * to give distinct answers, I believe that virtual functions are an appropriate 29 | * representation. 30 | * 31 | * Design complications: 32 | * 1) An instance of each of the descriptor types is automatically "implicitly" (in ODBC language) 33 | * allocated for each OdbcStatement. 34 | * 2) The application can "explicitly" allocate new ARD or APD sub-handle-types 35 | * with AllocHandle and then change the members of an OdbcStatement handle to 36 | * those "explicitly" allocated handles instead. 37 | * 3) Explicitly allocated handles have a lifetime that matches the OdbcConnection 38 | * handle it is constructed with, not the OdbcStatement it is assigned to. 39 | * Explicitly allocated handles must therefore also tell any OdbcStatement that 40 | * is using it that it is being destroyed so they can go back to "implicit" handles. 41 | * 4) Explicitly allocated handles can be assigned to multiple OdbcStatements. 42 | * 5) When a handle is "explicitly" allocated, you do not know whether it will become 43 | * an ARD or APD until it is assigned to a statement. Because the user may set it 44 | * to more than one statement, this means a level of indirection is needed so that 45 | * the user can continue using the same pointer that they got from AllocHandle in 46 | * the first place. This is why I have an OdbcDescriptorImpl class. 47 | * 48 | * Alternative design considered: 49 | * Could have had all of the required data for each descriptor sub-type in the same class. 50 | * Could have used a discriminated union. This would mean more switches, but removes the 51 | * need for the Impl class/indirection/allocation. Could have ignored the need to have a 52 | * descriptor type at all and jammed everything into OdbcStatement, which is how this 53 | * project originally grew. I'm still not certain that I fully understand what all descriptor 54 | * sub-types store and where they would store it. At a minimum, the IPD and IRD types may be 55 | * much more transparent to the user because they cannot ever be set. One reason to separate 56 | * these things out is so that implementing the function SQLCopyDesc is easy/maintainable. 57 | */ 58 | 59 | /** 60 | * APD 61 | * Information about application buffers bound to the parameters in an SQL statement 62 | * such as their addresses, lengths, and C datatypes 63 | */ 64 | final class ApplicationParameterDescriptor : OdbcDescriptorImpl { 65 | this(OdbcConnection connection) { 66 | super(connection); 67 | } 68 | } 69 | 70 | /** 71 | * IPD 72 | * Information about parameters in an SQL statement, 73 | * such as their datatypes, lengths, and nullability 74 | */ 75 | final class ImplementationParameterDescriptor : OdbcDescriptorImpl { 76 | this(OdbcConnection connection) { 77 | super(connection); 78 | } 79 | } 80 | 81 | /** 82 | * ARD 83 | * Information about application buffers bound to the columns in a result set, 84 | * such as their addresses, lengths, and C datatypes 85 | */ 86 | final class ApplicationRowDescriptor : OdbcDescriptorImpl { 87 | this(OdbcConnection connection) { 88 | super(connection); 89 | } 90 | 91 | ColumnBinding[uint] columnBindings; 92 | } 93 | 94 | /** 95 | * IRD 96 | * Information about columns in a result set, 97 | * such as their datatypes, lengths, and nullability 98 | */ 99 | final class ImplementationRowDescriptor : OdbcDescriptorImpl { 100 | this(OdbcConnection connection) { 101 | super(connection); 102 | } 103 | } 104 | 105 | /** 106 | * An OdbcDescriptor is a handle to a specific type of descriptor. In particular, one of: 107 | * 1. Application Parameter Descriptor (APD) 108 | * 2. Implementation Parameter Descriptor (IPD) 109 | * 3. Application Row Descriptor (ARD) 110 | * 4. Implementation Row Descriptor (IRD) 111 | * It does not know which type it is at construction. 112 | */ 113 | final class OdbcDescriptor { 114 | this(OdbcConnection connection) { 115 | dllEnforce(connection !is null); 116 | this.connection = connection; 117 | connection.explicitlyAllocatedDescriptors[this] = true; 118 | } 119 | 120 | this(OdbcConnection connection, OdbcDescriptorImpl impl) { 121 | dllEnforce(connection !is null); 122 | this.connection = connection; 123 | this.impl = impl; 124 | } 125 | 126 | void setThisDescriptorAs(); 127 | 128 | OdbcConnection connection; 129 | OdbcDescriptorImpl impl = null; 130 | } 131 | 132 | private abstract class OdbcDescriptorImpl { 133 | this(OdbcConnection connection) { 134 | dllEnforce(connection !is null); 135 | this.connection = connection; 136 | } 137 | 138 | OdbcDescriptorImpl newImpl(this T)() { 139 | return T(connection); 140 | } 141 | 142 | OdbcConnection connection; 143 | } 144 | 145 | 146 | /** 147 | * An OdbcStatement handle object is allocated for each HSTATEMENT requested by the driver/client. 148 | */ 149 | final class OdbcStatement { 150 | this(OdbcConnection connection) { 151 | dllEnforce(connection !is null); 152 | this.connection = connection; 153 | this.latestOdbcResult = makeWithoutGC!EmptyOdbcResult(); 154 | this.applicationParameterDescriptor = makeWithoutGC!ApplicationParameterDescriptor(connection); 155 | this.implementationParameterDescriptor = makeWithoutGC!ImplementationParameterDescriptor(connection); 156 | this.applicationRowDescriptor = makeWithoutGC!ApplicationRowDescriptor(connection); 157 | this.implementationRowDescriptor = makeWithoutGC!ImplementationRowDescriptor(connection); 158 | } 159 | 160 | OdbcConnection connection; 161 | 162 | wstring query() { 163 | return query_; 164 | } 165 | 166 | void query(wstring query) { 167 | executedQuery = false; 168 | query_ = query; 169 | } 170 | 171 | private wstring query_; 172 | bool executedQuery; 173 | OdbcResult latestOdbcResult; 174 | OdbcException[] errors; 175 | SQLULEN rowArraySize = 1; 176 | SQLULEN* rowsFetched; 177 | RowStatus* rowStatusPtr; 178 | 179 | void applicationParameterDescriptor(ApplicationParameterDescriptor apd) { 180 | applicationParameterDescriptor_ = makeWithoutGC!OdbcDescriptor(connection, apd); 181 | } 182 | 183 | void implementationParameterDescriptor(ImplementationParameterDescriptor ipd) { 184 | implementationParameterDescriptor_ = makeWithoutGC!OdbcDescriptor(connection, ipd); 185 | } 186 | 187 | void applicationRowDescriptor(ApplicationRowDescriptor ard) { 188 | applicationRowDescriptor_ = makeWithoutGC!OdbcDescriptor(connection, ard); 189 | } 190 | 191 | void implementationRowDescriptor(ImplementationRowDescriptor ird) { 192 | implementationRowDescriptor_ = makeWithoutGC!OdbcDescriptor(connection, ird); 193 | } 194 | 195 | ApplicationParameterDescriptor applicationParameterDescriptor() { 196 | return cast(ApplicationParameterDescriptor) applicationParameterDescriptor_.impl; 197 | } 198 | 199 | ImplementationParameterDescriptor implementationParameterDescriptor() { 200 | return cast(ImplementationParameterDescriptor) implementationParameterDescriptor_.impl; 201 | } 202 | 203 | ApplicationRowDescriptor applicationRowDescriptor() { 204 | return cast(ApplicationRowDescriptor) applicationRowDescriptor_.impl; 205 | } 206 | 207 | ImplementationRowDescriptor implementationRowDescriptor() { 208 | return cast(ImplementationRowDescriptor) implementationRowDescriptor_.impl; 209 | } 210 | 211 | OdbcDescriptor applicationParameterDescriptor_; 212 | OdbcDescriptor implementationParameterDescriptor_; 213 | OdbcDescriptor applicationRowDescriptor_; 214 | OdbcDescriptor implementationRowDescriptor_; 215 | } 216 | 217 | final class OdbcConnection { 218 | import presto.client.statementclient : StatementClient, ClientSession; 219 | 220 | this(OdbcEnvironment environment) { 221 | dllEnforce(environment !is null); 222 | this.environment = environment; 223 | this.session.source = "presto-odbc"; 224 | } 225 | 226 | 227 | bool canConnect() { 228 | import std.net.curl : CurlException; 229 | import presto.client.mockcurl : get; 230 | 231 | logMessage("Checking connection to", session.endpoint, session.timeout); 232 | try { 233 | //We found a compiler bug whereby if you remove the variable that 234 | //follows, even though we do not use the result of runQuery here, 235 | //the destructor of StatementClient is not called and execution of 236 | //the program seems to mysteriously halt. 237 | auto dontRemoveThisVariable = runQuery("SELECT 1"); 238 | } catch (CurlException e) { 239 | logCriticalMessage(e); 240 | return false; 241 | } 242 | return true; 243 | } 244 | 245 | StatementClient runQuery(string query) { 246 | return StatementClient(session, query); 247 | } 248 | 249 | OdbcEnvironment environment; 250 | bool[OdbcDescriptor] explicitlyAllocatedDescriptors; 251 | ClientSession session; 252 | OdbcException[] errors; 253 | } 254 | 255 | final class OdbcEnvironment { 256 | 257 | } 258 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /client/queryresults.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.client.queryresults; 15 | 16 | import facebook.json : JSON_TYPE, JSONValue; 17 | import std.typecons : Tuple, tuple; 18 | import std.typetuple : TypeTuple; 19 | import std.conv : text, to; 20 | import std.array : front, popFront, empty; 21 | import std.stdio; 22 | 23 | import presto.client.prestoerrors; 24 | import presto.client.util; 25 | 26 | version(unittest) { 27 | import facebook.json : parseJSON; 28 | import std.exception : assertThrown; 29 | } 30 | 31 | immutable(QueryResults) queryResults(JSONValue rawResult) { 32 | return new immutable(QueryResults)(rawResult); 33 | } 34 | 35 | final class QueryResults { 36 | 37 | this(JSONValue rawResult) immutable { 38 | id_ = rawResult["id"].str; 39 | infoURI_ = rawResult["infoUri"].str; 40 | partialCancelURI_ = getPropertyOrDefault!(string,"partialCancelUri")(rawResult); 41 | nextURI_ = getPropertyOrDefault!(string, "nextUri")(rawResult); 42 | stats_ = QueryStats(rawResult["stats"]); 43 | 44 | columnMetadata_ = parseColumnMetadata(rawResult); 45 | 46 | if ("data" in rawResult) { 47 | data_ = rawResult["data"]; 48 | } else { 49 | data_ = emptyJSONArray(); 50 | } 51 | if ("error" in rawResult) { 52 | error_ = new immutable(QueryException)(rawResult["error"]); 53 | } 54 | } 55 | 56 | const nothrow { 57 | string id() { return id_; } 58 | string infoURI() { return infoURI_; } 59 | string partialCancelURI() { return partialCancelURI_; } 60 | string nextURI() { return nextURI_; } 61 | auto columnMetadata() { return columnMetadata_; } 62 | 63 | auto stats() { return stats_;} 64 | bool succeeded() { return error_ is null; } 65 | } 66 | auto data() const { 67 | enforceSucceeded(); 68 | return data_; 69 | } 70 | auto error() const { 71 | assert(!succeeded()); 72 | return error_; 73 | } 74 | 75 | 76 | auto byRow(RowTList...)() const { 77 | enforceSucceeded(); 78 | if (columnMetadata_.empty) { 79 | return Range!RowTList(); 80 | } 81 | return Range!RowTList(this, data_.array); 82 | } 83 | 84 | static struct Range(RowTList...) { 85 | this(const(QueryResults) qr, immutable(JSONValue)[] data) { 86 | static assert(isJSONTypeList!UnnamedRowTList, "Types must be bool/long/double/string"); 87 | static assert(2 * UnnamedRowTList.length == RowTList.length, 88 | "All types must have names to match against the JSON"); 89 | 90 | this.qr = qr; 91 | this.data = data; 92 | foreach (i, column; qr.columnMetadata_) { 93 | fieldNameToIndex[column.name] = i; 94 | } 95 | foreach (fieldName; fieldNames) { 96 | if (fieldName !in fieldNameToIndex) { 97 | throw new NoSuchColumn("Asked for a column that does not exist"); 98 | } 99 | } 100 | } 101 | 102 | const { 103 | Tuple!RowTList front() { 104 | assert(!data.empty); 105 | 106 | auto jsonRow = data[0]; 107 | 108 | //Decompose row data into tuple: 109 | Tuple!RowTList result; 110 | foreach (i, T; UnnamedRowTList) { 111 | alias name = fieldNames[i]; 112 | auto fieldIndex = fieldNameToIndex[name]; 113 | 114 | //TODO: Consider running this check only on the 0th row. 115 | requireMatchingType!T(fieldIndex, qr, jsonRow); 116 | 117 | auto elt = jsonRow.array[fieldIndex]; 118 | mixin("result." ~ name) = jsonValueAs!T(elt); 119 | } 120 | 121 | return result; 122 | } 123 | 124 | bool empty() { 125 | return data.empty; 126 | } 127 | } 128 | 129 | void popFront() { 130 | assert(!empty); 131 | data = data[1 .. $]; 132 | } 133 | 134 | private: 135 | alias UnnamedRowTList = Tuple!RowTList.Types; 136 | alias fieldNames = extractCTFEStrings!RowTList; 137 | const(QueryResults) qr; 138 | immutable(JSONValue)[] data; 139 | size_t[string] fieldNameToIndex; 140 | } 141 | 142 | private: 143 | version(unittest) { 144 | this(inout ColumnMetadata[] cols) { 145 | columnMetadata_ = cols.idup; 146 | stats_ = QueryStats(); 147 | data_ = emptyJSONArray(); 148 | } 149 | } 150 | 151 | void enforceSucceeded() const { 152 | if (!succeeded) { 153 | throw error; 154 | } 155 | } 156 | 157 | string id_; 158 | string infoURI_; 159 | string partialCancelURI_; 160 | string nextURI_; 161 | immutable(ColumnMetadata[]) columnMetadata_; 162 | immutable(JSONValue) data_; 163 | immutable(QueryStats) stats_; 164 | QueryException error_; 165 | } 166 | 167 | version(unittest) { 168 | final class JSBuilder { 169 | private string js = "{ 170 | \"id\" : \"123\", 171 | \"infoUri\" : \"localhost\", 172 | \"stats\" : {}"; 173 | 174 | JSBuilder withNext() { 175 | js ~= ",\"nextUri\" : \"localhost\""; 176 | return this; 177 | } 178 | 179 | JSBuilder withColumns() { 180 | js ~= ",\"columns\" : [ { \"name\" : \"col1\", \"type\" : \"varchar\" }, 181 | { \"name\" : \"col2\", \"type\" : \"bigint\" } ]"; 182 | return this; 183 | } 184 | 185 | JSBuilder withData() { 186 | js ~= ",\"data\" : [ [\"testentry1\", 45], [\"testentry2\", 3] ]"; 187 | return this; 188 | } 189 | 190 | JSONValue build() { 191 | return parseJSON(toString()); 192 | } 193 | 194 | override string toString() { 195 | return js ~ "}"; 196 | } 197 | } 198 | } 199 | 200 | unittest { 201 | auto qr = queryResults(new JSBuilder().withNext().build()); 202 | assert(qr.id == "123"); 203 | assert(qr.nextURI == "localhost"); 204 | assert(qr.columnMetadata.empty); 205 | assert(qr.data.array.empty); 206 | assert(qr.byRow().empty); 207 | } 208 | 209 | unittest { 210 | auto qr = queryResults(new JSBuilder().withNext().withColumns().build()); 211 | assert(qr.id == "123"); 212 | assert(qr.nextURI == "localhost"); 213 | assert(!qr.columnMetadata.empty); 214 | assert(qr.data.array.empty); 215 | assert(qr.byRow().empty); 216 | assert(qr.byRow!(long, "col2").empty); 217 | assert(qr.columnMetadata[0].name == "col1"); 218 | assertThrown!NoSuchColumn(qr.byRow!(long, "doesNotExist")); 219 | } 220 | 221 | unittest { 222 | auto qr = queryResults(new JSBuilder().withNext().withColumns().withData().build()); 223 | assert(qr.id == "123"); 224 | assert(qr.nextURI == "localhost"); 225 | assert(!qr.columnMetadata.empty); 226 | assert(!qr.data.array.empty); 227 | assert(!qr.byRow().empty); 228 | assert(qr.columnMetadata[0].name == "col1"); 229 | 230 | auto rng = qr.byRow!(long, "col2"); 231 | assert(rng.front[0] == 45); 232 | rng.popFront(); 233 | assert(!rng.empty); 234 | assert(rng.front[0] == 3); 235 | rng.popFront(); 236 | assert(rng.empty); 237 | 238 | auto rng2 = qr.byRow!(long, "col2", string, "col1"); 239 | assert(rng2.front[0] == 45 && rng2.front[1] == "testentry1"); 240 | 241 | auto badRng = qr.byRow!(string, "col2"); 242 | assertThrown!(WrongTypeException!string)(badRng.front); 243 | } 244 | 245 | struct QueryStats { 246 | this(JSONValue rawResult) { 247 | //TODO 248 | } 249 | } 250 | 251 | struct ColumnMetadata { 252 | string name; 253 | string type; 254 | } 255 | 256 | class WrongTypeException(Expected) : PrestoClientException { 257 | this(string received = "bad runtime type") { 258 | super("Expected " ~ Expected.stringof ~ " received " ~ received); 259 | } 260 | } 261 | 262 | class NoSuchColumn : PrestoClientException { 263 | this(string column = "") { 264 | super("Asked for a non-existent column (" ~ column ~ ")"); 265 | } 266 | } 267 | 268 | private void requireMatchingType(T)(size_t fieldIndex, const(QueryResults) qr, const JSONValue jsonRow) { 269 | if (!typeMatchesColumnTypeName!T(qr.columnMetadata[fieldIndex].type) 270 | || !typeMatchesJSONType!T(jsonRow[fieldIndex].type)) { 271 | throw new WrongTypeException!T; 272 | } 273 | } 274 | 275 | unittest { 276 | auto js = parseJSON("[\"test\"]"); 277 | auto qr = new QueryResults([ ColumnMetadata("test", "varchar") ]); 278 | requireMatchingType!string(0, qr, js); 279 | assertThrown!(WrongTypeException!long)(requireMatchingType!long(0, qr, js)); 280 | assertThrown!(WrongTypeException!bool)(requireMatchingType!bool(0, qr, js)); 281 | } 282 | 283 | private pure nothrow bool typeMatchesJSONType(T)(JSON_TYPE jsonType) { 284 | static if (is(T == bool)) { 285 | return jsonType == JSON_TYPE.TRUE || jsonType == JSON_TYPE.FALSE; 286 | } else static if (is(T == long)) { 287 | return jsonType == JSON_TYPE.INTEGER; 288 | } else static if (is(T == double)) { 289 | return jsonType == JSON_TYPE.FLOAT; 290 | } else { 291 | return jsonType == JSON_TYPE.STRING; 292 | } 293 | } 294 | 295 | unittest { 296 | static assert(typeMatchesJSONType!bool(JSON_TYPE.TRUE)); 297 | static assert(typeMatchesJSONType!string(JSON_TYPE.STRING)); 298 | } 299 | 300 | private pure nothrow bool typeMatchesColumnTypeName(T)(string typeName) { 301 | static if (is(T == bool)) { 302 | return typeName == "boolean"; 303 | } else static if (is(T == long)) { 304 | return typeName == "bigint"; 305 | } else static if (is(T == double)) { 306 | return typeName == "double"; 307 | } else { 308 | return true; 309 | } 310 | } 311 | 312 | unittest { 313 | static assert(typeMatchesColumnTypeName!bool("boolean")); 314 | static assert(!typeMatchesColumnTypeName!bool("bool")); 315 | } 316 | 317 | private pure nothrow bool isJSONTypeList(TList...)() { 318 | foreach(T; TList) { 319 | if (!isJSONType!T) { 320 | return false; 321 | } 322 | } 323 | return true; 324 | } 325 | 326 | unittest { 327 | static assert(isJSONTypeList!(string)); 328 | static assert(isJSONTypeList!(string, string)); 329 | static assert(isJSONTypeList!(string, long)); 330 | static assert(!isJSONTypeList!(string, int)); 331 | } 332 | 333 | private pure nothrow bool isJSONType(T)() { 334 | static if (is(T == string) || is(T == long) || is(T == bool) || is(T == double)) { 335 | return true; 336 | } else { 337 | return false; 338 | } 339 | } 340 | 341 | unittest { 342 | static assert(isJSONType!string); 343 | static assert(isJSONType!long); 344 | static assert(!isJSONType!int); 345 | } 346 | 347 | private template extractCTFEStrings(RowTList...) { 348 | static if (RowTList.length == 0) { 349 | alias extractCTFEStrings = TypeTuple!(); 350 | } else static if (is(typeof(RowTList[0]) : string)) { 351 | alias extractCTFEStrings = TypeTuple!(RowTList[0], extractCTFEStrings!(RowTList[1 .. $])); 352 | } else { 353 | alias extractCTFEStrings = extractCTFEStrings!(RowTList[1 .. $]); 354 | } 355 | } 356 | 357 | unittest { 358 | static assert(extractCTFEStrings!(int,"1","2",string,"3") == TypeTuple!("1","2","3")); 359 | } 360 | 361 | immutable(ColumnMetadata[]) parseColumnMetadata(JSONValue rawResult) { 362 | if ("columns" !in rawResult) { 363 | return []; 364 | } 365 | 366 | import std.array; 367 | import std.algorithm : map; 368 | auto columnMetadata = appender!(ColumnMetadata[]); 369 | columnMetadata.reserve(rawResult["columns"].array.length); 370 | 371 | return rawResult["columns"].array.map!(v => ColumnMetadata(v["name"].str, v["type"].str)).array.idup; 372 | } 373 | 374 | JSONValue emptyJSONArray() { 375 | auto result = JSONValue(); 376 | result.array = []; 377 | return result; 378 | } 379 | -------------------------------------------------------------------------------- /odbc/odbcinst.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 1996-2014 by OpenLink Software 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 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in 13 | * the documentation and/or other materials provided with the 14 | * distribution. 15 | * 3. Neither the name of OpenLink Software Inc. nor the names of its 16 | * contributors may be used to endorse or promote products derived 17 | * from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OPENLINK OR 23 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | module odbc.odbcinst; 32 | 33 | import odbc.sql; 34 | 35 | enum USERDSN_ONLY = 0; 36 | enum SYSTEMDSN_ONLY = 1; 37 | 38 | //SQLConfigDataSource 39 | enum ODBC_ADD_DSN = 1; 40 | enum ODBC_CONFIG_DSN = 2; 41 | enum ODBC_REMOVE_DSN = 3; 42 | 43 | enum ODBC_ADD_SYS_DSN = 4; 44 | enum ODBC_CONFIG_SYS_DSN = 5; 45 | enum ODBC_REMOVE_SYS_DSN = 6; 46 | enum ODBC_REMOVE_DEFAULT_DSN = 7; 47 | 48 | //Install request flags 49 | enum ODBC_INSTALL_INQUIRY = 1; 50 | enum ODBC_INSTALL_COMPLETE = 2; 51 | 52 | //Config driver flags 53 | enum ODBC_INSTALL_DRIVER = 1; 54 | enum ODBC_REMOVE_DRIVER = 2; 55 | enum ODBC_CONFIG_DRIVER = 3; 56 | enum ODBC_CONFIG_DRIVER_MAX = 100; 57 | 58 | //SQLGetConfigMode and SQLSetConfigMode flags 59 | enum ODBC_BOTH_DSN = 0; 60 | enum ODBC_USER_DSN = 1; 61 | enum ODBC_SYSTEM_DSN = 2; 62 | 63 | //SQLInstallerError code 64 | enum ODBC_ERROR_GENERAL_ERR = 1; 65 | enum ODBC_ERROR_INVALID_BUFF_LEN = 2; 66 | enum ODBC_ERROR_INVALID_HWND = 3; 67 | enum ODBC_ERROR_INVALID_STR = 4; 68 | enum ODBC_ERROR_INVALID_REQUEST_TYPE = 5; 69 | enum ODBC_ERROR_COMPONENT_NOT_FOUND = 6; 70 | enum ODBC_ERROR_INVALID_NAME = 7; 71 | enum ODBC_ERROR_INVALID_KEYWORD_VALUE = 8; 72 | enum ODBC_ERROR_INVALID_DSN = 9; 73 | enum ODBC_ERROR_INVALID_INF = 10; 74 | enum ODBC_ERROR_REQUEST_FAILED = 11; 75 | enum ODBC_ERROR_INVALID_PATH = 12; 76 | enum ODBC_ERROR_LOAD_LIB_FAILED = 13; 77 | enum ODBC_ERROR_INVALID_PARAM_SEQUENCE = 14; 78 | enum ODBC_ERROR_INVALID_LOG_FILE = 15; 79 | enum ODBC_ERROR_USER_CANCELED = 16; 80 | enum ODBC_ERROR_USAGE_UPDATE_FAILED = 17; 81 | enum ODBC_ERROR_CREATE_DSN_FAILED = 18; 82 | enum ODBC_ERROR_WRITING_SYSINFO_FAILED = 19; 83 | enum ODBC_ERROR_REMOVE_DSN_FAILED = 20; 84 | enum ODBC_ERROR_OUT_OF_MEM = 21; 85 | enum ODBC_ERROR_OUTPUT_STRING_TRUNCATED = 22; 86 | enum ODBC_ERROR_NOTRANINFO = 23; 87 | 88 | version(UNICODE) { 89 | alias SQLInstallODBCW = SQLInstallODBC; 90 | alias SQLCreateDataSourceW = SQLCreateDataSource; 91 | alias SQLGetTranslatorW = SQLGetTranslator; 92 | alias SQLInstallDriverW = SQLInstallDriver; 93 | alias SQLInstallDriverManagerW = SQLInstallDriverManager; 94 | alias SQLGetInstalledDriversW = SQLGetInstalledDrivers; 95 | alias SQLGetAvailableDriversW = SQLGetAvailableDrivers; 96 | alias SQLConfigDataSourceW = SQLConfigDataSource; 97 | alias SQLWriteDSNToIniW = SQLWriteDSNToIni; 98 | alias SQLRemoveDSNFromIniW = SQLRemoveDSNFromIni; 99 | alias SQLValidDSNW = SQLValidDSN; 100 | alias SQLWritePrivateProfileStringW = SQLWritePrivateProfileString; 101 | alias SQLGetPrivateProfileStringW = SQLGetPrivateProfileString; 102 | alias SQLInstallTranslatorW = SQLInstallTranslator; 103 | alias SQLRemoveTranslatorW = SQLRemoveTranslator; 104 | alias SQLRemoveDriverW = SQLRemoveDriver; 105 | alias SQLConfigDriverW = SQLConfigDriver; 106 | alias SQLInstallerErrorW = SQLInstallerError; 107 | alias SQLPostInstallerErrorW = SQLPostInstallerError; 108 | alias SQLReadFileDSNW = SQLReadFileDSN; 109 | alias SQLWriteFileDSNW = SQLWriteFileDSN; 110 | alias SQLInstallDriverExW = SQLInstallDriverEx; 111 | alias SQLInstallTranslatorExW = SQLInstallTranslatorEx; 112 | } 113 | 114 | extern(System): 115 | 116 | //Function Prototypes 117 | 118 | BOOL SQLGetConfigMode(UWORD* pwConfigMode); 119 | 120 | BOOL SQLInstallDriverEx( 121 | LPCSTR lpszDriver, 122 | LPCSTR lpszPathIn, 123 | LPSTR lpszPathOut, 124 | WORD cbPathOutMax, 125 | WORD* pcbPathOut, 126 | WORD fRequest, 127 | LPDWORD lpdwUsageCount); 128 | 129 | 130 | BOOL SQLInstallDriverExW( 131 | LPCWSTR lpszDriver, 132 | LPCWSTR lpszPathIn, 133 | LPWSTR lpszPathOut, 134 | WORD cbPathOutMax, 135 | WORD* pcbPathOut, 136 | WORD fRequest, 137 | LPDWORD lpdwUsageCount); 138 | 139 | SQLRETURN SQLInstallerError( 140 | WORD iError, 141 | DWORD* pfErrorCode, 142 | LPSTR lpszErrorMsg, 143 | WORD cbErrorMsgMax, 144 | WORD* pcbErrorMsg); 145 | 146 | SQLRETURN SQLInstallerErrorW( 147 | WORD iError, 148 | DWORD* pfErrorCode, 149 | LPWSTR lpszErrorMsg, 150 | WORD cbErrorMsgMax, 151 | WORD* pcbErrorMsg); 152 | 153 | SQLRETURN SQLPostInstallerError(DWORD dwErrorCode, LPCSTR lpszErrMsg); 154 | 155 | SQLRETURN SQLPostInstallerErrorW(DWORD dwErrorCode, LPCWSTR lpszErrorMsg); 156 | 157 | BOOL SQLInstallTranslatorEx( 158 | LPCSTR lpszTranslator, 159 | LPCSTR lpszPathIn, 160 | LPSTR lpszPathOut, 161 | WORD cbPathOutMax, 162 | WORD* pcbPathOut, 163 | WORD fRequest, 164 | LPDWORD lpdwUsageCount); 165 | 166 | 167 | BOOL SQLInstallTranslatorExW( 168 | LPCWSTR lpszTranslator, 169 | LPCWSTR lpszPathIn, 170 | LPWSTR lpszPathOut, 171 | WORD cbPathOutMax, 172 | WORD* pcbPathOut, 173 | WORD fRequest, 174 | LPDWORD lpdwUsageCount); 175 | 176 | 177 | BOOL SQLReadFileDSN( 178 | LPCSTR lpszFileName, 179 | LPCSTR lpszAppName, 180 | LPCSTR lpszKeyName, 181 | LPSTR lpszString, 182 | WORD cbString, 183 | WORD* pcbString); 184 | 185 | BOOL SQLReadFileDSNW( 186 | LPCWSTR lpszFileName, 187 | LPCWSTR lpszAppName, 188 | LPCWSTR lpszKeyName, 189 | LPWSTR lpszString, 190 | WORD cbString, 191 | WORD* pcbString); 192 | 193 | BOOL SQLWriteFileDSN( 194 | LPCSTR lpszFileName, 195 | LPCSTR lpszAppName, 196 | LPCSTR lpszKeyName, 197 | LPCSTR lpszString); 198 | 199 | BOOL SQLWriteFileDSNW( 200 | LPCWSTR lpszFileName, 201 | LPCWSTR lpszAppName, 202 | LPCWSTR lpszKeyName, 203 | LPCWSTR lpszString); 204 | 205 | BOOL SQLSetConfigMode(UWORD wConfigMode); 206 | 207 | BOOL SQLInstallODBC( 208 | HWND hwndParent, 209 | LPCSTR lpszInfFile, 210 | LPCSTR lpszSrcPath, 211 | LPCSTR lpszDrivers); 212 | 213 | BOOL SQLInstallODBCW( 214 | HWND hwndParent, 215 | LPCWSTR lpszInfFile, 216 | LPCWSTR lpszSrcPath, 217 | LPCWSTR lpszDrivers); 218 | 219 | BOOL SQLManageDataSources(HWND hwndParent); 220 | 221 | BOOL SQLCreateDataSource(HWND hwndParent, LPCSTR lpszDSN); 222 | 223 | BOOL SQLCreateDataSourceW(HWND hwndParent, LPCWSTR lpszDSN); 224 | 225 | BOOL SQLGetTranslator( 226 | HWND hwnd, 227 | LPSTR lpszName, 228 | WORD cbNameMax, 229 | WORD* pcbNameOut, 230 | LPSTR lpszPath, 231 | WORD cbPathMax, 232 | WORD* pcbPathOut, 233 | DWORD* pvOption); 234 | 235 | BOOL SQLGetTranslatorW( 236 | HWND hwnd, 237 | LPWSTR lpszName, 238 | WORD cbNameMax, 239 | WORD* pcbNameOut, 240 | LPWSTR lpszPath, 241 | WORD cbPathMax, 242 | WORD* pcbPathOut, 243 | DWORD* pvOption); 244 | 245 | /* Low level APIs 246 | * NOTE: The high-level APIs should always be used. These APIs 247 | * have been left for compatibility. 248 | */ 249 | BOOL SQLInstallDriver( 250 | LPCSTR lpszInfFile, 251 | LPCSTR lpszDriver, 252 | LPSTR lpszPath, 253 | WORD cbPathMax, 254 | WORD* pcbPathOut); 255 | 256 | BOOL SQLInstallDriverW( 257 | LPCWSTR lpszInfFile, 258 | LPCWSTR lpszDriver, 259 | LPWSTR lpszPath, 260 | WORD cbPathMax, 261 | WORD* pcbPathOut); 262 | 263 | BOOL SQLInstallDriverManager( 264 | LPSTR lpszPath, 265 | WORD cbPathMax, 266 | WORD* pcbPathOut); 267 | 268 | BOOL SQLInstallDriverManagerW( 269 | LPWSTR lpszPath, 270 | WORD cbPathMax, 271 | WORD* pcbPathOut); 272 | 273 | BOOL SQLGetInstalledDrivers( 274 | LPSTR lpszBuf, 275 | WORD cbBufMax, 276 | WORD* pcbBufOut); 277 | 278 | BOOL SQLGetInstalledDriversW( 279 | LPWSTR lpszBuf, 280 | WORD cbBufMax, 281 | WORD* pcbBufOut); 282 | 283 | BOOL SQLGetAvailableDrivers( 284 | LPCSTR lpszInfFile, 285 | LPSTR lpszBuf, 286 | WORD cbBufMax, 287 | WORD* pcbBufOut); 288 | 289 | BOOL SQLGetAvailableDriversW( 290 | LPCWSTR lpszInfFile, 291 | LPWSTR lpszBuf, 292 | WORD cbBufMax, 293 | WORD* pcbBufOut); 294 | 295 | BOOL SQLConfigDataSource( 296 | HWND hwndParent, 297 | WORD fRequest, 298 | LPCSTR lpszDriver, 299 | LPCSTR lpszAttributes); 300 | 301 | BOOL SQLConfigDataSourceW( 302 | HWND hwndParent, 303 | WORD fRequest, 304 | LPCWSTR lpszDriver, 305 | LPCWSTR lpszAttributes); 306 | 307 | BOOL SQLRemoveDefaultDataSource(); 308 | 309 | BOOL SQLWriteDSNToIni(LPCSTR lpszDSN, LPCSTR lpszDriver); 310 | 311 | BOOL SQLWriteDSNToIniW(LPCWSTR lpszDSN, LPCWSTR lpszDriver); 312 | 313 | BOOL SQLRemoveDSNFromIni(LPCSTR lpszDSN); 314 | 315 | BOOL SQLRemoveDSNFromIniW(LPCWSTR lpszDSN); 316 | 317 | BOOL SQLValidDSN(LPCSTR lpszDSN); 318 | 319 | BOOL SQLValidDSNW(LPCWSTR lpszDSN); 320 | 321 | BOOL SQLWritePrivateProfileString( 322 | LPCSTR lpszSection, 323 | LPCSTR lpszEntry, 324 | LPCSTR lpszString, 325 | LPCSTR lpszFilename); 326 | 327 | BOOL SQLWritePrivateProfileStringW( 328 | LPCWSTR lpszSection, 329 | LPCWSTR lpszEntry, 330 | LPCWSTR lpszString, 331 | LPCWSTR lpszFilename); 332 | 333 | int SQLGetPrivateProfileString( 334 | LPCSTR lpszSection, 335 | LPCSTR lpszEntry, 336 | LPCSTR lpszDefault, 337 | LPSTR lpszRetBuffer, 338 | int cbRetBuffer, 339 | LPCSTR lpszFilename); 340 | 341 | int SQLGetPrivateProfileStringW( 342 | LPCWSTR lpszSection, 343 | LPCWSTR lpszEntry, 344 | LPCWSTR lpszDefault, 345 | LPWSTR lpszRetBuffer, 346 | int cbRetBuffer, 347 | LPCWSTR lpszFilename); 348 | 349 | BOOL SQLRemoveDriverManager(LPDWORD lpdwUsageCount); 350 | 351 | BOOL SQLInstallTranslator( 352 | LPCSTR lpszInfFile, 353 | LPCSTR lpszTranslator, 354 | LPCSTR lpszPathIn, 355 | LPSTR lpszPathOut, 356 | WORD cbPathOutMax, 357 | WORD* pcbPathOut, 358 | WORD fRequest, 359 | LPDWORD lpdwUsageCount); 360 | 361 | BOOL SQLInstallTranslatorW( 362 | LPCWSTR lpszInfFile, 363 | LPCWSTR lpszTranslator, 364 | LPCWSTR lpszPathIn, 365 | LPWSTR lpszPathOut, 366 | WORD cbPathOutMax, 367 | WORD* pcbPathOut, 368 | WORD fRequest, 369 | LPDWORD lpdwUsageCount); 370 | 371 | BOOL SQLRemoveTranslator(LPCSTR lpszTranslator, LPDWORD lpdwUsageCount); 372 | 373 | BOOL SQLRemoveTranslatorW(LPCWSTR lpszTranslator, LPDWORD lpdwUsageCount); 374 | 375 | BOOL SQLRemoveDriver( 376 | LPCSTR lpszDriver, 377 | BOOL fRemoveDSN, 378 | LPDWORD lpdwUsageCount); 379 | 380 | BOOL SQLRemoveDriverW( 381 | LPCWSTR lpszDriver, 382 | BOOL fRemoveDSN, 383 | LPDWORD lpdwUsageCount); 384 | 385 | BOOL SQLConfigDriver( 386 | HWND hwndParent, 387 | WORD fRequest, 388 | LPCSTR lpszDriver, 389 | LPCSTR lpszArgs, 390 | LPSTR lpszMsg, 391 | WORD cbMsgMax, 392 | WORD* pcbMsgOut); 393 | 394 | BOOL ConfigDSN( 395 | HWND hwndParent, 396 | WORD fRequest, 397 | LPCSTR lpszDriver, 398 | LPCSTR lpszAttributes); 399 | 400 | BOOL ConfigDSNW( 401 | HWND hwndParent, 402 | WORD fRequest, 403 | LPCWSTR lpszDriver, 404 | LPCWSTR lpszAttributes); 405 | 406 | BOOL ConfigTranslator( 407 | HWND hwndParent, 408 | DWORD* pvOption); 409 | 410 | BOOL ConfigDriver( 411 | HWND hwndParent, 412 | WORD fRequest, 413 | LPCSTR lpszDriver, 414 | LPCSTR lpszArgs, 415 | LPSTR lpszMsg, 416 | WORD cbMsgMax, 417 | WORD* pcbMsgOut); 418 | 419 | BOOL ConfigDriverW( 420 | HWND hwndParent, 421 | WORD fRequest, 422 | LPCWSTR lpszDriver, 423 | LPCWSTR lpszArgs, 424 | LPWSTR lpszMsg, 425 | WORD cbMsgMax, 426 | WORD* pcbMsgOut); 427 | -------------------------------------------------------------------------------- /driver/typeinfo.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.odbcdriver.typeinfo; 15 | 16 | import std.variant : Variant; 17 | 18 | import odbc.sqlext; 19 | import odbc.odbcinst; 20 | 21 | import presto.odbcdriver.bindings : OdbcResult, OdbcResultRow; 22 | 23 | 24 | // http://msdn.microsoft.com/en-us/library/ms711786(v=vs.85).aspx 25 | //Does not include SQL_DECIMAL/SQL_NUMERIC 26 | enum SQLSMALLINT[SQL_TYPE_ID] columnSizeMap = [ 27 | SQL_TYPE_ID.SQL_CHAR : SQL_NO_TOTAL, 28 | SQL_TYPE_ID.SQL_VARCHAR : SQL_NO_TOTAL, 29 | SQL_TYPE_ID.SQL_LONGVARCHAR : SQL_NO_TOTAL, 30 | SQL_TYPE_ID.SQL_WCHAR : SQL_NO_TOTAL, 31 | SQL_TYPE_ID.SQL_WVARCHAR : SQL_NO_TOTAL, 32 | SQL_TYPE_ID.SQL_WLONGVARCHAR : SQL_NO_TOTAL, 33 | SQL_TYPE_ID.SQL_BINARY : SQL_NO_TOTAL, 34 | SQL_TYPE_ID.SQL_VARBINARY : SQL_NO_TOTAL, 35 | SQL_TYPE_ID.SQL_LONGVARBINARY : SQL_NO_TOTAL, 36 | SQL_TYPE_ID.SQL_BIT : 1, 37 | SQL_TYPE_ID.SQL_TINYINT : 3, 38 | SQL_TYPE_ID.SQL_SMALLINT : 5, 39 | SQL_TYPE_ID.SQL_INTEGER : 10, 40 | SQL_TYPE_ID.SQL_BIGINT : 19, 41 | SQL_TYPE_ID.SQL_REAL : 7, 42 | SQL_TYPE_ID.SQL_FLOAT : 15, 43 | SQL_TYPE_ID.SQL_DOUBLE : 15, 44 | SQL_TYPE_ID.SQL_DATE : 10, 45 | SQL_TYPE_ID.SQL_TIME : 11, 46 | SQL_TYPE_ID.SQL_TIMESTAMP : 22, 47 | SQL_TYPE_ID.SQL_DATETIME : 22, 48 | //Told by Dain to pick an arbitary large number for intervals 49 | SQL_TYPE_ID.SQL_INTERVAL_YEAR : 30, 50 | SQL_TYPE_ID.SQL_INTERVAL_MONTH : 30, 51 | SQL_TYPE_ID.SQL_INTERVAL_DAY : 30, 52 | SQL_TYPE_ID.SQL_INTERVAL_HOUR : 30, 53 | SQL_TYPE_ID.SQL_INTERVAL_MINUTE : 30, 54 | SQL_TYPE_ID.SQL_INTERVAL_SECOND : 30, 55 | SQL_TYPE_ID.SQL_INTERVAL_YEAR_TO_MONTH : 30, 56 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_HOUR : 30, 57 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_MINUTE : 30, 58 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_SECOND : 30, 59 | SQL_TYPE_ID.SQL_INTERVAL_HOUR_TO_MINUTE : 30, 60 | SQL_TYPE_ID.SQL_INTERVAL_HOUR_TO_SECOND : 30, 61 | SQL_TYPE_ID.SQL_INTERVAL_MINUTE_TO_SECOND : 30, 62 | SQL_TYPE_ID.SQL_GUID : 36, 63 | ]; 64 | 65 | // http://msdn.microsoft.com/en-us/library/ms709314(v=vs.85).aspx 66 | //Does not include SQL_DECIMAL/SQL_NUMERIC 67 | enum SQLSMALLINT[SQL_TYPE_ID] decimalDigitsMap = [ 68 | SQL_TYPE_ID.SQL_CHAR : 0, 69 | SQL_TYPE_ID.SQL_VARCHAR : 0, 70 | SQL_TYPE_ID.SQL_LONGVARCHAR : 0, 71 | SQL_TYPE_ID.SQL_WCHAR : 0, 72 | SQL_TYPE_ID.SQL_WVARCHAR : 0, 73 | SQL_TYPE_ID.SQL_WLONGVARCHAR : 0, 74 | SQL_TYPE_ID.SQL_BINARY : 0, 75 | SQL_TYPE_ID.SQL_VARBINARY : 0, 76 | SQL_TYPE_ID.SQL_LONGVARBINARY : 0, 77 | SQL_TYPE_ID.SQL_BIT : 0, 78 | SQL_TYPE_ID.SQL_TINYINT : 0, 79 | SQL_TYPE_ID.SQL_SMALLINT : 0, 80 | SQL_TYPE_ID.SQL_INTEGER : 0, 81 | SQL_TYPE_ID.SQL_BIGINT : 0, 82 | SQL_TYPE_ID.SQL_REAL : 0, 83 | SQL_TYPE_ID.SQL_FLOAT : 0, 84 | SQL_TYPE_ID.SQL_DOUBLE : 0, 85 | SQL_TYPE_ID.SQL_DATE : 0, 86 | SQL_TYPE_ID.SQL_TIME : 3, 87 | SQL_TYPE_ID.SQL_TIMESTAMP : 3, 88 | SQL_TYPE_ID.SQL_DATETIME : 3, 89 | SQL_TYPE_ID.SQL_INTERVAL_YEAR : 0, 90 | SQL_TYPE_ID.SQL_INTERVAL_MONTH : 0, 91 | SQL_TYPE_ID.SQL_INTERVAL_DAY : 0, 92 | SQL_TYPE_ID.SQL_INTERVAL_HOUR : 0, 93 | SQL_TYPE_ID.SQL_INTERVAL_MINUTE : 0, 94 | SQL_TYPE_ID.SQL_INTERVAL_SECOND : 3, 95 | SQL_TYPE_ID.SQL_INTERVAL_YEAR_TO_MONTH : 0, 96 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_HOUR : 0, 97 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_MINUTE : 0, 98 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_SECOND : 3, 99 | SQL_TYPE_ID.SQL_INTERVAL_HOUR_TO_MINUTE : 0, 100 | SQL_TYPE_ID.SQL_INTERVAL_HOUR_TO_SECOND : 3, 101 | SQL_TYPE_ID.SQL_INTERVAL_MINUTE_TO_SECOND : 3, 102 | SQL_TYPE_ID.SQL_GUID : 0, 103 | ]; 104 | 105 | // http://msdn.microsoft.com/en-us/library/ms713979(v=vs.85).aspx 106 | //Does not include SQL_DECIMAL/SQL_NUMERIC 107 | enum SQLSMALLINT[SQL_TYPE_ID] octetLengthMap = [ 108 | SQL_TYPE_ID.SQL_CHAR : SQL_NO_TOTAL, 109 | SQL_TYPE_ID.SQL_VARCHAR : SQL_NO_TOTAL, 110 | SQL_TYPE_ID.SQL_LONGVARCHAR : SQL_NO_TOTAL, 111 | SQL_TYPE_ID.SQL_WCHAR : SQL_NO_TOTAL, 112 | SQL_TYPE_ID.SQL_WVARCHAR : SQL_NO_TOTAL, 113 | SQL_TYPE_ID.SQL_WLONGVARCHAR : SQL_NO_TOTAL, 114 | SQL_TYPE_ID.SQL_BINARY : SQL_NO_TOTAL, 115 | SQL_TYPE_ID.SQL_VARBINARY : SQL_NO_TOTAL, 116 | SQL_TYPE_ID.SQL_LONGVARBINARY : SQL_NO_TOTAL, 117 | SQL_TYPE_ID.SQL_BIT : 1, 118 | SQL_TYPE_ID.SQL_TINYINT : 1, 119 | SQL_TYPE_ID.SQL_SMALLINT : 2, 120 | SQL_TYPE_ID.SQL_INTEGER : 4, 121 | SQL_TYPE_ID.SQL_BIGINT : 40, 122 | SQL_TYPE_ID.SQL_REAL : 4, 123 | SQL_TYPE_ID.SQL_FLOAT : 8, 124 | SQL_TYPE_ID.SQL_DOUBLE : 8, 125 | SQL_TYPE_ID.SQL_DATE : 6, 126 | SQL_TYPE_ID.SQL_TIME : 6, 127 | SQL_TYPE_ID.SQL_TIMESTAMP : 16, 128 | SQL_TYPE_ID.SQL_DATETIME : 16, 129 | SQL_TYPE_ID.SQL_INTERVAL_YEAR : 34, 130 | SQL_TYPE_ID.SQL_INTERVAL_MONTH : 34, 131 | SQL_TYPE_ID.SQL_INTERVAL_DAY : 34, 132 | SQL_TYPE_ID.SQL_INTERVAL_HOUR : 34, 133 | SQL_TYPE_ID.SQL_INTERVAL_MINUTE : 34, 134 | SQL_TYPE_ID.SQL_INTERVAL_SECOND : 34, 135 | SQL_TYPE_ID.SQL_INTERVAL_YEAR_TO_MONTH : 34, 136 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_HOUR : 34, 137 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_MINUTE : 34, 138 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_SECOND : 34, 139 | SQL_TYPE_ID.SQL_INTERVAL_HOUR_TO_MINUTE : 34, 140 | SQL_TYPE_ID.SQL_INTERVAL_HOUR_TO_SECOND : 34, 141 | SQL_TYPE_ID.SQL_INTERVAL_MINUTE_TO_SECOND : 34, 142 | SQL_TYPE_ID.SQL_INTERVAL : 34, 143 | SQL_TYPE_ID.SQL_GUID : 16, 144 | ]; 145 | 146 | // http://msdn.microsoft.com/en-us/library/ms713974(v=vs.85).aspx 147 | //Does not include SQL_DECIMAL/SQL_NUMERIC 148 | enum SQLSMALLINT[SQL_TYPE_ID] displaySizeMap = [ 149 | SQL_TYPE_ID.SQL_CHAR : SQL_NO_TOTAL, 150 | SQL_TYPE_ID.SQL_VARCHAR : SQL_NO_TOTAL, 151 | SQL_TYPE_ID.SQL_LONGVARCHAR : SQL_NO_TOTAL, 152 | SQL_TYPE_ID.SQL_WCHAR : SQL_NO_TOTAL, 153 | SQL_TYPE_ID.SQL_WVARCHAR : SQL_NO_TOTAL, 154 | SQL_TYPE_ID.SQL_WLONGVARCHAR : SQL_NO_TOTAL, 155 | SQL_TYPE_ID.SQL_BINARY : SQL_NO_TOTAL, 156 | SQL_TYPE_ID.SQL_VARBINARY : SQL_NO_TOTAL, 157 | SQL_TYPE_ID.SQL_LONGVARBINARY : SQL_NO_TOTAL, 158 | SQL_TYPE_ID.SQL_BIT : 1, 159 | SQL_TYPE_ID.SQL_TINYINT : 4, 160 | SQL_TYPE_ID.SQL_SMALLINT : 6, 161 | SQL_TYPE_ID.SQL_INTEGER : 11, 162 | SQL_TYPE_ID.SQL_BIGINT : 20, 163 | SQL_TYPE_ID.SQL_REAL : 14, 164 | SQL_TYPE_ID.SQL_FLOAT : 24, 165 | SQL_TYPE_ID.SQL_DOUBLE : 24, 166 | SQL_TYPE_ID.SQL_DATE : 10, 167 | SQL_TYPE_ID.SQL_TIME : 11, 168 | SQL_TYPE_ID.SQL_TIMESTAMP : 22, 169 | SQL_TYPE_ID.SQL_DATETIME : 22, 170 | //Told by Dain to pick an arbitary large number for intervals 171 | SQL_TYPE_ID.SQL_INTERVAL_YEAR : 30, 172 | SQL_TYPE_ID.SQL_INTERVAL_MONTH : 30, 173 | SQL_TYPE_ID.SQL_INTERVAL_DAY : 30, 174 | SQL_TYPE_ID.SQL_INTERVAL_HOUR : 30, 175 | SQL_TYPE_ID.SQL_INTERVAL_MINUTE : 30, 176 | SQL_TYPE_ID.SQL_INTERVAL_SECOND : 30, 177 | SQL_TYPE_ID.SQL_INTERVAL_YEAR_TO_MONTH : 30, 178 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_HOUR : 30, 179 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_MINUTE : 30, 180 | SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_SECOND : 30, 181 | SQL_TYPE_ID.SQL_INTERVAL_HOUR_TO_MINUTE : 30, 182 | SQL_TYPE_ID.SQL_INTERVAL_HOUR_TO_SECOND : 30, 183 | SQL_TYPE_ID.SQL_INTERVAL_MINUTE_TO_SECOND : 30, 184 | SQL_TYPE_ID.SQL_GUID : 36, 185 | ]; 186 | 187 | bool isNumericalTypeId(SQL_TYPE_ID typeId) { 188 | with (SQL_TYPE_ID) { 189 | switch (typeId) { 190 | case SQL_BIT: 191 | case SQL_TINYINT: 192 | case SQL_SMALLINT: 193 | case SQL_INTEGER: 194 | case SQL_BIGINT: 195 | case SQL_REAL: 196 | case SQL_FLOAT: 197 | case SQL_DOUBLE: 198 | return true; 199 | default: 200 | return false; 201 | } 202 | } 203 | } 204 | 205 | bool isStringTypeId(SQL_TYPE_ID typeId) { 206 | with (SQL_TYPE_ID) { 207 | switch (typeId) { 208 | case SQL_CHAR: 209 | case SQL_VARCHAR: 210 | case SQL_LONGVARCHAR: 211 | case SQL_WCHAR: 212 | case SQL_WVARCHAR: 213 | case SQL_WLONGVARCHAR: 214 | return true; 215 | default: 216 | return false; 217 | } 218 | } 219 | } 220 | 221 | bool isInterval(SQL_TYPE_ID typeId) { 222 | with (SQL_TYPE_ID) { 223 | switch (typeId) { 224 | case SQL_INTERVAL_YEAR: 225 | case SQL_INTERVAL_MONTH: 226 | case SQL_INTERVAL_DAY: 227 | case SQL_INTERVAL_HOUR: 228 | case SQL_INTERVAL_MINUTE: 229 | case SQL_INTERVAL_SECOND: 230 | case SQL_INTERVAL_YEAR_TO_MONTH: 231 | case SQL_INTERVAL_DAY_TO_HOUR: 232 | case SQL_INTERVAL_DAY_TO_MINUTE: 233 | case SQL_INTERVAL_DAY_TO_SECOND: 234 | case SQL_INTERVAL_HOUR_TO_MINUTE: 235 | case SQL_INTERVAL_HOUR_TO_SECOND: 236 | case SQL_INTERVAL_MINUTE_TO_SECOND: 237 | return true; 238 | default: 239 | return false; 240 | } 241 | } 242 | } 243 | 244 | bool isTimeRelated(SQL_TYPE_ID typeId) { 245 | with (SQL_TYPE_ID) { 246 | switch (typeId) { 247 | case SQL_TYPE_DATE: 248 | case SQL_TYPE_TIME: 249 | case SQL_TYPE_TIMESTAMP: 250 | case SQL_INTERVAL_YEAR: 251 | case SQL_INTERVAL_MONTH: 252 | case SQL_INTERVAL_DAY: 253 | case SQL_INTERVAL_HOUR: 254 | case SQL_INTERVAL_MINUTE: 255 | case SQL_INTERVAL_SECOND: 256 | case SQL_INTERVAL_YEAR_TO_MONTH: 257 | case SQL_INTERVAL_DAY_TO_HOUR: 258 | case SQL_INTERVAL_DAY_TO_MINUTE: 259 | case SQL_INTERVAL_DAY_TO_SECOND: 260 | case SQL_INTERVAL_HOUR_TO_MINUTE: 261 | case SQL_INTERVAL_HOUR_TO_SECOND: 262 | case SQL_INTERVAL_MINUTE_TO_SECOND: 263 | return true; 264 | default: 265 | return false; 266 | } 267 | } 268 | } 269 | 270 | bool isTimeOrDate(SQL_TYPE_ID typeId) { 271 | with (SQL_TYPE_ID) { 272 | switch (typeId) { 273 | case SQL_TYPE_DATE: 274 | case SQL_TYPE_TIME: 275 | case SQL_TYPE_TIMESTAMP: 276 | return true; 277 | default: 278 | return false; 279 | } 280 | } 281 | } 282 | 283 | SQL_TYPE_ID toVerboseType(SQL_TYPE_ID typeId) { 284 | with (SQL_TYPE_ID) { 285 | switch (typeId) { 286 | case SQL_TYPE_DATE: 287 | case SQL_TYPE_TIME: 288 | case SQL_TYPE_TIMESTAMP: 289 | return SQL_DATETIME; 290 | case SQL_INTERVAL_YEAR: 291 | case SQL_INTERVAL_MONTH: 292 | case SQL_INTERVAL_DAY: 293 | case SQL_INTERVAL_HOUR: 294 | case SQL_INTERVAL_MINUTE: 295 | case SQL_INTERVAL_SECOND: 296 | case SQL_INTERVAL_YEAR_TO_MONTH: 297 | case SQL_INTERVAL_DAY_TO_HOUR: 298 | case SQL_INTERVAL_DAY_TO_MINUTE: 299 | case SQL_INTERVAL_DAY_TO_SECOND: 300 | case SQL_INTERVAL_HOUR_TO_MINUTE: 301 | case SQL_INTERVAL_HOUR_TO_SECOND: 302 | case SQL_INTERVAL_MINUTE_TO_SECOND: 303 | return SQL_INTERVAL; 304 | default: 305 | return typeId; 306 | } 307 | } 308 | } 309 | 310 | // http://msdn.microsoft.com/en-us/library/ms711683%28v=vs.85%29.aspx 311 | // (When looking at the reference on the above page and the values 312 | // from the source listed for column size, this seems to be the 313 | // appropriate result) 314 | int typeToNumPrecRadix(SQL_TYPE_ID typeId) { 315 | return isNumericalTypeId(typeId) ? 10 : 0; 316 | } 317 | 318 | final class TypeInfoResult(RowT) : OdbcResult { 319 | this(TList...)(TList vs) { 320 | result = new RowT(vs); 321 | } 322 | 323 | override bool empty() const { 324 | return result is null; 325 | } 326 | 327 | override inout(RowT) front() inout { 328 | assert(!empty); 329 | return result; 330 | } 331 | 332 | override void popFront() { 333 | result = null; 334 | } 335 | 336 | override size_t numberOfColumns() { 337 | return TypeInfoResultColumns.max; 338 | } 339 | 340 | private: 341 | RowT result; 342 | } 343 | 344 | alias SQL_SIGNED = SQL_FALSE; 345 | alias SQL_UNSIGNED = SQL_TRUE; 346 | enum SQL_HAS_NO_SIGN = null; 347 | 348 | /* 349 | * TODO: Turns out that these should be named after the SQL type, not the Presto type 350 | * I believe that this one is named appropriately, should we need other type info 351 | * classes, bear that in mind. 352 | * TODO: The information in this file has not been double-checked for correctness in a 353 | * while. It should be reviewed when this file is refactored. 354 | */ 355 | // http://msdn.microsoft.com/en-us/library/ms714632%28v=vs.85%29.aspx 356 | final class VarcharTypeInfoResultRow : OdbcResultRow { 357 | this(Nullability isNullable) { 358 | this.isNullable = isNullable; 359 | } 360 | 361 | override Variant dataAt(int column) { 362 | switch(column) { 363 | case TypeInfoResultColumns.TYPE_NAME: 364 | case TypeInfoResultColumns.LOCAL_TYPE_NAME: 365 | return Variant("VARCHAR"); 366 | case TypeInfoResultColumns.DATA_TYPE: 367 | case TypeInfoResultColumns.SQL_DATA_TYPE: 368 | return Variant(SQL_TYPE_ID.SQL_VARCHAR); 369 | case TypeInfoResultColumns.COLUMN_SIZE: 370 | return Variant(columnSizeMap[SQL_TYPE_ID.SQL_VARCHAR]); 371 | case TypeInfoResultColumns.LITERAL_PREFIX: 372 | case TypeInfoResultColumns.LITERAL_SUFFIX: 373 | return Variant("'"); 374 | case TypeInfoResultColumns.CREATE_PARAMS: 375 | return Variant("length"); 376 | case TypeInfoResultColumns.NULLABLE: 377 | return Variant(isNullable); 378 | case TypeInfoResultColumns.CASE_SENSITIVE: 379 | return Variant(SQL_TRUE); 380 | case TypeInfoResultColumns.SEARCHABLE: 381 | return Variant(SQL_SEARCHABLE); 382 | case TypeInfoResultColumns.UNSIGNED_ATTRIBUTE: 383 | return Variant(SQL_HAS_NO_SIGN); 384 | case TypeInfoResultColumns.FIXED_PREC_SCALE: 385 | return Variant(SQL_FALSE); 386 | case TypeInfoResultColumns.AUTO_UNIQUE_VALUE: 387 | return Variant(SQL_FALSE); 388 | case TypeInfoResultColumns.MINIMUM_SCALE: 389 | case TypeInfoResultColumns.MAXIMUM_SCALE: 390 | return Variant(null); 391 | case TypeInfoResultColumns.SQL_DATETIME_SUB: 392 | return Variant(null); 393 | case TypeInfoResultColumns.NUM_PREC_RADIX: 394 | return Variant(typeToNumPrecRadix(typeId)); 395 | case TypeInfoResultColumns.INTERVAL_PRECISION: 396 | return Variant(null); 397 | default: 398 | assert(false); 399 | } 400 | } 401 | private: 402 | Nullability isNullable; 403 | enum typeId = SQL_TYPE_ID.SQL_VARCHAR; 404 | } 405 | 406 | enum TypeInfoResultColumns { 407 | TYPE_NAME = 1, 408 | DATA_TYPE, 409 | COLUMN_SIZE, 410 | LITERAL_PREFIX, 411 | LITERAL_SUFFIX, 412 | CREATE_PARAMS, 413 | NULLABLE, 414 | CASE_SENSITIVE, 415 | SEARCHABLE, 416 | UNSIGNED_ATTRIBUTE, 417 | FIXED_PREC_SCALE, 418 | AUTO_UNIQUE_VALUE, 419 | LOCAL_TYPE_NAME, 420 | MINIMUM_SCALE, 421 | MAXIMUM_SCALE, 422 | SQL_DATA_TYPE, 423 | SQL_DATETIME_SUB, 424 | NUM_PREC_RADIX, 425 | INTERVAL_PRECISION, 426 | } 427 | -------------------------------------------------------------------------------- /driver/util.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.odbcdriver.util; 15 | 16 | import std.array : front, popFront, empty, appender; 17 | import std.algorithm : min; 18 | import std.stdio : writeln; 19 | import std.conv : to, text, wtext; 20 | import std.file : append, write, readText, remove, exists, FileException; 21 | import std.traits : isNumeric, isIntegral, isSomeString, isSomeChar, Unqual; 22 | import core.stdc.stdlib : abort; 23 | import core.exception : Exception; 24 | import core.time : dur, Duration; 25 | import std.net.curl : HTTP, CurlException; 26 | import std.string: format; 27 | import std.datetime: Clock; 28 | 29 | import odbc.sqlext; 30 | import odbc.odbcinst; 31 | 32 | import presto.client.statementclient : StatementClient, ClientSession; 33 | 34 | import presto.odbcdriver.handles : OdbcStatement, OdbcConnection; 35 | 36 | version (Windows) { 37 | enum tempPath = "C:\\temp\\"; 38 | } else { 39 | enum tempPath = "/tmp/"; 40 | } 41 | auto logBuffer = appender!wstring; 42 | 43 | void logMessage(string file = __FILE__, int line = __LINE__, TList...)(auto ref TList vs) { 44 | logBuffer ~= buildDebugMessage!(file, line)(vs); 45 | logBuffer ~= '\n'; 46 | if (logBuffer.data.length > 100000) { 47 | flushLogBuffer(); 48 | } 49 | } 50 | 51 | void logCriticalMessage(string file = __FILE__, int line = __LINE__, TList...)(auto ref TList vs) { 52 | logBuffer ~= buildDebugMessage!(file, line)(vs); 53 | logBuffer ~= '\n'; 54 | flushLogBuffer(); 55 | } 56 | 57 | void flushLogBuffer() { 58 | enum logFile = tempPath ~ "presto_odbc.log"; 59 | 60 | FileException fileException; 61 | for (;;) { 62 | try { 63 | logFile.append(logBuffer.data); 64 | logBuffer.clear(); 65 | if (fileException) { 66 | append(logFile, "Had at least one file exception, latest:\n"w ~ wtext(fileException)); 67 | } 68 | break; 69 | } catch (FileException e) { 70 | if (fileException) { 71 | e.next = fileException; 72 | } 73 | fileException = e; 74 | } 75 | } 76 | } 77 | 78 | void showPopupMessage(TList...)(auto ref TList vs) { 79 | version (Windows) { 80 | import std.c.windows.windows; 81 | auto message = buildDebugMessage(vs) ~ '\0'; 82 | MessageBoxW(GetForegroundWindow(), message.ptr, "Presto ODBC Driver", MB_OK); 83 | } 84 | } 85 | 86 | unittest { 87 | assert(buildDebugMessage("Hi", "there", 5) == "Hi there 5"w); 88 | assert(buildDebugMessage(2, "there", 5) == "2 there 5"w); 89 | assert(buildDebugMessage(2, 3, 4) == "2 3 4"w); 90 | assert(buildDebugMessage("Hi", null, 5) == "Hi null 5"w); 91 | long addr = 12345; 92 | auto ptr = cast(void*)(addr); 93 | assert(buildDebugMessage("Hi", ptr, 5) == "Hi 3039 5"w); 94 | } 95 | 96 | wstring buildDebugMessage(string file = __FILE__, int line = __LINE__, TList...)(auto ref TList vs) { 97 | import std.conv : wtext; 98 | import std.algorithm : joiner; 99 | 100 | wstring[] rngOfVs; 101 | foreach (v; vs) { 102 | static assert(!isSomeCString!(typeof(v)), "Should not be using C-strings. See the coding conventions"); 103 | rngOfVs ~= wtext(v); 104 | } 105 | 106 | version (unittest) { 107 | //Cannot predict time/date/file/line in the test strings 108 | return wtext(joiner(rngOfVs, " ")); 109 | } else { 110 | return wtext(format("%-28s %25s:%-5s %s", Clock.currTime().toSimpleString(), file, line, joiner(rngOfVs, " "))); 111 | } 112 | } 113 | 114 | /** 115 | * TODO: Create a "real" GUI 116 | * This is a temporary/hacky solution that allows us to offer a 117 | * "GUI" for entering connection properties without having to actually 118 | * include/link with a cross-platform graphics library. This will be 119 | * removed in the future. 120 | */ 121 | string getTextInput(string fileTemplate = "") { 122 | import std.process : execute; 123 | 124 | auto tempFile = makeTempFile(fileTemplate); 125 | scope (exit) { tempFile.remove; } 126 | if (auto rc = execute(["notepad.exe", tempFile]).status != 0) { 127 | throw new OdbcException(StatusCode.GENERAL_ERROR, "Notepad returned bad return code "w ~ wtext(rc)); 128 | } 129 | return tempFile.readText!string(); 130 | } 131 | 132 | string makeTempFile(string content = "") 133 | out(tempFile) { 134 | dllEnforce(tempFile.exists, "File must exist"); 135 | } body { 136 | import std.random : uniform; 137 | 138 | Exception eSaved; 139 | for (auto i = 0; i < 10; ++i) { 140 | try { 141 | //As long as this driver is single-threaded I don't believe we're likely 142 | //to find this problematic. 143 | auto tempFile = tempPath ~ "temp" ~ text(uniform(0, 1_000_000)); 144 | if (tempFile.exists) { 145 | continue; 146 | } 147 | tempFile.write(content); 148 | return tempFile; 149 | } catch (Exception e) { eSaved = e; } 150 | } 151 | throw new OdbcException(StatusCode.GENERAL_ERROR, "Failed to make temp file"w ~ wtext(eSaved)); 152 | } 153 | 154 | void dllEnforce(bool condition, lazy string message = "dllEnforce failed", string file = __FILE__, int line = __LINE__) { 155 | import core.exception : Exception; 156 | 157 | if (!condition) { 158 | auto ex = new Exception(message, file, line); 159 | logCriticalMessage(ex); 160 | showPopupMessage(ex); 161 | abort(); 162 | } 163 | } 164 | 165 | SQLRETURN exceptionBoundary(alias fun, TList...)(auto ref TList args) { 166 | import presto.client.prestoerrors : QueryException; 167 | 168 | try { 169 | return fun(args); 170 | } catch (OdbcException e) { 171 | logCriticalMessage("OdbcException:", e.file ~ ":" ~ text(e.line), e.msg); 172 | return SQL_ERROR; 173 | } catch (QueryException e) { 174 | logCriticalMessage("QueryException:", e.message); 175 | return SQL_ERROR; 176 | } catch (Exception e) { 177 | logCriticalMessage(e); 178 | return SQL_ERROR; 179 | } catch (Error e) { 180 | logCriticalMessage(e); 181 | abort(); 182 | assert(false, "Silence compiler errors about not returning"); 183 | } 184 | } 185 | 186 | unittest { 187 | try { 188 | throw new OdbcException("HY000", "Test"); 189 | } catch (OdbcException e) {} 190 | } 191 | 192 | class OdbcException : Exception { 193 | 194 | this(T)(T handle, StatusCode sqlState, wstring message, int code = 1, 195 | string file = __FILE__, int line = __LINE__) if (is(T == OdbcStatement) || is(T == OdbcConnection)){ 196 | this(sqlState, message, code, file, line); 197 | handle.errors ~= this; 198 | } 199 | this(wstring sqlState, wstring message, int code = 1, string file = __FILE__, int line = __LINE__) { 200 | super(text(message), file, line); 201 | dllEnforce(sqlState.length == 5); 202 | this.sqlState = sqlState; 203 | this.message = message; 204 | this.code = code; 205 | } 206 | 207 | immutable wstring sqlState; 208 | immutable wstring message; 209 | immutable int code; 210 | } 211 | 212 | ///wstring source, dest < src 213 | unittest { 214 | auto src = "happySrcString"w; 215 | byte[] dest; 216 | dest.length = 12; 217 | SQLSMALLINT numberOfBytesCopied; 218 | copyToBuffer(src, outputWChar(dest, &numberOfBytesCopied)); 219 | assert(numberOfBytesCopied == 10); 220 | assert(cast(wchar[]) dest == "happy\0"); 221 | } 222 | 223 | ///wstring source, dest > src 224 | unittest { 225 | auto src = "happySrcString"w; 226 | byte[] dest; 227 | dest.length = 100; 228 | SQLSMALLINT numberOfBytesCopied; 229 | copyToBuffer(src, outputWChar(dest, &numberOfBytesCopied)); 230 | assert(numberOfBytesCopied == src.length * wchar.sizeof); 231 | auto result = cast(wchar[]) (dest[0 .. numberOfBytesCopied]); 232 | assert(result == src); 233 | } 234 | 235 | void copyToBuffer(N)(const(wchar)[] src, OutputWChar!N dest) { 236 | import std.c.string : memcpy; 237 | const numberOfCharsCopied = min(src.length, dest.length - 1); 238 | if (numberOfCharsCopied < src.length) { 239 | logMessage("Truncated in wide copyToBuffer: ", src, dest.length); 240 | } 241 | memcpy(dest.buffer.ptr, src.ptr, numberOfCharsCopied * wchar.sizeof); 242 | dest[numberOfCharsCopied] = 0; 243 | dest.lengthBytes = wcharsToBytes(numberOfCharsCopied); 244 | } 245 | 246 | SQLSMALLINT copyToNarrowBuffer(const(char)[] src, char[] dest) { 247 | import std.c.string : memcpy; 248 | const numberOfCharsCopied = min(src.length, dest.length - 1); 249 | if (numberOfCharsCopied < src.length) { 250 | logMessage("Truncated in narrow copyToBuffer: ", src, dest.length); 251 | } 252 | memcpy(dest.ptr, src.ptr, numberOfCharsCopied); 253 | dest[numberOfCharsCopied] = 0; 254 | //dest.length = numberOfCharsCopied + 1; 255 | return to!SQLSMALLINT(numberOfCharsCopied); 256 | } 257 | 258 | size_t wcharsToBytes(size_t count) { 259 | return count * wchar.sizeof; 260 | } 261 | 262 | size_t bytesToWchars(size_t count) { 263 | return count / wchar.sizeof; 264 | } 265 | 266 | void convertPtrBytesToWChars(T)(T* lengthBytes) { 267 | if (!lengthBytes) { 268 | return; 269 | } 270 | *lengthBytes = cast(T) bytesToWchars(*lengthBytes); 271 | } 272 | 273 | unittest { 274 | auto testArray = [1, 2, 3, 4, 5]; 275 | assert(popAndSave(testArray) == 1); 276 | assert(popAndSave(testArray) == 2); 277 | assert(popAndSave(testArray) == 3); 278 | } 279 | 280 | auto popAndSave(Range)(ref Range r) { 281 | auto result = r.front; 282 | r.popFront; 283 | return result; 284 | } 285 | 286 | 287 | ///Test with class 288 | unittest { 289 | import std.c.stdlib : free; 290 | 291 | class TestClass { 292 | this() {} 293 | this(int x, int y) { 294 | this.x = x; 295 | this.y = y; 296 | } 297 | int x; 298 | int y; 299 | } 300 | auto ptr = makeWithoutGC!TestClass(); 301 | assert(ptr.x == 0); 302 | assert(ptr.y == 0); 303 | free(cast(void*) ptr); 304 | 305 | ptr = makeWithoutGC!TestClass(2, 3); 306 | assert(ptr.x == 2); 307 | assert(ptr.y == 3); 308 | free(cast(void*) ptr); 309 | } 310 | 311 | ///Test with struct 312 | unittest { 313 | import std.c.stdlib : free; 314 | 315 | struct TestStruct { 316 | this(int x, int y) { 317 | this.x = x; 318 | this.y = y; 319 | } 320 | int x; 321 | int y; 322 | } 323 | 324 | auto ptr = makeWithoutGC!TestStruct(); 325 | assert(ptr.x == 0); 326 | assert(ptr.y == 0); 327 | free(cast(void*) ptr); 328 | 329 | ptr = makeWithoutGC!TestStruct(2, 3); 330 | assert(ptr.x == 2); 331 | assert(ptr.y == 3); 332 | free(cast(void*) ptr); 333 | 334 | } 335 | 336 | auto makeWithoutGC(T, TList...)(auto ref TList args) { 337 | import std.c.stdlib : malloc; 338 | auto ptr = malloc(getInstanceSize!T); 339 | return emplaceWrapper!T(ptr, args); 340 | } 341 | 342 | private auto emplaceWrapper(T, TList...)(void* memory, auto ref TList args) { 343 | import std.conv : emplace; 344 | 345 | static if (is(T == class)) { 346 | return emplace!T(cast(void[]) memory[0 .. getInstanceSize!T], args); 347 | } else { 348 | return emplace!T(cast(T*) memory, args); 349 | } 350 | } 351 | 352 | size_t getInstanceSize(T)() if (is(T == class)) { 353 | return __traits(classInstanceSize, T); 354 | } 355 | 356 | size_t getInstanceSize(T)() if (!is(T == class)) { 357 | return T.sizeof; 358 | } 359 | 360 | unittest { 361 | auto test1 = "Hello, world!"; 362 | assert(strlen(test1.ptr) == test1.length); 363 | auto test2 = "Hello, world!"w; 364 | assert(strlen(test2.ptr) == test2.length); 365 | auto test3 = "Hello, world!"d; 366 | assert(strlen(test3.ptr) == test3.length); 367 | } 368 | 369 | size_t strlen(C)(const(C)* str) { 370 | size_t length = 0; 371 | for (; str[length] != 0; ++length) { 372 | //Intentionally blank 373 | } 374 | return length; 375 | } 376 | 377 | unittest { 378 | static assert(isSomeCString!(char*)); 379 | static assert(isSomeCString!(wchar*)); 380 | static assert(isSomeCString!(const (wchar)*)); 381 | static assert(!isSomeCString!(char[])); 382 | static assert(!isSomeCString!(string)); 383 | } 384 | 385 | bool isSomeCString(T)() { 386 | import std.traits : isPointer, PointerTarget; 387 | static if (isPointer!T && !is(T == void*) && isSomeChar!(PointerTarget!T)) { 388 | return true; 389 | } else { 390 | return false; 391 | } 392 | } 393 | 394 | unittest { 395 | auto str = "Hello world"; 396 | auto wstr = "Hello world"w; 397 | assert(toDString(str.ptr, SQL_NTS) == str); 398 | assert(toDString(str.ptr, str.length) == str); 399 | assert(toDString(wstr.ptr, SQL_NTS) == wstr); 400 | assert(toDString(wstr.ptr, wstr.length) == wstr); 401 | } 402 | 403 | inout(C)[] toDString(C)(inout(C)* cString, size_t lengthChars) if (isSomeChar!C) { 404 | if (cString == null) { 405 | return null; 406 | } 407 | if (lengthChars == SQL_NTS) { 408 | lengthChars = strlen(cString); 409 | } 410 | return cString[0 .. lengthChars]; 411 | } 412 | 413 | template UnqualString(T) { 414 | static if (!isSomeString!T) { 415 | static assert(false, "Not a string"); 416 | } else { 417 | alias CharType = typeof(T.init[0]); 418 | alias UnqualString = Unqual!(CharType)[]; 419 | } 420 | } 421 | 422 | auto outputWChar(LengthType)(wchar* buffer, size_t maxLengthBytes, LengthType* lengthBytes) { 423 | return OutputWChar!LengthType(buffer, maxLengthBytes, lengthBytes); 424 | } 425 | 426 | auto outputWChar(LengthType)(SQLPOINTER buffer, size_t maxLengthBytes, LengthType* lengthBytes) { 427 | return outputWChar(cast(wchar*) buffer, maxLengthBytes, lengthBytes); 428 | } 429 | 430 | auto outputWChar(LengthType)(void[] buffer, LengthType* lengthBytes) { 431 | return outputWChar!LengthType(buffer.ptr, buffer.length, lengthBytes); 432 | } 433 | 434 | struct OutputWChar(LengthType) { 435 | private this(wchar* buffer, size_t maxLengthBytes, LengthType* lengthBytes) { 436 | lengthBytes_ = lengthBytes; 437 | this.lengthBytes = 0; 438 | 439 | if (buffer == null) { 440 | this.buffer = null; 441 | return; 442 | } 443 | 444 | if (maxLengthBytes % 2 != 0 || maxLengthBytes < 2) { 445 | logMessage("Warning! maxLengthBytes is", maxLengthBytes); 446 | this.buffer = null; 447 | return; 448 | } 449 | 450 | this.buffer = buffer[0 .. maxLengthBytes / 2]; 451 | } 452 | 453 | void lengthBytes(size_t lengthBytes) { 454 | if (!lengthBytes_) { 455 | return; 456 | } 457 | *lengthBytes_ = cast(LengthType) lengthBytes; 458 | } 459 | 460 | bool opEquals(T : typeof(null))(T value) { 461 | return buffer == null; 462 | } 463 | 464 | bool opCast(T : bool)() { 465 | return buffer != null; 466 | } 467 | 468 | unittest { 469 | auto x = OutputWChar(null, 0, null); 470 | assert(x == null); 471 | } 472 | 473 | wchar[] buffer; 474 | LengthType* lengthBytes_; 475 | alias buffer this; 476 | } 477 | 478 | unittest { 479 | auto associativeArray = ["bob":1, "joe":2]; 480 | assert(associativeArray.getOrDefault("bob") == 1); 481 | assert(associativeArray.getOrDefault("frank") == 0); 482 | } 483 | 484 | V getOrDefault(V, K)(V[K] associativeArray, K key, V default_ = V.init) { 485 | if (auto valuePointer = key in associativeArray) { 486 | return *valuePointer; 487 | } 488 | return default_; 489 | } 490 | 491 | unittest { 492 | assert(escapeSqlIdentifier(r"bob") == `"bob"`); 493 | assert(escapeSqlIdentifier(`"bob"`) == `"""bob"""`); 494 | } 495 | 496 | string escapeSqlIdentifier(string identifier) { 497 | auto result = appender!string; 498 | result.reserve(identifier.length + 2); 499 | result ~= '"'; 500 | 501 | foreach (c; identifier) { 502 | if (c != '"') { 503 | result ~= c; 504 | } else { 505 | result ~= `""`; 506 | } 507 | } 508 | result ~= '"'; 509 | 510 | return result.data; 511 | } 512 | -------------------------------------------------------------------------------- /driver/columnresults.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.odbcdriver.columnresults; 15 | 16 | import std.array : front, empty, popFront; 17 | import std.conv : text, to; 18 | import std.variant : Variant; 19 | 20 | import odbc.sqlext; 21 | import odbc.odbcinst; 22 | 23 | import presto.client.util : asBool; 24 | 25 | import presto.odbcdriver.handles : OdbcConnection; 26 | import presto.odbcdriver.bindings : OdbcResult, OdbcResultRow; 27 | import presto.odbcdriver.typeinfo : columnSizeMap, decimalDigitsMap, typeToNumPrecRadix; 28 | import presto.odbcdriver.util; 29 | 30 | // http://msdn.microsoft.com/en-us/library/ms711683%28v=vs.85%29.aspx 31 | 32 | ColumnsResult listColumnsInTable(OdbcConnection connectionHandle, string tableName) { 33 | with (connectionHandle) with (session) { 34 | auto client = connectionHandle.runQuery("SHOW COLUMNS FROM " ~ tableName.escapeSqlIdentifier); 35 | auto result = makeWithoutGC!ColumnsResult(); 36 | foreach (resultBatch; client) { 37 | foreach (i, row; resultBatch.data.array) { 38 | auto columnName = row.array[0].str; 39 | auto type = row.array[1].str; 40 | auto isNullable = asBool(row.array[2]) ? Nullability.SQL_NULLABLE : Nullability.SQL_NO_NULLS; 41 | auto partitionKey = asBool(row.array[3]); 42 | 43 | auto columnsResult = prestoTypeToColumnsResult(type, catalog, schema, text(tableName), columnName, isNullable, i + 1); 44 | 45 | if (columnsResult) { 46 | result.addColumn(columnsResult); 47 | } 48 | logMessage("listColumnsInTable found column: ", columnName, type, isNullable, i + 1); 49 | } 50 | } 51 | return result; 52 | } 53 | } 54 | 55 | OdbcResultRow prestoTypeToColumnsResult( 56 | string prestoType, string catalogName, string schemaName, 57 | string tableName, string columnName, Nullability isNullable, size_t ordinalPosition) { 58 | dllEnforce(ordinalPosition != 0, "Columns are 1-indexed"); 59 | switch (prestoType) { 60 | case "varchar": 61 | return makeWithoutGC!VarcharColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 62 | case "bigint": 63 | return makeWithoutGC!BigIntColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 64 | case "double": 65 | return makeWithoutGC!DoubleColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 66 | case "date": 67 | return makeWithoutGC!DateColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 68 | case "time": 69 | return makeWithoutGC!TimeColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 70 | case "time with time zone": 71 | return makeWithoutGC!VarcharColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 72 | case "timestamp": 73 | return makeWithoutGC!TimestampColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 74 | case "timestamp with time zone": 75 | return makeWithoutGC!VarcharColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 76 | case "interval year to month": 77 | return makeWithoutGC!YearToMonthColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 78 | case "interval day to second": 79 | return makeWithoutGC!DayToSecondColumnsResultRow(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 80 | default: 81 | logMessage("Unexpected type in prestoTypeToColumnsResult: " ~ prestoType); 82 | return null; 83 | } 84 | } 85 | 86 | SQL_TYPE_ID prestoTypeToSqlTypeId(string prestoType) { 87 | with (SQL_TYPE_ID) { 88 | switch (prestoType) { 89 | case "varchar": 90 | return SQL_VARCHAR; 91 | case "bigint": 92 | return SQL_BIGINT; 93 | case "double": 94 | return SQL_DOUBLE; 95 | case "date": 96 | return SQL_DATE; 97 | case "time": 98 | return SQL_TIME; 99 | case "time with time zone": 100 | return SQL_VARCHAR; 101 | case "timestamp": 102 | return SQL_TIMESTAMP; 103 | case "timestamp with time zone": 104 | return SQL_VARCHAR; 105 | case "interval year to month": 106 | return SQL_INTERVAL_YEAR_TO_MONTH; 107 | case "interval day to second": 108 | return SQL_INTERVAL_DAY_TO_SECOND; 109 | default: 110 | logMessage("Unexpected type in prestoTypeToSqlTypeId: " ~ prestoType); 111 | return SQL_UNKNOWN_TYPE; 112 | } 113 | } 114 | } 115 | 116 | final class ColumnsResult : OdbcResult { 117 | void addColumn(OdbcResultRow column) { 118 | results_ ~= column; 119 | } 120 | 121 | auto results() const { 122 | return results_; 123 | } 124 | 125 | override bool empty() const { 126 | return results_.empty; 127 | } 128 | 129 | override inout(OdbcResultRow) front() inout { 130 | assert(!empty); 131 | return results_.front; 132 | } 133 | 134 | override void popFront() { 135 | results_.popFront(); 136 | } 137 | 138 | override size_t numberOfColumns() { 139 | return ColumnsResultColumns.max; 140 | } 141 | 142 | private: 143 | OdbcResultRow[] results_; 144 | } 145 | 146 | private class ColumnsResultRow : OdbcResultRow { 147 | this(string catalogName, string schemaName, string tableName, 148 | string columnName, Nullability isNullable, size_t ordinalPosition) { 149 | this.catalogName = catalogName; 150 | this.schemaName = schemaName; 151 | this.tableName = tableName; 152 | this.columnName = columnName; 153 | this.isNullable = isNullable; 154 | this.ordinalPosition = to!int(ordinalPosition); 155 | } 156 | 157 | abstract override Variant dataAt(int column); 158 | 159 | final Variant dataAt(ColumnsResultColumns column) { 160 | return dataAt(cast(int) column); 161 | } 162 | 163 | protected: 164 | string catalogName; 165 | string schemaName; 166 | string tableName; 167 | string columnName; 168 | Nullability isNullable; 169 | int ordinalPosition; 170 | } 171 | 172 | alias BigIntColumnsResultRow = BigIntBasedColumnsResultRow!(SQL_TYPE_ID.SQL_BIGINT); 173 | alias IntegerColumnsResultRow = BigIntBasedColumnsResultRow!(SQL_TYPE_ID.SQL_INTEGER); 174 | alias SmallIntColumnsResultRow = BigIntBasedColumnsResultRow!(SQL_TYPE_ID.SQL_SMALLINT); 175 | alias TinyIntColumnsResultRow = BigIntBasedColumnsResultRow!(SQL_TYPE_ID.SQL_TINYINT); 176 | 177 | final class BigIntBasedColumnsResultRow(SQL_TYPE_ID typeId) : ColumnsResultRow { 178 | this(string catalogName, string schemaName, string tableName, 179 | string columnName, Nullability isNullable, size_t ordinalPosition) { 180 | super(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 181 | } 182 | 183 | override Variant dataAt(int column) { 184 | with (ColumnsResultColumns) { 185 | switch (column) { 186 | case TABLE_CAT: 187 | return Variant(catalogName); 188 | case TABLE_SCHEM: 189 | return Variant(schemaName); 190 | case TABLE_NAME: 191 | return Variant(tableName); 192 | case COLUMN_NAME: 193 | return Variant(columnName); 194 | case DATA_TYPE: 195 | case SQL_DATA_TYPE: 196 | return Variant(typeId); 197 | case TYPE_NAME: 198 | return Variant("BIGINT"); 199 | case COLUMN_SIZE: 200 | case BUFFER_LENGTH: 201 | return Variant(columnSizeMap[typeId]); 202 | case DECIMAL_DIGITS: 203 | return Variant(decimalDigitsMap[typeId]); 204 | case NUM_PREC_RADIX: 205 | return Variant(typeToNumPrecRadix(typeId)); 206 | case NULLABLE: 207 | return Variant(isNullable); 208 | case REMARKS: 209 | return Variant("No remarks"); 210 | case COLUMN_DEF: 211 | return Variant("0"); 212 | case SQL_DATETIME_SUB: 213 | return Variant(null); 214 | case CHAR_OCTET_LENGTH: 215 | return Variant(null); 216 | case ORDINAL_POSITION: 217 | return Variant(ordinalPosition); 218 | case IS_NULLABLE: 219 | return Variant(isNullable); 220 | default: 221 | dllEnforce(false, "Non-existent column " ~ text(cast(ColumnsResultColumns) column)); 222 | assert(false, "Silence compiler errors about not returning"); 223 | } 224 | } 225 | } 226 | private: 227 | } 228 | 229 | alias DoubleColumnsResultRow = DoubleBasedColumnsResultRow!(SQL_TYPE_ID.SQL_DOUBLE); 230 | alias FloatColumnsResultRow = DoubleBasedColumnsResultRow!(SQL_TYPE_ID.SQL_FLOAT); 231 | alias RealColumnsResultRow = DoubleBasedColumnsResultRow!(SQL_TYPE_ID.SQL_REAL); 232 | 233 | final class DoubleBasedColumnsResultRow(SQL_TYPE_ID typeId) : ColumnsResultRow { 234 | this(string catalogName, string schemaName, string tableName, 235 | string columnName, Nullability isNullable, size_t ordinalPosition) { 236 | super(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 237 | } 238 | 239 | override Variant dataAt(int column) { 240 | with (ColumnsResultColumns) { 241 | switch (column) { 242 | case TABLE_CAT: 243 | return Variant(catalogName); 244 | case TABLE_SCHEM: 245 | return Variant(schemaName); 246 | case TABLE_NAME: 247 | return Variant(tableName); 248 | case COLUMN_NAME: 249 | return Variant(columnName); 250 | case DATA_TYPE: 251 | case SQL_DATA_TYPE: 252 | return Variant(typeId); 253 | case TYPE_NAME: 254 | return Variant("DOUBLE"); 255 | case COLUMN_SIZE: 256 | case BUFFER_LENGTH: 257 | return Variant(columnSizeMap[typeId]); 258 | case DECIMAL_DIGITS: 259 | return Variant(null); 260 | case NUM_PREC_RADIX: 261 | return Variant(typeToNumPrecRadix(typeId)); 262 | case NULLABLE: 263 | return Variant(isNullable); 264 | case REMARKS: 265 | return Variant("No remarks"); 266 | case COLUMN_DEF: 267 | return Variant("0"); 268 | case SQL_DATETIME_SUB: 269 | return Variant(null); 270 | case CHAR_OCTET_LENGTH: 271 | return Variant(null); 272 | case ORDINAL_POSITION: 273 | return Variant(ordinalPosition); 274 | case IS_NULLABLE: 275 | return Variant(isNullable); 276 | default: 277 | dllEnforce(false, "Non-existent column " ~ text(cast(ColumnsResultColumns) column)); 278 | assert(false, "Silence compiler errors about not returning"); 279 | } 280 | } 281 | } 282 | private: 283 | } 284 | 285 | 286 | final class VarcharColumnsResultRow : ColumnsResultRow { 287 | this(string catalogName, string schemaName, string tableName, 288 | string columnName, Nullability isNullable, size_t ordinalPosition) { 289 | super(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 290 | } 291 | 292 | override Variant dataAt(int column) { 293 | with (ColumnsResultColumns) { 294 | switch (column) { 295 | case TABLE_CAT: 296 | return Variant(catalogName); 297 | case TABLE_SCHEM: 298 | return Variant(schemaName); 299 | case TABLE_NAME: 300 | return Variant(tableName); 301 | case COLUMN_NAME: 302 | return Variant(columnName); 303 | case DATA_TYPE: 304 | case SQL_DATA_TYPE: 305 | return Variant(typeId); 306 | case TYPE_NAME: 307 | return Variant("VARCHAR"); 308 | case COLUMN_SIZE: 309 | case BUFFER_LENGTH: 310 | return Variant(columnSizeMap[typeId]); 311 | case DECIMAL_DIGITS: 312 | return Variant(null); 313 | case NUM_PREC_RADIX: 314 | return Variant(null); 315 | case NULLABLE: 316 | return Variant(isNullable); 317 | case REMARKS: 318 | return Variant("No remarks"); 319 | case COLUMN_DEF: 320 | return Variant("''"); 321 | case SQL_DATETIME_SUB: 322 | return Variant(null); 323 | case CHAR_OCTET_LENGTH: 324 | return Variant(SQL_NO_TOTAL); //not sure if this value works here 325 | case ORDINAL_POSITION: 326 | return Variant(ordinalPosition); 327 | case IS_NULLABLE: 328 | return Variant(isNullable); 329 | default: 330 | dllEnforce(false, "Non-existent column " ~ text(cast(ColumnsResultColumns) column)); 331 | assert(false, "Silence compiler errors about not returning"); 332 | } 333 | } 334 | } 335 | private: 336 | enum typeId = SQL_TYPE_ID.SQL_VARCHAR; 337 | } 338 | 339 | alias DateColumnsResultRow = DateTimeBasedColumnsResultRow!(SQL_TYPE_ID.SQL_DATE, "DATE"); 340 | alias TimeColumnsResultRow = DateTimeBasedColumnsResultRow!(SQL_TYPE_ID.SQL_TIME, "TIME"); 341 | alias TimestampColumnsResultRow = DateTimeBasedColumnsResultRow!(SQL_TYPE_ID.SQL_TIMESTAMP, "TIMESTAMP"); 342 | 343 | final class DateTimeBasedColumnsResultRow(SQL_TYPE_ID typeId, string typeName) : ColumnsResultRow { 344 | this(string catalogName, string schemaName, string tableName, 345 | string columnName, Nullability isNullable, size_t ordinalPosition) { 346 | super(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 347 | } 348 | 349 | override Variant dataAt(int column) { 350 | with (ColumnsResultColumns) { 351 | switch (column) { 352 | case TABLE_CAT: 353 | return Variant(catalogName); 354 | case TABLE_SCHEM: 355 | return Variant(schemaName); 356 | case TABLE_NAME: 357 | return Variant(tableName); 358 | case COLUMN_NAME: 359 | return Variant(columnName); 360 | case DATA_TYPE: 361 | case SQL_DATA_TYPE: 362 | return Variant(typeId); 363 | case TYPE_NAME: 364 | return Variant(typeName); 365 | case COLUMN_SIZE: 366 | case BUFFER_LENGTH: 367 | return Variant(columnSizeMap[typeId]); 368 | case DECIMAL_DIGITS: 369 | return Variant(null); 370 | case NUM_PREC_RADIX: 371 | return Variant(null); 372 | case NULLABLE: 373 | return Variant(isNullable); 374 | case REMARKS: 375 | return Variant("No remarks"); 376 | case COLUMN_DEF: 377 | return Variant(typeName ~ " ''"); 378 | case SQL_DATETIME_SUB: 379 | return Variant(dateTimeCodeMap[typeId]); 380 | case CHAR_OCTET_LENGTH: 381 | return Variant(null); 382 | case ORDINAL_POSITION: 383 | return Variant(ordinalPosition); 384 | case IS_NULLABLE: 385 | return Variant(isNullable); 386 | default: 387 | dllEnforce(false, "Non-existent column " ~ text(cast(ColumnsResultColumns) column)); 388 | assert(false, "Silence compiler errors about not returning"); 389 | } 390 | } 391 | } 392 | private: 393 | } 394 | 395 | alias YearToMonthColumnsResultRow = IntervalBasedColumnsResultRow!(SQL_TYPE_ID.SQL_INTERVAL_YEAR_TO_MONTH, "month"); 396 | alias DayToSecondColumnsResultRow = IntervalBasedColumnsResultRow!(SQL_TYPE_ID.SQL_INTERVAL_DAY_TO_SECOND, "second"); 397 | 398 | final class IntervalBasedColumnsResultRow(SQL_TYPE_ID typeId, string typeName) : ColumnsResultRow { 399 | this(string catalogName, string schemaName, string tableName, 400 | string columnName, Nullability isNullable, size_t ordinalPosition) { 401 | super(catalogName, schemaName, tableName, columnName, isNullable, ordinalPosition); 402 | } 403 | 404 | override Variant dataAt(int column) { 405 | with (ColumnsResultColumns) { 406 | switch (column) { 407 | case TABLE_CAT: 408 | return Variant(catalogName); 409 | case TABLE_SCHEM: 410 | return Variant(schemaName); 411 | case TABLE_NAME: 412 | return Variant(tableName); 413 | case COLUMN_NAME: 414 | return Variant(columnName); 415 | case DATA_TYPE: 416 | case SQL_DATA_TYPE: 417 | return Variant(typeId); 418 | case TYPE_NAME: 419 | return Variant(typeName); 420 | case COLUMN_SIZE: 421 | case BUFFER_LENGTH: 422 | return Variant(columnSizeMap[typeId]); 423 | case DECIMAL_DIGITS: 424 | return Variant(null); 425 | case NUM_PREC_RADIX: 426 | return Variant(null); 427 | case NULLABLE: 428 | return Variant(isNullable); 429 | case REMARKS: 430 | return Variant("No remarks"); 431 | case COLUMN_DEF: 432 | return Variant("interval '' " ~ typeName); 433 | case SQL_DATETIME_SUB: 434 | return Variant(intervalCodeMap[typeId]); 435 | case CHAR_OCTET_LENGTH: 436 | return Variant(null); 437 | case ORDINAL_POSITION: 438 | return Variant(ordinalPosition); 439 | case IS_NULLABLE: 440 | return Variant(isNullable); 441 | default: 442 | dllEnforce(false, "Non-existent column " ~ text(cast(ColumnsResultColumns) column)); 443 | assert(false, "Silence compiler errors about not returning"); 444 | } 445 | } 446 | } 447 | private: 448 | } 449 | 450 | enum ColumnsResultColumns { 451 | TABLE_CAT = 1, 452 | TABLE_SCHEM, 453 | TABLE_NAME, 454 | COLUMN_NAME, 455 | DATA_TYPE, 456 | TYPE_NAME, 457 | COLUMN_SIZE, 458 | BUFFER_LENGTH, //How many bytes a BindCol buffer must have to accept this 459 | DECIMAL_DIGITS, 460 | NUM_PREC_RADIX, 461 | NULLABLE, 462 | REMARKS, 463 | COLUMN_DEF, 464 | SQL_DATA_TYPE, 465 | SQL_DATETIME_SUB, 466 | CHAR_OCTET_LENGTH, 467 | ORDINAL_POSITION, //Which # column in the table this is 468 | IS_NULLABLE 469 | } 470 | -------------------------------------------------------------------------------- /odbc/sqlucode.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 1996-2014 by OpenLink Software 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 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in 13 | * the documentation and/or other materials provided with the 14 | * distribution. 15 | * 3. Neither the name of OpenLink Software Inc. nor the names of its 16 | * contributors may be used to endorse or promote products derived 17 | * from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OPENLINK OR 23 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | module odbc.sqlucode; 32 | 33 | import odbc.sqlext; 34 | 35 | //Size of SQLSTATE - Unicode 36 | enum SQL_SQLSTATE_SIZEW = 10; 37 | 38 | // Mapping aliases for Unicode 39 | version(UNICODE) { 40 | alias SQLColAttribute = SQLColAttributeW; 41 | alias SQLColAttributes = SQLColAttributesW; 42 | alias SQLConnect = SQLConnectW; 43 | alias SQLDescribeCol = SQLDescribeColW; 44 | alias SQLError = SQLErrorW; 45 | alias SQLExecDirect = SQLExecDirectW; 46 | alias SQLGetConnectAttr = SQLGetConnectAttrW; 47 | alias SQLGetCursorName = SQLGetCursorNameW; 48 | alias SQLGetDescField = SQLGetDescFieldW; 49 | alias SQLGetDescRec = SQLGetDescRecW; 50 | alias SQLGetDiagField = SQLGetDiagFieldW; 51 | alias SQLGetDiagRec = SQLGetDiagRecW; 52 | alias SQLPrepare = SQLPrepareW; 53 | alias SQLSetConnectAttr = SQLSetConnectAttrW; 54 | alias SQLSetCursorName = SQLSetCursorNameW; 55 | alias SQLSetDescField = SQLSetDescFieldW; 56 | alias SQLSetStmtAttr = SQLSetStmtAttrW; 57 | alias SQLGetStmtAttr = SQLGetStmtAttrW; 58 | alias SQLColumns = SQLColumnsW; 59 | alias SQLGetConnectOption = SQLGetConnectOptionW; 60 | alias SQLGetInfo = SQLGetInfoW; 61 | alias SQLGetTypeInfo = SQLGetTypeInfoW; 62 | alias SQLSetConnectOption = SQLSetConnectOptionW; 63 | alias SQLSpecialColumns = SQLSpecialColumnsW; 64 | alias SQLStatistics = SQLStatisticsW; 65 | alias SQLTables = SQLTablesW; 66 | alias SQLDataSources = SQLDataSourcesW; 67 | alias SQLDriverConnect = SQLDriverConnectW; 68 | alias SQLBrowseConnect = SQLBrowseConnectW; 69 | alias SQLColumnPrivileges = SQLColumnPrivilegesW; 70 | alias SQLForeignKeys = SQLForeignKeysW; 71 | alias SQLNativeSql = SQLNativeSqlW; 72 | alias SQLPrimaryKeys = SQLPrimaryKeysW; 73 | alias SQLProcedureColumns = SQLProcedureColumnsW; 74 | alias SQLProcedures = SQLProceduresW; 75 | alias SQLTablePrivileges = SQLTablePrivilegesW; 76 | alias SQLDrivers = SQLDriversW; 77 | } 78 | 79 | extern(System): 80 | 81 | //Function Prototypes - Unicode 82 | SQLRETURN SQLColAttributeW( 83 | SQLHSTMT hstmt, 84 | SQLUSMALLINT iCol, 85 | SQLUSMALLINT iField, 86 | SQLPOINTER pCharAttr, 87 | SQLSMALLINT cbDescMax, 88 | SQLSMALLINT *pcbCharAttr, 89 | SQLLEN *pNumAttr); 90 | 91 | SQLRETURN SQLColAttributesW( 92 | SQLHSTMT hstmt, 93 | SQLUSMALLINT icol, 94 | SQLUSMALLINT fDescType, 95 | SQLPOINTER rgbDesc, 96 | SQLSMALLINT cbDescMax, 97 | SQLSMALLINT *pcbDesc, 98 | SQLLEN *pfDesc); 99 | 100 | SQLRETURN SQLConnectW( 101 | SQLHDBC hdbc, 102 | SQLWCHAR* szDSN, 103 | SQLSMALLINT cbDSN, 104 | SQLWCHAR* szUID, 105 | SQLSMALLINT cbUID, 106 | SQLWCHAR* szAuthStr, 107 | SQLSMALLINT cbAuthStr); 108 | 109 | SQLRETURN SQLDescribeColW( 110 | SQLHSTMT hstmt, 111 | SQLUSMALLINT icol, 112 | SQLWCHAR* szColName, 113 | SQLSMALLINT cbColNameMax, 114 | SQLSMALLINT* pcbColName, 115 | SQLSMALLINT* pfSqlType, 116 | SQLULEN* pcbColDef, 117 | SQLSMALLINT* pibScale, 118 | SQLSMALLINT* pfNullable); 119 | 120 | SQLRETURN SQLErrorW( 121 | SQLHENV henv, 122 | SQLHDBC hdbc, 123 | SQLHSTMT hstmt, 124 | SQLWCHAR* szSqlState, 125 | SQLINTEGER* pfNativeError, 126 | SQLWCHAR* szErrorMsg, 127 | SQLSMALLINT cbErrorMsgMax, 128 | SQLSMALLINT* pcbErrorMsg); 129 | 130 | SQLRETURN SQLExecDirectW( 131 | SQLHSTMT hstmt, 132 | SQLWCHAR* szSqlStr, 133 | SQLINTEGER TextLength); 134 | 135 | SQLRETURN SQLGetConnectAttrW( 136 | SQLHDBC hdbc, 137 | SQLINTEGER fAttribute, 138 | SQLPOINTER rgbValue, 139 | SQLINTEGER cbValueMax, 140 | SQLINTEGER* pcbValue); 141 | 142 | SQLRETURN SQLGetCursorNameW( 143 | SQLHSTMT hstmt, 144 | SQLWCHAR* szCursor, 145 | SQLSMALLINT cbCursorMax, 146 | SQLSMALLINT* pcbCursor); 147 | 148 | SQLRETURN SQLSetDescFieldW( 149 | SQLHDESC DescriptorHandle, 150 | SQLSMALLINT RecNumber, 151 | SQLSMALLINT FieldIdentifier, 152 | SQLPOINTER Value, 153 | SQLINTEGER BufferLength); 154 | 155 | SQLRETURN SQLSetDescFieldA( 156 | SQLHDESC DescriptorHandle, 157 | SQLSMALLINT RecNumber, 158 | SQLSMALLINT FieldIdentifier, 159 | SQLPOINTER Value, 160 | SQLINTEGER BufferLength); 161 | 162 | SQLRETURN SQLGetDescFieldW( 163 | SQLHDESC hdesc, 164 | SQLSMALLINT iRecord, 165 | SQLSMALLINT iField, 166 | SQLPOINTER rgbValue, 167 | SQLINTEGER cbValueMax, 168 | SQLINTEGER* pcbValue); 169 | 170 | SQLRETURN SQLGetDescRecW( 171 | SQLHDESC hdesc, 172 | SQLSMALLINT iRecord, 173 | SQLWCHAR* szName, 174 | SQLSMALLINT cbNameMax, 175 | SQLSMALLINT* pcbName, 176 | SQLSMALLINT* pfType, 177 | SQLSMALLINT* pfSubType, 178 | SQLLEN* pLength, 179 | SQLSMALLINT* pPrecision, 180 | SQLSMALLINT* pScale, 181 | SQLSMALLINT* pNullable); 182 | 183 | SQLRETURN SQLGetDiagFieldW 184 | ( 185 | SQLSMALLINT fHandleType, 186 | SQLHANDLE handle, 187 | SQLSMALLINT iRecord, 188 | SQLSMALLINT fDiagField, 189 | SQLPOINTER rgbDiagInfo, 190 | SQLSMALLINT cbBufferLength, 191 | SQLSMALLINT* pcbDiagInfo); 192 | 193 | SQLRETURN SQLGetDiagRecW( 194 | SQLSMALLINT fHandleType, 195 | SQLHANDLE handle, 196 | SQLSMALLINT iRecord, 197 | SQLWCHAR* szSqlState, 198 | SQLINTEGER* pfNativeError, 199 | SQLWCHAR* szErrorMsg, 200 | SQLSMALLINT cbErrorMsgMax, 201 | SQLSMALLINT* pcbErrorMsg); 202 | 203 | SQLRETURN SQLPrepareW( 204 | SQLHSTMT hstmt, 205 | SQLWCHAR* szSqlStr, 206 | SQLINTEGER cbSqlStr); 207 | 208 | SQLRETURN SQLSetConnectAttrW( 209 | SQLHDBC hdbc, 210 | SQLINTEGER fAttribute, 211 | SQLPOINTER rgbValue, 212 | SQLINTEGER cbValue); 213 | 214 | SQLRETURN SQLSetCursorNameW( 215 | SQLHSTMT hstmt, 216 | SQLWCHAR* szCursor, 217 | SQLSMALLINT cbCursor); 218 | 219 | SQLRETURN SQLColumnsW( 220 | SQLHSTMT hstmt, 221 | SQLWCHAR* szCatalogName, 222 | SQLSMALLINT cbCatalogName, 223 | SQLWCHAR* szSchemaName, 224 | SQLSMALLINT cbSchemaName, 225 | SQLWCHAR* szTableName, 226 | SQLSMALLINT cbTableName, 227 | SQLWCHAR* szColumnName, 228 | SQLSMALLINT cbColumnName); 229 | 230 | SQLRETURN SQLGetConnectOptionW( 231 | SQLHDBC hdbc, 232 | SQLUSMALLINT fOption, 233 | SQLPOINTER pvParam); 234 | 235 | SQLRETURN SQLGetInfoW( 236 | SQLHDBC hdbc, 237 | SQLUSMALLINT fInfoType, 238 | SQLPOINTER rgbInfoValue, 239 | SQLSMALLINT cbInfoValueMax, 240 | SQLSMALLINT* pcbInfoValue); 241 | 242 | SQLRETURN SQLGetTypeInfoW( 243 | SQLHSTMT StatementHandle, 244 | SQLSMALLINT DataType); 245 | 246 | SQLRETURN SQLSetConnectOptionW( 247 | SQLHDBC hdbc, 248 | SQLUSMALLINT fOption, 249 | SQLULEN vParam); 250 | 251 | SQLRETURN SQLSpecialColumnsW( 252 | SQLHSTMT hstmt, 253 | SQLUSMALLINT fColType, 254 | SQLWCHAR* szCatalogName, 255 | SQLSMALLINT cbCatalogName, 256 | SQLWCHAR* szSchemaName, 257 | SQLSMALLINT cbSchemaName, 258 | SQLWCHAR* szTableName, 259 | SQLSMALLINT cbTableName, 260 | SQLUSMALLINT fScope, 261 | SQLUSMALLINT fNullable); 262 | 263 | SQLRETURN SQLStatisticsW( 264 | SQLHSTMT hstmt, 265 | SQLWCHAR* szCatalogName, 266 | SQLSMALLINT cbCatalogName, 267 | SQLWCHAR* szSchemaName, 268 | SQLSMALLINT cbSchemaName, 269 | SQLWCHAR* szTableName, 270 | SQLSMALLINT cbTableName, 271 | SQLUSMALLINT fUnique, 272 | SQLUSMALLINT fAccuracy); 273 | 274 | SQLRETURN SQLTablesW( 275 | SQLHSTMT hstmt, 276 | SQLWCHAR* szCatalogName, 277 | SQLSMALLINT cbCatalogName, 278 | SQLWCHAR* szSchemaName, 279 | SQLSMALLINT cbSchemaName, 280 | SQLWCHAR* szTableName, 281 | SQLSMALLINT cbTableName, 282 | SQLWCHAR* szTableType, 283 | SQLSMALLINT cbTableType); 284 | 285 | SQLRETURN SQLDataSourcesW( 286 | SQLHENV henv, 287 | SQLUSMALLINT fDirection, 288 | SQLWCHAR* szDSN, 289 | SQLSMALLINT cbDSNMax, 290 | SQLSMALLINT* pcbDSN, 291 | SQLWCHAR* szDescription, 292 | SQLSMALLINT cbDescriptionMax, 293 | SQLSMALLINT* pcbDescription); 294 | 295 | SQLRETURN SQLDriverConnectW( 296 | SQLHDBC hdbc, 297 | SQLHWND hwnd, 298 | SQLWCHAR* szConnStrIn, 299 | SQLSMALLINT cbConnStrIn, 300 | SQLWCHAR* szConnStrOut, 301 | SQLSMALLINT cbConnStrOutMax, 302 | SQLSMALLINT* pcbConnStrOut, 303 | SQLUSMALLINT fDriverCompletion 304 | ); 305 | 306 | SQLRETURN SQLBrowseConnectW( 307 | SQLHDBC hdbc, 308 | SQLWCHAR* szConnStrIn, 309 | SQLSMALLINT cbConnStrIn, 310 | SQLWCHAR* szConnStrOut, 311 | SQLSMALLINT cbConnStrOutMax, 312 | SQLSMALLINT* pcbConnStrOut); 313 | 314 | SQLRETURN SQLColumnPrivilegesW( 315 | SQLHSTMT hstmt, 316 | SQLWCHAR* szCatalogName, 317 | SQLSMALLINT cbCatalogName, 318 | SQLWCHAR* szSchemaName, 319 | SQLSMALLINT cbSchemaName, 320 | SQLWCHAR* szTableName, 321 | SQLSMALLINT cbTableName, 322 | SQLWCHAR* szColumnName, 323 | SQLSMALLINT cbColumnName); 324 | 325 | SQLRETURN SQLGetStmtAttrW( 326 | SQLHSTMT hstmt, 327 | SQLINTEGER fAttribute, 328 | SQLPOINTER rgbValue, 329 | SQLINTEGER cbValueMax, 330 | SQLINTEGER* pcbValue); 331 | 332 | SQLRETURN SQLSetStmtAttrW( 333 | SQLHSTMT hstmt, 334 | SQLINTEGER fAttribute, 335 | SQLPOINTER rgbValue, 336 | SQLINTEGER cbValueMax); 337 | 338 | SQLRETURN SQLForeignKeysW( 339 | SQLHSTMT hstmt, 340 | SQLWCHAR* szPkCatalogName, 341 | SQLSMALLINT cbPkCatalogName, 342 | SQLWCHAR* szPkSchemaName, 343 | SQLSMALLINT cbPkSchemaName, 344 | SQLWCHAR* szPkTableName, 345 | SQLSMALLINT cbPkTableName, 346 | SQLWCHAR* szFkCatalogName, 347 | SQLSMALLINT cbFkCatalogName, 348 | SQLWCHAR* szFkSchemaName, 349 | SQLSMALLINT cbFkSchemaName, 350 | SQLWCHAR* szFkTableName, 351 | SQLSMALLINT cbFkTableName); 352 | 353 | SQLRETURN SQLNativeSqlW( 354 | SQLHDBC hdbc, 355 | SQLWCHAR* szSqlStrIn, 356 | SQLINTEGER cbSqlStrIn, 357 | SQLWCHAR* szSqlStr, 358 | SQLINTEGER cbSqlStrMax, 359 | SQLINTEGER* pcbSqlStr); 360 | 361 | SQLRETURN SQLPrimaryKeysW( 362 | SQLHSTMT hstmt, 363 | SQLWCHAR* szCatalogName, 364 | SQLSMALLINT cbCatalogName, 365 | SQLWCHAR* szSchemaName, 366 | SQLSMALLINT cbSchemaName, 367 | SQLWCHAR* szTableName, 368 | SQLSMALLINT cbTableName); 369 | 370 | SQLRETURN SQLProcedureColumnsW( 371 | SQLHSTMT hstmt, 372 | SQLWCHAR* szCatalogName, 373 | SQLSMALLINT cbCatalogName, 374 | SQLWCHAR* szSchemaName, 375 | SQLSMALLINT cbSchemaName, 376 | SQLWCHAR* szProcName, 377 | SQLSMALLINT cbProcName, 378 | SQLWCHAR* szColumnName, 379 | SQLSMALLINT cbColumnName); 380 | 381 | SQLRETURN SQLProceduresW( 382 | SQLHSTMT hstmt, 383 | SQLWCHAR* szCatalogName, 384 | SQLSMALLINT cbCatalogName, 385 | SQLWCHAR* szSchemaName, 386 | SQLSMALLINT cbSchemaName, 387 | SQLWCHAR* szProcName, 388 | SQLSMALLINT cbProcName); 389 | 390 | SQLRETURN SQLTablePrivilegesW( 391 | SQLHSTMT hstmt, 392 | SQLWCHAR* szCatalogName, 393 | SQLSMALLINT cbCatalogName, 394 | SQLWCHAR* szSchemaName, 395 | SQLSMALLINT cbSchemaName, 396 | SQLWCHAR* szTableName, 397 | SQLSMALLINT cbTableName); 398 | 399 | SQLRETURN SQLDriversW( 400 | SQLHENV henv, 401 | SQLUSMALLINT fDirection, 402 | SQLWCHAR* szDriverDesc, 403 | SQLSMALLINT cbDriverDescMax, 404 | SQLSMALLINT* pcbDriverDesc, 405 | SQLWCHAR* szDriverAttributes, 406 | SQLSMALLINT cbDrvrAttrMax, 407 | SQLSMALLINT* pcbDrvrAttr); 408 | 409 | //Function prototypes - ANSI 410 | SQLRETURN SQLColAttributeA( 411 | SQLHSTMT hstmt, 412 | SQLSMALLINT iCol, 413 | SQLSMALLINT iField, 414 | SQLPOINTER pCharAttr, 415 | SQLSMALLINT cbCharAttrMax, 416 | SQLSMALLINT* pcbCharAttr, 417 | SQLLEN* pNumAttr); 418 | 419 | SQLRETURN SQLColAttributesA( 420 | SQLHSTMT hstmt, 421 | SQLUSMALLINT icol, 422 | SQLUSMALLINT fDescType, 423 | SQLPOINTER rgbDesc, 424 | SQLSMALLINT cbDescMax, 425 | SQLSMALLINT* pcbDesc, 426 | SQLLEN* pfDesc); 427 | 428 | SQLRETURN SQLConnectA( 429 | SQLHDBC hdbc, 430 | SQLCHAR* szDSN, 431 | SQLSMALLINT cbDSN, 432 | SQLCHAR* szUID, 433 | SQLSMALLINT cbUID, 434 | SQLCHAR* szAuthStr, 435 | SQLSMALLINT cbAuthStr); 436 | 437 | SQLRETURN SQLDescribeColA( 438 | SQLHSTMT hstmt, 439 | SQLUSMALLINT icol, 440 | SQLCHAR* szColName, 441 | SQLSMALLINT cbColNameMax, 442 | SQLSMALLINT* pcbColName, 443 | SQLSMALLINT* pfSqlType, 444 | SQLULEN* pcbColDef, 445 | SQLSMALLINT* pibScale, 446 | SQLSMALLINT* pfNullable); 447 | 448 | SQLRETURN SQLErrorA( 449 | SQLHENV henv, 450 | SQLHDBC hdbc, 451 | SQLHSTMT hstmt, 452 | SQLCHAR* szSqlState, 453 | SQLINTEGER* pfNativeError, 454 | SQLCHAR* szErrorMsg, 455 | SQLSMALLINT cbErrorMsgMax, 456 | SQLSMALLINT* pcbErrorMsg); 457 | 458 | SQLRETURN SQLExecDirectA( 459 | SQLHSTMT hstmt, 460 | SQLCHAR* szSqlStr, 461 | SQLINTEGER cbSqlStr); 462 | 463 | SQLRETURN SQLGetConnectAttrA( 464 | SQLHDBC hdbc, 465 | SQLINTEGER fAttribute, 466 | SQLPOINTER rgbValue, 467 | SQLINTEGER cbValueMax, 468 | SQLINTEGER* pcbValue); 469 | 470 | SQLRETURN SQLGetCursorNameA( 471 | SQLHSTMT hstmt, 472 | 473 | SQLCHAR* szCursor, 474 | SQLSMALLINT cbCursorMax, 475 | SQLSMALLINT* pcbCursor); 476 | 477 | SQLRETURN SQLGetDescFieldA( 478 | SQLHDESC hdesc, 479 | SQLSMALLINT iRecord, 480 | SQLSMALLINT iField, 481 | SQLPOINTER rgbValue, 482 | SQLINTEGER cbBufferLength, 483 | SQLINTEGER* StringLength); 484 | 485 | SQLRETURN SQLGetDescRecA( 486 | SQLHDESC hdesc, 487 | SQLSMALLINT iRecord, 488 | SQLCHAR* szName, 489 | SQLSMALLINT cbNameMax, 490 | SQLSMALLINT* pcbName, 491 | SQLSMALLINT* pfType, 492 | SQLSMALLINT* pfSubType, 493 | SQLLEN* pLength, 494 | SQLSMALLINT* pPrecision, 495 | SQLSMALLINT* pScale, 496 | SQLSMALLINT* pNullable); 497 | 498 | SQLRETURN SQLGetDiagFieldA( 499 | SQLSMALLINT fHandleType, 500 | SQLHANDLE handle, 501 | SQLSMALLINT iRecord, 502 | SQLSMALLINT fDiagField, 503 | SQLPOINTER rgbDiagInfo, 504 | SQLSMALLINT cbDiagInfoMax, 505 | SQLSMALLINT* pcbDiagInfo); 506 | 507 | SQLRETURN SQLGetDiagRecA( 508 | SQLSMALLINT fHandleType, 509 | SQLHANDLE handle, 510 | SQLSMALLINT iRecord, 511 | SQLCHAR* szSqlState, 512 | SQLINTEGER* pfNativeError, 513 | SQLCHAR* szErrorMsg, 514 | SQLSMALLINT cbErrorMsgMax, 515 | SQLSMALLINT* pcbErrorMsg); 516 | 517 | SQLRETURN SQLPrepareA( 518 | SQLHSTMT hstmt, 519 | SQLCHAR* szSqlStr, 520 | SQLINTEGER cbSqlStr); 521 | 522 | SQLRETURN SQLSetConnectAttrA( 523 | SQLHDBC hdbc, 524 | SQLINTEGER fAttribute, 525 | SQLPOINTER rgbValue, 526 | SQLINTEGER cbValue); 527 | 528 | SQLRETURN SQLSetCursorNameA( 529 | SQLHSTMT hstmt, 530 | SQLCHAR* szCursor, 531 | SQLSMALLINT cbCursor); 532 | 533 | SQLRETURN SQLColumnsA( 534 | SQLHSTMT hstmt, 535 | SQLCHAR* szCatalogName, 536 | SQLSMALLINT cbCatalogName, 537 | SQLCHAR* szSchemaName, 538 | SQLSMALLINT cbSchemaName, 539 | SQLCHAR* szTableName, 540 | SQLSMALLINT cbTableName, 541 | SQLCHAR* szColumnName, 542 | SQLSMALLINT cbColumnName); 543 | 544 | SQLRETURN SQLGetConnectOptionA( 545 | SQLHDBC hdbc, 546 | SQLUSMALLINT fOption, 547 | SQLPOINTER pvParam); 548 | 549 | SQLRETURN SQLGetInfoA( 550 | SQLHDBC hdbc, 551 | SQLUSMALLINT fInfoType, 552 | SQLPOINTER rgbInfoValue, 553 | SQLSMALLINT cbInfoValueMax, 554 | SQLSMALLINT* pcbInfoValue); 555 | 556 | SQLRETURN SQLGetTypeInfoA( 557 | SQLHSTMT StatementHandle, 558 | SQLSMALLINT DataType); 559 | 560 | SQLRETURN SQLSetConnectOptionA( 561 | SQLHDBC hdbc, 562 | SQLUSMALLINT fOption, 563 | SQLULEN vParam); 564 | 565 | SQLRETURN SQLSpecialColumnsA( 566 | SQLHSTMT hstmt, 567 | SQLUSMALLINT fColType, 568 | SQLCHAR* szCatalogName, 569 | SQLSMALLINT cbCatalogName, 570 | SQLCHAR* szSchemaName, 571 | SQLSMALLINT cbSchemaName, 572 | SQLCHAR* szTableName, 573 | SQLSMALLINT cbTableName, 574 | SQLUSMALLINT fScope, 575 | SQLUSMALLINT fNullable); 576 | 577 | SQLRETURN SQLStatisticsA( 578 | SQLHSTMT hstmt, 579 | SQLCHAR* szCatalogName, 580 | SQLSMALLINT cbCatalogName, 581 | SQLCHAR* szSchemaName, 582 | SQLSMALLINT cbSchemaName, 583 | SQLCHAR* szTableName, 584 | SQLSMALLINT cbTableName, 585 | SQLUSMALLINT fUnique, 586 | SQLUSMALLINT fAccuracy); 587 | 588 | SQLRETURN SQLTablesA( 589 | SQLHSTMT hstmt, 590 | SQLCHAR* szCatalogName, 591 | SQLSMALLINT cbCatalogName, 592 | SQLCHAR* szSchemaName, 593 | SQLSMALLINT cbSchemaName, 594 | SQLCHAR* szTableName, 595 | SQLSMALLINT cbTableName, 596 | SQLCHAR* szTableType, 597 | SQLSMALLINT cbTableType); 598 | 599 | SQLRETURN SQLDataSourcesA( 600 | SQLHENV henv, 601 | SQLUSMALLINT fDirection, 602 | SQLCHAR* szDSN, 603 | SQLSMALLINT cbDSNMax, 604 | SQLSMALLINT* pcbDSN, 605 | SQLCHAR* szDescription, 606 | SQLSMALLINT cbDescriptionMax, 607 | SQLSMALLINT* pcbDescription); 608 | 609 | SQLRETURN SQLDriverConnectA( 610 | SQLHDBC hdbc, 611 | SQLHWND hwnd, 612 | SQLCHAR* szConnStrIn, 613 | SQLSMALLINT cbConnStrIn, 614 | SQLCHAR* szConnStrOut, 615 | SQLSMALLINT cbConnStrOutMax, 616 | SQLSMALLINT* pcbConnStrOut, 617 | SQLUSMALLINT fDriverCompletion); 618 | 619 | SQLRETURN SQLBrowseConnectA( 620 | SQLHDBC hdbc, 621 | SQLCHAR* szConnStrIn, 622 | SQLSMALLINT cbConnStrIn, 623 | SQLCHAR* szConnStrOut, 624 | SQLSMALLINT cbConnStrOutMax, 625 | SQLSMALLINT* pcbConnStrOut); 626 | 627 | SQLRETURN SQLColumnPrivilegesA( 628 | SQLHSTMT hstmt, 629 | SQLCHAR* szCatalogName, 630 | SQLSMALLINT cbCatalogName, 631 | SQLCHAR* szSchemaName, 632 | SQLSMALLINT cbSchemaName, 633 | SQLCHAR* szTableName, 634 | SQLSMALLINT cbTableName, 635 | SQLCHAR* szColumnName, 636 | SQLSMALLINT cbColumnName); 637 | 638 | SQLRETURN SQLGetStmtAttrA( 639 | SQLHSTMT hstmt, 640 | SQLINTEGER fAttribute, 641 | SQLPOINTER rgbValue, 642 | SQLINTEGER cbValueMax, 643 | SQLINTEGER* pcbValue); 644 | 645 | SQLRETURN SQLForeignKeysA( 646 | SQLHSTMT hstmt, 647 | SQLCHAR* szPkCatalogName, 648 | SQLSMALLINT cbPkCatalogName, 649 | SQLCHAR* szPkSchemaName, 650 | SQLSMALLINT cbPkSchemaName, 651 | SQLCHAR* szPkTableName, 652 | SQLSMALLINT cbPkTableName, 653 | SQLCHAR* szFkCatalogName, 654 | SQLSMALLINT cbFkCatalogName, 655 | SQLCHAR* szFkSchemaName, 656 | SQLSMALLINT cbFkSchemaName, 657 | SQLCHAR* szFkTableName, 658 | SQLSMALLINT cbFkTableName); 659 | 660 | SQLRETURN SQLNativeSqlA( 661 | SQLHDBC hdbc, 662 | SQLCHAR* szSqlStrIn, 663 | SQLINTEGER cbSqlStrIn, 664 | SQLCHAR* szSqlStr, 665 | SQLINTEGER cbSqlStrMax, 666 | SQLINTEGER* pcbSqlStr); 667 | 668 | SQLRETURN SQLPrimaryKeysA( 669 | SQLHSTMT hstmt, 670 | SQLCHAR* szCatalogName, 671 | SQLSMALLINT cbCatalogName, 672 | SQLCHAR* szSchemaName, 673 | SQLSMALLINT cbSchemaName, 674 | SQLCHAR* szTableName, 675 | SQLSMALLINT cbTableName); 676 | 677 | SQLRETURN SQLProcedureColumnsA( 678 | SQLHSTMT hstmt, 679 | SQLCHAR* szCatalogName, 680 | SQLSMALLINT cbCatalogName, 681 | SQLCHAR* szSchemaName, 682 | SQLSMALLINT cbSchemaName, 683 | SQLCHAR* szProcName, 684 | SQLSMALLINT cbProcName, 685 | SQLCHAR* szColumnName, 686 | SQLSMALLINT cbColumnName); 687 | 688 | SQLRETURN SQLProceduresA( 689 | SQLHSTMT hstmt, 690 | SQLCHAR* szCatalogName, 691 | SQLSMALLINT cbCatalogName, 692 | SQLCHAR* szSchemaName, 693 | SQLSMALLINT cbSchemaName, 694 | SQLCHAR* szProcName, 695 | SQLSMALLINT cbProcName); 696 | 697 | SQLRETURN SQLTablePrivilegesA( 698 | SQLHSTMT hstmt, 699 | SQLCHAR* szCatalogName, 700 | SQLSMALLINT cbCatalogName, 701 | SQLCHAR* szSchemaName, 702 | SQLSMALLINT cbSchemaName, 703 | SQLCHAR* szTableName, 704 | SQLSMALLINT cbTableName); 705 | 706 | SQLRETURN SQLDriversA( 707 | SQLHENV henv, 708 | SQLUSMALLINT fDirection, 709 | SQLCHAR* szDriverDesc, 710 | SQLSMALLINT cbDriverDescMax, 711 | SQLSMALLINT* pcbDriverDesc, 712 | SQLCHAR* szDriverAttributes, 713 | SQLSMALLINT cbDrvrAttrMax, 714 | SQLSMALLINT* pcbDrvrAttr); 715 | -------------------------------------------------------------------------------- /driver/getinfo.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module presto.odbcdriver.getinfo; 15 | 16 | import std.array : front, empty, popFront; 17 | import std.conv : text, wtext, to; 18 | 19 | import odbc.sqlext; 20 | import odbc.odbcinst; 21 | 22 | import presto.odbcdriver.handles : OdbcConnection; 23 | import presto.odbcdriver.util; 24 | 25 | 26 | ///// SQLGetInfo ///// 27 | 28 | export extern(System) 29 | SQLRETURN SQLGetInfoW( 30 | OdbcConnection connectionHandle, 31 | OdbcInfo infoType, 32 | SQLPOINTER _infoValue, 33 | SQLSMALLINT _bufferMaxLengthBytes, 34 | SQLSMALLINT* _stringLengthBytes) { 35 | return exceptionBoundary!(() => { 36 | connectionHandle.errors = []; 37 | auto stringResult = outputWChar(_infoValue, _bufferMaxLengthBytes, _stringLengthBytes); 38 | logMessage("SQLGetInfo", infoType); 39 | with (OdbcInfo) with(connectionHandle) with (session) { 40 | switch (infoType) { 41 | case SQL_MAX_DRIVER_CONNECTIONS: //0 42 | *cast(SQLUSMALLINT*)(_infoValue) = 1; 43 | break; 44 | case SQL_MAX_CONCURRENT_ACTIVITIES: //1 45 | *cast(SQLUSMALLINT*)(_infoValue) = 1; 46 | break; 47 | case SQL_DATA_SOURCE_NAME: //2 48 | copyToBuffer(""w, stringResult); 49 | break; 50 | case SQL_DRIVER_NAME: //6 51 | copyToBuffer("presto.dll"w, stringResult); 52 | break; 53 | case SQL_DRIVER_VER: //7 54 | copyToBuffer("00.01.0000"w, stringResult); 55 | break; 56 | case SQL_FETCH_DIRECTION: //8 57 | enum bitmask = 0; 58 | *cast(SQLINTEGER*)(_infoValue) = bitmask; 59 | break; 60 | case SQL_ODBC_API_CONFORMANCE: //9 61 | //Can probably increase this in the future. 62 | *cast(SQLSMALLINT*)(_infoValue) = SQL_OAC_NONE; 63 | break; 64 | case SQL_ODBC_VER: //10 65 | copyToBuffer("03.00"w, stringResult); 66 | break; 67 | case SQL_ROW_UPDATES: //12 68 | copyToBuffer("N"w, stringResult); 69 | break; 70 | case SQL_ODBC_SAG_CLI_CONFORMANCE: //12 71 | *cast(SQLUINTEGER*)(_infoValue) = SQL_OSCC_NOT_COMPLIANT; 72 | break; 73 | case SQL_SEARCH_PATTERN_ESCAPE: //14 74 | copyToBuffer("%"w, stringResult); 75 | break; 76 | case SQL_ODBC_SQL_CONFORMANCE: //15 77 | *cast(SQLSMALLINT*)(_infoValue) = SQL_OSC_MINIMUM; 78 | break; 79 | case SQL_DATABASE_NAME: //16 80 | copyToBuffer(wtext(catalog), stringResult); 81 | break; 82 | case SQL_DBMS_NAME: //17 83 | copyToBuffer("Presto"w, stringResult); 84 | break; 85 | case SQL_DBMS_VER: //18 86 | copyToBuffer("00.73.0000"w, stringResult); 87 | break; 88 | case SQL_ACCESSIBLE_TABLES: //19 89 | copyToBuffer("Y"w, stringResult); 90 | break; 91 | case SQL_CONCAT_NULL_BEHAVIOR: //22 92 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_CB_NULL; 93 | break; 94 | case SQL_CURSOR_COMMIT_BEHAVIOR: //23 95 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_CB_DELETE; 96 | break; 97 | case SQL_CURSOR_ROLLBACK_BEHAVIOR: //24 98 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_CB_DELETE; 99 | break; 100 | case SQL_DATA_SOURCE_READ_ONLY: //25 101 | copyToBuffer("Y"w, stringResult); 102 | break; 103 | case SQL_EXPRESSIONS_IN_ORDERBY: //27 104 | copyToBuffer("Y"w, stringResult); 105 | break; 106 | case SQL_IDENTIFIER_CASE: //28 107 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_IC_LOWER; 108 | break; 109 | case SQL_IDENTIFIER_QUOTE_CHAR: //29 110 | copyToBuffer("\""w, stringResult); 111 | break; 112 | case SQL_MAXIMUM_COLUMN_NAME_LENGTH: //30 113 | case SQL_MAXIMUM_CURSOR_NAME_LENGTH: //31 114 | case SQL_MAXIMUM_SCHEMA_NAME_LENGTH: //32 115 | case SQL_MAXIMUM_PROCEDURE_NAME_LENGTH: //33 116 | case SQL_MAXIMUM_QUALIFIER_NAME_LENGTH: //34 117 | case SQL_MAXIMUM_TABLE_NAME_LENGTH: //35 118 | *cast(SQLUSMALLINT*)(_infoValue) = 0; 119 | break; 120 | case SQL_SCHEMA_TERM: //39 121 | copyToBuffer("schema"w, stringResult); 122 | break; 123 | case SQL_CATALOG_NAME_SEPARATOR: //41 124 | copyToBuffer("."w, stringResult); 125 | break; 126 | case SQL_CATALOG_TERM: //42 127 | copyToBuffer("catalog"w, stringResult); 128 | break; 129 | case SQL_TABLE_TERM: //45 130 | copyToBuffer("table"w, stringResult); 131 | break; 132 | case SQL_TRANSACTION_CAPABLE: //46 133 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_TC_NONE; 134 | break; 135 | case SQL_CONVERT_FUNCTIONS: //48 136 | case SQL_NUMERIC_FUNCTIONS: //49 137 | case SQL_STRING_FUNCTIONS: //50 138 | case SQL_SYSTEM_FUNCTIONS: //51 139 | //Supporting these would require a parser to translate ODBC escapes 140 | // http://msdn.microsoft.com/en-us/library/ms711838%28v=VS.85%29.aspx 141 | *cast(SQLUINTEGER*)(_infoValue) = 0; 142 | break; 143 | case SQL_TIMEDATE_FUNCTIONS: //52 144 | //Additional support would require a parser to translate ODBC escapes 145 | // http://msdn.microsoft.com/en-us/library/ms711838%28v=VS.85%29.aspx 146 | *cast(SQLUINTEGER*)(_infoValue) = SQL_FN_TD_EXTRACT; 147 | break; 148 | case SQL_CONVERT_BIGINT: //53 149 | case SQL_CONVERT_BIT: //55 150 | case SQL_CONVERT_CHAR: //56 151 | case SQL_CONVERT_INTEGER: //61 152 | case SQL_CONVERT_LONGVARCHAR: //62 153 | case SQL_CONVERT_SMALLINT: //65 154 | case SQL_CONVERT_TINYINT: //68 155 | case SQL_CONVERT_VARCHAR: //70 156 | enum bitmask = 157 | SQL_CVT_BIGINT | 158 | SQL_CVT_BIT | 159 | SQL_CVT_CHAR | 160 | SQL_CVT_INTEGER | 161 | SQL_CVT_LONGVARCHAR | 162 | SQL_CVT_SMALLINT | 163 | SQL_CVT_TINYINT | 164 | SQL_CVT_VARCHAR; 165 | *cast(SQLUINTEGER*)(_infoValue) = bitmask; 166 | break; 167 | case SQL_CONVERT_BINARY: //54 168 | case SQL_CONVERT_DATE: //57 169 | case SQL_CONVERT_DECIMAL: //58 170 | case SQL_CONVERT_DOUBLE: //59 171 | case SQL_CONVERT_FLOAT: //60 172 | case SQL_CONVERT_NUMERIC: //63 173 | case SQL_CONVERT_REAL: //64 174 | case SQL_CONVERT_TIME: //66 175 | case SQL_CONVERT_TIMESTAMP: //67 176 | case SQL_CONVERT_VARBINARY: //69 177 | case SQL_CONVERT_LONGVARBINARY: //71 178 | case SQL_CONVERT_WCHAR: //122 179 | case SQL_CONVERT_INTERVAL_DAY_TIME: //123 180 | case SQL_CONVERT_INTERVAL_YEAR_MONTH: //124 181 | case SQL_CONVERT_WLONGVARCHAR: //125 182 | case SQL_CONVERT_WVARCHAR: //126 183 | *cast(SQLUINTEGER*)(_infoValue) = 0; 184 | break; 185 | case SQL_CORRELATION_NAME: //74 186 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_CN_ANY; 187 | break; 188 | case SQL_NON_NULLABLE_COLUMNS: //75 189 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_NNC_NON_NULL; 190 | break; 191 | case SQL_DRIVER_ODBC_VER: // 77 192 | //Latest version of ODBC is 3.8 (as of 6/19/14) 193 | copyToBuffer("03.51"w, stringResult); 194 | break; 195 | case SQL_POS_OPERATIONS: //79 196 | *cast(SQLINTEGER*)(_infoValue) = 0; 197 | break; 198 | case SQL_POSITIONED_STATEMENTS: //80 199 | *cast(SQLINTEGER*)(_infoValue) = 0; 200 | break; 201 | case SQL_GETDATA_EXTENSIONS: //81 202 | *cast(SQLUINTEGER*)(_infoValue) = SQL_GD_ANY_ORDER; 203 | break; 204 | case SQL_STATIC_SENSITIVITY: //83 205 | *cast(SQLINTEGER*)(_infoValue) = 0; 206 | break; 207 | case SQL_FILE_USAGE: //84 208 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_FILE_CATALOG; 209 | break; 210 | case SQL_ALTER_TABLE: //86 211 | *cast(SQLUINTEGER*)(_infoValue) = 0; 212 | break; 213 | case SQL_COLUMN_ALIAS: //87 214 | copyToBuffer("N"w, stringResult); 215 | break; 216 | case SQL_GROUP_BY: //88 217 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_GB_GROUP_BY_CONTAINS_SELECT; 218 | break; 219 | case SQL_KEYWORDS: //89 220 | copyToBuffer("EXPLAIN,CATALOGS,COLUMNS,FUNCTIONS,PARTITIONS,SCHEMAS,TABLES"w, stringResult); 221 | break; 222 | case SQL_ORDER_BY_COLUMNS_IN_SELECT: //90 223 | copyToBuffer("N"w, stringResult); 224 | break; 225 | case SQL_SCHEMA_USAGE: //91 226 | auto bitmask = 227 | SQL_SU_DML_STATEMENTS | 228 | SQL_SU_PROCEDURE_INVOCATION | 229 | SQL_SU_TABLE_DEFINITION | 230 | SQL_SU_INDEX_DEFINITION | 231 | SQL_SU_PRIVILEGE_DEFINITION; 232 | *cast(SQLUINTEGER*)(_infoValue) = bitmask; 233 | break; 234 | case SQL_CATALOG_USAGE: //92 235 | auto bitmask = 236 | SQL_CU_DML_STATEMENTS | 237 | SQL_CU_PROCEDURE_INVOCATION | 238 | SQL_CU_TABLE_DEFINITION | 239 | SQL_CU_INDEX_DEFINITION | 240 | SQL_CU_PRIVILEGE_DEFINITION; 241 | *cast(SQLUINTEGER*)(_infoValue) = bitmask; 242 | break; 243 | case SQL_QUOTED_IDENTIFIER_CASE: //93 244 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_IC_SENSITIVE; 245 | break; 246 | case SQL_SPECIAL_CHARACTERS: //94 247 | copyToBuffer(""w, stringResult); 248 | break; 249 | case SQL_SUBQUERIES: //95 250 | *cast(SQLUINTEGER*)(_infoValue) = SQL_SQ_IN; 251 | break; 252 | case SQL_UNION: //96 253 | *cast(SQLUINTEGER*)(_infoValue) = SQL_U_UNION | SQL_U_UNION_ALL; 254 | break; 255 | case SQL_MAXIMUM_COLUMNS_IN_GROUP_BY: //97 256 | case SQL_MAXIMUM_COLUMNS_IN_INDEX: //98 257 | case SQL_MAXIMUM_COLUMNS_IN_ORDER_BY: //99 258 | case SQL_MAXIMUM_COLUMNS_IN_SELECT: //100 259 | case SQL_MAXIMUM_COLUMNS_IN_TABLE: //101 260 | case SQL_MAXIMUM_INDEX_SIZE: //102 261 | case SQL_MAXIMUM_TABLES_IN_SELECT: //106 262 | case SQL_MAXIMUM_USER_NAME_LENGTH: //107 263 | *cast(SQLUSMALLINT*)(_infoValue) = 0; 264 | break; 265 | case SQL_MAXIMUM_ROW_SIZE_INCLUDES_LONG: //103 266 | copyToBuffer("Y"w, stringResult); 267 | break; 268 | case SQL_MAXIMUM_ROW_SIZE: //104 269 | case SQL_MAXIMUM_STATEMENT_LENGTH: //105 270 | case SQL_MAXIMUM_CHAR_LITERAL_LENGTH: //108 271 | *cast(SQLUINTEGER*)(_infoValue) = 0; 272 | break; 273 | case SQL_TIMEDATE_ADD_INTERVALS: //109 274 | case SQL_TIMEDATE_DIFF_INTERVALS: //110 275 | enum bitmask = 276 | SQL_FN_TSI_SECOND | 277 | SQL_FN_TSI_MINUTE | 278 | SQL_FN_TSI_HOUR | 279 | SQL_FN_TSI_DAY | 280 | SQL_FN_TSI_MONTH | 281 | SQL_FN_TSI_YEAR; 282 | *cast(SQLUINTEGER*)(_infoValue) = bitmask; 283 | break; 284 | case SQL_NEED_LONG_DATA_LEN: //111 285 | copyToBuffer("N"w, stringResult); 286 | break; 287 | case SQL_MAX_BINARY_LITERAL_LEN: //112 288 | *cast(SQLUINTEGER*)(_infoValue) = 0; 289 | break; 290 | case SQL_LIKE_ESCAPE_CLAUSE: //113 291 | copyToBuffer("Y"w, stringResult); 292 | break; 293 | case SQL_CATALOG_LOCATION: //114 294 | *cast(SQLUSMALLINT*)(_infoValue) = SQL_CL_START; 295 | break; 296 | case SQL_OUTER_JOIN_CAPABILITIES: //115 297 | enum bitmask = 298 | SQL_OJ_INNER | 299 | SQL_OJ_LEFT | 300 | SQL_OJ_RIGHT | 301 | SQL_OJ_NESTED | 302 | SQL_OJ_NOT_ORDERED | 303 | SQL_OJ_ALL_COMPARISON_OPS; 304 | *cast(SQLUINTEGER*)(_infoValue) = bitmask; 305 | break; 306 | case SQL_ACTIVE_ENVIRONMENTS: //116 307 | *cast(SQLUSMALLINT*)(_infoValue) = 0; 308 | break; 309 | case SQL_ALTER_DOMAIN: //117 310 | *cast(SQLUINTEGER*)(_infoValue) = 0; 311 | break; 312 | case SQL_SQL_CONFORMANCE: //118 313 | *cast(SQLUINTEGER*)(_infoValue) = 0; 314 | break; 315 | case SQL_DATETIME_LITERALS: //119 316 | //Supporting these would require a parser to translate ODBC escapes 317 | // http://msdn.microsoft.com/en-us/library/ms711838%28v=VS.85%29.aspx 318 | *cast(SQLUINTEGER*)(_infoValue) = 0; 319 | break; 320 | case SQL_BATCH_SUPPORT: //121 321 | *cast(SQLUINTEGER*)(_infoValue) = 0; 322 | break; 323 | case SQL_CREATE_ASSERTION: //127 324 | case SQL_CREATE_CHARACTER_SET: //128 325 | case SQL_CREATE_COLLATION: //129 326 | case SQL_CREATE_DOMAIN: //130 327 | case SQL_CREATE_SCHEMA: //131 328 | case SQL_CREATE_TABLE: //132 329 | case SQL_CREATE_VIEW: //134 330 | case SQL_DROP_TABLE: //141 331 | case SQL_DROP_VIEW: //143 332 | case SQL_CREATE_TRANSLATION: //133 333 | *cast(SQLUINTEGER*)(_infoValue) = 0; 334 | break; 335 | case SQL_DROP_ASSERTION: //136 336 | case SQL_DROP_CHARACTER_SET: //137 337 | case SQL_DROP_COLLATION: //138 338 | case SQL_DROP_DOMAIN: //139 339 | case SQL_DROP_SCHEMA: //140 340 | case SQL_DROP_TRANSLATION: //142 341 | *cast(SQLUINTEGER*)(_infoValue) = 0; 342 | break; 343 | case SQL_INDEX_KEYWORDS: //148 344 | *cast(SQLUINTEGER*)(_infoValue) = SQL_IK_ASC | SQL_IK_DESC; 345 | break; 346 | case SQL_ODBC_INTERFACE_CONFORMANCE: //152 347 | *cast(SQLUINTEGER*)(_infoValue) = SQL_OIC_CORE; 348 | break; 349 | case SQL_PARAM_ARRAY_ROW_COUNTS: //153 350 | *cast(SQLUINTEGER*)(_infoValue) = SQL_PARC_NO_BATCH; 351 | break; 352 | case SQL_PARAM_ARRAY_SELECTS: //154 353 | *cast(SQLUINTEGER*)(_infoValue) = SQL_PARC_NO_BATCH; 354 | break; 355 | case SQL_SQL92_DATETIME_FUNCTIONS: //155 356 | //Supporting these would require a parser to translate ODBC escapes 357 | // http://msdn.microsoft.com/en-us/library/ms711838%28v=VS.85%29.aspx 358 | *cast(SQLUINTEGER*)(_infoValue) = 0; 359 | break; 360 | case SQL_SQL92_FOREIGN_KEY_DELETE_RULE: //156 361 | case SQL_SQL92_FOREIGN_KEY_UPDATE_RULE: //157 362 | case SQL_SQL92_GRANT: //158 363 | case SQL_SQL92_REVOKE: //162 364 | *cast(SQLUINTEGER*)(_infoValue) = 0; 365 | break; 366 | case SQL_SQL92_NUMERIC_VALUE_FUNCTIONS: //159 367 | *cast(SQLUINTEGER*)(_infoValue) = 0; 368 | break; 369 | case SQL_SQL92_STRING_FUNCTIONS: //164 370 | //Supporting these would require a parser to translate ODBC escapes 371 | // http://msdn.microsoft.com/en-us/library/ms711838%28v=VS.85%29.aspx 372 | *cast(SQLUINTEGER*)(_infoValue) = 0; 373 | break; 374 | case SQL_STANDARD_CLI_CONFORMANCE: //166 375 | *cast(SQLUINTEGER*)(_infoValue) = 0; 376 | break; 377 | case SQL_AGGREGATE_FUNCTIONS: //169 378 | enum bitmask = 379 | SQL_AF_AVG | 380 | SQL_AF_COUNT | 381 | SQL_AF_MAX | 382 | SQL_AF_MIN | 383 | SQL_AF_SUM; 384 | *cast(SQLUINTEGER*)(_infoValue) = bitmask; 385 | break; 386 | case SQL_DDL_INDEX: //170 387 | *cast(SQLUINTEGER*)(_infoValue) = 0; 388 | break; 389 | case SQL_INSERT_STATEMENT: //172 390 | *cast(SQLUINTEGER*)(_infoValue) = 0; 391 | break; 392 | case SQL_DESCRIBE_PARAMETER: //10002 393 | copyToBuffer("N"w, stringResult); 394 | break; 395 | case SQL_CATALOG_NAME: //10003 396 | copyToBuffer("Y"w, stringResult); 397 | break; 398 | case SQL_COLLATION_SEQ: //10004 399 | copyToBuffer(""w, stringResult); 400 | break; 401 | case SQL_MAXIMUM_IDENTIFIER_LENGTH: //10005 402 | *cast(SQLUSMALLINT*)(_infoValue) = 128; 403 | break; 404 | case SQL_ASYNC_MODE: // 10021 405 | *cast(SQLUINTEGER*)(_infoValue) = SQL_AM_NONE; 406 | break; 407 | case SQL_MAX_ASYNC_CONCURRENT_STATEMENTS: // 10022 408 | *cast(SQLUINTEGER*)(_infoValue) = 0; 409 | break; 410 | case SQL_DRIVER_HDBC: //3 411 | case SQL_DRIVER_HENV: //4 412 | case SQL_DRIVER_HSTMT: //5 413 | case SQL_DRIVER_HLIB: //76 414 | case SQL_DRIVER_HDESC: //135 415 | dllEnforce(false, "Only the Driver Manager implements this"); 416 | break; 417 | default: 418 | case SQL_SERVER_NAME: //13 419 | case SQL_ACCESSIBLE_PROCEDURES: //20 420 | case SQL_PROCEDURES: //21 421 | case SQL_DEFAULT_TXN_ISOLATION: //26 422 | case SQL_MULT_RESULT_SETS: //36 423 | case SQL_MULTIPLE_ACTIVE_TXN: //37 424 | case SQL_OUTER_JOINS: //38 425 | case SQL_PROCEDURE_TERM: //40 426 | case SQL_SCROLL_CONCURRENCY: //43 427 | case SQL_SCROLL_OPTIONS: //44 428 | case SQL_USER_NAME: //47 429 | case SQL_TRANSACTION_ISOLATION_OPTION: //72 430 | case SQL_INTEGRITY: //73 431 | case SQL_LOCK_TYPES: //78 432 | case SQL_BOOKMARK_PERSISTENCE: //82 433 | case SQL_NULL_COLLATION: //85 434 | case SQL_BATCH_ROW_COUNT: //120 435 | case SQL_DYNAMIC_CURSOR_ATTRIBUTES1: //144 436 | case SQL_DYNAMIC_CURSOR_ATTRIBUTES2: //145 437 | case SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1: //146 438 | case SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2: //147 439 | case SQL_KEYSET_CURSOR_ATTRIBUTES1: //150 440 | case SQL_KEYSET_CURSOR_ATTRIBUTES2: //151 441 | case SQL_STATIC_CURSOR_ATTRIBUTES1: //167 442 | case SQL_STATIC_CURSOR_ATTRIBUTES2: //168 443 | case SQL_INFO_SCHEMA_VIEWS: //149 444 | case SQL_SQL92_PREDICATES: //160 445 | case SQL_SQL92_RELATIONAL_JOIN_OPERATORS: //161 446 | case SQL_SQL92_ROW_VALUE_CONSTRUCTOR: //163 447 | case SQL_SQL92_VALUE_EXPRESSIONS: //165 448 | case SQL_DM_VER: //171 449 | case SQL_XOPEN_CLI_YEAR: //10000 450 | case SQL_CURSOR_SENSITIVITY: //10001 451 | throw new OdbcException(connectionHandle, StatusCode.OPTIONAL_FEATURE, 452 | "Unsupported info type"w ~ wtext(infoType)); 453 | } //switch 454 | } 455 | return SQL_SUCCESS; 456 | }()); 457 | } 458 | --------------------------------------------------------------------------------