From a46ddbbebe8807d3a57fe334d429c13df88cf487 Mon Sep 17 00:00:00 2001 From: Korinne Adler Date: Tue, 7 Jul 2026 12:49:30 -0500 Subject: [PATCH 1/2] style: Reformat code with clang-format Use a clang-format based on LLVM's style. Also add a lint step to check formatting for PRs. --- .clang-format | 200 ++ .github/workflows/build.yml | 6 + src/odbc.cpp | 1069 +++++--- src/odbc.h | 306 ++- src/odbc_connection.cpp | 4849 +++++++++++++++++------------------ src/odbc_connection.h | 39 +- src/odbc_cursor.cpp | 221 +- src/odbc_cursor.h | 29 +- src/odbc_statement.cpp | 581 ++--- src/odbc_statement.h | 26 +- 10 files changed, 3773 insertions(+), 3553 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..fcf633ab --- /dev/null +++ b/.clang-format @@ -0,0 +1,200 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: Left +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterJavaFieldAnnotations: false +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false + +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c39df96..d11e256d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,3 +27,9 @@ jobs: run: brew install unixodbc - name: Build package run: npm ci --production --build-from-source + lint: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v7 + - name: Check source formatting + run: clang-format-20 --Werror --dry-run src/* diff --git a/src/odbc.cpp b/src/odbc.cpp index a9a7bb8b..e1768a8a 100644 --- a/src/odbc.cpp +++ b/src/odbc.cpp @@ -33,26 +33,27 @@ // const char* COUNT = "count\0"; // const char* COLUMNS = "columns\0"; -size_t strlen16(const char16_t* string) -{ - const char16_t* str = string; - while(*str) str++; - return str - string; +size_t strlen16(const char16_t* string) { + const char16_t* str = string; + while (*str) { + str++; + } + return str - string; } // error strings const char* ODBC_ERRORS = "odbcErrors\0"; -const char* STATE = "state\0"; -const char* CODE = "code\0"; -const char* MESSAGE = "message\0"; +const char* STATE = "state\0"; +const char* CODE = "code\0"; +const char* MESSAGE = "message\0"; #ifdef UNICODE - const SQLTCHAR NO_STATE_TEXT = L'\0'; - const SQLTCHAR* NO_MSG_TEXT = (SQLTCHAR *)L"\0"; - const size_t NO_MSG_TEXT_LENGTH = strlen16((char16_t *)NO_MSG_TEXT); +const SQLTCHAR NO_STATE_TEXT = L'\0'; +const SQLTCHAR* NO_MSG_TEXT = (SQLTCHAR*)L"\0"; +const size_t NO_MSG_TEXT_LENGTH = strlen16((char16_t*)NO_MSG_TEXT); #else - const SQLTCHAR NO_STATE_TEXT = '\0'; - const char* NO_MSG_TEXT = "\0"; - const size_t NO_MSG_TEXT_LENGTH = strlen(NO_MSG_TEXT); +const SQLTCHAR NO_STATE_TEXT = '\0'; +const char* NO_MSG_TEXT = "\0"; +const size_t NO_MSG_TEXT_LENGTH = strlen(NO_MSG_TEXT); #endif // byte count, needed for memcpy const size_t NO_MSG_TEXT_SIZE = NO_MSG_TEXT_LENGTH * sizeof(SQLTCHAR); @@ -68,74 +69,320 @@ Napi::Value ODBC::Init(Napi::Env env, Napi::Object exports) { // Wrap ODBC constants in an object that we can then expand std::vector ODBC_CONSTANTS; - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("ODBCVER", Napi::Number::New(env, ODBCVER), napi_enumerable)); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "ODBCVER", Napi::Number::New(env, ODBCVER), napi_enumerable + ) + ); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_COMMIT", Napi::Number::New(env, SQL_COMMIT), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_ROLLBACK", Napi::Number::New(env, SQL_ROLLBACK), napi_enumerable)); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_COMMIT", Napi::Number::New(env, SQL_COMMIT), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_ROLLBACK", Napi::Number::New(env, SQL_ROLLBACK), napi_enumerable + ) + ); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_USER_NAME", Napi::Number::New(env, SQL_USER_NAME), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_PARAM_INPUT", Napi::Number::New(env, SQL_PARAM_INPUT), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_PARAM_INPUT_OUTPUT", Napi::Number::New(env, SQL_PARAM_INPUT_OUTPUT), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_PARAM_OUTPUT", Napi::Number::New(env, SQL_PARAM_OUTPUT), napi_enumerable)); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_USER_NAME", Napi::Number::New(env, SQL_USER_NAME), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_PARAM_INPUT", Napi::Number::New(env, SQL_PARAM_INPUT), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_PARAM_INPUT_OUTPUT", Napi::Number::New(env, SQL_PARAM_INPUT_OUTPUT), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_PARAM_OUTPUT", Napi::Number::New(env, SQL_PARAM_OUTPUT), + napi_enumerable + ) + ); // Export the integer values for each data type so developers can utilize // them programmatically if needed - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_CHAR", Napi::Number::New(env, SQL_CHAR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_VARCHAR", Napi::Number::New(env, SQL_VARCHAR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_LONGVARCHAR", Napi::Number::New(env, SQL_LONGVARCHAR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_WCHAR", Napi::Number::New(env, SQL_WCHAR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_WVARCHAR", Napi::Number::New(env, SQL_WVARCHAR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_WLONGVARCHAR", Napi::Number::New(env, SQL_WLONGVARCHAR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_DECIMAL", Napi::Number::New(env, SQL_DECIMAL), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_NUMERIC", Napi::Number::New(env, SQL_NUMERIC), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_SMALLINT", Napi::Number::New(env, SQL_SMALLINT), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTEGER", Napi::Number::New(env, SQL_INTEGER), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_REAL", Napi::Number::New(env, SQL_REAL), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_FLOAT", Napi::Number::New(env, SQL_FLOAT), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_DOUBLE", Napi::Number::New(env, SQL_DOUBLE), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_BIT", Napi::Number::New(env, SQL_BIT), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TINYINT", Napi::Number::New(env, SQL_TINYINT), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_BIGINT", Napi::Number::New(env, SQL_BIGINT), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_BINARY", Napi::Number::New(env, SQL_BINARY), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_VARBINARY", Napi::Number::New(env, SQL_VARBINARY), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_LONGVARBINARY", Napi::Number::New(env, SQL_LONGVARBINARY), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_DATE", Napi::Number::New(env, SQL_TYPE_DATE), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_TIME", Napi::Number::New(env, SQL_TYPE_TIME), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_TIMESTAMP", Napi::Number::New(env, SQL_TYPE_TIMESTAMP), napi_enumerable)); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_CHAR", Napi::Number::New(env, SQL_CHAR), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_VARCHAR", Napi::Number::New(env, SQL_VARCHAR), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_LONGVARCHAR", Napi::Number::New(env, SQL_LONGVARCHAR), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_WCHAR", Napi::Number::New(env, SQL_WCHAR), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_WVARCHAR", Napi::Number::New(env, SQL_WVARCHAR), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_WLONGVARCHAR", Napi::Number::New(env, SQL_WLONGVARCHAR), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_DECIMAL", Napi::Number::New(env, SQL_DECIMAL), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_NUMERIC", Napi::Number::New(env, SQL_NUMERIC), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_SMALLINT", Napi::Number::New(env, SQL_SMALLINT), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTEGER", Napi::Number::New(env, SQL_INTEGER), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_REAL", Napi::Number::New(env, SQL_REAL), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_FLOAT", Napi::Number::New(env, SQL_FLOAT), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_DOUBLE", Napi::Number::New(env, SQL_DOUBLE), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_BIT", Napi::Number::New(env, SQL_BIT), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TINYINT", Napi::Number::New(env, SQL_TINYINT), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_BIGINT", Napi::Number::New(env, SQL_BIGINT), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_BINARY", Napi::Number::New(env, SQL_BINARY), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_VARBINARY", Napi::Number::New(env, SQL_VARBINARY), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_LONGVARBINARY", Napi::Number::New(env, SQL_LONGVARBINARY), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TYPE_DATE", Napi::Number::New(env, SQL_TYPE_DATE), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TYPE_TIME", Napi::Number::New(env, SQL_TYPE_TIME), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TYPE_TIMESTAMP", Napi::Number::New(env, SQL_TYPE_TIMESTAMP), + napi_enumerable + ) + ); // These are listed in the Microsoft ODBC documentation, but don't appear to // be in unixODBC - // ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_UTCDATETIME", Napi::Number::New(env, SQL_TYPE_UTCDATETIME), napi_enumerable)); - // ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_UTCTIME", Napi::Number::New(env, SQL_TYPE_UTCTIME), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_MONTH", Napi::Number::New(env, SQL_INTERVAL_MONTH), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_YEAR", Napi::Number::New(env, SQL_INTERVAL_YEAR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_YEAR_TO_MONTH", Napi::Number::New(env, SQL_INTERVAL_YEAR_TO_MONTH), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_DAY", Napi::Number::New(env, SQL_INTERVAL_DAY), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_HOUR", Napi::Number::New(env, SQL_INTERVAL_HOUR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_MINUTE", Napi::Number::New(env, SQL_INTERVAL_MINUTE), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_SECOND", Napi::Number::New(env, SQL_INTERVAL_SECOND), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_DAY_TO_HOUR", Napi::Number::New(env, SQL_INTERVAL_DAY_TO_HOUR), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_DAY_TO_MINUTE", Napi::Number::New(env, SQL_INTERVAL_DAY_TO_MINUTE), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_DAY_TO_SECOND", Napi::Number::New(env, SQL_INTERVAL_DAY_TO_SECOND), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_HOUR_TO_MINUTE", Napi::Number::New(env, SQL_INTERVAL_HOUR_TO_MINUTE), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_HOUR_TO_SECOND", Napi::Number::New(env, SQL_INTERVAL_HOUR_TO_SECOND), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_MINUTE_TO_SECOND", Napi::Number::New(env, SQL_INTERVAL_MINUTE_TO_SECOND), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_GUID", Napi::Number::New(env, SQL_GUID), napi_enumerable)); + // ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_UTCDATETIME", + // Napi::Number::New(env, SQL_TYPE_UTCDATETIME), napi_enumerable)); + // ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_UTCTIME", + // Napi::Number::New(env, SQL_TYPE_UTCTIME), napi_enumerable)); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_MONTH", Napi::Number::New(env, SQL_INTERVAL_MONTH), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_YEAR", Napi::Number::New(env, SQL_INTERVAL_YEAR), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_YEAR_TO_MONTH", + Napi::Number::New(env, SQL_INTERVAL_YEAR_TO_MONTH), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_DAY", Napi::Number::New(env, SQL_INTERVAL_DAY), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_HOUR", Napi::Number::New(env, SQL_INTERVAL_HOUR), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_MINUTE", Napi::Number::New(env, SQL_INTERVAL_MINUTE), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_SECOND", Napi::Number::New(env, SQL_INTERVAL_SECOND), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_DAY_TO_HOUR", + Napi::Number::New(env, SQL_INTERVAL_DAY_TO_HOUR), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_DAY_TO_MINUTE", + Napi::Number::New(env, SQL_INTERVAL_DAY_TO_MINUTE), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_DAY_TO_SECOND", + Napi::Number::New(env, SQL_INTERVAL_DAY_TO_SECOND), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_HOUR_TO_MINUTE", + Napi::Number::New(env, SQL_INTERVAL_HOUR_TO_MINUTE), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_HOUR_TO_SECOND", + Napi::Number::New(env, SQL_INTERVAL_HOUR_TO_SECOND), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_INTERVAL_MINUTE_TO_SECOND", + Napi::Number::New(env, SQL_INTERVAL_MINUTE_TO_SECOND), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_GUID", Napi::Number::New(env, SQL_GUID), napi_enumerable + ) + ); // End data types - - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_NO_NULLS", Napi::Number::New(env, SQL_NO_NULLS), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_NULLABLE", Napi::Number::New(env, SQL_NULLABLE), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_NULLABLE_UNKNOWN", Napi::Number::New(env, SQL_NULLABLE_UNKNOWN), napi_enumerable)); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_NO_NULLS", Napi::Number::New(env, SQL_NO_NULLS), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_NULLABLE", Napi::Number::New(env, SQL_NULLABLE), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_NULLABLE_UNKNOWN", Napi::Number::New(env, SQL_NULLABLE_UNKNOWN), + napi_enumerable + ) + ); // setIsolationLevel options - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TXN_READ_UNCOMMITTED", Napi::Number::New(env, SQL_TXN_READ_UNCOMMITTED), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TRANSACTION_READ_UNCOMMITTED", Napi::Number::New(env, SQL_TRANSACTION_READ_UNCOMMITTED), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TXN_READ_COMMITTED", Napi::Number::New(env, SQL_TXN_READ_COMMITTED), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TRANSACTION_READ_COMMITTED", Napi::Number::New(env, SQL_TRANSACTION_READ_COMMITTED), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TXN_REPEATABLE_READ", Napi::Number::New(env, SQL_TXN_REPEATABLE_READ), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TRANSACTION_REPEATABLE_READ", Napi::Number::New(env, SQL_TRANSACTION_REPEATABLE_READ), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TXN_SERIALIZABLE", Napi::Number::New(env, SQL_TXN_SERIALIZABLE), napi_enumerable)); - ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TRANSACTION_SERIALIZABLE", Napi::Number::New(env, SQL_TRANSACTION_SERIALIZABLE), napi_enumerable)); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TXN_READ_UNCOMMITTED", + Napi::Number::New(env, SQL_TXN_READ_UNCOMMITTED), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TRANSACTION_READ_UNCOMMITTED", + Napi::Number::New(env, SQL_TRANSACTION_READ_UNCOMMITTED), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TXN_READ_COMMITTED", Napi::Number::New(env, SQL_TXN_READ_COMMITTED), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TRANSACTION_READ_COMMITTED", + Napi::Number::New(env, SQL_TRANSACTION_READ_COMMITTED), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TXN_REPEATABLE_READ", + Napi::Number::New(env, SQL_TXN_REPEATABLE_READ), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TRANSACTION_REPEATABLE_READ", + Napi::Number::New(env, SQL_TRANSACTION_REPEATABLE_READ), napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TXN_SERIALIZABLE", Napi::Number::New(env, SQL_TXN_SERIALIZABLE), + napi_enumerable + ) + ); + ODBC_CONSTANTS.push_back( + Napi::PropertyDescriptor::Value( + "SQL_TRANSACTION_SERIALIZABLE", + Napi::Number::New(env, SQL_TRANSACTION_SERIALIZABLE), napi_enumerable + ) + ); Napi::Object odbcConstants = Napi::Object::New(env); odbcConstants.DefineProperties(ODBC_CONSTANTS); @@ -150,29 +397,23 @@ Napi::Value ODBC::Init(Napi::Env env, Napi::Object exports) { uv_mutex_lock(&ODBC::g_odbcMutex); // Initialize the Environment handle - return_code = - SQLAllocHandle - ( - SQL_HANDLE_ENV, - SQL_NULL_HANDLE, - &hEnv - ); + return_code = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv); uv_mutex_unlock(&ODBC::g_odbcMutex); if (!SQL_SUCCEEDED(return_code)) { // TODO: Redo - // Napi::Error(env, Napi::String::New(env, (const char*)ODBC::GetSQLErrors(SQL_HANDLE_ENV, hEnv)[0].message)).ThrowAsJavaScriptException(); + // Napi::Error( + // env, + // Napi::String::New( + // env, (const char*)ODBC::GetSQLErrors(SQL_HANDLE_ENV, + // hEnv)[0].message)) + // .ThrowAsJavaScriptException(); return env.Null(); } // Use ODBC 3.x behavior - return_code = - SQLSetEnvAttr - ( - hEnv, - SQL_ATTR_ODBC_VERSION, - (SQLPOINTER) SQL_OV_ODBC3, - SQL_IS_UINTEGER + return_code = SQLSetEnvAttr( + hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_UINTEGER ); return exports; @@ -195,7 +436,7 @@ ODBC::~ODBC() { //////////////////////////////////////////////////////////////////////////////// // // This class extends Napi::AsyncWorker to standardize error handling for all -// AsyncWorkers used by the package +// AsyncWorkers used by the package // //////////////////////////////////////////////////////////////////////////////// @@ -203,7 +444,7 @@ ODBCAsyncWorker::ODBCAsyncWorker(Napi::Function& callback) : Napi::AsyncWorker(callback) {}; // TODO: Documentation for this function -void ODBCAsyncWorker::OnError(const Napi::Error &e) { +void ODBCAsyncWorker::OnError(const Napi::Error& e) { Napi::Env env = Env(); Napi::HandleScope scope(env); @@ -215,36 +456,31 @@ void ODBCAsyncWorker::OnError(const Napi::Error &e) { ODBCError odbcError = errors[i]; Napi::Object errorObject = Napi::Object::New(env); - errorObject.Set - ( + errorObject.Set( Napi::String::New(env, STATE), - #ifdef UNICODE - Napi::String::New(env, (const char16_t *)odbcError.state) - #else - Napi::String::New(env, (const char *)odbcError.state) - #endif +#ifdef UNICODE + Napi::String::New(env, (const char16_t*)odbcError.state) +#else + Napi::String::New(env, (const char*)odbcError.state) +#endif ); - errorObject.Set - ( - Napi::String::New(env, CODE), - Napi::Number::New(env, odbcError.code) + errorObject.Set( + Napi::String::New(env, CODE), Napi::Number::New(env, odbcError.code) ); - errorObject.Set - ( + errorObject.Set( Napi::String::New(env, MESSAGE), - #ifdef UNICODE - Napi::String::New(env, (const char16_t *)odbcError.message) - #else - Napi::String::New(env, (const char *)odbcError.message) - #endif +#ifdef UNICODE + Napi::String::New(env, (const char16_t*)odbcError.message) +#else + Napi::String::New(env, (const char*)odbcError.message) +#endif ); // Error message has been copied off of the C ODBC error stucture, and can // now be deleted - if (odbcError.message != NULL) - { + if (odbcError.message != NULL) { delete[] odbcError.message; odbcError.message = NULL; } @@ -252,10 +488,7 @@ void ODBCAsyncWorker::OnError(const Napi::Error &e) { odbcErrors.Set(i, errorObject); } - error.Set( - Napi::String::New(env, ODBC_ERRORS), - odbcErrors - ); + error.Set(Napi::String::New(env, ODBC_ERRORS), odbcErrors); std::vector callbackArguments; callbackArguments.push_back(error.Value()); @@ -266,30 +499,19 @@ void ODBCAsyncWorker::OnError(const Napi::Error &e) { // After a SQL Function doesn't pass SQL_SUCCEEDED, the handle type and handle // are sent to this function, which gets the information and stores it in an // array of ODBCErrors -ODBCError* ODBCAsyncWorker::GetODBCErrors -( - SQLSMALLINT handleType, - SQLHANDLE handle -) -{ +ODBCError* +ODBCAsyncWorker::GetODBCErrors(SQLSMALLINT handleType, SQLHANDLE handle) { SQLRETURN return_code; SQLSMALLINT error_message_length = ERROR_MESSAGE_BUFFER_CHARS; SQLINTEGER statusRecCount; - return_code = SQLGetDiagField - ( - handleType, // HandleType - handle, // Handle - 0, // RecNumber - SQL_DIAG_NUMBER, // DiagIdentifier - &statusRecCount, // DiagInfoPtr - SQL_IS_INTEGER, // BufferLength - NULL // StringLengthPtr + return_code = SQLGetDiagField( + handleType, handle, 0, SQL_DIAG_NUMBER, &statusRecCount, SQL_IS_INTEGER, + NULL ); - if (!SQL_SUCCEEDED(return_code)) - { - ODBCError *odbcErrors = new ODBCError[1]; + if (!SQL_SUCCEEDED(return_code)) { + ODBCError* odbcErrors = new ODBCError[1]; ODBCError error; error.state[0] = NO_STATE_TEXT; error.code = 0; @@ -299,7 +521,7 @@ ODBCError* ODBCAsyncWorker::GetODBCErrors return odbcErrors; } - ODBCError *odbcErrors = new ODBCError[statusRecCount]; + ODBCError* odbcErrors = new ODBCError[statusRecCount]; this->errorCount = statusRecCount; for (SQLSMALLINT i = 0; i < statusRecCount; i++) { @@ -309,25 +531,15 @@ ODBCError* ODBCAsyncWorker::GetODBCErrors SQLSMALLINT new_error_message_length; return_code = SQL_SUCCESS; - while(SQL_SUCCEEDED(return_code)) - { + while (SQL_SUCCEEDED(return_code)) { error.message = new SQLTCHAR[error_message_length]; - return_code = - SQLGetDiagRec - ( - handleType, // HandleType - handle, // Handle - i + 1, // RecNumber - error.state, // SQLState - &error.code, // NativeErrorPtr - error.message, // MessageText - error_message_length, // BufferLength - &new_error_message_length // TextLengthPtr + return_code = SQLGetDiagRec( + handleType, handle, i + 1, error.state, &error.code, error.message, + error_message_length, &new_error_message_length ); - if (error_message_length > new_error_message_length) - { + if (error_message_length > new_error_message_length) { break; } @@ -335,11 +547,10 @@ ODBCError* ODBCAsyncWorker::GetODBCErrors error_message_length = new_error_message_length + 1; } - if (!SQL_SUCCEEDED(return_code)) - { + if (!SQL_SUCCEEDED(return_code)) { error.state[0] = NO_STATE_TEXT; error.code = 0; - memcpy(error.message, NO_MSG_TEXT, NO_MSG_TEXT_SIZE + 1); + memcpy(error.message, NO_MSG_TEXT, NO_MSG_TEXT_SIZE + 1); } odbcErrors[i] = error; @@ -349,7 +560,10 @@ ODBCError* ODBCAsyncWorker::GetODBCErrors } // TODO: Documentation for this function -bool ODBCAsyncWorker::CheckAndHandleErrors(SQLRETURN return_code, SQLSMALLINT handleType, SQLHANDLE handle, const char *message) { +bool ODBCAsyncWorker::CheckAndHandleErrors( + SQLRETURN return_code, SQLSMALLINT handleType, SQLHANDLE handle, + const char* message +) { if (!SQL_SUCCEEDED(return_code)) { this->errors = GetODBCErrors(handleType, handle); SetError(message); @@ -366,214 +580,187 @@ bool ODBCAsyncWorker::CheckAndHandleErrors(SQLRETURN return_code, SQLSMALLINT ha class ConnectAsyncWorker : public ODBCAsyncWorker { private: + SQLTCHAR* connectionStringPtr; + ConnectionOptions* options; + GetInfoResults get_info_results; - SQLTCHAR *connectionStringPtr; - ConnectionOptions *options; - GetInfoResults get_info_results; + SQLHENV hEnv; + SQLHDBC hDBC; - SQLHENV hEnv; - SQLHDBC hDBC; + void Execute() { - void Execute() { + SQLRETURN return_code; - SQLRETURN return_code; + uv_mutex_lock(&ODBC::g_odbcMutex); - uv_mutex_lock(&ODBC::g_odbcMutex); + return_code = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDBC); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_ENV, hEnv); + SetError("[odbc] Error allocating the connection handle"); + return; + } - return_code = SQLAllocHandle( - SQL_HANDLE_DBC, - hEnv, - &hDBC + if (options->connectionTimeout > 0) { + return_code = SQLSetConnectAttr( + hDBC, SQL_ATTR_CONNECTION_TIMEOUT, + (SQLPOINTER)(intptr_t)options->connectionTimeout, SQL_IS_UINTEGER ); if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_ENV, hEnv); - SetError("[odbc] Error allocating the connection handle"); + this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); + SetError("[odbc] Error setting the connection timeout"); return; } + } - if (options->connectionTimeout > 0) { - return_code = SQLSetConnectAttr( - hDBC, // ConnectionHandle - SQL_ATTR_CONNECTION_TIMEOUT, // Attribute - (SQLPOINTER) (intptr_t) options->connectionTimeout, // ValuePtr - SQL_IS_UINTEGER // StringLength - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); - SetError("[odbc] Error setting the connection timeout"); - return; - } + if (options->loginTimeout > 0) { + return_code = SQLSetConnectAttr( + hDBC, SQL_ATTR_LOGIN_TIMEOUT, + (SQLPOINTER)(intptr_t)options->loginTimeout, SQL_IS_UINTEGER + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); + SetError("[odbc] Error setting the login timeout"); + return; } - - if (options->loginTimeout > 0) { - return_code = - SQLSetConnectAttr - ( - hDBC, // ConnectionHandle - SQL_ATTR_LOGIN_TIMEOUT, // Attribute - (SQLPOINTER) (intptr_t) options->loginTimeout, // ValuePtr - SQL_IS_UINTEGER // StringLength + // "If the specified timeout exceeds the maximum login timeout in the + // data source, the driver substitutes that value and returns SQLSTATE + // 01S02 (Option value changed)." + if (return_code == SQL_SUCCESS_WITH_INFO) { + return_code = SQLGetConnectAttr( + hDBC, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)&options->loginTimeout, + IGNORED_PARAMETER, NULL ); if (!SQL_SUCCEEDED(return_code)) { this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); - SetError("[odbc] Error setting the login timeout"); + SetError("[odbc] Error setting retrieving the changed login timeout"); return; } - // "If the specified timeout exceeds the maximum login timeout in the - // data source, the driver substitutes that value and returns SQLSTATE - // 01S02 (Option value changed)." - if (return_code == SQL_SUCCESS_WITH_INFO) - { - return_code = - SQLGetConnectAttr - ( - hDBC, - SQL_ATTR_LOGIN_TIMEOUT, - (SQLPOINTER) &options->loginTimeout, - IGNORED_PARAMETER, - NULL - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); - SetError("[odbc] Error setting retrieving the changed login timeout"); - return; - } - } - } - - //Attempt to connect - return_code = - SQLDriverConnect - ( - hDBC, // ConnectionHandle - NULL, // WindowHandle - connectionStringPtr, // InConnectionString - SQL_NTS, // StringLength1 - NULL, // OutConnectionString - 0, // BufferLength - in characters - NULL, // StringLength2Ptr - SQL_DRIVER_NOPROMPT // DriverCompletion - ); - uv_mutex_unlock(&ODBC::g_odbcMutex); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); - SetError("[odbc] Error connecting to the database"); - return; - } - - // get information about the connection - // maximum column length - return_code = - SQLGetInfo - ( - hDBC, // ConnectionHandle - SQL_MAX_COLUMN_NAME_LEN, // InfoType - &get_info_results.max_column_name_length, // InfoValuePtr - sizeof(SQLSMALLINT), // BufferLength - NULL // StringLengthPtr - ); - // Some poorly-behaved drivers do not implement SQL_MAX_COLUMN_NAME_LEN, - // and return SQL_ERROR instead of setting the value to 0 like the spec - // requires. Bite the bullet and ignore any errors here, instead setting - // the value to something sane like 128 ("An FIPS Intermediate - // level-conformant driver will return at least 128."). - if (!SQL_SUCCEEDED(return_code) || get_info_results.max_column_name_length == 0) { - get_info_results.max_column_name_length = 128; - // this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); - // SetError("[odbc] Error getting information about maximum column length from the connection"); - // return; - } - - // valid transaction levels - return_code = - SQLGetInfo - ( - hDBC, // ConnectionHandle - SQL_TXN_ISOLATION_OPTION, // InfoType - &get_info_results.available_isolation_levels, // InfoValuePtr - sizeof(SQLUINTEGER), // BufferLength - NULL // StringLengthPtr - ); - // Some poorly-behaved drivers do not implement SQL_TXN_ISOLATION_OPTION, - // and return SQL_ERROR. Bite the bullet again and ignore any errors here, - // instead setting the bitmask to 0 so no isolation levels are listed as - // supported. - if (!SQL_SUCCEEDED(return_code)) { - get_info_results.available_isolation_levels = 0; - // this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); - // SetError("[odbc] Error getting information about available transaction isolation options from the connection"); - // return; - } - - SQLUINTEGER sql_getdata_extensions_bitmask; - - // valid get data extensions - return_code = - SQLGetInfo - ( - hDBC, // ConnectionHandle - SQL_GETDATA_EXTENSIONS, // InfoType - (SQLPOINTER) &sql_getdata_extensions_bitmask, // InfoValuePtr - IGNORED_PARAMETER, // BufferLength - IGNORED_PARAMETER // StringLengthPtr - ); - if (!SQL_SUCCEEDED(return_code)) { - // Some drivers don't feel the need to implement the - // SQL_GETDATA_EXTENSIONS option for SQLGetInfo, so this call will - // return an error. Instead of returning an error, just set all of - // the SQLGetData extensions to false and continue. - get_info_results.sql_get_data_supports.any_column = false; - get_info_results.sql_get_data_supports.any_order = false; - get_info_results.sql_get_data_supports.block = false; - get_info_results.sql_get_data_supports.bound = false; - get_info_results.sql_get_data_supports.output_params = false; - } else { - // call the bitmask to populate the sql_get_data_supports struct - get_info_results.sql_get_data_supports.any_column = - (bool) (sql_getdata_extensions_bitmask & SQL_GD_ANY_COLUMN); - get_info_results.sql_get_data_supports.any_order = - (bool) (sql_getdata_extensions_bitmask & SQL_GD_ANY_ORDER); - get_info_results.sql_get_data_supports.block = - (bool) (sql_getdata_extensions_bitmask & SQL_GD_BLOCK); - get_info_results.sql_get_data_supports.bound = - (bool) (sql_getdata_extensions_bitmask & SQL_GD_BOUND); - get_info_results.sql_get_data_supports.output_params = - (bool) (sql_getdata_extensions_bitmask & SQL_GD_OUTPUT_PARAMS); } } - void OnOK() { + // Attempt to connect + return_code = SQLDriverConnect( + hDBC, NULL, connectionStringPtr, SQL_NTS, NULL, 0, NULL, + SQL_DRIVER_NOPROMPT + ); + uv_mutex_unlock(&ODBC::g_odbcMutex); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); + SetError("[odbc] Error connecting to the database"); + return; + } - Napi::Env env = Env(); - Napi::HandleScope scope(env); + // get information about the connection + // maximum column length + return_code = SQLGetInfo( + hDBC, SQL_MAX_COLUMN_NAME_LEN, &get_info_results.max_column_name_length, + sizeof(SQLSMALLINT), NULL + ); + // Some poorly-behaved drivers do not implement SQL_MAX_COLUMN_NAME_LEN, + // and return SQL_ERROR instead of setting the value to 0 like the spec + // requires. Bite the bullet and ignore any errors here, instead setting + // the value to something sane like 128 ("An FIPS Intermediate + // level-conformant driver will return at least 128."). + if (!SQL_SUCCEEDED(return_code) || + get_info_results.max_column_name_length == 0) { + get_info_results.max_column_name_length = 128; + // this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); + // SetError("[odbc] Error getting information about maximum column length + // from the connection"); return; + } - // pass the HENV and HDBC values to the ODBCConnection constructor - std::vector connectionArguments; - connectionArguments.push_back(Napi::External::New(env, &hEnv)); // connectionArguments[0] - connectionArguments.push_back(Napi::External::New(env, &hDBC)); // connectionArguments[1] - connectionArguments.push_back(Napi::External::New(env, options)); // connectionArguments[2] - connectionArguments.push_back(Napi::External::New(env, &get_info_results)); // connectionArguments[3] - - Napi::Value connection = ODBCConnection::constructor.New(connectionArguments); + // valid transaction levels + return_code = SQLGetInfo( + hDBC, SQL_TXN_ISOLATION_OPTION, + &get_info_results.available_isolation_levels, sizeof(SQLUINTEGER), NULL + ); + // Some poorly-behaved drivers do not implement SQL_TXN_ISOLATION_OPTION, + // and return SQL_ERROR. Bite the bullet again and ignore any errors here, + // instead setting the bitmask to 0 so no isolation levels are listed as + // supported. + if (!SQL_SUCCEEDED(return_code)) { + get_info_results.available_isolation_levels = 0; + // this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC); + // SetError("[odbc] Error getting information about available transaction + // isolation options from the connection"); return; + } - // pass the arguments to the callback function - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); // callbackArguments[0] - callbackArguments.push_back(connection); // callbackArguments[1] + SQLUINTEGER sql_getdata_extensions_bitmask; - Callback().Call(callbackArguments); + // valid get data extensions + return_code = SQLGetInfo( + hDBC, SQL_GETDATA_EXTENSIONS, (SQLPOINTER)&sql_getdata_extensions_bitmask, + IGNORED_PARAMETER, IGNORED_PARAMETER + ); + if (!SQL_SUCCEEDED(return_code)) { + // Some drivers don't feel the need to implement the + // SQL_GETDATA_EXTENSIONS option for SQLGetInfo, so this call will + // return an error. Instead of returning an error, just set all of + // the SQLGetData extensions to false and continue. + get_info_results.sql_get_data_supports.any_column = false; + get_info_results.sql_get_data_supports.any_order = false; + get_info_results.sql_get_data_supports.block = false; + get_info_results.sql_get_data_supports.bound = false; + get_info_results.sql_get_data_supports.output_params = false; + } + else { + // call the bitmask to populate the sql_get_data_supports struct + get_info_results.sql_get_data_supports.any_column = + (bool)(sql_getdata_extensions_bitmask & SQL_GD_ANY_COLUMN); + get_info_results.sql_get_data_supports.any_order = + (bool)(sql_getdata_extensions_bitmask & SQL_GD_ANY_ORDER); + get_info_results.sql_get_data_supports.block = + (bool)(sql_getdata_extensions_bitmask & SQL_GD_BLOCK); + get_info_results.sql_get_data_supports.bound = + (bool)(sql_getdata_extensions_bitmask & SQL_GD_BOUND); + get_info_results.sql_get_data_supports.output_params = + (bool)(sql_getdata_extensions_bitmask & SQL_GD_OUTPUT_PARAMS); } + } + + void OnOK() { + + Napi::Env env = Env(); + Napi::HandleScope scope(env); + + // pass the HENV and HDBC values to the ODBCConnection constructor + std::vector connectionArguments; + connectionArguments.push_back( + Napi::External::New(env, &hEnv) + ); // connectionArguments[0] + connectionArguments.push_back( + Napi::External::New(env, &hDBC) + ); // connectionArguments[1] + connectionArguments.push_back( + Napi::External::New(env, options) + ); // connectionArguments[2] + connectionArguments.push_back( + Napi::External::New(env, &get_info_results) + ); // connectionArguments[3] + + Napi::Value connection = + ODBCConnection::constructor.New(connectionArguments); + + // pass the arguments to the callback function + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); // callbackArguments[0] + callbackArguments.push_back(connection); // callbackArguments[1] + + Callback().Call(callbackArguments); + } public: - ConnectAsyncWorker(HENV hEnv, SQLTCHAR *connectionStringPtr, ConnectionOptions *options, Napi::Function& callback) : ODBCAsyncWorker(callback), - connectionStringPtr(connectionStringPtr), - options(options), - hEnv(hEnv) {} - - ~ConnectAsyncWorker() { - delete options; - delete[] connectionStringPtr; - } + ConnectAsyncWorker( + HENV hEnv, SQLTCHAR* connectionStringPtr, ConnectionOptions* options, + Napi::Function& callback + ) + : ODBCAsyncWorker(callback), connectionStringPtr(connectionStringPtr), + options(options), hEnv(hEnv) {} + + ~ConnectAsyncWorker() { + delete options; + delete[] connectionStringPtr; + } }; // Connect @@ -585,52 +772,77 @@ Napi::Value ODBC::Connect(const Napi::CallbackInfo& info) { Napi::String connectionString; Napi::Function callback; - SQLTCHAR *connectionStringPtr = nullptr; + SQLTCHAR* connectionStringPtr = nullptr; - ConnectionOptions *options = new ConnectionOptions(); + ConnectionOptions* options = new ConnectionOptions(); options->connectionTimeout = 0; options->loginTimeout = 0; options->fetchArray = false; - if(info.Length() != 2) { - Napi::TypeError::New(env, "connect(connectionString, callback) requires 2 parameters.").ThrowAsJavaScriptException(); + if (info.Length() != 2) { + Napi::TypeError::New( + env, "connect(connectionString, callback) requires 2 parameters." + ) + .ThrowAsJavaScriptException(); return env.Null(); } if (info[0].IsString()) { connectionString = info[0].As(); connectionStringPtr = ODBC::NapiStringToSQLTCHAR(connectionString); - } else if (info[0].IsObject()) { + } + else if (info[0].IsObject()) { Napi::Object connectionObject = info[0].As(); - if (connectionObject.Has("connectionString") && connectionObject.Get("connectionString").IsString()) { - connectionString = connectionObject.Get("connectionString").As(); + if (connectionObject.Has("connectionString") && + connectionObject.Get("connectionString").IsString()) { + connectionString = + connectionObject.Get("connectionString").As(); connectionStringPtr = ODBC::NapiStringToSQLTCHAR(connectionString); - } else { - Napi::TypeError::New(env, "connect: A configuration object must have a 'connectionString' property that is a string.").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New( + env, "connect: A configuration object must have a " + "'connectionString' property that is a string." + ) + .ThrowAsJavaScriptException(); return env.Null(); } - if (connectionObject.Has("connectionTimeout") && connectionObject.Get("connectionTimeout").IsNumber()) { - options->connectionTimeout = connectionObject.Get("connectionTimeout").As().Int32Value(); + if (connectionObject.Has("connectionTimeout") && + connectionObject.Get("connectionTimeout").IsNumber()) { + options->connectionTimeout = connectionObject.Get("connectionTimeout") + .As() + .Int32Value(); } - if (connectionObject.Has("loginTimeout") && connectionObject.Get("loginTimeout").IsNumber()) { - options->loginTimeout = connectionObject.Get("loginTimeout").As().Int32Value(); + if (connectionObject.Has("loginTimeout") && + connectionObject.Get("loginTimeout").IsNumber()) { + options->loginTimeout = + connectionObject.Get("loginTimeout").As().Int32Value(); } - if (connectionObject.Has("fetchArray") && connectionObject.Get("fetchArray").IsBoolean()) { - options->fetchArray = connectionObject.Get("fetchArray").As(); + if (connectionObject.Has("fetchArray") && + connectionObject.Get("fetchArray").IsBoolean()) { + options->fetchArray = + connectionObject.Get("fetchArray").As(); } - } else { - Napi::TypeError::New(env, "connect: first parameter must be a string or an object.").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New( + env, "connect: first parameter must be a string or an object." + ) + .ThrowAsJavaScriptException(); return env.Null(); } if (info[1].IsFunction()) { callback = info[1].As(); - } else { - Napi::TypeError::New(env, "connect: second parameter must be a function.").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New(env, "connect: second parameter must be a function.") + .ThrowAsJavaScriptException(); return env.Null(); } - ConnectAsyncWorker *worker = new ConnectAsyncWorker(hEnv, connectionStringPtr, options, callback); + ConnectAsyncWorker* worker = + new ConnectAsyncWorker(hEnv, connectionStringPtr, options, callback); worker->Queue(); return env.Undefined(); @@ -647,14 +859,14 @@ SQLTCHAR* ODBC::NapiStringToSQLTCHAR(Napi::String string) { size_t byteCount = 0; - #ifdef UNICODE - std::u16string tempString = string.Utf16Value(); - byteCount = (tempString.length() + 1) * 2; - #else - std::string tempString = string.Utf8Value(); - byteCount = tempString.length() + 1; - #endif - SQLTCHAR *sqlString = new SQLTCHAR[byteCount]; +#ifdef UNICODE + std::u16string tempString = string.Utf16Value(); + byteCount = (tempString.length() + 1) * 2; +#else + std::string tempString = string.Utf8Value(); + byteCount = tempString.length() + 1; +#endif + SQLTCHAR* sqlString = new SQLTCHAR[byteCount]; std::memcpy(sqlString, tempString.c_str(), byteCount); return sqlString; } @@ -667,113 +879,133 @@ SQLTCHAR* ODBC::NapiStringToSQLTCHAR(Napi::String string) { * GetParametersFromArray * Array of parameters can hold either/and: * Value: - * One value to bind, In/Out defaults to SQL_PARAM_INPUT, dataType defaults based on the value - * Arrays:ns when you elect n - * between 1 and 3 entries in lenth, with the following signfigance and default values: + * One value to bind, In/Out defaults to SQL_PARAM_INPUT, dataType defaults + * based on the value Arrays:ns when you elect n between 1 and 3 entries in + * lenth, with the following signfigance and default values: * 1. Value (REQUIRED): The value to bind * 2. In/Out (Optional): Defaults to SQL_PARAM_INPUT - * 3. DataType (Optional): Defaults based on the value + * 3. DataType (Optional): Defaults based on the value * Objects: - * can hold any of the following properties (but requires at least 'value' property) - * value (Requited): The value to bind - * inOut (Optional): the In/Out type to use, Defaults to SQL_PARAM_INPUT - * dataType (Optional): The data type, defaults based on the value + * can hold any of the following properties (but requires at least 'value' + * property) value (Requited): The value to bind inOut (Optional): the In/Out + * type to use, Defaults to SQL_PARAM_INPUT dataType (Optional): The data type, + * defaults based on the value * * */ - -// This function solves the problem of losing access to "Napi::Value"s when entering -// an AsyncWorker. In Connection::Query, once we enter an AsyncWorker we do not leave it again, -// but we can't call SQLNumParams and SQLDescribeParam until after SQLPrepare. So the array of -// values to bind to parameters must be saved off in the closest, largest data type to then -// convert to the right C Type once the SQL Type of the parameter is known. -void ODBC::StoreBindValues(Napi::Array *values, Parameter **parameters) { +// This function solves the problem of losing access to "Napi::Value"s when +// entering an AsyncWorker. In Connection::Query, once we enter an AsyncWorker +// we do not leave it again, but we can't call SQLNumParams and SQLDescribeParam +// until after SQLPrepare. So the array of values to bind to parameters must be +// saved off in the closest, largest data type to then convert to the right C +// Type once the SQL Type of the parameter is known. +void ODBC::StoreBindValues(Napi::Array* values, Parameter** parameters) { uint32_t numParameters = values->Length(); for (uint32_t i = 0; i < numParameters; i++) { Napi::Value value = values->Get(i); - Parameter *parameter = parameters[i]; + Parameter* parameter = parameters[i]; - if(value.IsNull()) { + if (value.IsNull()) { parameter->ValueType = SQL_C_DEFAULT; parameter->ParameterValuePtr = NULL; parameter->StrLen_or_IndPtr = SQL_NULL_DATA; - } else if (value.IsBigInt()) { + } + else if (value.IsBigInt()) { // TODO: need to check for signed/unsigned? bool lossless = true; parameter->ValueType = SQL_C_SBIGINT; - parameter->ParameterValuePtr = new SQLBIGINT(value.As().Int64Value(&lossless)); + parameter->ParameterValuePtr = + new SQLBIGINT(value.As().Int64Value(&lossless)); parameter->isbigint = true; - } else if (value.IsNumber()) { + } + else if (value.IsNumber()) { double double_val = value.As().DoubleValue(); int64_t int_val = value.As().Int64Value(); if (double_val == int_val) { parameter->ValueType = SQL_C_SBIGINT; - parameter->ParameterValuePtr = new SQLBIGINT(value.As().Int64Value()); + parameter->ParameterValuePtr = + new SQLBIGINT(value.As().Int64Value()); parameter->isbigint = false; - } else { + } + else { parameter->ValueType = SQL_C_DOUBLE; - parameter->ParameterValuePtr = new SQLDOUBLE(value.As().DoubleValue()); + parameter->ParameterValuePtr = + new SQLDOUBLE(value.As().DoubleValue()); } - } else if (value.IsBoolean()) { + } + else if (value.IsBoolean()) { parameter->ValueType = SQL_C_BIT; - parameter->ParameterValuePtr = new bool(value.As().Value()); - } else if (value.IsBuffer()) { + parameter->ParameterValuePtr = + new bool(value.As().Value()); + } + else if (value.IsBuffer()) { Napi::Buffer bufferValue = value.As>(); parameter->ValueType = SQL_C_BINARY; parameter->BufferLength = bufferValue.Length(); parameter->ParameterValuePtr = new SQLCHAR[parameter->BufferLength](); parameter->StrLen_or_IndPtr = parameter->BufferLength; - memcpy((SQLCHAR *) parameter->ParameterValuePtr, bufferValue.Data(), parameter->BufferLength); - } else if (value.IsArrayBuffer()) { + memcpy( + (SQLCHAR*)parameter->ParameterValuePtr, bufferValue.Data(), + parameter->BufferLength + ); + } + else if (value.IsArrayBuffer()) { Napi::ArrayBuffer arrayBufferValue = value.As(); parameter->ValueType = SQL_C_BINARY; parameter->BufferLength = arrayBufferValue.ByteLength(); parameter->ParameterValuePtr = new SQLCHAR[parameter->BufferLength]; parameter->StrLen_or_IndPtr = parameter->BufferLength; - memcpy((SQLCHAR *) parameter->ParameterValuePtr, arrayBufferValue.Data(), parameter->BufferLength); - } else if (value.IsString()) { + memcpy( + (SQLCHAR*)parameter->ParameterValuePtr, arrayBufferValue.Data(), + parameter->BufferLength + ); + } + else if (value.IsString()) { // Napi::String string = value.ToString(); // parameter->ValueType = SQL_C_WCHAR; - // parameter->BufferLength = (string.Utf16Value().length() + 1) * sizeof(SQLWCHAR); - // parameter->ParameterValuePtr = new SQLWCHAR[parameter->BufferLength]; - // parameter->StrLen_or_IndPtr = SQL_NTS; - // memcpy((SQLWCHAR*) parameter->ParameterValuePtr, string.Utf16Value().c_str(), parameter->BufferLength); + // parameter->BufferLength = (string.Utf16Value().length() + 1) * + // sizeof(SQLWCHAR); parameter->ParameterValuePtr = new + // SQLWCHAR[parameter->BufferLength]; parameter->StrLen_or_IndPtr = + // SQL_NTS; memcpy((SQLWCHAR*) parameter->ParameterValuePtr, + // string.Utf16Value().c_str(), parameter->BufferLength); Napi::String string = value.ToString(); parameter->ValueType = SQL_C_CHAR; parameter->BufferLength = (string.Utf8Value().length() + 1); parameter->ParameterValuePtr = new SQLCHAR[parameter->BufferLength](); parameter->StrLen_or_IndPtr = SQL_NTS; - memcpy((SQLCHAR*) parameter->ParameterValuePtr, string.Utf8Value().c_str(), parameter->BufferLength); - } else { + memcpy( + (SQLCHAR*)parameter->ParameterValuePtr, string.Utf8Value().c_str(), + parameter->BufferLength + ); + } + else { // TODO: Throw error, don't support other types } } } -SQLRETURN ODBC::DescribeParameters(SQLHSTMT hstmt, Parameter **parameters, SQLSMALLINT parameterCount) { +SQLRETURN ODBC::DescribeParameters( + SQLHSTMT hstmt, Parameter** parameters, SQLSMALLINT parameterCount +) { - SQLRETURN return_code = SQL_SUCCESS; // if no parameters, will return SQL_SUCCESS + SQLRETURN return_code = + SQL_SUCCESS; // if no parameters, will return SQL_SUCCESS for (SQLSMALLINT i = 0; i < parameterCount; i++) { - Parameter *parameter = parameters[i]; + Parameter* parameter = parameters[i]; - // "Except in calls to procedures, all parameters in SQL statements are input parameters." + // "Except in calls to procedures, all parameters in SQL statements are + // input parameters." parameter->InputOutputType = SQL_PARAM_INPUT; - return_code = - SQLDescribeParam - ( - hstmt, // StatementHandle, - i + 1, // ParameterNumber, - ¶meter->ParameterType, // DataTypePtr, - ¶meter->ColumnSize, // ParameterSizePtr, - ¶meter->DecimalDigits, // DecimalDigitsPtr, - ¶meter->Nullable // NullablePtr + return_code = SQLDescribeParam( + hstmt, i + 1, ¶meter->ParameterType, ¶meter->ColumnSize, + ¶meter->DecimalDigits, ¶meter->Nullable ); // if there is an error, return early and retrieve error in calling function @@ -785,25 +1017,22 @@ SQLRETURN ODBC::DescribeParameters(SQLHSTMT hstmt, Parameter **parameters, SQLSM return return_code; } -SQLRETURN ODBC::BindParameters(SQLHSTMT hstmt, Parameter **parameters, SQLSMALLINT parameterCount) { +SQLRETURN ODBC::BindParameters( + SQLHSTMT hstmt, Parameter** parameters, SQLSMALLINT parameterCount +) { - SQLRETURN return_code = SQL_SUCCESS; // if no parameters, will return SQL_SUCCESS + SQLRETURN return_code = + SQL_SUCCESS; // if no parameters, will return SQL_SUCCESS for (int i = 0; i < parameterCount; i++) { Parameter* parameter = parameters[i]; return_code = SQLBindParameter( - hstmt, // StatementHandle - i + 1, // ParameterNumber - parameter->InputOutputType, // InputOutputType - parameter->ValueType, // ValueType - parameter->ParameterType, // ParameterType - parameter->ColumnSize, // ColumnSize - parameter->DecimalDigits, // DecimalDigits - parameter->ParameterValuePtr, // ParameterValuePtr - parameter->BufferLength, // BufferLength - ¶meter->StrLen_or_IndPtr // StrLen_or_IndPtr + hstmt, i + 1, parameter->InputOutputType, parameter->ValueType, + parameter->ParameterType, parameter->ColumnSize, parameter->DecimalDigits, + parameter->ParameterValuePtr, parameter->BufferLength, + ¶meter->StrLen_or_IndPtr // StrLen_or_IndPtr ); // If there was an error, return early if (!SQL_SUCCEEDED(return_code)) { diff --git a/src/odbc.h b/src/odbc.h index 38e52a58..9a971aa5 100644 --- a/src/odbc.h +++ b/src/odbc.h @@ -45,16 +45,16 @@ #define ERROR_MESSAGE_BUFFER_CHARS 2048 #endif -#define FETCH_ARRAY 3 +#define FETCH_ARRAY 3 #define FETCH_OBJECT 4 -#define SQL_DESTROY 9999 +#define SQL_DESTROY 9999 #define IGNORED_PARAMETER 0 typedef struct ODBCError { - SQLTCHAR state[6]; - SQLINTEGER code; - SQLTCHAR *message; + SQLTCHAR state[6]; + SQLINTEGER code; + SQLTCHAR* message; } ODBCError; typedef struct GetDataExtensionsSupport { @@ -66,71 +66,71 @@ typedef struct GetDataExtensionsSupport { } GetDataExtensionsSupport; typedef struct GetInfoResults { - SQLSMALLINT max_column_name_length; + SQLSMALLINT max_column_name_length; GetDataExtensionsSupport sql_get_data_supports; - SQLUINTEGER available_isolation_levels; + SQLUINTEGER available_isolation_levels; } GetInfoResults; typedef struct ConnectionOptions { unsigned int connectionTimeout; unsigned int loginTimeout; - bool fetchArray; + bool fetchArray; } ConnectionOptions; typedef struct Column { - SQLUSMALLINT index; - SQLTCHAR *ColumnName = NULL; - SQLSMALLINT BufferLength; - SQLSMALLINT NameLength; - SQLSMALLINT DataType; - SQLULEN ColumnSize; - SQLSMALLINT DecimalDigits; - SQLLEN StrLen_or_IndPtr; - SQLSMALLINT Nullable; + SQLUSMALLINT index; + SQLTCHAR* ColumnName = NULL; + SQLSMALLINT BufferLength; + SQLSMALLINT NameLength; + SQLSMALLINT DataType; + SQLULEN ColumnSize; + SQLSMALLINT DecimalDigits; + SQLLEN StrLen_or_IndPtr; + SQLSMALLINT Nullable; // data used when binding to the column - SQLSMALLINT bind_type; // when unraveling ColumnData - SQLLEN buffer_size; // size of the buffer bound - bool is_long_data; // set to true if data type is SQL_(W)LONG* + SQLSMALLINT bind_type; // when unraveling ColumnData + SQLLEN buffer_size; // size of the buffer bound + bool is_long_data; // set to true if data type is SQL_(W)LONG* } Column; typedef struct ColumnBuffer { - SQLPOINTER buffer; - SQLLEN *length_or_indicator_array; + SQLPOINTER buffer; + SQLLEN* length_or_indicator_array; } ColumnBuffer; typedef struct BindData { - SQLLEN string_length_or_indicator; + SQLLEN string_length_or_indicator; SQLPOINTER data; } BindData; // Amalgamation of the information returned by SQLDescribeParam and // SQLProcedureColumns as well as the information needed by SQLBindParameter typedef struct Parameter { - SQLSMALLINT InputOutputType; // returned by SQLProcedureColumns - SQLSMALLINT ValueType; - SQLSMALLINT ParameterType; - SQLULEN ColumnSize; - SQLSMALLINT DecimalDigits; - SQLPOINTER ParameterValuePtr; - SQLLEN BufferLength; - SQLLEN StrLen_or_IndPtr; - SQLSMALLINT Nullable; - bool isbigint; + SQLSMALLINT InputOutputType; // returned by SQLProcedureColumns + SQLSMALLINT ValueType; + SQLSMALLINT ParameterType; + SQLULEN ColumnSize; + SQLSMALLINT DecimalDigits; + SQLPOINTER ParameterValuePtr; + SQLLEN BufferLength; + SQLLEN StrLen_or_IndPtr; + SQLSMALLINT Nullable; + bool isbigint; } Parameter; typedef struct ColumnData { SQLSMALLINT bind_type; bool use_free; union { - SQLCHAR *char_data; - SQLWCHAR *wchar_data; - SQLDOUBLE double_data; - SQLUSMALLINT usmallint_data; - SQLSMALLINT smallint_data; - SQLINTEGER integer_data; - SQLBIGINT bigint_data; + SQLCHAR* char_data; + SQLWCHAR* wchar_data; + SQLDOUBLE double_data; + SQLUSMALLINT usmallint_data; + SQLSMALLINT smallint_data; + SQLINTEGER integer_data; + SQLBIGINT bigint_data; }; - SQLLEN size; + SQLLEN size; ~ColumnData() { if (bind_type == SQL_C_CHAR || bind_type == SQL_C_BINARY) { @@ -156,21 +156,22 @@ typedef struct ColumnData { #define MB_SIZE 1048576 typedef struct QueryOptions { - bool use_cursor = false; - SQLTCHAR *cursor_name = nullptr; - SQLSMALLINT cursor_name_length = 0; - SQLULEN fetch_size = 1; - SQLULEN timeout = 0; - SQLLEN initial_long_data_buffer_size = MB_SIZE; + bool use_cursor = false; + SQLTCHAR* cursor_name = nullptr; + SQLSMALLINT cursor_name_length = 0; + SQLULEN fetch_size = 1; + SQLULEN timeout = 0; + SQLLEN initial_long_data_buffer_size = MB_SIZE; // JavaScript property keys for query options - static constexpr const char *CURSOR_PROPERTY = "cursor"; - static constexpr const char *FETCH_SIZE_PROPERTY = "fetchSize"; - static constexpr const char *TIMEOUT_PROPERTY = "timeout"; - static constexpr const char *INITIAL_BUFFER_SIZE_PROPERTY = "initialBufferSize"; + static constexpr const char* CURSOR_PROPERTY = "cursor"; + static constexpr const char* FETCH_SIZE_PROPERTY = "fetchSize"; + static constexpr const char* TIMEOUT_PROPERTY = "timeout"; + static constexpr const char* INITIAL_BUFFER_SIZE_PROPERTY = + "initialBufferSize"; void reset() { - this->use_cursor = false; + this->use_cursor = false; this->cursor_name = nullptr; this->cursor_name_length = 0; this->fetch_size = 1; @@ -183,8 +184,8 @@ typedef struct QueryOptions { // StatementData typedef struct StatementData { - SQLHENV henv; - SQLHDBC hdbc; + SQLHENV henv; + SQLHDBC hdbc; SQLHSTMT hstmt = SQL_NULL_HANDLE; QueryOptions query_options; @@ -196,33 +197,33 @@ typedef struct StatementData { Parameter** parameters = NULL; // columns and rows - bool simple_binding = false; - Column **columns = NULL; - SQLSMALLINT column_count; - ColumnBuffer *bound_columns = NULL; - std::vector storedRows; - SQLLEN rowCount; + bool simple_binding = false; + Column** columns = NULL; + SQLSMALLINT column_count; + ColumnBuffer* bound_columns = NULL; + std::vector storedRows; + SQLLEN rowCount; - SQLSMALLINT maxColumnNameLength; + SQLSMALLINT maxColumnNameLength; - SQLUSMALLINT *row_status_array; - SQLUINTEGER fetch_size; - SQLULEN rows_fetched; - bool result_set_end_reached = false; + SQLUSMALLINT* row_status_array; + SQLUINTEGER fetch_size; + SQLULEN rows_fetched; + bool result_set_end_reached = false; - bool fetch_array = false; + bool fetch_array = false; // query options - SQLTCHAR *sql = NULL; - SQLTCHAR *catalog = NULL; - SQLTCHAR *schema = NULL; - SQLTCHAR *table = NULL; - SQLTCHAR *fkCatalog = NULL; - SQLTCHAR *fkSchema = NULL; - SQLTCHAR *fkTable = NULL; - SQLTCHAR *type = NULL; - SQLTCHAR *column = NULL; - SQLTCHAR *procedure = NULL; + SQLTCHAR* sql = NULL; + SQLTCHAR* catalog = NULL; + SQLTCHAR* schema = NULL; + SQLTCHAR* table = NULL; + SQLTCHAR* fkCatalog = NULL; + SQLTCHAR* fkSchema = NULL; + SQLTCHAR* fkTable = NULL; + SQLTCHAR* type = NULL; + SQLTCHAR* column = NULL; + SQLTCHAR* procedure = NULL; ~StatementData() { deleteColumns(); @@ -231,40 +232,51 @@ typedef struct StatementData { Parameter* parameter = this->parameters[i]; if (parameter->ParameterValuePtr != NULL) { switch (parameter->ValueType) { - case SQL_C_SBIGINT: - delete (int64_t*)parameter->ParameterValuePtr; - break; - case SQL_C_DOUBLE: - delete (double*)parameter->ParameterValuePtr; - break; - case SQL_C_BIT: - delete (bool*)parameter->ParameterValuePtr; - break; - case SQL_C_TCHAR: - default: - delete[] (SQLTCHAR*)parameter->ParameterValuePtr; - break; + case SQL_C_SBIGINT: + delete (int64_t*)parameter->ParameterValuePtr; + break; + case SQL_C_DOUBLE: + delete (double*)parameter->ParameterValuePtr; + break; + case SQL_C_BIT: + delete (bool*)parameter->ParameterValuePtr; + break; + case SQL_C_TCHAR: + default: + delete[] (SQLTCHAR*)parameter->ParameterValuePtr; + break; } } parameter->ParameterValuePtr = NULL; delete parameter; } - delete[] this->parameters; this->parameters = NULL; + delete[] this->parameters; + this->parameters = NULL; this->parameterCount = 0; - delete[] sql; sql = NULL; - delete[] this->catalog; this->catalog = NULL; - delete[] this->schema; this->schema = NULL; - delete[] this->table; this->table = NULL; - delete[] this->fkCatalog; this->fkCatalog = NULL; - delete[] this->fkSchema; this->fkSchema = NULL; - delete[] this->fkTable; this->fkTable = NULL; - delete[] this->type; this->type = NULL; - delete[] this->column; this->column = NULL; - delete[] this->procedure; this->procedure = NULL; + delete[] sql; + sql = NULL; + delete[] this->catalog; + this->catalog = NULL; + delete[] this->schema; + this->schema = NULL; + delete[] this->table; + this->table = NULL; + delete[] this->fkCatalog; + this->fkCatalog = NULL; + delete[] this->fkSchema; + this->fkSchema = NULL; + delete[] this->fkTable; + this->fkTable = NULL; + delete[] this->type; + this->type = NULL; + delete[] this->column; + this->column = NULL; + delete[] this->procedure; + this->procedure = NULL; } - + void deleteColumns() { for (size_t h = 0; h < this->storedRows.size(); h++) { delete[] storedRows[h]; @@ -273,25 +285,25 @@ typedef struct StatementData { for (int i = 0; i < this->column_count; i++) { switch (this->columns[i]->bind_type) { - case SQL_C_CHAR: - case SQL_C_BINARY: - delete[] (SQLCHAR *)this->bound_columns[i].buffer; - break; - case SQL_C_WCHAR: - delete[] (SQLWCHAR *)this->bound_columns[i].buffer; - break; - case SQL_C_DOUBLE: - delete[] (SQLDOUBLE *)this->bound_columns[i].buffer; - break; - case SQL_C_USHORT: - delete[] (SQLUSMALLINT *)this->bound_columns[i].buffer; - break; - case SQL_C_SLONG: - delete[] (SQLUINTEGER *)this->bound_columns[i].buffer; - break; - case SQL_C_UBIGINT: - delete[] (SQLUBIGINT *)this->bound_columns[i].buffer; - break; + case SQL_C_CHAR: + case SQL_C_BINARY: + delete[] (SQLCHAR*)this->bound_columns[i].buffer; + break; + case SQL_C_WCHAR: + delete[] (SQLWCHAR*)this->bound_columns[i].buffer; + break; + case SQL_C_DOUBLE: + delete[] (SQLDOUBLE*)this->bound_columns[i].buffer; + break; + case SQL_C_USHORT: + delete[] (SQLUSMALLINT*)this->bound_columns[i].buffer; + break; + case SQL_C_SLONG: + delete[] (SQLUINTEGER*)this->bound_columns[i].buffer; + break; + case SQL_C_UBIGINT: + delete[] (SQLUBIGINT*)this->bound_columns[i].buffer; + break; } delete[] this->columns[i]->ColumnName; @@ -300,9 +312,12 @@ typedef struct StatementData { } this->column_count = 0; - delete[] row_status_array; row_status_array = NULL; - delete[] columns; columns = NULL; - delete[] bound_columns; bound_columns = NULL; + delete[] row_status_array; + row_status_array = NULL; + delete[] columns; + columns = NULL; + delete[] bound_columns; + bound_columns = NULL; } } StatementData; @@ -311,39 +326,46 @@ size_t strlen16(const char16_t* string); class ODBC { public: - static uv_mutex_t g_odbcMutex; - static SQLHENV hEnv; + static uv_mutex_t g_odbcMutex; + static SQLHENV hEnv; - static Napi::Value Init(Napi::Env env, Napi::Object exports); + static Napi::Value Init(Napi::Env env, Napi::Object exports); - static SQLTCHAR* NapiStringToSQLTCHAR(Napi::String string); + static SQLTCHAR* NapiStringToSQLTCHAR(Napi::String string); - static void StoreBindValues(Napi::Array *values, Parameter **parameters); + static void StoreBindValues(Napi::Array* values, Parameter** parameters); - static SQLRETURN DescribeParameters(SQLHSTMT hstmt, Parameter **parameters, SQLSMALLINT parameterCount); - static SQLRETURN BindParameters(SQLHSTMT hstmt, Parameter **parameters, SQLSMALLINT parameterCount); - static Napi::Array ParametersToArray(Napi::Env env, StatementData *data); + static SQLRETURN DescribeParameters( + SQLHSTMT hstmt, Parameter** parameters, SQLSMALLINT parameterCount + ); + static SQLRETURN BindParameters( + SQLHSTMT hstmt, Parameter** parameters, SQLSMALLINT parameterCount + ); + static Napi::Array ParametersToArray(Napi::Env env, StatementData* data); - void Free(); + void Free(); - ~ODBC(); + ~ODBC(); - static Napi::Value Connect(const Napi::CallbackInfo& info); + static Napi::Value Connect(const Napi::CallbackInfo& info); }; class ODBCAsyncWorker : public Napi::AsyncWorker { public: - ODBCAsyncWorker(Napi::Function& callback); - // ~ODBCAsyncWorker(); // TODO: Delete error stuff + ODBCAsyncWorker(Napi::Function& callback); + // ~ODBCAsyncWorker(); // TODO: Delete error stuff protected: - ODBCError *errors; - SQLINTEGER errorCount = 0; - - bool CheckAndHandleErrors(SQLRETURN return_code, SQLSMALLINT handleType, SQLHANDLE handle, const char *message); - ODBCError* GetODBCErrors(SQLSMALLINT handleType, SQLHANDLE handle); - void OnError(const Napi::Error &e); + ODBCError* errors; + SQLINTEGER errorCount = 0; + + bool CheckAndHandleErrors( + SQLRETURN return_code, SQLSMALLINT handleType, SQLHANDLE handle, + const char* message + ); + ODBCError* GetODBCErrors(SQLSMALLINT handleType, SQLHANDLE handle); + void OnError(const Napi::Error& e); }; #endif diff --git a/src/odbc_connection.cpp b/src/odbc_connection.cpp index da490d1a..54861792 100644 --- a/src/odbc_connection.cpp +++ b/src/odbc_connection.cpp @@ -24,17 +24,17 @@ #define MAX_UTF8_BYTES 4 // object keys for the result object -const char* NAME = "name"; -const char* DATA_TYPE = "dataType"; +const char* NAME = "name"; +const char* DATA_TYPE = "dataType"; const char* DATA_TYPE_NAME = "dataTypeName"; -const char* COLUMN_SIZE = "columnSize"; +const char* COLUMN_SIZE = "columnSize"; const char* DECIMAL_DIGITS = "decimalDigits"; -const char* NULLABLE = "nullable"; -const char* STATEMENT = "statement"; -const char* PARAMETERS = "parameters"; -const char* RETURN = "return"; -const char* COUNT = "count"; -const char* COLUMNS = "columns"; +const char* NULLABLE = "nullable"; +const char* STATEMENT = "statement"; +const char* PARAMETERS = "parameters"; +const char* RETURN = "return"; +const char* COUNT = "count"; +const char* COLUMNS = "columns"; Napi::FunctionReference ODBCConnection::constructor; @@ -42,29 +42,38 @@ Napi::Object ODBCConnection::Init(Napi::Env env, Napi::Object exports) { Napi::HandleScope scope(env); - Napi::Function constructorFunction = DefineClass(env, "ODBCConnection", { - - InstanceMethod("close", &ODBCConnection::Close), - InstanceMethod("createStatement", &ODBCConnection::CreateStatement), - InstanceMethod("query", &ODBCConnection::Query), - InstanceMethod("cancel", &ODBCConnection::Cancel), - InstanceMethod("beginTransaction", &ODBCConnection::BeginTransaction), - InstanceMethod("commit", &ODBCConnection::Commit), - InstanceMethod("rollback", &ODBCConnection::Rollback), - InstanceMethod("callProcedure", &ODBCConnection::CallProcedure), - InstanceMethod("getUsername", &ODBCConnection::GetUsername), - InstanceMethod("primaryKeys", &ODBCConnection::PrimaryKeys), - InstanceMethod("foreignKeys", &ODBCConnection::ForeignKeys), - InstanceMethod("tables", &ODBCConnection::Tables), - InstanceMethod("columns", &ODBCConnection::Columns), - InstanceMethod("setIsolationLevel", &ODBCConnection::SetIsolationLevel), - - InstanceAccessor("connected", &ODBCConnection::ConnectedGetter, nullptr), - InstanceAccessor("autocommit", &ODBCConnection::AutocommitGetter, nullptr), - InstanceAccessor("connectionTimeout", &ODBCConnection::ConnectionTimeoutGetter, nullptr), - InstanceAccessor("loginTimeout", &ODBCConnection::LoginTimeoutGetter, nullptr) - - }); + Napi::Function constructorFunction = DefineClass( + env, "ODBCConnection", + { + + InstanceMethod("close", &ODBCConnection::Close), + InstanceMethod("createStatement", &ODBCConnection::CreateStatement), + InstanceMethod("query", &ODBCConnection::Query), + InstanceMethod("cancel", &ODBCConnection::Cancel), + InstanceMethod("beginTransaction", &ODBCConnection::BeginTransaction), + InstanceMethod("commit", &ODBCConnection::Commit), + InstanceMethod("rollback", &ODBCConnection::Rollback), + InstanceMethod("callProcedure", &ODBCConnection::CallProcedure), + InstanceMethod("getUsername", &ODBCConnection::GetUsername), + InstanceMethod("primaryKeys", &ODBCConnection::PrimaryKeys), + InstanceMethod("foreignKeys", &ODBCConnection::ForeignKeys), + InstanceMethod("tables", &ODBCConnection::Tables), + InstanceMethod("columns", &ODBCConnection::Columns), + InstanceMethod("setIsolationLevel", &ODBCConnection::SetIsolationLevel), + + InstanceAccessor("connected", &ODBCConnection::ConnectedGetter, nullptr), + InstanceAccessor( + "autocommit", &ODBCConnection::AutocommitGetter, nullptr + ), + InstanceAccessor( + "connectionTimeout", &ODBCConnection::ConnectionTimeoutGetter, nullptr + ), + InstanceAccessor( + "loginTimeout", &ODBCConnection::LoginTimeoutGetter, nullptr + ) + + } + ); constructor = Napi::Persistent(constructorFunction); constructor.SuppressDestruct(); @@ -80,46 +89,47 @@ Napi::Object ODBCConnection::Init(Napi::Env env, Napi::Object exports) { class SetIsolationLevelAsyncWorker : public ODBCAsyncWorker { public: - SetIsolationLevelAsyncWorker(ODBCConnection *odbcConnectionObject, SQLUINTEGER isolationLevel, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject), - isolationLevel(isolationLevel){} + SetIsolationLevelAsyncWorker( + ODBCConnection* odbcConnectionObject, SQLUINTEGER isolationLevel, + Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject), + isolationLevel(isolationLevel) {} - ~SetIsolationLevelAsyncWorker() {} + ~SetIsolationLevelAsyncWorker() {} private: - ODBCConnection *odbcConnectionObject; - SQLUINTEGER isolationLevel; + ODBCConnection* odbcConnectionObject; + SQLUINTEGER isolationLevel; - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - return_code = SQLSetConnectAttr( - odbcConnectionObject->hDBC, // ConnectionHandle - SQL_ATTR_TXN_ISOLATION, // Attribute - (SQLPOINTER) (uintptr_t) isolationLevel, // ValuePtr - SQL_NTS // StringLength - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error setting isolation level\0"); - return; - } + return_code = SQLSetConnectAttr( + odbcConnectionObject->hDBC, SQL_ATTR_TXN_ISOLATION, + (SQLPOINTER)(uintptr_t)isolationLevel, SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError("[odbc] Error setting isolation level\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } }; -Napi::Value ODBCConnection::SetIsolationLevel(const Napi::CallbackInfo &info) { +Napi::Value ODBCConnection::SetIsolationLevel(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); @@ -129,11 +139,18 @@ Napi::Value ODBCConnection::SetIsolationLevel(const Napi::CallbackInfo &info) { if (!(isolationLevel & this->getInfoResults.available_isolation_levels)) { std::vector callbackArguments; - callbackArguments.push_back(Napi::Error::New(env, "Isolation level passed to setIsolationLevel is not valid for the connection!").Value()); + callbackArguments.push_back( + Napi::Error::New( + env, "Isolation level passed to setIsolationLevel is " + "not valid for the connection!" + ) + .Value() + ); callback.Call(callbackArguments); } - SetIsolationLevelAsyncWorker *worker = new SetIsolationLevelAsyncWorker(this, isolationLevel, callback); + SetIsolationLevelAsyncWorker* worker = + new SetIsolationLevelAsyncWorker(this, isolationLevel, callback); worker->Queue(); return env.Undefined(); @@ -145,36 +162,31 @@ Napi::Value ODBCConnection::AutocommitGetter(const Napi::CallbackInfo& info) { SQLINTEGER autocommit; - SQLGetConnectAttr( - this->hDBC, // ConnectionHandle - SQL_ATTR_AUTOCOMMIT, // Attribute - &autocommit, // ValuePtr - 0, // BufferLength - NULL // StringLengthPtr - ); + SQLGetConnectAttr(this->hDBC, SQL_ATTR_AUTOCOMMIT, &autocommit, 0, NULL); if (autocommit == SQL_AUTOCOMMIT_OFF) { return Napi::Boolean::New(env, false); - } else if (autocommit == SQL_AUTOCOMMIT_ON) { + } + else if (autocommit == SQL_AUTOCOMMIT_ON) { return Napi::Boolean::New(env, true); } return Napi::Boolean::New(env, false); } -ODBCConnection::ODBCConnection(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { +ODBCConnection::ODBCConnection(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { this->hENV = *(info[0].As>().Data()); this->hDBC = *(info[1].As>().Data()); - this->connectionOptions = *(info[2].As>().Data()); + this->connectionOptions = + *(info[2].As>().Data()); this->getInfoResults = *(info[3].As>().Data()); uv_mutex_init(&this->activeStatementsMutex); } - -ODBCConnection::~ODBCConnection() -{ +ODBCConnection::~ODBCConnection() { this->Free(); uv_mutex_destroy(&this->activeStatementsMutex); } @@ -195,16 +207,10 @@ SQLRETURN ODBCConnection::Free() { SQLRETURN return_code = SQL_SUCCESS; - if (this->hDBC != SQL_NULL_HANDLE) - { + if (this->hDBC != SQL_NULL_HANDLE) { { uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = - SQLFreeHandle - ( - SQL_HANDLE_DBC, - this->hDBC - ); + return_code = SQLFreeHandle(SQL_HANDLE_DBC, this->hDBC); this->hDBC = SQL_NULL_HANDLE; uv_mutex_unlock(&ODBC::g_odbcMutex); } @@ -219,13 +225,7 @@ Napi::Value ODBCConnection::ConnectedGetter(const Napi::CallbackInfo& info) { SQLINTEGER connection = SQL_CD_TRUE; - SQLGetConnectAttr( - this->hDBC, // ConnectionHandle - SQL_ATTR_CONNECTION_DEAD, // Attribute - &connection, // ValuePtr - 0, // BufferLength - NULL // StringLengthPtr - ); + SQLGetConnectAttr(this->hDBC, SQL_ATTR_CONNECTION_DEAD, &connection, 0, NULL); if (connection == SQL_CD_FALSE) { // connection dead is false return Napi::Boolean::New(env, true); @@ -234,8 +234,8 @@ Napi::Value ODBCConnection::ConnectedGetter(const Napi::CallbackInfo& info) { return Napi::Boolean::New(env, false); } -Napi::Value ODBCConnection::ConnectionTimeoutGetter(const Napi::CallbackInfo& info) -{ +Napi::Value +ODBCConnection::ConnectionTimeoutGetter(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); @@ -243,8 +243,7 @@ Napi::Value ODBCConnection::ConnectionTimeoutGetter(const Napi::CallbackInfo& in return Napi::Number::New(env, this->connectionOptions.connectionTimeout); } -Napi::Value ODBCConnection::LoginTimeoutGetter(const Napi::CallbackInfo& info) -{ +Napi::Value ODBCConnection::LoginTimeoutGetter(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); @@ -260,69 +259,73 @@ Napi::Value ODBCConnection::LoginTimeoutGetter(const Napi::CallbackInfo& info) class CloseAsyncWorker : public ODBCAsyncWorker { public: - CloseAsyncWorker(ODBCConnection *odbcConnectionObject, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject) {} + CloseAsyncWorker( + ODBCConnection* odbcConnectionObject, Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject) {} - ~CloseAsyncWorker() {} + ~CloseAsyncWorker() {} private: - ODBCConnection *odbcConnectionObject; + ODBCConnection* odbcConnectionObject; - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - uv_mutex_lock(&ODBC::g_odbcMutex); - // When closing, make sure any transactions are closed as well. Because we don't know whether - // we should commit or rollback, so we default to rollback. - if (odbcConnectionObject->hDBC != SQL_NULL_HANDLE) { - return_code = SQLEndTran( - SQL_HANDLE_DBC, // HandleType - odbcConnectionObject->hDBC, // Handle - SQL_ROLLBACK // CompletionType + uv_mutex_lock(&ODBC::g_odbcMutex); + // When closing, make sure any transactions are closed as well. Because we + // don't know whether we should commit or rollback, so we default to + // rollback. + if (odbcConnectionObject->hDBC != SQL_NULL_HANDLE) { + return_code = + SQLEndTran(SQL_HANDLE_DBC, odbcConnectionObject->hDBC, SQL_ROLLBACK); + if (!SQL_SUCCEEDED(return_code)) { + uv_mutex_unlock(&ODBC::g_odbcMutex); + this->errors = + GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError( + "[odbc] Error ending potential transactions when closing the " + "connection\0" ); - if (!SQL_SUCCEEDED(return_code)) { - uv_mutex_unlock(&ODBC::g_odbcMutex); - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error ending potential transactions when closing the connection\0"); - return; - } + return; + } - return_code = SQLDisconnect( - odbcConnectionObject->hDBC // ConnectionHandle - ); - if (!SQL_SUCCEEDED(return_code)) { - uv_mutex_unlock(&ODBC::g_odbcMutex); - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error disconnecting when closing the connection\0"); - return; - } + return_code = SQLDisconnect(odbcConnectionObject->hDBC); + if (!SQL_SUCCEEDED(return_code)) { + uv_mutex_unlock(&ODBC::g_odbcMutex); + this->errors = + GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError("[odbc] Error disconnecting when closing the connection\0"); + return; + } - return_code = SQLFreeHandle( - SQL_HANDLE_DBC, // HandleType - odbcConnectionObject->hDBC // Handle + return_code = SQLFreeHandle(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + if (!SQL_SUCCEEDED(return_code)) { + uv_mutex_unlock(&ODBC::g_odbcMutex); + this->errors = + GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError( + "[odbc] Error freeing connection handle when closing the " + "connection\0" ); - if (!SQL_SUCCEEDED(return_code)) { - uv_mutex_unlock(&ODBC::g_odbcMutex); - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error freeing connection handle when closing the connection\0"); - return; - } - odbcConnectionObject->hDBC = SQL_NULL_HANDLE; + return; } - uv_mutex_unlock(&ODBC::g_odbcMutex); + odbcConnectionObject->hDBC = SQL_NULL_HANDLE; } + uv_mutex_unlock(&ODBC::g_odbcMutex); + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } }; /* @@ -352,7 +355,7 @@ Napi::Value ODBCConnection::Close(const Napi::CallbackInfo& info) { Napi::Function callback = info[0].As(); - CloseAsyncWorker *worker = new CloseAsyncWorker(this, callback); + CloseAsyncWorker* worker = new CloseAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); @@ -366,54 +369,54 @@ Napi::Value ODBCConnection::Close(const Napi::CallbackInfo& info) { class CreateStatementAsyncWorker : public ODBCAsyncWorker { private: - ODBCConnection *odbcConnectionObject; - HSTMT hstmt; + ODBCConnection* odbcConnectionObject; + HSTMT hstmt; - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = - SQLAllocHandle - ( - SQL_HANDLE_STMT, // HandleType - odbcConnectionObject->hDBC, // InputHandle - &hstmt // OutputHandlePtr - ); - uv_mutex_unlock(&ODBC::g_odbcMutex); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, hstmt); - SetError("[odbc] Error allocating a handle to create a new Statement\0"); - return; - } + uv_mutex_lock(&ODBC::g_odbcMutex); + return_code = + SQLAllocHandle(SQL_HANDLE_STMT, odbcConnectionObject->hDBC, &hstmt); + uv_mutex_unlock(&ODBC::g_odbcMutex); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, hstmt); + SetError("[odbc] Error allocating a handle to create a new Statement\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - // arguments for the ODBCStatement constructor - std::vector statementArguments; - statementArguments.push_back(Napi::External::New(env, odbcConnectionObject)); - statementArguments.push_back(Napi::External::New(env, &hstmt)); + // arguments for the ODBCStatement constructor + std::vector statementArguments; + statementArguments.push_back( + Napi::External::New(env, odbcConnectionObject) + ); + statementArguments.push_back(Napi::External::New(env, &hstmt)); - // create a new ODBCStatement object as a Napi::Value - Napi::Value statementObject = ODBCStatement::constructor.New(statementArguments); + // create a new ODBCStatement object as a Napi::Value + Napi::Value statementObject = + ODBCStatement::constructor.New(statementArguments); - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); // callbackArguments[0] - callbackArguments.push_back(statementObject); // callbackArguments[1] + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); // callbackArguments[0] + callbackArguments.push_back(statementObject); // callbackArguments[1] - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } public: - CreateStatementAsyncWorker(ODBCConnection *odbcConnectionObject, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject) {} + CreateStatementAsyncWorker( + ODBCConnection* odbcConnectionObject, Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject) {} - ~CreateStatementAsyncWorker() {} + ~CreateStatementAsyncWorker() {} }; /* @@ -444,7 +447,8 @@ Napi::Value ODBCConnection::CreateStatement(const Napi::CallbackInfo& info) { Napi::Function callback = info[0].As(); - CreateStatementAsyncWorker *worker = new CreateStatementAsyncWorker(this, callback); + CreateStatementAsyncWorker* worker = + new CreateStatementAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); @@ -454,36 +458,32 @@ Napi::Value ODBCConnection::CreateStatement(const Napi::CallbackInfo& info) { ********************************** QUERY ************************************* *****************************************************************************/ -Napi::Value -parse_query_options -( - Napi::Env env, - Napi::Value options_value, - QueryOptions *query_options -) -{ +Napi::Value parse_query_options( + Napi::Env env, Napi::Value options_value, QueryOptions* query_options +) { query_options->reset(); - if (options_value.IsNull()) - { + if (options_value.IsNull()) { return env.Null(); } Napi::Object options_object = options_value.As(); // .cursor property - if (options_object.HasOwnProperty(QueryOptions::CURSOR_PROPERTY)) - { + if (options_object.HasOwnProperty(QueryOptions::CURSOR_PROPERTY)) { Napi::Value cursor_value = options_object.Get(QueryOptions::CURSOR_PROPERTY); - - if (!(cursor_value.IsString() || cursor_value.IsBoolean())) - { - return Napi::TypeError::New(env, std::string("Connection.query options: .") + QueryOptions::CURSOR_PROPERTY + " must be a STRING or BOOLEAN value.").Value(); + + if (!(cursor_value.IsString() || cursor_value.IsBoolean())) { + return Napi::TypeError::New( + env, std::string("Connection.query options: .") + + QueryOptions::CURSOR_PROPERTY + + " must be a STRING or BOOLEAN value." + ) + .Value(); } - - if (cursor_value.IsString()) - { + + if (cursor_value.IsString()) { query_options->use_cursor = true; #ifdef UNICODE std::u16string cursor_string; @@ -492,83 +492,106 @@ parse_query_options std::string cursor_string; cursor_string = cursor_value.As().Utf8Value(); #endif - query_options->cursor_name = (SQLTCHAR *)cursor_string.c_str(); + query_options->cursor_name = (SQLTCHAR*)cursor_string.c_str(); query_options->cursor_name_length = cursor_string.length(); } - else if (cursor_value.IsBoolean()) - { - query_options->use_cursor = cursor_value.As().Value(); + else if (cursor_value.IsBoolean()) { + query_options->use_cursor = cursor_value.As().Value(); query_options->cursor_name = NULL; } } // END .cursor property // .fetchSize property - if (options_object.HasOwnProperty(QueryOptions::FETCH_SIZE_PROPERTY)) - { + if (options_object.HasOwnProperty(QueryOptions::FETCH_SIZE_PROPERTY)) { Napi::Value fetch_size_value = options_object.Get(QueryOptions::FETCH_SIZE_PROPERTY); - if (!fetch_size_value.IsNumber()) - { - return Napi::TypeError::New(env, std::string("Connection.query options: .") + QueryOptions::FETCH_SIZE_PROPERTY + " must be a NUMBER value.").Value(); + if (!fetch_size_value.IsNumber()) { + return Napi::TypeError::New( + env, std::string("Connection.query options: .") + + QueryOptions::FETCH_SIZE_PROPERTY + + " must be a NUMBER value." + ) + .Value(); } int64_t temp_value = fetch_size_value.As().Int64Value(); - if (temp_value < 1) - { - return Napi::RangeError::New(env, std::string("Connection.query options: .") + QueryOptions::FETCH_SIZE_PROPERTY + " must be greater than 0.").Value(); + if (temp_value < 1) { + return Napi::RangeError::New( + env, std::string("Connection.query options: .") + + QueryOptions::FETCH_SIZE_PROPERTY + + " must be greater than 0." + ) + .Value(); } // even if the user didn't explicitly set use_cursor to true, if they are // passing a fetch size, it should be assumed. query_options->use_cursor = true; - query_options->fetch_size = (SQLULEN) temp_value; + query_options->fetch_size = (SQLULEN)temp_value; } // END .fetchSize property // .timeout property - if (options_object.HasOwnProperty(QueryOptions::TIMEOUT_PROPERTY)) - { + if (options_object.HasOwnProperty(QueryOptions::TIMEOUT_PROPERTY)) { Napi::Value timeout_value = options_object.Get(QueryOptions::TIMEOUT_PROPERTY); - if (!timeout_value.IsNumber()) - { - return Napi::TypeError::New(env, std::string("Connection.query options: .") + QueryOptions::TIMEOUT_PROPERTY + " must be a NUMBER value.").Value(); + if (!timeout_value.IsNumber()) { + return Napi::TypeError::New( + env, std::string("Connection.query options: .") + + QueryOptions::TIMEOUT_PROPERTY + + " must be a NUMBER value." + ) + .Value(); } int64_t temp_value = timeout_value.As().Int64Value(); - if (temp_value < 1) - { - return Napi::RangeError::New(env, std::string("Connection.query options: .") + QueryOptions::TIMEOUT_PROPERTY + " must be greater than 0.").Value(); + if (temp_value < 1) { + return Napi::RangeError::New( + env, std::string("Connection.query options: .") + + QueryOptions::TIMEOUT_PROPERTY + + " must be greater than 0." + ) + .Value(); } - query_options->timeout = (SQLULEN) temp_value; + query_options->timeout = (SQLULEN)temp_value; } // END .timeout property // .initialBufferSize property - if (options_object.HasOwnProperty(QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY)) - { + if (options_object.HasOwnProperty( + QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY + )) { Napi::Value initial_long_data_buffer_size_value = options_object.Get(QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY); - if (!initial_long_data_buffer_size_value.IsNumber()) - { - return Napi::TypeError::New(env, std::string("Connection.query options: .") + QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY + " must be a NUMBER value.").Value(); + if (!initial_long_data_buffer_size_value.IsNumber()) { + return Napi::TypeError::New( + env, std::string("Connection.query options: .") + + QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY + + " must be a NUMBER value." + ) + .Value(); } - int64_t temp_value = initial_long_data_buffer_size_value.As().Int64Value(); + int64_t temp_value = + initial_long_data_buffer_size_value.As().Int64Value(); - if (temp_value < 1) - { - return Napi::RangeError::New(env, std::string("Connection.query options: .") + QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY + " must be greater than 0.").Value(); + if (temp_value < 1) { + return Napi::RangeError::New( + env, std::string("Connection.query options: .") + + QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY + + " must be greater than 0." + ) + .Value(); } - query_options->initial_long_data_buffer_size = (SQLLEN) temp_value; + query_options->initial_long_data_buffer_size = (SQLLEN)temp_value; } // END .initialBufferSize @@ -576,71 +599,49 @@ parse_query_options } SQLRETURN -set_fetch_size -( - StatementData *data, - SQLULEN fetch_size -) -{ +set_fetch_size(StatementData* data, SQLULEN fetch_size) { SQLRETURN return_code; - return_code = - SQLSetStmtAttr - ( - data->hstmt, - SQL_ATTR_ROW_ARRAY_SIZE, - (SQLPOINTER) fetch_size, - 0 + return_code = SQLSetStmtAttr( + data->hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)fetch_size, 0 ); data->fetch_size = fetch_size; - if (!SQL_SUCCEEDED(return_code)) - { + if (!SQL_SUCCEEDED(return_code)) { // Some drivers don't feel the need to implement the // SQL_ATTR_ROW_ARRAY_SIZE option for SQLSetStmtAttr, so this call will // return an error. Instead of returning an error, first check that the // fetch size wasn't set to something bigger than 1, then continue. If the // user set the fetch size, then throw an error to let them know that they // should rerun with a fetch size of 1. - if (fetch_size != 1) - { + if (fetch_size != 1) { // The user set a fetch size, but their driver doesn't support this call. return return_code; } - SQLRETURN diagnostic_return_code; + SQLRETURN diagnostic_return_code; SQLSMALLINT textLength; - SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1]; - SQLINTEGER nativeError; - SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES]; - - diagnostic_return_code = - SQLGetDiagRec - ( - SQL_HANDLE_STMT, // HandleType - data->hstmt, // Handle - 1, // RecNumber - errorSQLState, // SQLState - &nativeError, // NativeErrorPtr - errorMessage, // MessageText - ERROR_MESSAGE_BUFFER_CHARS, // BufferLength - &textLength // TextLengthPtr + SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER nativeError; + SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES]; + + diagnostic_return_code = SQLGetDiagRec( + SQL_HANDLE_STMT, data->hstmt, 1, errorSQLState, &nativeError, + errorMessage, ERROR_MESSAGE_BUFFER_CHARS, &textLength ); - if (!SQL_SUCCEEDED(diagnostic_return_code)) - { + if (!SQL_SUCCEEDED(diagnostic_return_code)) { return return_code; } // HY092 is the SQLState produced by drivers that are known to not implement // SQL_ATTR_ROW_ARRAY_SIZE. Set that return code to SQL_SUCCESS, since fetch // size was 1 anyways. We will set simple_binding below - if (strcmp("HY092", (const char*)errorSQLState) == 0) - { + if (strcmp("HY092", (const char*)errorSQLState) == 0) { return_code = SQL_SUCCESS; } - + return return_code; } @@ -650,78 +651,50 @@ set_fetch_size // SQL_SUCCESS_WITH_INFO, check if the SQLSTATE is 01S02, and then get the // substituted value to store. We don't save off any other warnings, just // check for that particular SQLSTATE. - if (return_code == SQL_SUCCESS_WITH_INFO) - { - SQLRETURN diagnostic_return_code; + if (return_code == SQL_SUCCESS_WITH_INFO) { + SQLRETURN diagnostic_return_code; SQLSMALLINT textLength; - SQLINTEGER statusRecCount; - SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1]; - SQLINTEGER nativeError; - SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES]; - - diagnostic_return_code = - SQLGetDiagField ( - SQL_HANDLE_STMT, // HandleType - data->hstmt, // Handle - 0, // RecNumber - SQL_DIAG_NUMBER, // DiagIdentifier - &statusRecCount, // DiagInfoPtr - SQL_IS_INTEGER, // BufferLength - NULL // StringLengthPtr + SQLINTEGER statusRecCount; + SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER nativeError; + SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES]; + + diagnostic_return_code = SQLGetDiagField( + SQL_HANDLE_STMT, data->hstmt, 0, SQL_DIAG_NUMBER, &statusRecCount, + SQL_IS_INTEGER, NULL ); for (SQLSMALLINT i = 0; i < statusRecCount; i++) { - diagnostic_return_code = - SQLGetDiagRec - ( - SQL_HANDLE_STMT, // HandleType - data->hstmt, // Handle - i + 1, // RecNumber - errorSQLState, // SQLState - &nativeError, // NativeErrorPtr - errorMessage, // MessageText - ERROR_MESSAGE_BUFFER_CHARS, // BufferLength - &textLength // TextLengthPtr + diagnostic_return_code = SQLGetDiagRec( + SQL_HANDLE_STMT, data->hstmt, i + 1, errorSQLState, &nativeError, + errorMessage, ERROR_MESSAGE_BUFFER_CHARS, &textLength ); - if (SQL_SUCCEEDED(diagnostic_return_code)) - { - if (strcmp("01S02", (const char*)errorSQLState) == 0) - { + if (SQL_SUCCEEDED(diagnostic_return_code)) { + if (strcmp("01S02", (const char*)errorSQLState) == 0) { // We found the SQLSTATE we were looking for, need to get the // driver-substituted vaue - return_code = - SQLGetStmtAttr - ( - data->hstmt, - SQL_ATTR_ROW_ARRAY_SIZE, - (SQLPOINTER) &data->fetch_size, - SQL_IS_INTEGER, - IGNORED_PARAMETER + return_code = SQLGetStmtAttr( + data->hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)&data->fetch_size, + SQL_IS_INTEGER, IGNORED_PARAMETER ); - if (!SQL_SUCCEEDED(return_code)) - { + if (!SQL_SUCCEEDED(return_code)) { return return_code; } } - } else { + } + else { return return_code; } } } - data->row_status_array = - new SQLUSMALLINT[fetch_size](); + data->row_status_array = new SQLUSMALLINT[fetch_size](); - return_code = - SQLSetStmtAttr - ( - data->hstmt, - SQL_ATTR_ROW_STATUS_PTR, - (SQLPOINTER) data->row_status_array, - 0 + return_code = SQLSetStmtAttr( + data->hstmt, SQL_ATTR_ROW_STATUS_PTR, (SQLPOINTER)data->row_status_array, 0 ); return return_code; @@ -731,307 +704,255 @@ set_fetch_size class QueryAsyncWorker : public ODBCAsyncWorker { private: + ODBCConnection* odbcConnectionObject; + Napi::Reference napiParameters; + StatementData* data; - ODBCConnection *odbcConnectionObject; - Napi::Reference napiParameters; - StatementData *data; + void Execute() { - void Execute() { + SQLRETURN return_code; - SQLRETURN return_code; - - // allocate a new statement handle - uv_mutex_lock(&ODBC::g_odbcMutex); - if (odbcConnectionObject->hDBC == SQL_NULL_HANDLE) { - uv_mutex_unlock(&ODBC::g_odbcMutex); - SetError("[odbc] Database connection handle was no longer valid. Cannot run a query after closing the connection."); + // allocate a new statement handle + uv_mutex_lock(&ODBC::g_odbcMutex); + if (odbcConnectionObject->hDBC == SQL_NULL_HANDLE) { + uv_mutex_unlock(&ODBC::g_odbcMutex); + SetError( + "[odbc] Database connection handle was no longer valid. Cannot " + "run a query after closing the connection." + ); + return; + } + else { + return_code = SQLAllocHandle( + SQL_HANDLE_STMT, odbcConnectionObject->hDBC, &(data->hstmt) + ); + uv_mutex_unlock(&ODBC::g_odbcMutex); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = + GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError("[odbc] Error allocating a handle to run the SQL statement\0"); return; - } else { - return_code = SQLAllocHandle( - SQL_HANDLE_STMT, // HandleType - odbcConnectionObject->hDBC, // InputHandle - &(data->hstmt) // OutputHandlePtr + } + + // make the handle reachable by connection.cancel() while this worker + // is executing + odbcConnectionObject->RegisterActiveStatement(data->hstmt); + + // set SQL_ATTR_QUERY_TIMEOUT + if (data->query_options.timeout > 0) { + return_code = SQLSetStmtAttr( + data->hstmt, SQL_ATTR_QUERY_TIMEOUT, + (SQLPOINTER)data->query_options.timeout, IGNORED_PARAMETER ); - uv_mutex_unlock(&ODBC::g_odbcMutex); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error allocating a handle to run the SQL statement\0"); - return; - } - // make the handle reachable by connection.cancel() while this worker - // is executing - odbcConnectionObject->RegisterActiveStatement(data->hstmt); - - // set SQL_ATTR_QUERY_TIMEOUT - if (data->query_options.timeout > 0) { - return_code = - SQLSetStmtAttr - ( - data->hstmt, - SQL_ATTR_QUERY_TIMEOUT, - (SQLPOINTER) data->query_options.timeout, + // It is possible that SQLSetStmtAttr returns a warning with SQLSTATE + // 01S02, indicating that the driver changed the value specified. + // Although we never use the timeout variable again (and so we don't + // REALLY need it to be correct in the code), its just good to have + // the correct value if we need it. + if (return_code == SQL_SUCCESS_WITH_INFO) { + return_code = SQLGetStmtAttr( + data->hstmt, SQL_ATTR_QUERY_TIMEOUT, + (SQLPOINTER)&data->query_options.timeout, SQL_IS_UINTEGER, IGNORED_PARAMETER ); + } - // It is possible that SQLSetStmtAttr returns a warning with SQLSTATE - // 01S02, indicating that the driver changed the value specified. - // Although we never use the timeout variable again (and so we don't - // REALLY need it to be correct in the code), its just good to have - // the correct value if we need it. - if (return_code == SQL_SUCCESS_WITH_INFO) - { - return_code = - SQLGetStmtAttr - ( - data->hstmt, - SQL_ATTR_QUERY_TIMEOUT, - (SQLPOINTER) &data->query_options.timeout, - SQL_IS_UINTEGER, - IGNORED_PARAMETER - ); - } - - // Both of the SQL_ATTR_QUERY_TIMEOUT calls are combined here - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error setting the query timeout on the statement\0"); - return; - } + // Both of the SQL_ATTR_QUERY_TIMEOUT calls are combined here + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error setting the query timeout on the statement\0"); + return; } + } - // querying with parameters, need to prepare, bind, execute - if (data->parameterCount > 0) { - // binds all parameters to the query - return_code = - SQLPrepare - ( - data->hstmt, - data->sql, - SQL_NTS - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error preparing the SQL statement\0"); - return; - } + // querying with parameters, need to prepare, bind, execute + if (data->parameterCount > 0) { + // binds all parameters to the query + return_code = SQLPrepare(data->hstmt, data->sql, SQL_NTS); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error preparing the SQL statement\0"); + return; + } - SQLSMALLINT parameterMarkerCount; + SQLSMALLINT parameterMarkerCount; - return_code = - SQLNumParams - ( - data->hstmt, - ¶meterMarkerCount + return_code = SQLNumParams(data->hstmt, ¶meterMarkerCount); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError( + "[odbc] Error getting information about the number of " + "parameter markers in the statment\0" ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error getting information about the number of parameter markers in the statment\0"); - return; - } + return; + } - if (parameterMarkerCount != data->parameterCount) { - SetError("[odbc] The number of parameter markers in the statement does not equal the number of bind values passed to the function."); - return; - } + if (parameterMarkerCount != data->parameterCount) { + SetError( + "[odbc] The number of parameter markers in the statement does not " + "equal the number of bind values passed to the function." + ); + return; + } - return_code = ODBC::DescribeParameters(data->hstmt, data->parameters, data->parameterCount); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error getting information about parameters\0"); - return; - } + return_code = ODBC::DescribeParameters( + data->hstmt, data->parameters, data->parameterCount + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error getting information about parameters\0"); + return; + } - return_code = ODBC::BindParameters(data->hstmt, data->parameters, data->parameterCount); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error binding parameters\0"); - return; - } + return_code = ODBC::BindParameters( + data->hstmt, data->parameters, data->parameterCount + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error binding parameters\0"); + return; + } - return_code = SQLExecute(data->hstmt); - if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error executing the sql statement\0"); - return; - } + return_code = SQLExecute(data->hstmt); + if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error executing the sql statement\0"); + return; } - // querying without parameters, can just use SQLExecDirect - else { - return_code = - SQLExecDirect - ( - data->hstmt, - data->sql, - SQL_NTS - ); - if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error executing the sql statement\0"); - return; - } + } + // querying without parameters, can just use SQLExecDirect + else { + return_code = SQLExecDirect(data->hstmt, data->sql, SQL_NTS); + if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error executing the sql statement\0"); + return; } + } - if (return_code != SQL_NO_DATA) { - - if (data->query_options.use_cursor) - { - if (data->query_options.cursor_name != NULL) - { - return_code = - SQLSetCursorName - ( - data->hstmt, - data->query_options.cursor_name, - data->query_options.cursor_name_length - ); + if (return_code != SQL_NO_DATA) { - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error setting the cursor name on the statement\0"); - return; - } + if (data->query_options.use_cursor) { + if (data->query_options.cursor_name != NULL) { + return_code = SQLSetCursorName( + data->hstmt, data->query_options.cursor_name, + data->query_options.cursor_name_length + ); + + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError( + "[odbc] Error setting the cursor name on the statement\0" + ); + return; } } + } - return_code = - set_fetch_size - ( - data, - data->query_options.fetch_size - ); + return_code = set_fetch_size(data, data->query_options.fetch_size); - // set_fetch_size will swallow errors in the case that the driver - // doesn't implement SQL_ATTR_ROW_ARRAY_SIZE for SQLSetStmtAttr and - // the fetch size was 1. If the fetch size was set by the user to a - // value greater than 1, throw an error. - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error setting the fetch size on the statement\0"); + // set_fetch_size will swallow errors in the case that the driver + // doesn't implement SQL_ATTR_ROW_ARRAY_SIZE for SQLSetStmtAttr and + // the fetch size was 1. If the fetch size was set by the user to a + // value greater than 1, throw an error. + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error setting the fetch size on the statement\0"); + return; + } + + return_code = prepare_for_fetch(data); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error preparing for fetch\0"); + return; + } + + if (!data->query_options.use_cursor) { + bool alloc_error = false; + return_code = fetch_all_and_store(data, true, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when " + "fetching data. No ODBC error information available.\0" + ); return; } - - return_code = - prepare_for_fetch - ( - data - ); if (!SQL_SUCCEEDED(return_code)) { this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error preparing for fetch\0"); - return; - } - - - if (!data->query_options.use_cursor) - { - bool alloc_error = false; - return_code = - fetch_all_and_store - ( - data, - true, - &alloc_error + SetError( + "[odbc] Error retrieving the result set from the statement\0" ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving the result set from the statement\0"); - return; - } + return; } } } } + } - void OnOK() { - - Napi::Env env = Env(); - Napi::HandleScope scope(env); - - std::vector callbackArguments; - - if (data->query_options.use_cursor) - { - // arguments for the ODBCCursor constructor - std::vector cursor_arguments = - { - Napi::External::New(env, data), - Napi::External::New(env, odbcConnectionObject), - napiParameters.Value(), - Napi::Boolean::New(env, true) - }; - - // create a new ODBCCursor object as a Napi::Value - Napi::Value cursorObject = ODBCCursor::constructor.New(cursor_arguments); - - // return cursor - std::vector callbackArguments = - { - env.Null(), - cursorObject - }; - - Callback().Call(callbackArguments); - } - else - { - Napi::Array rows = process_data_for_napi(env, data, napiParameters.Value()); - - std::vector callbackArguments = - { - env.Null(), - rows - }; - - // return results - Callback().Call(callbackArguments); - } + void OnOK() { - return; + Napi::Env env = Env(); + Napi::HandleScope scope(env); + + std::vector callbackArguments; + + if (data->query_options.use_cursor) { + // arguments for the ODBCCursor constructor + std::vector cursor_arguments = { + Napi::External::New(env, data), + Napi::External::New(env, odbcConnectionObject), + napiParameters.Value(), Napi::Boolean::New(env, true) + }; + + // create a new ODBCCursor object as a Napi::Value + Napi::Value cursorObject = ODBCCursor::constructor.New(cursor_arguments); + + // return cursor + std::vector callbackArguments = { env.Null(), cursorObject }; + + Callback().Call(callbackArguments); } + else { + Napi::Array rows = + process_data_for_napi(env, data, napiParameters.Value()); - public: - QueryAsyncWorker - ( - ODBCConnection *odbcConnectionObject, - Napi::Array napiParameterArray, - StatementData *data, - Napi::Function& callback - ) - : - ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject), - data(data) - { - napiParameters = Napi::Persistent(napiParameterArray.As()); + std::vector callbackArguments = { env.Null(), rows }; + + // return results + Callback().Call(callbackArguments); } - ~QueryAsyncWorker() { - odbcConnectionObject->UnregisterActiveStatement(data->hstmt); - if (!data->query_options.use_cursor) - { - uv_mutex_lock(&ODBC::g_odbcMutex); - // It is possible the connection handle has been freed, which freed the - // statement handle as well. Freeing again would cause a segfault. - if (this->odbcConnectionObject->hDBC != SQL_NULL_HANDLE) { - SQLFreeHandle - ( - SQL_HANDLE_STMT, - this->data->hstmt - ); - } - this->data->hstmt = SQL_NULL_HANDLE; - uv_mutex_unlock(&ODBC::g_odbcMutex); - delete data; - data = NULL; + return; + } + + public: + QueryAsyncWorker( + ODBCConnection* odbcConnectionObject, Napi::Array napiParameterArray, + StatementData* data, Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject), + data(data) { + napiParameters = Napi::Persistent(napiParameterArray.As()); + } + + ~QueryAsyncWorker() { + odbcConnectionObject->UnregisterActiveStatement(data->hstmt); + if (!data->query_options.use_cursor) { + uv_mutex_lock(&ODBC::g_odbcMutex); + // It is possible the connection handle has been freed, which freed the + // statement handle as well. Freeing again would cause a segfault. + if (this->odbcConnectionObject->hDBC != SQL_NULL_HANDLE) { + SQLFreeHandle(SQL_HANDLE_STMT, this->data->hstmt); } - napiParameters.Reset(); + this->data->hstmt = SQL_NULL_HANDLE; + uv_mutex_unlock(&ODBC::g_odbcMutex); + delete data; + data = NULL; } + napiParameters.Reset(); + } }; /* @@ -1061,42 +982,31 @@ Napi::Value ODBCConnection::Query(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); - StatementData *data = new StatementData(); - data->henv = this->hENV; - data->hdbc = this->hDBC; - data->fetch_array = this->connectionOptions.fetchArray; - data->maxColumnNameLength = this->getInfoResults.max_column_name_length; - data->get_data_supports = this->getInfoResults.sql_get_data_supports; - Napi::Array napiParameterArray = Napi::Array::New(env); - size_t argument_count = info.Length(); + StatementData* data = new StatementData(); + data->henv = this->hENV; + data->hdbc = this->hDBC; + data->fetch_array = this->connectionOptions.fetchArray; + data->maxColumnNameLength = this->getInfoResults.max_column_name_length; + data->get_data_supports = this->getInfoResults.sql_get_data_supports; + Napi::Array napiParameterArray = Napi::Array::New(env); + size_t argument_count = info.Length(); // For the C++ node-addon-api code, all of the function signatures must // include a callback function as the final parameter because we need to // have a function to pass to the AsyncWorker as a Callback. The JavaScript // wrapper functions enforce the correct number of arguments, so just need // to check for null/undefined in a given spot. - if - ( - argument_count != 4 || - info[0].IsNull() || - info[0].IsUndefined() || - !info[0].IsString() || - !( - info[1].IsArray() || - info[1].IsNull() || - info[1].IsUndefined() - ) || - !( - info[2].IsObject() || - info[2].IsNull() || - info[2].IsUndefined() - ) || - info[3].IsNull() || - info[3].IsUndefined() || - !info[3].IsFunction() - ) - { - Napi::TypeError::New(env, "[node-odbc]: Wrong function signature in call to Connection.query({string}, {array}[optional], {object}[optional], {function}).").ThrowAsJavaScriptException(); + if (argument_count != 4 || info[0].IsNull() || info[0].IsUndefined() || + !info[0].IsString() || + !(info[1].IsArray() || info[1].IsNull() || info[1].IsUndefined()) || + !(info[2].IsObject() || info[2].IsNull() || info[2].IsUndefined()) || + info[3].IsNull() || info[3].IsUndefined() || !info[3].IsFunction()) { + Napi::TypeError::New( + env, "[node-odbc]: Wrong function signature in call to " + "Connection.query({string}, {array}[optional], " + "{object}[optional], {function})." + ) + .ThrowAsJavaScriptException(); return env.Null(); } @@ -1108,8 +1018,7 @@ Napi::Value ODBCConnection::Query(const Napi::CallbackInfo& info) { Napi::Function callback = info[3].As(); // If info[1] is not null or undefined, it is an array holding our parameters - if (!(info[1].IsNull() || info[1].IsUndefined())) - { + if (!(info[1].IsNull() || info[1].IsUndefined())) { napiParameterArray = info[1].As(); data->parameterCount = (SQLSMALLINT)napiParameterArray.Length(); data->parameters = new Parameter*[data->parameterCount]; @@ -1119,52 +1028,28 @@ Napi::Value ODBCConnection::Query(const Napi::CallbackInfo& info) { ODBC::StoreBindValues(&napiParameterArray, data->parameters); } - Napi::Value error; + Napi::Value error; // if info[2] is not null or undefined or an array or a function, it is an // object holding the query options - if ( - ( - !info[2].IsObject() || - info[2].IsNull() || - info[2].IsUndefined() || - info[2].IsArray() || - info[2].IsFunction() - ) - ) - { - error = - parse_query_options - ( - env, - env.Null(), - &data->query_options - ); + if ((!info[2].IsObject() || info[2].IsNull() || info[2].IsUndefined() || + info[2].IsArray() || info[2].IsFunction())) { + error = parse_query_options(env, env.Null(), &data->query_options); } - else - { - error = - parse_query_options - ( - env, - info[2].As(), - &data->query_options + else { + error = parse_query_options( + env, info[2].As(), &data->query_options ); } - if (!error.IsNull()) - { + if (!error.IsNull()) { // Error when parsing the query options. Return the callback with the error - std::vector callback_argument = - { - error - }; + std::vector callback_argument = { error }; callback.Call(callback_argument); } - else - { + else { // Have parsed the arguments, now create the AsyncWorker and queue the work - QueryAsyncWorker *worker; + QueryAsyncWorker* worker; worker = new QueryAsyncWorker(this, napiParameterArray, data, callback); worker->Queue(); } @@ -1224,9 +1109,12 @@ Napi::Value ODBCConnection::Cancel(const Napi::CallbackInfo& info) { std::vector callbackArguments; if (failure_count > 0) { - Napi::Error error = Napi::Error::New(env, "[odbc] Error canceling one or more active statements"); + Napi::Error error = Napi::Error::New( + env, "[odbc] Error canceling one or more active statements" + ); callbackArguments.push_back(error.Value()); - } else { + } + else { callbackArguments.push_back(env.Null()); } callback.Call(callbackArguments); @@ -1237,74 +1125,99 @@ Napi::Value ODBCConnection::Cancel(const Napi::CallbackInfo& info) { // If we have a parameter with input/output params (e.g. calling a procedure), // then we need to take the Parameter structures of the StatementData and create // a Napi::Array from those that were overwritten. -void ODBCConnection::ParametersToArray(Napi::Reference *napiParameters, StatementData *data, unsigned char *overwriteParameters) { - Parameter **parameters = data->parameters; +void ODBCConnection::ParametersToArray( + Napi::Reference* napiParameters, StatementData* data, + unsigned char* overwriteParameters +) { + Parameter** parameters = data->parameters; Napi::Array napiArray = napiParameters->Value(); Napi::Env env = napiParameters->Env(); for (SQLSMALLINT i = 0; i < data->parameterCount; i++) { - Parameter *parameter = parameters[i]; + Parameter* parameter = parameters[i]; if (overwriteParameters[i] == 1) { Napi::Value value; // check for null data if (parameter->StrLen_or_IndPtr == SQL_NULL_DATA) { value = env.Null(); - } else { + } + else { // Create a JavaScript value based on the C type that was bound to - switch(parameter->ValueType) { - case SQL_C_DOUBLE: - value = Napi::Number::New(env, *(double*)parameter->ParameterValuePtr); - break; - case SQL_C_SLONG: - value = Napi::Number::New(env, *(SQLINTEGER*)parameter->ParameterValuePtr); - break; - case SQL_C_ULONG: - value = Napi::Number::New(env, *(SQLUINTEGER*)parameter->ParameterValuePtr); - break; - // Napi::BigInt - case SQL_C_SBIGINT: - if (parameter->isbigint == true) { - value = Napi::BigInt::New(env, *(int64_t*)parameter->ParameterValuePtr); - } else { - value = Napi::Number::New(env, *(int64_t*)parameter->ParameterValuePtr); - } - break; - case SQL_C_UBIGINT: - if (parameter->isbigint == true) { - value = Napi::BigInt::New(env, *(uint64_t*)parameter->ParameterValuePtr); - } else { - value = Napi::Number::New(env, *(uint64_t*)parameter->ParameterValuePtr); - } - break; - case SQL_C_SSHORT: - value = Napi::Number::New(env, *(signed short*)parameter->ParameterValuePtr); - break; - case SQL_C_USHORT: - value = Napi::Number::New(env, *(unsigned short*)parameter->ParameterValuePtr); - break; - // Napi::ArrayBuffer - case SQL_C_BINARY: - // value = Napi::Buffer::New(env, (SQLCHAR*)parameter->ParameterValuePtr, *parameter->StrLen_or_IndPtr); - value = Napi::Buffer::Copy(env, (SQLCHAR*)parameter->ParameterValuePtr, parameter->StrLen_or_IndPtr); - break; - // Napi::String (char16_t) - case SQL_C_WCHAR: - value = Napi::String::New(env, (const char16_t*)parameter->ParameterValuePtr, parameter->StrLen_or_IndPtr/sizeof(SQLWCHAR)); - break; - // Napi::String (char) - case SQL_C_CHAR: - default: - value = Napi::String::New(env, (const char*)parameter->ParameterValuePtr); - break; + switch (parameter->ValueType) { + case SQL_C_DOUBLE: + value = + Napi::Number::New(env, *(double*)parameter->ParameterValuePtr); + break; + case SQL_C_SLONG: + value = + Napi::Number::New(env, *(SQLINTEGER*)parameter->ParameterValuePtr); + break; + case SQL_C_ULONG: + value = + Napi::Number::New(env, *(SQLUINTEGER*)parameter->ParameterValuePtr); + break; + // Napi::BigInt + case SQL_C_SBIGINT: + if (parameter->isbigint == true) { + value = + Napi::BigInt::New(env, *(int64_t*)parameter->ParameterValuePtr); + } + else { + value = + Napi::Number::New(env, *(int64_t*)parameter->ParameterValuePtr); + } + break; + case SQL_C_UBIGINT: + if (parameter->isbigint == true) { + value = + Napi::BigInt::New(env, *(uint64_t*)parameter->ParameterValuePtr); + } + else { + value = + Napi::Number::New(env, *(uint64_t*)parameter->ParameterValuePtr); + } + break; + case SQL_C_SSHORT: + value = Napi::Number::New( + env, *(signed short*)parameter->ParameterValuePtr + ); + break; + case SQL_C_USHORT: + value = Napi::Number::New( + env, *(unsigned short*)parameter->ParameterValuePtr + ); + break; + // Napi::ArrayBuffer + case SQL_C_BINARY: + // value = Napi::Buffer::New(env, + // (SQLCHAR*)parameter->ParameterValuePtr, + // *parameter->StrLen_or_IndPtr); + value = Napi::Buffer::Copy( + env, (SQLCHAR*)parameter->ParameterValuePtr, + parameter->StrLen_or_IndPtr + ); + break; + // Napi::String (char16_t) + case SQL_C_WCHAR: + value = Napi::String::New( + env, (const char16_t*)parameter->ParameterValuePtr, + parameter->StrLen_or_IndPtr / sizeof(SQLWCHAR) + ); + break; + // Napi::String (char) + case SQL_C_CHAR: + default: + value = + Napi::String::New(env, (const char*)parameter->ParameterValuePtr); + break; } } napiArray.Set(i, value); - } + } } } - /****************************************************************************** ***************************** CALL PROCEDURE ********************************* *****************************************************************************/ @@ -1313,692 +1226,677 @@ void ODBCConnection::ParametersToArray(Napi::Reference *napiParamet class CallProcedureAsyncWorker : public ODBCAsyncWorker { private: + ODBCConnection* odbcConnectionObject; + Napi::Reference napiParameters; + StatementData* data; + unsigned char* overwriteParams { nullptr }; - ODBCConnection *odbcConnectionObject; - Napi::Reference napiParameters; - StatementData *data; - unsigned char *overwriteParams {nullptr}; - - void Execute() { - - SQLRETURN return_code; - - #ifndef UNICODE - char *combinedProcedureName = new char[1024](); - snprintf ( - combinedProcedureName, - 1024, - "%s%s%s%s%s", - data->catalog ? (const char*)data->catalog : "", - data->catalog ? "." : "", - data->schema ? (const char*)data->schema : "", - data->schema ? "." : "", - data->procedure - ); - #else - wchar_t *combinedProcedureName = new wchar_t[1024](); - swprintf ( - combinedProcedureName, - 1024, - L"%s%s%s%s%s", - data->catalog ? data->catalog : L"", - data->catalog ? L"." : L"", - data->schema ? data->schema : L"", - data->schema ? L"." : L"", - data->procedure - ); - #endif + void Execute() { - // allocate a new statement handle - uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = - SQLAllocHandle - ( - SQL_HANDLE_STMT, // HandleType - odbcConnectionObject->hDBC, // InputHandle - &data->hstmt // OutputHandlePtr + SQLRETURN return_code; + +#ifndef UNICODE + char* combinedProcedureName = new char[1024](); + snprintf( + combinedProcedureName, 1024, "%s%s%s%s%s", + data->catalog ? (const char*)data->catalog : "", data->catalog ? "." : "", + data->schema ? (const char*)data->schema : "", data->schema ? "." : "", + data->procedure + ); +#else + wchar_t* combinedProcedureName = new wchar_t[1024](); + swprintf( + combinedProcedureName, 1024, L"%s%s%s%s%s", + data->catalog ? data->catalog : L"", data->catalog ? L"." : L"", + data->schema ? data->schema : L"", data->schema ? L"." : L"", + data->procedure + ); +#endif + + // allocate a new statement handle + uv_mutex_lock(&ODBC::g_odbcMutex); + return_code = + SQLAllocHandle(SQL_HANDLE_STMT, odbcConnectionObject->hDBC, &data->hstmt); + uv_mutex_unlock(&ODBC::g_odbcMutex); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError( + "[odbc] Error allocating a statment handle to get procedure " + "information\0" ); - uv_mutex_unlock(&ODBC::g_odbcMutex); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error allocating a statment handle to get procedure information\0"); - return; - } + return; + } - // make the handle reachable by connection.cancel() while this worker - // is executing - odbcConnectionObject->RegisterActiveStatement(data->hstmt); + // make the handle reachable by connection.cancel() while this worker + // is executing + odbcConnectionObject->RegisterActiveStatement(data->hstmt); - return_code = - set_fetch_size - ( - data, - 1 + return_code = set_fetch_size(data, 1); + + return_code = SQLProcedures( + data->hstmt, data->catalog, SQL_NTS, data->schema, SQL_NTS, + data->procedure, SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError( + "[odbc] Error retrieving information about the procedures in " + "the database\0" ); + return; + } - return_code = - SQLProcedures - ( - data->hstmt, // StatementHandle - data->catalog, // CatalogName - SQL_NTS, // NameLengh1 - data->schema, // SchemaName - SQL_NTS, // NameLength2 - data->procedure, // ProcName - SQL_NTS // NameLength3 + return_code = prepare_for_fetch(data); + bool alloc_error = false; + return_code = fetch_all_and_store(data, false, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when fetching " + "data. No ODBC error information available.\0" ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving information about the procedures in the database\0"); - return; - } + return; + } + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error retrieving information about procedures\0"); + return; + } - - return_code = prepare_for_fetch(data); - bool alloc_error = false; - return_code = - fetch_all_and_store - ( - data, - false, - &alloc_error + if (data->storedRows.size() == 0) { + char errorString[255]; +#ifndef UNICODE + snprintf( + errorString, sizeof(errorString), + "[odbc] CallProcedureAsyncWorker::Execute: Stored procedure " + "'%s' doesn't exist", + combinedProcedureName ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving information about procedures\0"); - return; - } +#else + snprintf( + errorString, sizeof(errorString), + "[odbc] CallProcedureAsyncWorker::Execute: Stored procedure " + "'%S' doesn't exist", + combinedProcedureName + ); +#endif + SetError(errorString); + return; + } - if (data->storedRows.size() == 0) { - char errorString[255]; - #ifndef UNICODE - snprintf(errorString, sizeof(errorString), "[odbc] CallProcedureAsyncWorker::Execute: Stored procedure '%s' doesn't exist", combinedProcedureName); - #else - snprintf(errorString, sizeof(errorString), "[odbc] CallProcedureAsyncWorker::Execute: Stored procedure '%S' doesn't exist", combinedProcedureName); - #endif - SetError(errorString); - return; - } + data->deleteColumns(); // delete data in columns for next result set - data->deleteColumns(); // delete data in columns for next result set + return_code = set_fetch_size(data, 1); - return_code = - set_fetch_size - ( - data, - 1 + return_code = SQLProcedureColumns( + data->hstmt, data->catalog, SQL_NTS, data->schema, SQL_NTS, + data->procedure, SQL_NTS, NULL, SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError( + "[odbc] Error retrieving information about the columns in the " + "procedure\0" ); + return; + } - return_code = - SQLProcedureColumns - ( - data->hstmt, // StatementHandle - data->catalog, // CatalogName - SQL_NTS, // NameLengh1 - data->schema, // SchemaName - SQL_NTS, // NameLength2 - data->procedure, // ProcName - SQL_NTS, // NameLength3 - NULL, // ColumnName - SQL_NTS // NameLength4 + return_code = prepare_for_fetch(data); + return_code = fetch_all_and_store(data, false, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when fetching " + "data. No ODBC error information available.\0" ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving information about the columns in the procedure\0"); - return; - } + return; + } + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError( + "[odbc] Error retrieving the result set containing information " + "about the columns in the procedure\0" + ); + return; + } - return_code = prepare_for_fetch(data); - return_code = - fetch_all_and_store - ( - data, - false, - &alloc_error + if (data->parameterCount != (SQLSMALLINT)data->storedRows.size()) { + SetError( + "[odbc] The number of parameters the procedure expects and and " + "the number of passed parameters is not equal\0" ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving the result set containing information about the columns in the procedure\0"); - return; - } + return; + } - if (data->parameterCount != (SQLSMALLINT)data->storedRows.size()) { - SetError("[odbc] The number of parameters the procedure expects and and the number of passed parameters is not equal\0"); - return; - } +#define SQLPROCEDURECOLUMNS_COLUMN_TYPE_INDEX 4 +#define SQLPROCEDURECOLUMNS_DATA_TYPE_INDEX 5 +#define SQLPROCEDURECOLUMNS_COLUMN_SIZE_INDEX 7 +#define SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX 9 +#define SQLPROCEDURECOLUMNS_NULLABLE_INDEX 11 + + // get stored column parameter data from the result set + + for (int i = 0; i < data->parameterCount; i++) { + + Parameter* parameter = data->parameters[i]; + + data->parameters[i]->InputOutputType = + data->storedRows[i][SQLPROCEDURECOLUMNS_COLUMN_TYPE_INDEX] + .smallint_data; + data->parameters[i]->ParameterType = + data->storedRows[i][SQLPROCEDURECOLUMNS_DATA_TYPE_INDEX] + .smallint_data; // DataType -> ParameterType + data->parameters[i]->ColumnSize = + data->storedRows[i][SQLPROCEDURECOLUMNS_COLUMN_SIZE_INDEX] + .integer_data; // ParameterSize -> ColumnSize + data->parameters[i]->Nullable = + data->storedRows[i][SQLPROCEDURECOLUMNS_NULLABLE_INDEX].smallint_data; + + // For each parameter, need to manipulate the data buffer and C type + // depending on what the InputOutputType is: + // + // SQL_PARAM_INPUT: + // No changes required, data should be able to be parsed based on + // the C type defined when the parameters were read from JavaScript + // + // SQL_PARAM_INPUT_OUTPUT: + // Need to preserve the data so that it can be sent in correctly, + // but also need to ensure that the buffer is large enough to return + // any value returned on the out portion. Also, preserve the bind type + // on both the input and output: + // + // e.g. A user sends in a character string for a BIGINT field. Resize + // the buffer for the character string to hold any potential + // value on the out portion, but keep it bound to SQL_C_CHAR. + + // declare buffersize, used for many of the code paths below + SQLLEN bufferSize = 0; + switch (parameter->InputOutputType) { + case SQL_PARAM_INPUT_OUTPUT: { + switch (parameter->ParameterType) { + case SQL_BIGINT: { + switch (parameter->ValueType) { + case SQL_C_SBIGINT: + // Don't need to do anything, should be bound correctly + parameter->BufferLength = sizeof(SQLBIGINT); + break; - #define SQLPROCEDURECOLUMNS_COLUMN_TYPE_INDEX 4 - #define SQLPROCEDURECOLUMNS_DATA_TYPE_INDEX 5 - #define SQLPROCEDURECOLUMNS_COLUMN_SIZE_INDEX 7 - #define SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX 9 - #define SQLPROCEDURECOLUMNS_NULLABLE_INDEX 11 - - // get stored column parameter data from the result set - - for (int i = 0; i < data->parameterCount; i++) { - - Parameter *parameter = data->parameters[i]; - - data->parameters[i]->InputOutputType = data->storedRows[i][SQLPROCEDURECOLUMNS_COLUMN_TYPE_INDEX].smallint_data; - data->parameters[i]->ParameterType = data->storedRows[i][SQLPROCEDURECOLUMNS_DATA_TYPE_INDEX].smallint_data; // DataType -> ParameterType - data->parameters[i]->ColumnSize = data->storedRows[i][SQLPROCEDURECOLUMNS_COLUMN_SIZE_INDEX].integer_data; // ParameterSize -> ColumnSize - data->parameters[i]->Nullable = data->storedRows[i][SQLPROCEDURECOLUMNS_NULLABLE_INDEX].smallint_data; - - // For each parameter, need to manipulate the data buffer and C type - // depending on what the InputOutputType is: - // - // SQL_PARAM_INPUT: - // No changes required, data should be able to be parsed based on - // the C type defined when the parameters were read from JavaScript - // - // SQL_PARAM_INPUT_OUTPUT: - // Need to preserve the data so that it can be sent in correctly, - // but also need to ensure that the buffer is large enough to return - // any value returned on the out portion. Also, preserve the bind type - // on both the input and output: - // - // e.g. A user sends in a character string for a BIGINT field. Resize - // the buffer for the character string to hold any potential - // value on the out portion, but keep it bound to SQL_C_CHAR. - - // declare buffersize, used for many of the code paths below - SQLLEN bufferSize = 0; - switch (parameter->InputOutputType) { - case SQL_PARAM_INPUT_OUTPUT: - { - switch(parameter->ParameterType) - { - case SQL_BIGINT: { - switch(parameter->ValueType) - { - case SQL_C_SBIGINT: - // Don't need to do anything, should be bound correctly - parameter->BufferLength = sizeof(SQLBIGINT); - break; + case SQL_C_CHAR: + default: + // TODO: Don't just hardcode 21 + bufferSize = 21; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + break; + } + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: { + switch (parameter->ValueType) { + case SQL_C_BINARY: + default: { + bufferSize = parameter->ColumnSize * sizeof(SQLCHAR); + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } + case SQL_INTEGER: { + switch (parameter->ValueType) { + case SQL_C_SBIGINT: { + parameter->BufferLength = sizeof(SQLBIGINT); + break; + } + case SQL_C_CHAR: + default: { + // TODO: don't just hardcode 12 + bufferSize = 12; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } + case SQL_TINYINT: + case SQL_SMALLINT: { + switch (parameter->ValueType) { + case SQL_C_SBIGINT: { + parameter->BufferLength = sizeof(SQLBIGINT); + break; + } + case SQL_C_CHAR: + default: { + // TODO: don't just hardcode 7 + bufferSize = 7; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } - case SQL_C_CHAR: - default: - // TODO: Don't just hardcode 21 - bufferSize = 21; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - break; - } - case SQL_BINARY: - case SQL_VARBINARY: - case SQL_LONGVARBINARY: { - switch(parameter->ValueType) - { - case SQL_C_BINARY: - default: { - bufferSize = parameter->ColumnSize * sizeof(SQLCHAR); - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } - case SQL_INTEGER: { - switch(parameter->ValueType) - { - case SQL_C_SBIGINT: { - parameter->BufferLength = sizeof(SQLBIGINT); - break; - } - case SQL_C_CHAR: - default: { - // TODO: don't just hardcode 12 - bufferSize = 12; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } - case SQL_TINYINT: - case SQL_SMALLINT: { - switch(parameter->ValueType) - { - case SQL_C_SBIGINT: { - parameter->BufferLength = sizeof(SQLBIGINT); - break; - } - case SQL_C_CHAR: - default: { - // TODO: don't just hardcode 7 - bufferSize = 7; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } + case SQL_DECIMAL: + case SQL_NUMERIC: { + switch (parameter->ValueType) { + case SQL_C_DOUBLE: { + parameter->BufferLength = sizeof(SQLDOUBLE); + break; + } + case SQL_C_SBIGINT: { + parameter->BufferLength = sizeof(SQLBIGINT); + break; + } + case SQL_C_CHAR: + default: { + // ColumnSize + sign + decimal + null-terminator + bufferSize = (data->parameters[i]->ColumnSize + 3); + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } - case SQL_DECIMAL: - case SQL_NUMERIC: { - switch(parameter->ValueType) - { - case SQL_C_DOUBLE: { - parameter->BufferLength = sizeof(SQLDOUBLE); - break; - } - case SQL_C_SBIGINT: { - parameter->BufferLength = sizeof(SQLBIGINT); - break; - } - case SQL_C_CHAR: - default: { - // ColumnSize + sign + decimal + null-terminator - bufferSize = (data->parameters[i]->ColumnSize + 3); - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } - - case SQL_BIT: { - switch(parameter->ValueType) - { - case SQL_C_BIT: - case SQL_C_CHAR: - default: { - // TODO: don't just hardcode 2 - bufferSize = 2; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } + case SQL_BIT: { + switch (parameter->ValueType) { + case SQL_C_BIT: + case SQL_C_CHAR: + default: { + // TODO: don't just hardcode 2 + bufferSize = 2; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } - case SQL_REAL: { - switch(parameter->ValueType) - { - case SQL_C_DOUBLE: { - parameter->BufferLength = sizeof(SQLDOUBLE); - break; - } - case SQL_C_SBIGINT: { - parameter->BufferLength = sizeof(SQLBIGINT); - break; - } - case SQL_C_CHAR: - default: { - // TODO: don't just hardcode 15 - bufferSize = 15; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } + case SQL_REAL: { + switch (parameter->ValueType) { + case SQL_C_DOUBLE: { + parameter->BufferLength = sizeof(SQLDOUBLE); + break; + } + case SQL_C_SBIGINT: { + parameter->BufferLength = sizeof(SQLBIGINT); + break; + } + case SQL_C_CHAR: + default: { + // TODO: don't just hardcode 15 + bufferSize = 15; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } - case SQL_FLOAT: { - switch(parameter->ValueType) - { - case SQL_C_DOUBLE: { - parameter->BufferLength = sizeof(SQLDOUBLE); - break; - } - case SQL_C_SBIGINT: { - parameter->BufferLength = sizeof(SQLBIGINT); - break; - } - case SQL_C_CHAR: - default: { - // TODO: don't just hardcode 25 - bufferSize = 25; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } + case SQL_FLOAT: { + switch (parameter->ValueType) { + case SQL_C_DOUBLE: { + parameter->BufferLength = sizeof(SQLDOUBLE); + break; + } + case SQL_C_SBIGINT: { + parameter->BufferLength = sizeof(SQLBIGINT); + break; + } + case SQL_C_CHAR: + default: { + // TODO: don't just hardcode 25 + bufferSize = 25; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } - case SQL_DOUBLE: { - switch(parameter->ValueType) - { - case SQL_C_DOUBLE: { - parameter->BufferLength = sizeof(SQLDOUBLE); - break; - } - case SQL_C_SBIGINT: { - parameter->BufferLength = sizeof(SQLBIGINT); - break; - } - case SQL_C_CHAR: - default: { - // TODO: don't just hardcode 25 - bufferSize = 25; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } + case SQL_DOUBLE: { + switch (parameter->ValueType) { + case SQL_C_DOUBLE: { + parameter->BufferLength = sizeof(SQLDOUBLE); + break; + } + case SQL_C_SBIGINT: { + parameter->BufferLength = sizeof(SQLBIGINT); + break; + } + case SQL_C_CHAR: + default: { + // TODO: don't just hardcode 25 + bufferSize = 25; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } - case SQL_WCHAR: - case SQL_WVARCHAR: - case SQL_WLONGVARCHAR: { - switch(parameter->ValueType) - { - case SQL_C_WCHAR: { - bufferSize = data->parameters[i]->ColumnSize + 1; - SQLWCHAR *temp = new SQLWCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize * sizeof(SQLWCHAR); - break; - } - case SQL_C_CHAR: - default: { - bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLCHAR) * MAX_UTF8_BYTES; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: { + switch (parameter->ValueType) { + case SQL_C_WCHAR: { + bufferSize = data->parameters[i]->ColumnSize + 1; + SQLWCHAR* temp = new SQLWCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize * sizeof(SQLWCHAR); + break; + } + case SQL_C_CHAR: + default: { + bufferSize = (data->parameters[i]->ColumnSize + 1) * + sizeof(SQLCHAR) * MAX_UTF8_BYTES; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } - case SQL_CHAR: - case SQL_VARCHAR: - case SQL_LONGVARCHAR: { - switch(parameter->ValueType) - { - case SQL_C_CHAR: - default: { - bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLCHAR) * MAX_UTF8_BYTES; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: { + switch (parameter->ValueType) { + case SQL_C_CHAR: + default: { + bufferSize = (data->parameters[i]->ColumnSize + 1) * + sizeof(SQLCHAR) * MAX_UTF8_BYTES; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + } + break; + } - // It is possible that a driver-specific value was returned. - // If so, just go with whatever C type they bound with with - // reasonable values. - default: { - switch(parameter->ValueType) - { - case SQL_C_BINARY: { - bufferSize = parameter->ColumnSize * sizeof(SQLCHAR); - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - case SQL_C_CHAR: - default: { - bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLCHAR) * MAX_UTF8_BYTES; - SQLCHAR *temp = new SQLCHAR[bufferSize](); - memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); - delete[] reinterpret_cast(parameter->ParameterValuePtr); - parameter->ParameterValuePtr = temp; - parameter->BufferLength = bufferSize; - break; - } - } - break; - } - } + // It is possible that a driver-specific value was returned. + // If so, just go with whatever C type they bound with with + // reasonable values. + default: { + switch (parameter->ValueType) { + case SQL_C_BINARY: { + bufferSize = parameter->ColumnSize * sizeof(SQLCHAR); + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; + break; + } + case SQL_C_CHAR: + default: { + bufferSize = (data->parameters[i]->ColumnSize + 1) * + sizeof(SQLCHAR) * MAX_UTF8_BYTES; + SQLCHAR* temp = new SQLCHAR[bufferSize](); + memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength); + delete[] reinterpret_cast(parameter->ParameterValuePtr); + parameter->ParameterValuePtr = temp; + parameter->BufferLength = bufferSize; break; } - case SQL_PARAM_OUTPUT: - { - switch(parameter->ParameterType) - { - case SQL_DECIMAL: - case SQL_NUMERIC: - bufferSize = (data->parameters[i]->ColumnSize + 3) * sizeof(SQLCHAR); - data->parameters[i]->ValueType = SQL_C_CHAR; - data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize]; - data->parameters[i]->BufferLength = bufferSize; - data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data; - break; - - case SQL_DOUBLE: - case SQL_FLOAT: - data->parameters[i]->ValueType = SQL_C_DOUBLE; - data->parameters[i]->ParameterValuePtr = new SQLDOUBLE(); - data->parameters[i]->BufferLength = sizeof(SQLDOUBLE); - data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data; - break; - - case SQL_TINYINT: - case SQL_SMALLINT: - data->parameters[i]->ValueType = SQL_C_SSHORT; - data->parameters[i]->ParameterValuePtr = new SQLSMALLINT(); - data->parameters[i]->BufferLength = sizeof(SQLSMALLINT); - data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data; - break; - - case SQL_INTEGER: - data->parameters[i]->ValueType = SQL_C_SLONG; - data->parameters[i]->ParameterValuePtr = new SQLINTEGER(); - data->parameters[i]->BufferLength = sizeof(SQLINTEGER); - data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data; - break; - - case SQL_BIGINT: - parameter->ValueType = SQL_C_SBIGINT; - parameter->ParameterValuePtr = new SQLBIGINT(); - parameter->BufferLength = sizeof(SQLBIGINT); - parameter->isbigint = true; - break; - - case SQL_BINARY: - case SQL_VARBINARY: - case SQL_LONGVARBINARY: - bufferSize = data->parameters[i]->ColumnSize * sizeof(SQLCHAR); - data->parameters[i]->ValueType = SQL_C_BINARY; - data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize](); - data->parameters[i]->BufferLength = bufferSize; - break; - - case SQL_WCHAR: - case SQL_WVARCHAR: - case SQL_WLONGVARCHAR: - bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLWCHAR); - data->parameters[i]->ValueType = SQL_C_WCHAR; - data->parameters[i]->ParameterValuePtr = new SQLWCHAR[bufferSize](); - data->parameters[i]->BufferLength = bufferSize; - break; - - case SQL_CHAR: - case SQL_VARCHAR: - case SQL_LONGVARCHAR: - default: - bufferSize = ((data->parameters[i]->ColumnSize * MAX_UTF8_BYTES) + 1) * sizeof(SQLCHAR); - data->parameters[i]->ValueType = SQL_C_CHAR; - data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize](); - data->parameters[i]->BufferLength = bufferSize; - break; - } } + break; } + } + break; } + case SQL_PARAM_OUTPUT: { + switch (parameter->ParameterType) { + case SQL_DECIMAL: + case SQL_NUMERIC: + bufferSize = (data->parameters[i]->ColumnSize + 3) * sizeof(SQLCHAR); + data->parameters[i]->ValueType = SQL_C_CHAR; + data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize]; + data->parameters[i]->BufferLength = bufferSize; + data->parameters[i]->DecimalDigits = + data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX] + .smallint_data; + break; + + case SQL_DOUBLE: + case SQL_FLOAT: + data->parameters[i]->ValueType = SQL_C_DOUBLE; + data->parameters[i]->ParameterValuePtr = new SQLDOUBLE(); + data->parameters[i]->BufferLength = sizeof(SQLDOUBLE); + data->parameters[i]->DecimalDigits = + data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX] + .smallint_data; + break; + + case SQL_TINYINT: + case SQL_SMALLINT: + data->parameters[i]->ValueType = SQL_C_SSHORT; + data->parameters[i]->ParameterValuePtr = new SQLSMALLINT(); + data->parameters[i]->BufferLength = sizeof(SQLSMALLINT); + data->parameters[i]->DecimalDigits = + data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX] + .smallint_data; + break; + + case SQL_INTEGER: + data->parameters[i]->ValueType = SQL_C_SLONG; + data->parameters[i]->ParameterValuePtr = new SQLINTEGER(); + data->parameters[i]->BufferLength = sizeof(SQLINTEGER); + data->parameters[i]->DecimalDigits = + data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX] + .smallint_data; + break; + + case SQL_BIGINT: + parameter->ValueType = SQL_C_SBIGINT; + parameter->ParameterValuePtr = new SQLBIGINT(); + parameter->BufferLength = sizeof(SQLBIGINT); + parameter->isbigint = true; + break; + + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + bufferSize = data->parameters[i]->ColumnSize * sizeof(SQLCHAR); + data->parameters[i]->ValueType = SQL_C_BINARY; + data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize](); + data->parameters[i]->BufferLength = bufferSize; + break; - // We saved a reference to parameters passed it. Need to tell which - // parameters we have to overwrite, now that we have - this->overwriteParams = new unsigned char[data->parameterCount](); - for (int i = 0; i < data->parameterCount; i++) { - if (data->parameters[i]->InputOutputType == SQL_PARAM_OUTPUT || data->parameters[i]->InputOutputType == SQL_PARAM_INPUT_OUTPUT) { - this->overwriteParams[i] = 1; - } else { - this->overwriteParams[i] = 0; + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLWCHAR); + data->parameters[i]->ValueType = SQL_C_WCHAR; + data->parameters[i]->ParameterValuePtr = new SQLWCHAR[bufferSize](); + data->parameters[i]->BufferLength = bufferSize; + break; + + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + default: + bufferSize = + ((data->parameters[i]->ColumnSize * MAX_UTF8_BYTES) + 1) * + sizeof(SQLCHAR); + data->parameters[i]->ValueType = SQL_C_CHAR; + data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize](); + data->parameters[i]->BufferLength = bufferSize; + break; } } + } + } - return_code = ODBC::BindParameters(data->hstmt, data->parameters, data->parameterCount); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error binding parameters to the procedure\0"); - return; + // We saved a reference to parameters passed it. Need to tell which + // parameters we have to overwrite, now that we have + this->overwriteParams = new unsigned char[data->parameterCount](); + for (int i = 0; i < data->parameterCount; i++) { + if (data->parameters[i]->InputOutputType == SQL_PARAM_OUTPUT || + data->parameters[i]->InputOutputType == SQL_PARAM_INPUT_OUTPUT) { + this->overwriteParams[i] = 1; + } + else { + this->overwriteParams[i] = 0; } + } - // create the statement to call the stored procedure using the ODBC Call escape sequence: - // need to create the string "?,?,?,?" where the number of '?' is the number of parameters; - size_t parameterStringSize = (data->parameterCount * 2); - char *parameterString = new char[parameterStringSize]; - parameterString[0] = '\0'; - - for (int i = 0; i < data->parameterCount; i++) { - if (i == (data->parameterCount - 1)) { - strcat(parameterString, "?"); // for last parameter, don't add ',' - } else { - strcat(parameterString, "?,"); - } + return_code = + ODBC::BindParameters(data->hstmt, data->parameters, data->parameterCount); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error binding parameters to the procedure\0"); + return; + } + + // create the statement to call the stored procedure using the ODBC Call + // escape sequence: need to create the string "?,?,?,?" where the number of + // '?' is the number of parameters; + size_t parameterStringSize = (data->parameterCount * 2); + char* parameterString = new char[parameterStringSize]; + parameterString[0] = '\0'; + + for (int i = 0; i < data->parameterCount; i++) { + if (i == (data->parameterCount - 1)) { + strcat(parameterString, "?"); // for last parameter, don't add ',' + } + else { + strcat(parameterString, "?,"); } + } - data->deleteColumns(); // delete data in columns for next result set + data->deleteColumns(); // delete data in columns for next result set - // 13 non-template characters in { CALL %s (%s) }\0 - size_t sqlStringSize = 1024 + parameterStringSize + sizeof("{ CALL () }"); - data->sql = new SQLTCHAR[sqlStringSize]; + // 13 non-template characters in { CALL %s (%s) }\0 + size_t sqlStringSize = 1024 + parameterStringSize + sizeof("{ CALL () }"); + data->sql = new SQLTCHAR[sqlStringSize]; #ifndef UNICODE - snprintf((char *)data->sql, sqlStringSize, "{ CALL %s (%s) }", combinedProcedureName, parameterString); + snprintf( + (char*)data->sql, sqlStringSize, "{ CALL %s (%s) }", + combinedProcedureName, parameterString + ); #else - // Note: On Windows, %s and %S change their behavior depending on whether - // it's passed to a printf function or a wprintf function. Since we're passing - // narrow strings to a wide function in the case of parameters, we need to use %S. - swprintf(data->sql, sqlStringSize, L"{ CALL %s (%S) }", combinedProcedureName, parameterString); + // Note: On Windows, %s and %S change their behavior depending on whether + // it's passed to a printf function or a wprintf function. Since we're + // passing narrow strings to a wide function in the case of parameters, we + // need to use %S. + swprintf( + data->sql, sqlStringSize, L"{ CALL %s (%S) }", combinedProcedureName, + parameterString + ); #endif - delete[] combinedProcedureName; - delete[] parameterString; + delete[] combinedProcedureName; + delete[] parameterString; - set_fetch_size - ( - data, - 1 - ); + set_fetch_size(data, 1); - return_code = SQLExecDirect( - data->hstmt, // StatementHandle - data->sql, // StatementText - SQL_NTS // TextLength - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error calling the procedure\0"); - return; - } + return_code = SQLExecDirect(data->hstmt, data->sql, SQL_NTS); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error calling the procedure\0"); + return; + } - return_code = prepare_for_fetch(data); - return_code = - fetch_all_and_store - ( - data, - true, - &alloc_error + return_code = prepare_for_fetch(data); + return_code = fetch_all_and_store(data, true, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when fetching " + "data. No ODBC error information available." ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available."); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving the results from the procedure call\0"); - return; - } + return; + } + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error retrieving the results from the procedure call\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; + std::vector callbackArguments; - odbcConnectionObject->ParametersToArray(&napiParameters, data, overwriteParams); - Napi::Array rows = process_data_for_napi(env, data, napiParameters.Value()); + odbcConnectionObject->ParametersToArray( + &napiParameters, data, overwriteParams + ); + Napi::Array rows = process_data_for_napi(env, data, napiParameters.Value()); - callbackArguments.push_back(env.Null()); - callbackArguments.push_back(rows); + callbackArguments.push_back(env.Null()); + callbackArguments.push_back(rows); - // return results object - Callback().Call(callbackArguments); - } + // return results object + Callback().Call(callbackArguments); + } public: - CallProcedureAsyncWorker(ODBCConnection *odbcConnectionObject, Napi::Value napiParameterArray, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject), - data(data) - { - if (napiParameterArray.IsArray()) { - napiParameters = Napi::Persistent(napiParameterArray.As()); - } else { - napiParameters = Napi::Reference(); - } - } - - ~CallProcedureAsyncWorker() { - odbcConnectionObject->UnregisterActiveStatement(data->hstmt); - delete[] overwriteParams; - delete data; - data = NULL; + CallProcedureAsyncWorker( + ODBCConnection* odbcConnectionObject, Napi::Value napiParameterArray, + StatementData* data, Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject), + data(data) { + if (napiParameterArray.IsArray()) { + napiParameters = Napi::Persistent(napiParameterArray.As()); + } + else { + napiParameters = Napi::Reference(); } + } + + ~CallProcedureAsyncWorker() { + odbcConnectionObject->UnregisterActiveStatement(data->hstmt); + delete[] overwriteParams; + delete data; + data = NULL; + } }; /* TODO: Change @@ -2012,12 +1910,13 @@ class CallProcedureAsyncWorker : public ODBCAsyncWorker { * function arguments for 'query'. * * info[0]: String: the name of the procedure - * info[1?]: Array: optional array of parameters to bind to the procedure call + * info[1?]: Array: optional array of parameters to bind to the procedure + * call * info[1/2]: Function: callback function: - * function(error, result) - * error: An error object if the connection was not opened, or - * null if operation was successful. - * result: A string containing the info requested. + * function(error, result) + * error: An error object if the connection was not opened, or null + * if operation was successful. + * result: A string containing the info requested. * * Return: * Napi::Value: @@ -2028,19 +1927,23 @@ Napi::Value ODBCConnection::CallProcedure(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); - StatementData *data = new StatementData(); - data->henv = this->hENV; - data->hdbc = this->hDBC; - data->fetch_array = this->connectionOptions.fetchArray; - data->maxColumnNameLength = this->getInfoResults.max_column_name_length; - data->get_data_supports = this->getInfoResults.sql_get_data_supports; + StatementData* data = new StatementData(); + data->henv = this->hENV; + data->hdbc = this->hDBC; + data->fetch_array = this->connectionOptions.fetchArray; + data->maxColumnNameLength = this->getInfoResults.max_column_name_length; + data->get_data_supports = this->getInfoResults.sql_get_data_supports; std::vector values; Napi::Value napiParameterArray = env.Null(); if (info[0].IsString()) { data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString()); - } else if (!info[0].IsNull()) { - Napi::TypeError::New(env, "callProcedure: first argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[0].IsNull()) { + Napi::TypeError::New( + env, "callProcedure: first argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2048,8 +1951,12 @@ Napi::Value ODBCConnection::CallProcedure(const Napi::CallbackInfo& info) { if (info[1].IsString()) { data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString()); - } else if (!info[1].IsNull()) { - Napi::TypeError::New(env, "callProcedure: second argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[1].IsNull()) { + Napi::TypeError::New( + env, "callProcedure: second argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2057,8 +1964,10 @@ Napi::Value ODBCConnection::CallProcedure(const Napi::CallbackInfo& info) { if (info[2].IsString()) { data->procedure = ODBC::NapiStringToSQLTCHAR(info[2].ToString()); - } else { - Napi::TypeError::New(env, "callProcedure: third argument must be a string").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New(env, "callProcedure: third argument must be a string") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2074,16 +1983,24 @@ Napi::Value ODBCConnection::CallProcedure(const Napi::CallbackInfo& info) { data->parameters[i] = new Parameter(); } ODBC::StoreBindValues(&napiArray, data->parameters); - } else if ((info.Length() == 4 && info[4].IsFunction()) || (info.Length() == 5 && info[3].IsNull() && info[4].IsFunction())) { + } + else if ((info.Length() == 4 && info[4].IsFunction()) || + (info.Length() == 5 && info[3].IsNull() && info[4].IsFunction())) { data->parameters = 0; - } else { - Napi::TypeError::New(env, "[odbc]: Wrong function signature in call to Connection.callProcedure({string}, {array}[optional], {function}).").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New( + env, "[odbc]: Wrong function signature in call to " + "Connection.callProcedure({string}, {array}[optional], {function})." + ) + .ThrowAsJavaScriptException(); return env.Null(); } Napi::Function callback = info[info.Length() - 1].As(); - CallProcedureAsyncWorker *worker = new CallProcedureAsyncWorker(this, napiParameterArray, data, callback); + CallProcedureAsyncWorker* worker = + new CallProcedureAsyncWorker(this, napiParameterArray, data, callback); worker->Queue(); return env.Undefined(); } @@ -2117,32 +2034,33 @@ Napi::Value ODBCConnection::GetUsername(const Napi::CallbackInfo& info) { return this->GetInfo(env, SQL_USER_NAME); } -Napi::Value ODBCConnection::GetInfo(const Napi::Env env, const SQLUSMALLINT option) { +Napi::Value +ODBCConnection::GetInfo(const Napi::Env env, const SQLUSMALLINT option) { - SQLTCHAR infoValue[255]; + SQLTCHAR infoValue[255]; SQLSMALLINT infoLength; - SQLRETURN return_code; - - return_code = - SQLGetInfo - ( - this->hDBC, // ConnectionHandle - SQL_USER_NAME, // InfoType - infoValue, // InfoValuePtr - sizeof(infoValue), // BufferLength - &infoLength // StringLengthPtr + SQLRETURN return_code; + + return_code = SQLGetInfo( + this->hDBC, SQL_USER_NAME, infoValue, sizeof(infoValue), &infoLength ); if (SQL_SUCCEEDED(return_code)) { - #ifdef UNICODE - return Napi::String::New(env, (const char16_t *)infoValue, infoLength); - #else - return Napi::String::New(env, (const char *) infoValue, infoLength); - #endif +#ifdef UNICODE + return Napi::String::New(env, (const char16_t*)infoValue, infoLength); +#else + return Napi::String::New(env, (const char*)infoValue, infoLength); +#endif } // TODO: Fix - // Napi::Error(env, Napi::String::New(env, ODBC::GetSQLError(SQL_HANDLE_DBC, this->hDBC, (char *) "[node-odbc] Error in ODBCConnection::GetInfo"))).ThrowAsJavaScriptException(); + // Napi::Error(env, + // Napi::String::New( + // env, ODBC::GetSQLError( + // SQL_HANDLE_DBC, this->hDBC, + // (char*)"[node-odbc] Error in + // ODBCConnection::GetInfo"))) + // .ThrowAsJavaScriptException(); return env.Null(); } @@ -2153,101 +2071,85 @@ Napi::Value ODBCConnection::GetInfo(const Napi::Env env, const SQLUSMALLINT opti //////////////////////////////////////////////////////////////////////////////// // PrimaryKeysAsyncWorker, used by the PrimaryKeys function below -class PrimaryKeysAsyncWorker : public ODBCAsyncWorker -{ - private: - - ODBCConnection *odbcConnectionObject; - StatementData *data; +class PrimaryKeysAsyncWorker : public ODBCAsyncWorker { + private: + ODBCConnection* odbcConnectionObject; + StatementData* data; - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = SQLAllocHandle( - SQL_HANDLE_STMT, // HandleType - odbcConnectionObject->hDBC, // InputHandle - &data->hstmt // OutputHandlePtr + uv_mutex_lock(&ODBC::g_odbcMutex); + return_code = + SQLAllocHandle(SQL_HANDLE_STMT, odbcConnectionObject->hDBC, &data->hstmt); + uv_mutex_unlock(&ODBC::g_odbcMutex); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError( + "[odbc] Error allocating a statement handle to get primary key " + "information\0" ); - uv_mutex_unlock(&ODBC::g_odbcMutex); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error allocating a statement handle to get primary key information\0"); - return; - } + return; + } - return_code = - set_fetch_size - ( - data, - 1 - ); + return_code = set_fetch_size(data, 1); - return_code = - SQLPrimaryKeys - ( - data->hstmt, // StatementHandle - data->catalog, // CatalogName - SQL_NTS, // NameLength1 - data->schema, // SchemaName - SQL_NTS, // NameLength2 - data->table, // TableName - SQL_NTS // NameLength3 - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error getting table information\0"); - return; - } + return_code = SQLPrimaryKeys( + data->hstmt, data->catalog, SQL_NTS, data->schema, SQL_NTS, data->table, + SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error getting table information\0"); + return; + } - return_code = prepare_for_fetch(data); - bool alloc_error = false; - return_code = - fetch_all_and_store - ( - data, - false, - &alloc_error + return_code = prepare_for_fetch(data); + bool alloc_error = false; + return_code = fetch_all_and_store(data, false, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when fetching " + "data. No ODBC error information available.\0" ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving table information results set\0"); - return; - } + return; } + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error retrieving table information results set\0"); + return; + } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; + std::vector callbackArguments; - callbackArguments.push_back(env.Null()); + callbackArguments.push_back(env.Null()); - Napi::Array empty = Napi::Array::New(env); - Napi::Array rows = process_data_for_napi(env, data, empty); - callbackArguments.push_back(rows); + Napi::Array empty = Napi::Array::New(env); + Napi::Array rows = process_data_for_napi(env, data, empty); + callbackArguments.push_back(rows); - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } public: - - PrimaryKeysAsyncWorker(ODBCConnection *odbcConnectionObject, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject), + PrimaryKeysAsyncWorker( + ODBCConnection* odbcConnectionObject, StatementData* data, + Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject), data(data) {} - ~PrimaryKeysAsyncWorker() { - delete data; - data = NULL; - } + ~PrimaryKeysAsyncWorker() { + delete data; + data = NULL; + } }; // @@ -2279,55 +2181,73 @@ Napi::Value ODBCConnection::PrimaryKeys(const Napi::CallbackInfo& info) { Napi::HandleScope scope(env); if (info.Length() != 4) { - Napi::TypeError::New(env, "primaryKeys() function takes 4 arguments.").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "primaryKeys() function takes 4 arguments.") + .ThrowAsJavaScriptException(); } Napi::Function callback; StatementData* data = new (std::nothrow) StatementData(); // Napi doesn't have LowMemoryNotification like NAN did. Throw standard error. if (!data) { - Napi::TypeError::New(env, "Could not allocate enough memory to run query.").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Could not allocate enough memory to run query.") + .ThrowAsJavaScriptException(); return env.Null(); } - - data->henv = this->hENV; - data->hdbc = this->hDBC; - data->fetch_array = this->connectionOptions.fetchArray; + + data->henv = this->hENV; + data->hdbc = this->hDBC; + data->fetch_array = this->connectionOptions.fetchArray; data->maxColumnNameLength = this->getInfoResults.max_column_name_length; - data->get_data_supports = this->getInfoResults.sql_get_data_supports; + data->get_data_supports = this->getInfoResults.sql_get_data_supports; if (info[0].IsString()) { data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString()); - } else if (!info[0].IsNull()) { - Napi::TypeError::New(env, "primaryKeys: first argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[0].IsNull()) { + Napi::TypeError::New( + env, "primaryKeys: first argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } if (info[1].IsString()) { data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString()); - } else if (!info[1].IsNull()) { - Napi::TypeError::New(env, "primaryKeys: second argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[1].IsNull()) { + Napi::TypeError::New( + env, "primaryKeys: second argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } if (info[2].IsString()) { data->table = ODBC::NapiStringToSQLTCHAR(info[2].ToString()); - } else if (!info[2].IsNull()) { - Napi::TypeError::New(env, "primaryKeys: third argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[2].IsNull()) { + Napi::TypeError::New( + env, "primaryKeys: third argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } - if (info[3].IsFunction()) { callback = info[3].As(); } + if (info[3].IsFunction()) { + callback = info[3].As(); + } else { - Napi::TypeError::New(env, "primaryKeys: fourth argument must be a function").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "primaryKeys: fourth argument must be a function") + .ThrowAsJavaScriptException(); delete data; return env.Null(); } - PrimaryKeysAsyncWorker *worker = new PrimaryKeysAsyncWorker(this, data, callback); + PrimaryKeysAsyncWorker* worker = + new PrimaryKeysAsyncWorker(this, data, callback); worker->Queue(); return env.Undefined(); @@ -2340,107 +2260,86 @@ Napi::Value ODBCConnection::PrimaryKeys(const Napi::CallbackInfo& info) { //////////////////////////////////////////////////////////////////////////////// // ForeignKeysAsyncWorker, used by the ForeignKeys function below -class ForeignKeysAsyncWorker : public ODBCAsyncWorker -{ - private: - - ODBCConnection *odbcConnectionObject; - StatementData *data; +class ForeignKeysAsyncWorker : public ODBCAsyncWorker { + private: + ODBCConnection* odbcConnectionObject; + StatementData* data; - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = SQLAllocHandle( - SQL_HANDLE_STMT, // HandleType - odbcConnectionObject->hDBC, // InputHandle - &data->hstmt // OutputHandlePtr + uv_mutex_lock(&ODBC::g_odbcMutex); + return_code = + SQLAllocHandle(SQL_HANDLE_STMT, odbcConnectionObject->hDBC, &data->hstmt); + uv_mutex_unlock(&ODBC::g_odbcMutex); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError( + "[odbc] Error allocating a statement handle to get foriegn key " + "information\0" ); - uv_mutex_unlock(&ODBC::g_odbcMutex); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error allocating a statement handle to get foriegn key information\0"); - return; - } + return; + } - return_code = - set_fetch_size - ( - data, - 1 - ); + return_code = set_fetch_size(data, 1); - return_code = - SQLForeignKeys - ( - data->hstmt, // StatementHandle - data->catalog, // PKCatalogName - SQL_NTS, // NameLength1 - data->schema, // PKSchemaName - SQL_NTS, // NameLength2 - data->table, // PKTableName - SQL_NTS, // NameLength3 - data->fkCatalog, // FKCatalogName - SQL_NTS, // NameLength4, - data->fkSchema, // FKSchemaName - SQL_NTS, // NameLength5, - data->fkTable, // FKTableName, - SQL_NTS // NameLength6 - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error getting table information\0"); - return; - } + return_code = SQLForeignKeys( + data->hstmt, data->catalog, SQL_NTS, data->schema, SQL_NTS, data->table, + SQL_NTS, data->fkCatalog, SQL_NTS, data->fkSchema, SQL_NTS, data->fkTable, + SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error getting table information\0"); + return; + } - return_code = prepare_for_fetch(data); - bool alloc_error = false; - return_code = - fetch_all_and_store - ( - data, - false, - &alloc_error + return_code = prepare_for_fetch(data); + bool alloc_error = false; + return_code = fetch_all_and_store(data, false, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when fetching " + "data. No ODBC error information available.\0" ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving table information results set\0"); - return; - } + return; + } + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error retrieving table information results set\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; + std::vector callbackArguments; - callbackArguments.push_back(env.Null()); + callbackArguments.push_back(env.Null()); - Napi::Array empty = Napi::Array::New(env); - Napi::Array rows = process_data_for_napi(env, data, empty); - callbackArguments.push_back(rows); + Napi::Array empty = Napi::Array::New(env); + Napi::Array rows = process_data_for_napi(env, data, empty); + callbackArguments.push_back(rows); - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } public: - - ForeignKeysAsyncWorker(ODBCConnection *odbcConnectionObject, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject), + ForeignKeysAsyncWorker( + ODBCConnection* odbcConnectionObject, StatementData* data, + Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject), data(data) {} - ~ForeignKeysAsyncWorker() { - delete data; - data = NULL; - } + ~ForeignKeysAsyncWorker() { + delete data; + data = NULL; + } }; // @@ -2476,7 +2375,8 @@ Napi::Value ODBCConnection::ForeignKeys(const Napi::CallbackInfo& info) { Napi::HandleScope scope(env); if (info.Length() != 7) { - Napi::TypeError::New(env, "foriegnKeys() function takes 7 arguments.").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "foriegnKeys() function takes 7 arguments.") + .ThrowAsJavaScriptException(); } Napi::Function callback; @@ -2484,72 +2384,103 @@ Napi::Value ODBCConnection::ForeignKeys(const Napi::CallbackInfo& info) { // Napi doesn't have LowMemoryNotification like NAN did. Throw standard error. if (!data) { - Napi::TypeError::New(env, "Could not allocate enough memory to run query.").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Could not allocate enough memory to run query.") + .ThrowAsJavaScriptException(); return env.Null(); } - data->henv = this->hENV; - data->hdbc = this->hDBC; - data->fetch_array = this->connectionOptions.fetchArray; + data->henv = this->hENV; + data->hdbc = this->hDBC; + data->fetch_array = this->connectionOptions.fetchArray; data->maxColumnNameLength = this->getInfoResults.max_column_name_length; - data->get_data_supports = this->getInfoResults.sql_get_data_supports; + data->get_data_supports = this->getInfoResults.sql_get_data_supports; if (info[0].IsString()) { data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString()); - } else if (!info[0].IsNull()) { - Napi::TypeError::New(env, "foriegnKeys: first argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[0].IsNull()) { + Napi::TypeError::New( + env, "foriegnKeys: first argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } if (info[1].IsString()) { data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString()); - } else if (!info[1].IsNull()) { - Napi::TypeError::New(env, "foriegnKeys: second argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[1].IsNull()) { + Napi::TypeError::New( + env, "foriegnKeys: second argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } if (info[2].IsString()) { data->table = ODBC::NapiStringToSQLTCHAR(info[2].ToString()); - } else if (!info[2].IsNull()) { - Napi::TypeError::New(env, "foriegnKeys: third argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[2].IsNull()) { + Napi::TypeError::New( + env, "foriegnKeys: third argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } if (info[3].IsString()) { data->fkCatalog = ODBC::NapiStringToSQLTCHAR(info[3].ToString()); - } else if (!info[3].IsNull()) { - Napi::TypeError::New(env, "foriegnKeys: fourth argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[3].IsNull()) { + Napi::TypeError::New( + env, "foriegnKeys: fourth argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } if (info[4].IsString()) { data->fkSchema = ODBC::NapiStringToSQLTCHAR(info[4].ToString()); - } else if (!info[4].IsNull()) { - Napi::TypeError::New(env, "foriegnKeys: fifth argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[4].IsNull()) { + Napi::TypeError::New( + env, "foriegnKeys: fifth argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } if (info[5].IsString()) { data->fkTable = ODBC::NapiStringToSQLTCHAR(info[5].ToString()); - } else if (!info[5].IsNull()) { - Napi::TypeError::New(env, "foriegnKeys: sixth argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[5].IsNull()) { + Napi::TypeError::New( + env, "foriegnKeys: sixth argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } - if (info[6].IsFunction()) { callback = info[6].As(); } + if (info[6].IsFunction()) { + callback = info[6].As(); + } else { - Napi::TypeError::New(env, "foriegnKeys: seventh argument must be a function").ThrowAsJavaScriptException(); + Napi::TypeError::New( + env, "foriegnKeys: seventh argument must be a function" + ) + .ThrowAsJavaScriptException(); delete data; return env.Null(); } - ForeignKeysAsyncWorker *worker = new ForeignKeysAsyncWorker(this, data, callback); + ForeignKeysAsyncWorker* worker = + new ForeignKeysAsyncWorker(this, data, callback); worker->Queue(); return env.Undefined(); @@ -2563,100 +2494,83 @@ Napi::Value ODBCConnection::ForeignKeys(const Napi::CallbackInfo& info) { class TablesAsyncWorker : public ODBCAsyncWorker { private: + ODBCConnection* odbcConnectionObject; + StatementData* data; - ODBCConnection *odbcConnectionObject; - StatementData *data; + void Execute() { - void Execute() { + SQLRETURN return_code; - SQLRETURN return_code; - - uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = SQLAllocHandle( - SQL_HANDLE_STMT, // HandleType - odbcConnectionObject->hDBC, // InputHandle - &data->hstmt // OutputHandlePtr + uv_mutex_lock(&ODBC::g_odbcMutex); + return_code = + SQLAllocHandle(SQL_HANDLE_STMT, odbcConnectionObject->hDBC, &data->hstmt); + uv_mutex_unlock(&ODBC::g_odbcMutex); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError( + "[odbc] Error allocating a statement handle to get table " + "information\0" ); - uv_mutex_unlock(&ODBC::g_odbcMutex); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error allocating a statement handle to get table information\0"); - return; - } + return; + } - return_code = - set_fetch_size - ( - data, - 1 - ); + return_code = set_fetch_size(data, 1); - return_code = - SQLTables - ( - data->hstmt, // StatementHandle - data->catalog, // CatalogName - SQL_NTS, // NameLength1 - data->schema, // SchemaName - SQL_NTS, // NameLength2 - data->table, // TableName - SQL_NTS, // NameLength3 - data->type, // TableType - SQL_NTS // NameLength4 - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error getting table information\0"); - return; - } + return_code = SQLTables( + data->hstmt, data->catalog, SQL_NTS, data->schema, SQL_NTS, data->table, + SQL_NTS, data->type, SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error getting table information\0"); + return; + } - return_code = prepare_for_fetch(data); - bool alloc_error = false; - return_code = - fetch_all_and_store - ( - data, - false, - &alloc_error + return_code = prepare_for_fetch(data); + bool alloc_error = false; + return_code = fetch_all_and_store(data, false, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when fetching " + "data. No ODBC error information available.\0" ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving table information results set\0"); - return; - } + return; + } + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error retrieving table information results set\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; + std::vector callbackArguments; - callbackArguments.push_back(env.Null()); + callbackArguments.push_back(env.Null()); - Napi::Array empty = Napi::Array::New(env); - Napi::Array rows = process_data_for_napi(env, data, empty); - callbackArguments.push_back(rows); + Napi::Array empty = Napi::Array::New(env); + Napi::Array rows = process_data_for_napi(env, data, empty); + callbackArguments.push_back(rows); - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } public: - - TablesAsyncWorker(ODBCConnection *odbcConnectionObject, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject), + TablesAsyncWorker( + ODBCConnection* odbcConnectionObject, StatementData* data, + Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject), data(data) {} - ~TablesAsyncWorker() { - delete data; - data = NULL; - } + ~TablesAsyncWorker() { + delete data; + data = NULL; + } }; /* @@ -2689,19 +2603,21 @@ Napi::Value ODBCConnection::Tables(const Napi::CallbackInfo& info) { Napi::HandleScope scope(env); if (info.Length() != 5) { - Napi::TypeError::New(env, "tables() function takes 5 arguments.").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "tables() function takes 5 arguments.") + .ThrowAsJavaScriptException(); } Napi::Function callback; - StatementData* data = new StatementData(); - data->henv = this->hENV; - data->hdbc = this->hDBC; - data->fetch_array = this->connectionOptions.fetchArray; - data->maxColumnNameLength = this->getInfoResults.max_column_name_length; - data->get_data_supports = this->getInfoResults.sql_get_data_supports; + StatementData* data = new StatementData(); + data->henv = this->hENV; + data->hdbc = this->hDBC; + data->fetch_array = this->connectionOptions.fetchArray; + data->maxColumnNameLength = this->getInfoResults.max_column_name_length; + data->get_data_supports = this->getInfoResults.sql_get_data_supports; // Napi doesn't have LowMemoryNotification like NAN did. Throw standard error. if (!data) { - Napi::TypeError::New(env, "Could not allocate enough memory to run query.").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Could not allocate enough memory to run query.") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2709,8 +2625,10 @@ Napi::Value ODBCConnection::Tables(const Napi::CallbackInfo& info) { if (info[0].IsString()) { data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString()); - } else if (!info[0].IsNull()) { - Napi::TypeError::New(env, "tables: first argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[0].IsNull()) { + Napi::TypeError::New(env, "tables: first argument must be a string or null") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2718,8 +2636,12 @@ Napi::Value ODBCConnection::Tables(const Napi::CallbackInfo& info) { if (info[1].IsString()) { data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString()); - } else if (!info[1].IsNull()) { - Napi::TypeError::New(env, "tables: second argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[1].IsNull()) { + Napi::TypeError::New( + env, "tables: second argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2727,8 +2649,10 @@ Napi::Value ODBCConnection::Tables(const Napi::CallbackInfo& info) { if (info[2].IsString()) { data->table = ODBC::NapiStringToSQLTCHAR(info[2].ToString()); - } else if (!info[2].IsNull()) { - Napi::TypeError::New(env, "tables: third argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[2].IsNull()) { + Napi::TypeError::New(env, "tables: third argument must be a string or null") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2736,22 +2660,29 @@ Napi::Value ODBCConnection::Tables(const Napi::CallbackInfo& info) { if (info[3].IsString()) { data->type = ODBC::NapiStringToSQLTCHAR(info[3].ToString()); - } else if (!info[3].IsNull()) { - Napi::TypeError::New(env, "tables: fourth argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[3].IsNull()) { + Napi::TypeError::New( + env, "tables: fourth argument must be a string or null" + ) + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); } - if (info[4].IsFunction()) { callback = info[4].As(); } + if (info[4].IsFunction()) { + callback = info[4].As(); + } else { - Napi::TypeError::New(env, "tables: fifth argument must be a function").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "tables: fifth argument must be a function") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); } - TablesAsyncWorker *worker = new TablesAsyncWorker(this, data, callback); + TablesAsyncWorker* worker = new TablesAsyncWorker(this, data, callback); worker->Queue(); return env.Undefined(); @@ -2765,100 +2696,81 @@ Napi::Value ODBCConnection::Tables(const Napi::CallbackInfo& info) { class ColumnsAsyncWorker : public ODBCAsyncWorker { private: + ODBCConnection* odbcConnectionObject; + StatementData* data; - ODBCConnection *odbcConnectionObject; - StatementData *data; + void Execute() { - void Execute() { + SQLRETURN return_code; - SQLRETURN return_code; - - uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = - SQLAllocHandle - ( - SQL_HANDLE_STMT, // HandleType - odbcConnectionObject->hDBC, // InputHandle - &data->hstmt // OutputHandlePtr + uv_mutex_lock(&ODBC::g_odbcMutex); + return_code = + SQLAllocHandle(SQL_HANDLE_STMT, odbcConnectionObject->hDBC, &data->hstmt); + uv_mutex_unlock(&ODBC::g_odbcMutex); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError( + "[odbc] Error allocating a statement handle to get column " + "information\0" ); - uv_mutex_unlock(&ODBC::g_odbcMutex); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error allocating a statement handle to get column information\0"); - return; - } + return; + } - return_code = - set_fetch_size - ( - data, - 1 - ); + return_code = set_fetch_size(data, 1); - return_code = - SQLColumns - ( - data->hstmt, // StatementHandle - data->catalog, // CatalogName - SQL_NTS, // NameLength1 - data->schema, // SchemaName - SQL_NTS, // NameLength2 - data->table, // TableName - SQL_NTS, // NameLength3 - data->column, // ColumnName - SQL_NTS // NameLength4 - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error getting column information\0"); - return; - } + return_code = SQLColumns( + data->hstmt, data->catalog, SQL_NTS, data->schema, SQL_NTS, data->table, + SQL_NTS, data->column, SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error getting column information\0"); + return; + } - return_code = prepare_for_fetch(data); - bool alloc_error = false; - return_code = - fetch_all_and_store - ( - data, - false, - &alloc_error + return_code = prepare_for_fetch(data); + bool alloc_error = false; + return_code = fetch_all_and_store(data, false, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when fetching " + "data. No ODBC error information available.\0" ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving column information results set\0"); - return; - } + return; + } + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error retrieving column information results set\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - Napi::Array empty = Napi::Array::New(env); - Napi::Array rows = process_data_for_napi(env, data, empty); + Napi::Array empty = Napi::Array::New(env); + Napi::Array rows = process_data_for_napi(env, data, empty); - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); - callbackArguments.push_back(rows); - Callback().Call(callbackArguments); - } + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); + callbackArguments.push_back(rows); + Callback().Call(callbackArguments); + } public: - - ColumnsAsyncWorker(ODBCConnection *odbcConnectionObject, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject), + ColumnsAsyncWorker( + ODBCConnection* odbcConnectionObject, StatementData* data, + Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject), data(data) {} - ~ColumnsAsyncWorker() { - delete data; - data = NULL; - } + ~ColumnsAsyncWorker() { + delete data; + data = NULL; + } }; /* @@ -2889,24 +2801,27 @@ Napi::Value ODBCConnection::Columns(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); - StatementData* data = new StatementData(); - data->henv = this->hENV; - data->hdbc = this->hDBC; - data->fetch_array = this->connectionOptions.fetchArray; - data->maxColumnNameLength = this->getInfoResults.max_column_name_length; - data->get_data_supports = this->getInfoResults.sql_get_data_supports; + StatementData* data = new StatementData(); + data->henv = this->hENV; + data->hdbc = this->hDBC; + data->fetch_array = this->connectionOptions.fetchArray; + data->maxColumnNameLength = this->getInfoResults.max_column_name_length; + data->get_data_supports = this->getInfoResults.sql_get_data_supports; Napi::Function callback; // Napi doesn't have LowMemoryNotification like NAN did. Throw standard error. if (!data) { - Napi::Error::New(env, "Could not allocate enough memory to run query.").ThrowAsJavaScriptException(); + Napi::Error::New(env, "Could not allocate enough memory to run query.") + .ThrowAsJavaScriptException(); return env.Null(); } if (info[0].IsString()) { data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString()); - } else if (!info[0].IsNull()) { - Napi::Error::New(env, "columns: first argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[0].IsNull()) { + Napi::Error::New(env, "columns: first argument must be a string or null") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2914,8 +2829,10 @@ Napi::Value ODBCConnection::Columns(const Napi::CallbackInfo& info) { if (info[1].IsString()) { data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString()); - } else if (!info[1].IsNull()) { - Napi::Error::New(env, "columns: second argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[1].IsNull()) { + Napi::Error::New(env, "columns: second argument must be a string or null") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2923,8 +2840,10 @@ Napi::Value ODBCConnection::Columns(const Napi::CallbackInfo& info) { if (info[2].IsString()) { data->table = ODBC::NapiStringToSQLTCHAR(info[2].ToString()); - } else if (!info[2].IsNull()) { - Napi::Error::New(env, "columns: third argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[2].IsNull()) { + Napi::Error::New(env, "columns: third argument must be a string or null") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); @@ -2932,22 +2851,27 @@ Napi::Value ODBCConnection::Columns(const Napi::CallbackInfo& info) { if (info[3].IsString()) { data->type = ODBC::NapiStringToSQLTCHAR(info[3].ToString()); - } else if (!info[3].IsNull()) { - Napi::Error::New(env, "columns: fourth argument must be a string or null").ThrowAsJavaScriptException(); + } + else if (!info[3].IsNull()) { + Napi::Error::New(env, "columns: fourth argument must be a string or null") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); } - if (info[4].IsFunction()) { callback = info[4].As(); } + if (info[4].IsFunction()) { + callback = info[4].As(); + } else { - Napi::Error::New(env, "columns: fifth argument must be a function").ThrowAsJavaScriptException(); + Napi::Error::New(env, "columns: fifth argument must be a function") + .ThrowAsJavaScriptException(); delete data; data = NULL; return env.Null(); } - ColumnsAsyncWorker *worker = new ColumnsAsyncWorker(this, data, callback); + ColumnsAsyncWorker* worker = new ColumnsAsyncWorker(this, data, callback); worker->Queue(); return env.Undefined(); @@ -2961,47 +2885,43 @@ Napi::Value ODBCConnection::Columns(const Napi::CallbackInfo& info) { class BeginTransactionAsyncWorker : public ODBCAsyncWorker { public: + BeginTransactionAsyncWorker( + ODBCConnection* odbcConnectionObject, Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject) {} - BeginTransactionAsyncWorker(ODBCConnection *odbcConnectionObject, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject) {} - - ~BeginTransactionAsyncWorker() {}; + ~BeginTransactionAsyncWorker() {}; private: + ODBCConnection* odbcConnectionObject; - ODBCConnection *odbcConnectionObject; + void Execute() { - void Execute() { + SQLRETURN return_code; - SQLRETURN return_code; - - //set the connection manual commits - return_code = - SQLSetConnectAttr - ( - odbcConnectionObject->hDBC, // ConnectionHandle - SQL_ATTR_AUTOCOMMIT, // Attribute - (SQLPOINTER) SQL_AUTOCOMMIT_OFF, // ValuePtr - SQL_NTS // StringLength - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error turning off autocommit on the connection\0"); - return; - } + // set the connection manual commits + return_code = SQLSetConnectAttr( + odbcConnectionObject->hDBC, SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError("[odbc] Error turning off autocommit on the connection\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; + std::vector callbackArguments; - callbackArguments.push_back(env.Null()); + callbackArguments.push_back(env.Null()); - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } }; /* @@ -3032,10 +2952,16 @@ Napi::Value ODBCConnection::BeginTransaction(const Napi::CallbackInfo& info) { Napi::Function callback; - if (info[0].IsFunction()) { callback = info[0].As(); } - else { Napi::Error::New(env, "beginTransaction: first argument must be a function").ThrowAsJavaScriptException(); } + if (info[0].IsFunction()) { + callback = info[0].As(); + } + else { + Napi::Error::New(env, "beginTransaction: first argument must be a function") + .ThrowAsJavaScriptException(); + } - BeginTransactionAsyncWorker *worker = new BeginTransactionAsyncWorker(this, callback); + BeginTransactionAsyncWorker* worker = + new BeginTransactionAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); @@ -3045,69 +2971,60 @@ Napi::Value ODBCConnection::BeginTransaction(const Napi::CallbackInfo& info) { ***************************** END TRANSACTION ******************************** *****************************************************************************/ - // EndTransactionAsyncWorker, used by Commit and Rollback functions (see below) +// EndTransactionAsyncWorker, used by Commit and Rollback functions (see below) class EndTransactionAsyncWorker : public ODBCAsyncWorker { private: + ODBCConnection* odbcConnectionObject; + SQLSMALLINT completionType; - ODBCConnection *odbcConnectionObject; - SQLSMALLINT completionType; - - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - return_code = - SQLEndTran - ( - SQL_HANDLE_DBC, // HandleType - odbcConnectionObject->hDBC, // Handle - completionType // CompletionType - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error ending the transaction on the connection\0"); - return; - } + return_code = + SQLEndTran(SQL_HANDLE_DBC, odbcConnectionObject->hDBC, completionType); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError("[odbc] Error ending the transaction on the connection\0"); + return; + } - //Reset the connection back to autocommit - return_code = - SQLSetConnectAttr - ( - odbcConnectionObject->hDBC, // ConnectionHandle - SQL_ATTR_AUTOCOMMIT, // Attribute - (SQLPOINTER) SQL_AUTOCOMMIT_ON, // ValuePtr - SQL_NTS // StringLength - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); - SetError("[odbc] Error turning on autocommit on the connection\0"); - return; - } + // Reset the connection back to autocommit + return_code = SQLSetConnectAttr( + odbcConnectionObject->hDBC, SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_NTS + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC); + SetError("[odbc] Error turning on autocommit on the connection\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; + std::vector callbackArguments; - callbackArguments.push_back(env.Null()); + callbackArguments.push_back(env.Null()); - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } public: - - EndTransactionAsyncWorker(ODBCConnection *odbcConnectionObject, SQLSMALLINT completionType, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnectionObject(odbcConnectionObject), + EndTransactionAsyncWorker( + ODBCConnection* odbcConnectionObject, SQLSMALLINT completionType, + Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcConnectionObject(odbcConnectionObject), completionType(completionType) {} - ~EndTransactionAsyncWorker() {} + ~EndTransactionAsyncWorker() {} }; - /* * ODBCConnection::Commit * @@ -3128,20 +3045,24 @@ class EndTransactionAsyncWorker : public ODBCAsyncWorker { * Napi::Value: * Undefined */ -Napi::Value ODBCConnection::Commit(const Napi::CallbackInfo &info) { +Napi::Value ODBCConnection::Commit(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); if (!info[0].IsFunction()) { - Napi::TypeError::New(env, "[odbc]: commit(callback): first argument must be a function").ThrowAsJavaScriptException(); + Napi::TypeError::New( + env, "[odbc]: commit(callback): first argument must be a function" + ) + .ThrowAsJavaScriptException(); return env.Null(); } Napi::Function callback = info[0].As(); // calls EndTransactionAsyncWorker with SQL_COMMIT option - EndTransactionAsyncWorker *worker = new EndTransactionAsyncWorker(this, SQL_COMMIT, callback); + EndTransactionAsyncWorker* worker = + new EndTransactionAsyncWorker(this, SQL_COMMIT, callback); worker->Queue(); return env.Undefined(); @@ -3150,8 +3071,8 @@ Napi::Value ODBCConnection::Commit(const Napi::CallbackInfo &info) { /* * ODBCConnection::Rollback * - * Description: Rollback a transaction by calling SQLEndTran on the connection - * in an AsyncWorker with SQL_ROLLBACK option. + * Description: Rollback a transaction by calling SQLEndTran on the + * connection in an AsyncWorker with SQL_ROLLBACK option. * * Parameters: * const Napi::CallbackInfo& info: @@ -3167,50 +3088,41 @@ Napi::Value ODBCConnection::Commit(const Napi::CallbackInfo &info) { * Napi::Value: * Undefined */ -Napi::Value ODBCConnection::Rollback(const Napi::CallbackInfo &info) { +Napi::Value ODBCConnection::Rollback(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); if (info.Length() != 1 && !info[0].IsFunction()) { - Napi::TypeError::New(env, "[odbc]: rollback: first argument must be a function").ThrowAsJavaScriptException(); + Napi::TypeError::New( + env, "[odbc]: rollback: first argument must be a function" + ) + .ThrowAsJavaScriptException(); return env.Null(); } Napi::Function callback = info[0].As(); // calls EndTransactionAsyncWorker with SQL_ROLLBACK option - EndTransactionAsyncWorker *worker = new EndTransactionAsyncWorker(this, SQL_ROLLBACK, callback); + EndTransactionAsyncWorker* worker = + new EndTransactionAsyncWorker(this, SQL_ROLLBACK, callback); worker->Queue(); return env.Undefined(); } SQLRETURN -prepare_for_fetch -( - StatementData *data -) -{ +prepare_for_fetch(StatementData* data) { SQLRETURN return_code; - - return_code = - SQLRowCount - ( - data->hstmt, // StatementHandle - &data->rowCount // RowCountPtr - ); + + return_code = SQLRowCount(data->hstmt, &data->rowCount); if (!SQL_SUCCEEDED(return_code)) { // if SQLRowCount failed, return early with the return_code return return_code; } - return_code = - bind_buffers - ( - data - ); + return_code = bind_buffers(data); if (!SQL_SUCCEEDED(return_code)) { // if BindColumns failed, return early with the return_code @@ -3220,144 +3132,98 @@ prepare_for_fetch return return_code; } - SQLRETURN -bind_buffers -( - StatementData *data -) -{ +bind_buffers(StatementData* data) { SQLRETURN return_code; - return_code = - SQLNumResultCols - ( - data->hstmt, - &data->column_count - ); + return_code = SQLNumResultCols(data->hstmt, &data->column_count); - if (!SQL_SUCCEEDED(return_code)) - { + if (!SQL_SUCCEEDED(return_code)) { return return_code; } - return_code = - SQLSetStmtAttr - ( - data->hstmt, - SQL_ATTR_ROW_BIND_TYPE, - SQL_BIND_BY_COLUMN, - IGNORED_PARAMETER + return_code = SQLSetStmtAttr( + data->hstmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, IGNORED_PARAMETER ); // Some drivers don't feel the need to implement the SQL_ATTR_ROW_BIND_TYPE // option for SQLSetStmtAttr, so this call will return an error. Instead of // returning an error, just set "simple_binding" to true, and bind one row // at a time for fetching. - if (!SQL_SUCCEEDED(return_code)) - { - if (data->fetch_size != 1) - { + if (!SQL_SUCCEEDED(return_code)) { + if (data->fetch_size != 1) { return return_code; } - SQLRETURN diagnostic_return_code; + SQLRETURN diagnostic_return_code; SQLSMALLINT textLength; - SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1]; - SQLINTEGER nativeError; - SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES]; - - diagnostic_return_code = - SQLGetDiagRec - ( - SQL_HANDLE_STMT, // HandleType - data->hstmt, // Handle - 1, // RecNumber - errorSQLState, // SQLState - &nativeError, // NativeErrorPtr - errorMessage, // MessageText - ERROR_MESSAGE_BUFFER_CHARS, // BufferLength - &textLength // TextLengthPtr + SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER nativeError; + SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES]; + + diagnostic_return_code = SQLGetDiagRec( + SQL_HANDLE_STMT, data->hstmt, 1, errorSQLState, &nativeError, + errorMessage, ERROR_MESSAGE_BUFFER_CHARS, &textLength ); // Couldn't even get the diagnostic information! Something is wrong beyond // driver support for the SQLSetStmtAttr call, return the return_code - if (!SQL_SUCCEEDED(diagnostic_return_code)) - { + if (!SQL_SUCCEEDED(diagnostic_return_code)) { return return_code; } - // - if (strcmp("HY092", (const char*)errorSQLState) != 0) - { + // + if (strcmp("HY092", (const char*)errorSQLState) != 0) { return return_code; } - + data->simple_binding = true; - data->rows_fetched = 1; - return_code = SQL_SUCCESS; + data->rows_fetched = 1; + return_code = SQL_SUCCESS; } - else - { + else { // Create Columns for the column data to go into - data->columns = new Column*[data->column_count](); + data->columns = new Column*[data->column_count](); data->bound_columns = new ColumnBuffer[data->column_count](); - return_code = - SQLSetStmtAttr - ( - data->hstmt, - SQL_ATTR_ROWS_FETCHED_PTR, - (SQLPOINTER) &data->rows_fetched, + return_code = SQLSetStmtAttr( + data->hstmt, SQL_ATTR_ROWS_FETCHED_PTR, (SQLPOINTER)&data->rows_fetched, IGNORED_PARAMETER ); - if (!SQL_SUCCEEDED(return_code)) - { - if (data->fetch_size != 1) - { + if (!SQL_SUCCEEDED(return_code)) { + if (data->fetch_size != 1) { return return_code; } - SQLRETURN diagnostic_return_code; + SQLRETURN diagnostic_return_code; SQLSMALLINT textLength; - SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1]; - SQLINTEGER nativeError; - SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES]; - - diagnostic_return_code = - SQLGetDiagRec - ( - SQL_HANDLE_STMT, // HandleType - data->hstmt, // Handle - 1, // RecNumber - errorSQLState, // SQLState - &nativeError, // NativeErrorPtr - errorMessage, // MessageText - ERROR_MESSAGE_BUFFER_CHARS, // BufferLength - &textLength // TextLengthPtr + SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER nativeError; + SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES]; + + diagnostic_return_code = SQLGetDiagRec( + SQL_HANDLE_STMT, data->hstmt, 1, errorSQLState, &nativeError, + errorMessage, ERROR_MESSAGE_BUFFER_CHARS, &textLength ); - if (!SQL_SUCCEEDED(diagnostic_return_code)) - { + if (!SQL_SUCCEEDED(diagnostic_return_code)) { return return_code; } // HY092 is the SQLState produced by drivers that are known to not // implement SQL_ATTR_ROWS_FETCHED_PTR - if (strcmp("HY092", (const char*)errorSQLState) != 0) - { + if (strcmp("HY092", (const char*)errorSQLState) != 0) { return return_code; } data->simple_binding = true; - data->rows_fetched = 1; - return_code = SQL_SUCCESS; + data->rows_fetched = 1; + return_code = SQL_SUCCESS; } } - for (int i = 0; i < data->column_count; i++) - { - Column *column = new Column(); + for (int i = 0; i < data->column_count; i++) { + Column* column = new Column(); column->ColumnName = new SQLTCHAR[data->maxColumnNameLength + 1](); // TODO: Could just allocate one large SQLLEN buffer that was of size @@ -3365,18 +3231,10 @@ bind_buffers data->bound_columns[i].length_or_indicator_array = new SQLLEN[data->fetch_size](); - return_code = - SQLDescribeCol - ( - data->hstmt, // StatementHandle - i + 1, // ColumnNumber - column->ColumnName, // ColumnName - data->maxColumnNameLength + 1, // BufferLength - &column->NameLength, // NameLengthPtr - &column->DataType, // DataTypePtr - &column->ColumnSize, // ColumnSizePtr - &column->DecimalDigits, // DecimalDigitsPtr - &column->Nullable // NullablePtr + return_code = SQLDescribeCol( + data->hstmt, i + 1, column->ColumnName, data->maxColumnNameLength + 1, + &column->NameLength, &column->DataType, &column->ColumnSize, + &column->DecimalDigits, &column->Nullable ); if (!SQL_SUCCEEDED(return_code)) { return return_code; @@ -3384,17 +3242,17 @@ bind_buffers // ensuring ColumnSize values are valid according to ODBC docs if (column->DataType == SQL_TYPE_DATE && column->ColumnSize < 10) { - // ODBC docs say this should be 10, but some drivers have bugs that - // return invalid values. eg. 4D - // Ensure that it is a minimum of 10. - column->ColumnSize = 10; + // ODBC docs say this should be 10, but some drivers have bugs that + // return invalid values. eg. 4D + // Ensure that it is a minimum of 10. + column->ColumnSize = 10; } if (column->DataType == SQL_TYPE_TIME && column->ColumnSize < 8) { - // ODBC docs say this should be 8, but some drivers have bugs that - // return invalid values. eg. 4D - // Ensure that it is a minimum of 8. - column->ColumnSize = 8; + // ODBC docs say this should be 8, but some drivers have bugs that + // return invalid values. eg. 4D + // Ensure that it is a minimum of 8. + column->ColumnSize = 8; } // Assume that the column will be bound with SQLBindCol unless: @@ -3405,168 +3263,147 @@ bind_buffers column->is_long_data = false; // bind depending on the column - switch(column->DataType) { - - // LONG data types should be retrieved through SQLGetData and not - // SQLBindCol/SQLFetch, as the buffers for SQLBindCol could be absurd - // sizes for small amounts of data transferred. However, if the fetch - // size is greater than 1 and the user's driver doesn't support SQLGetData - // with block cursors, we need to bite the bullet and bind with - // SQLBindCol. - case SQL_WLONGVARCHAR: - column->bind_type = SQL_C_WCHAR; - if (data->fetch_size == 1 || data->get_data_supports.block) - { - column->is_long_data = true; - } else { - size_t character_count = column->ColumnSize + 1; - column->buffer_size = character_count * sizeof(SQLWCHAR); - data->bound_columns[i].buffer = - new SQLWCHAR[character_count * data->fetch_size](); - } - break; - case SQL_LONGVARCHAR: - column->bind_type = SQL_C_CHAR; - if (data->fetch_size == 1 || data->get_data_supports.block) - { - column->is_long_data = true; - } else { - size_t character_count = column->ColumnSize * MAX_UTF8_BYTES + 1; - column->buffer_size = character_count * sizeof(SQLCHAR); - data->bound_columns[i].buffer = - new SQLCHAR[character_count * data->fetch_size](); - } - break; - case SQL_LONGVARBINARY: - column->bind_type = SQL_C_BINARY; - if (data->fetch_size == 1 || data->get_data_supports.block) - { - column->is_long_data = true; - } else { - column->buffer_size = (column->ColumnSize) * sizeof(SQLCHAR); - data->bound_columns[i].buffer = new SQLCHAR[column->buffer_size](); - } - break; - - case SQL_REAL: - case SQL_DECIMAL: - case SQL_NUMERIC: - { - size_t character_count = column->ColumnSize + 2; + switch (column->DataType) { + + // LONG data types should be retrieved through SQLGetData and not + // SQLBindCol/SQLFetch, as the buffers for SQLBindCol could be absurd + // sizes for small amounts of data transferred. However, if the fetch + // size is greater than 1 and the user's driver doesn't support SQLGetData + // with block cursors, we need to bite the bullet and bind with + // SQLBindCol. + case SQL_WLONGVARCHAR: + column->bind_type = SQL_C_WCHAR; + if (data->fetch_size == 1 || data->get_data_supports.block) { + column->is_long_data = true; + } + else { + size_t character_count = column->ColumnSize + 1; + column->buffer_size = character_count * sizeof(SQLWCHAR); + data->bound_columns[i].buffer = + new SQLWCHAR[character_count * data->fetch_size](); + } + break; + case SQL_LONGVARCHAR: + column->bind_type = SQL_C_CHAR; + if (data->fetch_size == 1 || data->get_data_supports.block) { + column->is_long_data = true; + } + else { + size_t character_count = column->ColumnSize * MAX_UTF8_BYTES + 1; column->buffer_size = character_count * sizeof(SQLCHAR); - column->bind_type = SQL_C_CHAR; data->bound_columns[i].buffer = new SQLCHAR[character_count * data->fetch_size](); - break; } - - case SQL_FLOAT: - case SQL_DOUBLE: - { - column->buffer_size = sizeof(SQLDOUBLE); - column->bind_type = SQL_C_DOUBLE; - data->bound_columns[i].buffer = - new SQLDOUBLE[data->fetch_size](); - break; + break; + case SQL_LONGVARBINARY: + column->bind_type = SQL_C_BINARY; + if (data->fetch_size == 1 || data->get_data_supports.block) { + column->is_long_data = true; } - - // Bind SQL_TINYINT to SQL_C_SHORT: SQLSMALLINT can hold the full range - // of TINYINT columns, which could be signed (-128 to 127) or unsigned - // (0 to 255) depending on the DBMS. This ensures negative values don't - // fail conversion with SQLSTATE 22003 and unsigned values above 127 are - // preserved. - case SQL_TINYINT: - case SQL_SMALLINT: - { - column->buffer_size = sizeof(SQLSMALLINT); - column->bind_type = SQL_C_SHORT; - data->bound_columns[i].buffer = - new SQLSMALLINT[data->fetch_size](); - break; + else { + column->buffer_size = (column->ColumnSize) * sizeof(SQLCHAR); + data->bound_columns[i].buffer = new SQLCHAR[column->buffer_size](); } + break; + + case SQL_REAL: + case SQL_DECIMAL: + case SQL_NUMERIC: { + size_t character_count = column->ColumnSize + 2; + column->buffer_size = character_count * sizeof(SQLCHAR); + column->bind_type = SQL_C_CHAR; + data->bound_columns[i].buffer = + new SQLCHAR[character_count * data->fetch_size](); + break; + } - case SQL_INTEGER: - { - column->buffer_size = sizeof(SQLINTEGER); - column->bind_type = SQL_C_SLONG; - data->bound_columns[i].buffer = - new SQLINTEGER[data->fetch_size](); - break; - } + case SQL_FLOAT: + case SQL_DOUBLE: { + column->buffer_size = sizeof(SQLDOUBLE); + column->bind_type = SQL_C_DOUBLE; + data->bound_columns[i].buffer = new SQLDOUBLE[data->fetch_size](); + break; + } - case SQL_BIGINT: - { - column->buffer_size = sizeof(SQLBIGINT); - column->bind_type = SQL_C_SBIGINT; - data->bound_columns[i].buffer = - new SQLBIGINT[data->fetch_size](); - break; - } + // Bind SQL_TINYINT to SQL_C_SHORT: SQLSMALLINT can hold the full range + // of TINYINT columns, which could be signed (-128 to 127) or unsigned + // (0 to 255) depending on the DBMS. This ensures negative values don't + // fail conversion with SQLSTATE 22003 and unsigned values above 127 are + // preserved. + case SQL_TINYINT: + case SQL_SMALLINT: { + column->buffer_size = sizeof(SQLSMALLINT); + column->bind_type = SQL_C_SHORT; + data->bound_columns[i].buffer = new SQLSMALLINT[data->fetch_size](); + break; + } - case SQL_BINARY: - case SQL_VARBINARY: - { - column->bind_type = SQL_C_BINARY; - // Fixes a known issue with SQL Server and (max) length fields - if (column->ColumnSize == 0) - { - column->is_long_data = true; - break; - } - column->buffer_size = column->ColumnSize; - data->bound_columns[i].buffer = - new SQLCHAR[column->buffer_size * data->fetch_size](); + case SQL_INTEGER: { + column->buffer_size = sizeof(SQLINTEGER); + column->bind_type = SQL_C_SLONG; + data->bound_columns[i].buffer = new SQLINTEGER[data->fetch_size](); + break; + } + + case SQL_BIGINT: { + column->buffer_size = sizeof(SQLBIGINT); + column->bind_type = SQL_C_SBIGINT; + data->bound_columns[i].buffer = new SQLBIGINT[data->fetch_size](); + break; + } + + case SQL_BINARY: + case SQL_VARBINARY: { + column->bind_type = SQL_C_BINARY; + // Fixes a known issue with SQL Server and (max) length fields + if (column->ColumnSize == 0) { + column->is_long_data = true; break; } + column->buffer_size = column->ColumnSize; + data->bound_columns[i].buffer = + new SQLCHAR[column->buffer_size * data->fetch_size](); + break; + } - case SQL_WCHAR: - case SQL_WVARCHAR: - { - column->bind_type = SQL_C_WCHAR; - // Fixes a known issue with SQL Server and (max) length fields - if (column->ColumnSize == 0) - { - column->is_long_data = true; - break; - } - size_t character_count = column->ColumnSize + 1; - column->buffer_size = character_count * sizeof(SQLWCHAR); - data->bound_columns[i].buffer = - new SQLWCHAR[character_count * data->fetch_size](); + case SQL_WCHAR: + case SQL_WVARCHAR: { + column->bind_type = SQL_C_WCHAR; + // Fixes a known issue with SQL Server and (max) length fields + if (column->ColumnSize == 0) { + column->is_long_data = true; break; } + size_t character_count = column->ColumnSize + 1; + column->buffer_size = character_count * sizeof(SQLWCHAR); + data->bound_columns[i].buffer = + new SQLWCHAR[character_count * data->fetch_size](); + break; + } - case SQL_CHAR: - case SQL_VARCHAR: - default: - { - column->bind_type = SQL_C_CHAR; - // Fixes a known issue with SQL Server and (max) length fields - if (column->ColumnSize == 0) - { - column->is_long_data = true; - break; - } - size_t character_count = column->ColumnSize * MAX_UTF8_BYTES + 1; - column->buffer_size = character_count * sizeof(SQLCHAR); - data->bound_columns[i].buffer = - new SQLCHAR[character_count * data->fetch_size](); + case SQL_CHAR: + case SQL_VARCHAR: + default: { + column->bind_type = SQL_C_CHAR; + // Fixes a known issue with SQL Server and (max) length fields + if (column->ColumnSize == 0) { + column->is_long_data = true; break; } + size_t character_count = column->ColumnSize * MAX_UTF8_BYTES + 1; + column->buffer_size = character_count * sizeof(SQLCHAR); + data->bound_columns[i].buffer = + new SQLCHAR[character_count * data->fetch_size](); + break; + } } - if (!column->is_long_data) - { + if (!column->is_long_data) { // SQLBindCol binds application data buffers to columns in the result set. - return_code = - SQLBindCol - ( - data->hstmt, // StatementHandle - i + 1, // ColumnNumber - column->bind_type, // TargetType - data->bound_columns[i].buffer, // TargetValuePtr - column->buffer_size, // BufferLength - data->bound_columns[i].length_or_indicator_array // StrLen_or_Ind + return_code = SQLBindCol( + data->hstmt, i + 1, column->bind_type, data->bound_columns[i].buffer, + column->buffer_size, + data->bound_columns[i].length_or_indicator_array // StrLen_or_Ind ); if (!SQL_SUCCEEDED(return_code)) { @@ -3579,13 +3416,7 @@ bind_buffers } SQLRETURN -fetch_and_store -( - StatementData *data, - bool set_position, - bool *alloc_error -) -{ +fetch_and_store(StatementData* data, bool set_position, bool* alloc_error) { SQLRETURN return_code; // If column_count is 0, it likely means that the query was an UPDATE, INSERT, @@ -3593,86 +3424,65 @@ fetch_and_store // cursor. Check here so that SQLFetch is only run if there is an actual // result set. if (data->column_count > 0) { - return_code = - SQLFetch - ( - data->hstmt - ); + return_code = SQLFetch(data->hstmt); - if (SQL_SUCCEEDED(return_code)) - { + if (SQL_SUCCEEDED(return_code)) { // iterate through all of the rows fetched (but not the fetch size) - for (size_t row_index = 0; row_index < data->rows_fetched; row_index++) - { - if (set_position && data->get_data_supports.block && data->fetch_size > 1) - { + for (size_t row_index = 0; row_index < data->rows_fetched; row_index++) { + if (set_position && data->get_data_supports.block && + data->fetch_size > 1) { // In case the result set contains columns that contain LONG data // types, use SQLSetPos to set the row we are transferring bound data // from, and use SQLGetData in the same loop. - return_code = - SQLSetPos - ( - data->hstmt, - (SQLSETPOSIROW) row_index + 1, - SQL_POSITION, + return_code = SQLSetPos( + data->hstmt, (SQLSETPOSIROW)row_index + 1, SQL_POSITION, SQL_LOCK_NO_CHANGE ); - if (!SQL_SUCCEEDED(return_code)) - { + if (!SQL_SUCCEEDED(return_code)) { return return_code; } } // Copy the data over if the row status array indicates success - if - ( - data->simple_binding || - ( - data->row_status_array[row_index] == SQL_ROW_SUCCESS || - data->row_status_array[row_index] == SQL_ROW_SUCCESS_WITH_INFO - ) - ) - { - ColumnData *row = new ColumnData[data->column_count](); + if (data->simple_binding || + (data->row_status_array[row_index] == SQL_ROW_SUCCESS || + data->row_status_array[row_index] == SQL_ROW_SUCCESS_WITH_INFO)) { + ColumnData* row = new ColumnData[data->column_count](); // Iterate over each column, putting the data in the row object - for (int column_index = 0; column_index < data->column_count; column_index++) - { - row[column_index].bind_type = data->columns[column_index]->bind_type; + for (int column_index = 0; column_index < data->column_count; + column_index++) { + row[column_index].bind_type = + data->columns[column_index]->bind_type; // The column contained SQL_(W)LONG* data, so we didn't call // SQLBindCol, and therefore there is no data to move from a buffer. // Instead, call SQLGetData, and adjust buffer size accordingly - if (data->columns[column_index]->is_long_data) - { + if (data->columns[column_index]->is_long_data) { SQLPOINTER target_buffer; - SQLLEN buffer_size = - data->query_options.initial_long_data_buffer_size; - SQLLEN string_length_or_indicator; - SQLLEN data_returned_length = 0; + SQLLEN buffer_size = + data->query_options.initial_long_data_buffer_size; + SQLLEN string_length_or_indicator; + SQLLEN data_returned_length = 0; // We're allocating with malloc/realloc here, so the destructor // needs to use free instead of delete[]. row[column_index].use_free = true; - if (data->columns[column_index]->bind_type == SQL_C_WCHAR) - { - row[column_index].wchar_data = (SQLWCHAR *)malloc(buffer_size); + if (data->columns[column_index]->bind_type == SQL_C_WCHAR) { + row[column_index].wchar_data = (SQLWCHAR*)malloc(buffer_size); // if bad malloc, indicate and return - if (row[column_index].wchar_data == NULL) - { + if (row[column_index].wchar_data == NULL) { *alloc_error = true; return return_code; } target_buffer = row[column_index].wchar_data; } - else - { - row[column_index].char_data = (SQLCHAR *)malloc(buffer_size); + else { + row[column_index].char_data = (SQLCHAR*)malloc(buffer_size); // if bad malloc, indicate and return - if (row[column_index].char_data == NULL) - { + if (row[column_index].char_data == NULL) { *alloc_error = true; return return_code; } @@ -3680,194 +3490,150 @@ fetch_and_store } // Get the first chunk of data - return_code = - SQLGetData - ( - data->hstmt, - column_index + 1, - data->columns[column_index]->bind_type, - target_buffer, - buffer_size, - &string_length_or_indicator + return_code = SQLGetData( + data->hstmt, column_index + 1, + data->columns[column_index]->bind_type, target_buffer, + buffer_size, &string_length_or_indicator ); - if (!SQL_SUCCEEDED(return_code)) - { + if (!SQL_SUCCEEDED(return_code)) { return return_code; } // If the data is null, simply indicate and continue to the next // column - if (string_length_or_indicator == SQL_NULL_DATA) - { + if (string_length_or_indicator == SQL_NULL_DATA) { row[column_index].size = SQL_NULL_DATA; continue; } // If the data (+ whatever null terminator, not included in // string_length_or_indicator) was larger than the size of the // buffer, then need to call SQLGetData at least once more - else if ( - string_length_or_indicator == SQL_NO_TOTAL || - string_length_or_indicator >= buffer_size - ) - { + else if (string_length_or_indicator == SQL_NO_TOTAL || + string_length_or_indicator >= buffer_size) { // Continue looping as long as we don't know the final resize // that we need. Once a buffer size is returned instead of // SQL_NO_TOTAL, we might need to call SQLGetData one more time, // and the break out of this loop. - while (true) - { + while (true) { // Handling the data that was returned depends heavily on the // target type of the binding data. - switch(data->columns[column_index]->bind_type) - { - case SQL_C_BINARY: - { - data_returned_length = ( - row[column_index].size + string_length_or_indicator <= buffer_size ? - string_length_or_indicator : - buffer_size - row[column_index].size - ); - buffer_size = - string_length_or_indicator == SQL_NO_TOTAL ? - buffer_size * 2 : - row[column_index].size + string_length_or_indicator; - SQLCHAR *temp_realloc = - (SQLCHAR *) - realloc - ( - row[column_index].char_data, - buffer_size - ); - if (temp_realloc == NULL) - { - free(row[column_index].char_data); - *alloc_error = true; - return return_code; - } - row[column_index].char_data = temp_realloc; - row[column_index].size += data_returned_length; - target_buffer = - row[column_index].char_data + row[column_index].size; - break; + switch (data->columns[column_index]->bind_type) { + case SQL_C_BINARY: { + data_returned_length = + (row[column_index].size + string_length_or_indicator <= + buffer_size + ? string_length_or_indicator + : buffer_size - row[column_index].size); + buffer_size = + string_length_or_indicator == SQL_NO_TOTAL + ? buffer_size * 2 + : row[column_index].size + string_length_or_indicator; + SQLCHAR* temp_realloc = (SQLCHAR*)realloc( + row[column_index].char_data, buffer_size + ); + if (temp_realloc == NULL) { + free(row[column_index].char_data); + *alloc_error = true; + return return_code; } - case SQL_C_WCHAR: - { - data_returned_length = - strlen16 - ( - (const char16_t *) target_buffer - ) * sizeof(SQLWCHAR); - buffer_size = - string_length_or_indicator == SQL_NO_TOTAL ? - buffer_size * 2 : - row[column_index].size + string_length_or_indicator + sizeof(SQLWCHAR); - SQLWCHAR *temp_realloc = - (SQLWCHAR *) - realloc - ( - row[column_index].wchar_data, - buffer_size - ); - if (temp_realloc == NULL) - { - free(row[column_index].wchar_data); - *alloc_error = true; - return return_code; - } - row[column_index].wchar_data = temp_realloc; - row[column_index].size += data_returned_length; - target_buffer = - row[column_index].wchar_data + (row[column_index].size) / sizeof(SQLWCHAR); - break; + row[column_index].char_data = temp_realloc; + row[column_index].size += data_returned_length; + target_buffer = + row[column_index].char_data + row[column_index].size; + break; + } + case SQL_C_WCHAR: { + data_returned_length = + strlen16((const char16_t*)target_buffer) * + sizeof(SQLWCHAR); + buffer_size = string_length_or_indicator == SQL_NO_TOTAL + ? buffer_size * 2 + : row[column_index].size + + string_length_or_indicator + + sizeof(SQLWCHAR); + SQLWCHAR* temp_realloc = (SQLWCHAR*)realloc( + row[column_index].wchar_data, buffer_size + ); + if (temp_realloc == NULL) { + free(row[column_index].wchar_data); + *alloc_error = true; + return return_code; } - case SQL_C_CHAR: - default: - { - data_returned_length = - strlen - ( - (const char *) target_buffer - ); - buffer_size = - string_length_or_indicator == SQL_NO_TOTAL ? - buffer_size * 2 : - row[column_index].size + (string_length_or_indicator * MAX_UTF8_BYTES) + 1; - SQLCHAR *temp_realloc = - (SQLCHAR *) - realloc - ( - row[column_index].char_data, - buffer_size - ); - if (temp_realloc == NULL) - { - free(row[column_index].char_data); - *alloc_error = true; - return return_code; - } - row[column_index].char_data = temp_realloc; - row[column_index].size += data_returned_length; - target_buffer = - row[column_index].char_data + row[column_index].size; - break; + row[column_index].wchar_data = temp_realloc; + row[column_index].size += data_returned_length; + target_buffer = row[column_index].wchar_data + + (row[column_index].size) / sizeof(SQLWCHAR); + break; + } + case SQL_C_CHAR: + default: { + data_returned_length = strlen((const char*)target_buffer); + buffer_size = + string_length_or_indicator == SQL_NO_TOTAL + ? buffer_size * 2 + : row[column_index].size + + (string_length_or_indicator * MAX_UTF8_BYTES) + 1; + SQLCHAR* temp_realloc = (SQLCHAR*)realloc( + row[column_index].char_data, buffer_size + ); + if (temp_realloc == NULL) { + free(row[column_index].char_data); + *alloc_error = true; + return return_code; } + row[column_index].char_data = temp_realloc; + row[column_index].size += data_returned_length; + target_buffer = + row[column_index].char_data + row[column_index].size; + break; } + } + + SQLLEN buffer_free_space = + buffer_size - row[column_index].size; - SQLLEN buffer_free_space = buffer_size - row[column_index].size; - - return_code = - SQLGetData - ( - data->hstmt, - column_index + 1, - data->columns[column_index]->bind_type, - target_buffer, - buffer_free_space, - &string_length_or_indicator + return_code = SQLGetData( + data->hstmt, column_index + 1, + data->columns[column_index]->bind_type, target_buffer, + buffer_free_space, &string_length_or_indicator ); - if (!SQL_SUCCEEDED(return_code)) - { - if (return_code == SQL_NO_DATA) - { + if (!SQL_SUCCEEDED(return_code)) { + if (return_code == SQL_NO_DATA) { data->storedRows.push_back(row); } return return_code; } bool break_loop = false; - if (string_length_or_indicator != SQL_NO_TOTAL) - { - switch (data->columns[column_index]->bind_type) - { - case SQL_C_BINARY: - if (string_length_or_indicator <= buffer_free_space) - { - row[column_index].size += string_length_or_indicator; - break_loop = true; - } - break; - case SQL_C_WCHAR: - // SQLGetData requires space for the null-terminator - if (string_length_or_indicator <= buffer_free_space - (SQLLEN)sizeof(SQLWCHAR)) - { - row[column_index].size += string_length_or_indicator; - break_loop = true; - } - break; - case SQL_C_CHAR: - default: - // SQLGetData requires space for the null-terminator - if (string_length_or_indicator <= buffer_free_space - (SQLLEN)sizeof(SQLCHAR)) - { - row[column_index].size += string_length_or_indicator; - break_loop = true; - } - break; + if (string_length_or_indicator != SQL_NO_TOTAL) { + switch (data->columns[column_index]->bind_type) { + case SQL_C_BINARY: + if (string_length_or_indicator <= buffer_free_space) { + row[column_index].size += string_length_or_indicator; + break_loop = true; + } + break; + case SQL_C_WCHAR: + // SQLGetData requires space for the null-terminator + if (string_length_or_indicator <= + buffer_free_space - (SQLLEN)sizeof(SQLWCHAR)) { + row[column_index].size += string_length_or_indicator; + break_loop = true; + } + break; + case SQL_C_CHAR: + default: + // SQLGetData requires space for the null-terminator + if (string_length_or_indicator <= + buffer_free_space - (SQLLEN)sizeof(SQLCHAR)) { + row[column_index].size += string_length_or_indicator; + break_loop = true; + } + break; } } - if (break_loop) - { + if (break_loop) { break; } } @@ -3875,92 +3641,98 @@ fetch_and_store // The happy path, where there is no need to resize the buffer // and call SQLGetData again. Just note the size of data returned // and then continue - else - { + else { row[column_index].size = string_length_or_indicator; } } // Columns that were bound with SQLBinCol, because they did not // hold SQL_(W)LONG* data, or because the user's driver doesn't // support block cursors + SQLGetData. - else - { - if (data->bound_columns[column_index].length_or_indicator_array[row_index] == SQL_NULL_DATA) { + else { + if (data->bound_columns[column_index] + .length_or_indicator_array[row_index] == SQL_NULL_DATA) { row[column_index].size = SQL_NULL_DATA; } - else - { - switch(data->columns[column_index]->bind_type) - { - case SQL_C_DOUBLE: - row[column_index].double_data = - ((SQLDOUBLE *)(data->bound_columns[column_index].buffer))[row_index]; - break; - - case SQL_C_SSHORT: - case SQL_C_SHORT: - row[column_index].smallint_data = - ((SQLSMALLINT *)(data->bound_columns[column_index].buffer))[row_index]; - break; - - case SQL_C_USHORT: - row[column_index].usmallint_data = - ((SQLUSMALLINT *)(data->bound_columns[column_index].buffer))[row_index]; - break; - - case SQL_C_SLONG: - row[column_index].integer_data = - ((SQLINTEGER *)(data->bound_columns[column_index].buffer))[row_index]; - break; - - case SQL_C_SBIGINT: - row[column_index].bigint_data = - ((SQLBIGINT *)(data->bound_columns[column_index].buffer))[row_index]; - break; - - case SQL_C_BINARY: - { - row[column_index].size = data->bound_columns[column_index].length_or_indicator_array[row_index]; - row[column_index].char_data = new SQLCHAR[row[column_index].size](); - memcpy( - row[column_index].char_data, - (SQLCHAR *)data->bound_columns[column_index].buffer + row_index * data->columns[column_index]->buffer_size, - row[column_index].size - ); - break; - } + else { + switch (data->columns[column_index]->bind_type) { + case SQL_C_DOUBLE: + row[column_index].double_data = + ((SQLDOUBLE*)(data->bound_columns[column_index] + .buffer))[row_index]; + break; + + case SQL_C_SSHORT: + case SQL_C_SHORT: + row[column_index].smallint_data = + ((SQLSMALLINT*)(data->bound_columns[column_index] + .buffer))[row_index]; + break; + + case SQL_C_USHORT: + row[column_index].usmallint_data = + ((SQLUSMALLINT*)(data->bound_columns[column_index] + .buffer))[row_index]; + break; + + case SQL_C_SLONG: + row[column_index].integer_data = + ((SQLINTEGER*)(data->bound_columns[column_index] + .buffer))[row_index]; + break; + + case SQL_C_SBIGINT: + row[column_index].bigint_data = + ((SQLBIGINT*)(data->bound_columns[column_index] + .buffer))[row_index]; + break; + + case SQL_C_BINARY: { + row[column_index].size = + data->bound_columns[column_index] + .length_or_indicator_array[row_index]; + row[column_index].char_data = + new SQLCHAR[row[column_index].size](); + memcpy( + row[column_index].char_data, + (SQLCHAR*)data->bound_columns[column_index].buffer + + row_index * data->columns[column_index]->buffer_size, + row[column_index].size + ); + break; + } - case SQL_C_WCHAR: - { - SQLWCHAR *memory_start = (SQLWCHAR *)data->bound_columns[column_index].buffer + (row_index * (data->columns[column_index]->ColumnSize + 1)); - row[column_index].size = strlen16((const char16_t *)memory_start) * sizeof(SQLWCHAR); - row[column_index].wchar_data = new SQLWCHAR[(row[column_index].size / sizeof(SQLWCHAR)) + 1](); - memcpy - ( - row[column_index].wchar_data, - memory_start, - row[column_index].size - ); - break; - } + case SQL_C_WCHAR: { + SQLWCHAR* memory_start = + (SQLWCHAR*)data->bound_columns[column_index].buffer + + (row_index * (data->columns[column_index]->ColumnSize + 1)); + row[column_index].size = + strlen16((const char16_t*)memory_start) * sizeof(SQLWCHAR); + row[column_index].wchar_data = new SQLWCHAR + [(row[column_index].size / sizeof(SQLWCHAR)) + 1](); + memcpy( + row[column_index].wchar_data, memory_start, + row[column_index].size + ); + break; + } - case SQL_C_CHAR: - default: - { - SQLCHAR *memory_start = (SQLCHAR *)data->bound_columns[column_index].buffer + (row_index * data->columns[column_index]->buffer_size); - row[column_index].size = strlen((const char *)memory_start); - // Although fields going from SQL_C_CHAR to Napi::String use - // row[column_index].size, NUMERIC data uses atof() which requires - // a null terminator. Need to add an aditional byte. - row[column_index].char_data = new SQLCHAR[row[column_index].size + 1](); - memcpy - ( - row[column_index].char_data, - memory_start, - row[column_index].size - ); - break; - } + case SQL_C_CHAR: + default: { + SQLCHAR* memory_start = + (SQLCHAR*)data->bound_columns[column_index].buffer + + (row_index * data->columns[column_index]->buffer_size); + row[column_index].size = strlen((const char*)memory_start); + // Although fields going from SQL_C_CHAR to Napi::String use + // row[column_index].size, NUMERIC data uses atof() which + // requires a null terminator. Need to add an aditional byte. + row[column_index].char_data = + new SQLCHAR[row[column_index].size + 1](); + memcpy( + row[column_index].char_data, memory_start, + row[column_index].size + ); + break; + } // TODO: Unhandled C types: // SQL_C_SSHORT @@ -3979,22 +3751,26 @@ fetch_and_store // SQL_C_TYPE_NUMERIC // SQL_C_GUID } - row[column_index].bind_type = data->columns[column_index]->bind_type; + row[column_index].bind_type = + data->columns[column_index]->bind_type; } } } data->storedRows.push_back(row); - } else { + } + else { // TODO: } } } - } else { + } + else { return_code = SQL_NO_DATA; } - // If SQL_SUCCEEDED failed and return code isn't SQL_NO_DATA, there is an error - if(!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) { + // If SQL_SUCCEEDED failed and return code isn't SQL_NO_DATA, there is an + // error + if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) { return return_code; } @@ -4002,8 +3778,8 @@ fetch_and_store // returned SQL_NO_DATA, we have reached the end of the result set. Set // result_set_end_reached to true so that SQLFetch doesn't get called again. // SQLFetch again (sort of a short-circuit) - if (data->simple_binding == true || return_code == SQL_NO_DATA || data->row_status_array[data->fetch_size - 1] == SQL_ROW_NOROW) - { + if (data->simple_binding == true || return_code == SQL_NO_DATA || + data->row_status_array[data->fetch_size - 1] == SQL_ROW_NOROW) { data->result_set_end_reached = true; } @@ -4011,13 +3787,7 @@ fetch_and_store } SQLRETURN -fetch_all_and_store -( - StatementData *data, - bool set_position, - bool *alloc_error -) -{ +fetch_all_and_store(StatementData* data, bool set_position, bool* alloc_error) { SQLRETURN return_code; do { @@ -4026,13 +3796,13 @@ fetch_all_and_store // If there was an alloc error when fetching and storing, return and the // the caller will need to handle everything - if (*alloc_error == true) - { + if (*alloc_error == true) { return return_code; } - // If SQL_SUCCEEDED failed and return code isn't SQL_NO_DATA, there is an error - if(return_code != SQL_NO_DATA) { + // If SQL_SUCCEEDED failed and return code isn't SQL_NO_DATA, there is an + // error + if (return_code != SQL_NO_DATA) { return return_code; } @@ -4042,10 +3812,11 @@ fetch_all_and_store if (!SQL_SUCCEEDED(return_code)) { return return_code; } - } else { + } + else { return_code = SQL_SUCCESS; } - + return return_code; } @@ -4054,15 +3825,12 @@ fetch_all_and_store // header files. This is then used when returning column data to the user. If // the user desires the native (non-ODBC) data type, they should call the // columns() function available on the Connection object. -#define CASE_RETURN_DATA_TYPE_NAME(n) case n: return #n - -const char* -get_odbc_type_name -( - SQLSMALLINT dataType -) -{ - switch(dataType) { +#define CASE_RETURN_DATA_TYPE_NAME(n) \ + case n: \ + return #n + +const char* get_odbc_type_name(SQLSMALLINT dataType) { + switch (dataType) { CASE_RETURN_DATA_TYPE_NAME(SQL_CHAR); CASE_RETURN_DATA_TYPE_NAME(SQL_VARCHAR); CASE_RETURN_DATA_TYPE_NAME(SQL_LONGVARCHAR); @@ -4100,16 +3868,19 @@ get_odbc_type_name CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_MINUTE_TO_SECOND); CASE_RETURN_DATA_TYPE_NAME(SQL_GUID); - default: return "UNKNOWN"; + default: + return "UNKNOWN"; } } // All of data has been loaded into data->storedRows. Have to take the data // stored in there and convert it it into JavaScript to be given to the // Node.js runtime. -Napi::Array process_data_for_napi(Napi::Env env, StatementData *data, Napi::Array napiParameters) { +Napi::Array process_data_for_napi( + Napi::Env env, StatementData* data, Napi::Array napiParameters +) { - Column **columns = data->columns; + Column** columns = data->columns; SQLSMALLINT columnCount = data->column_count; // The rows array is the data structure that is returned from query results. @@ -4141,42 +3912,78 @@ Napi::Array process_data_for_napi(Napi::Env env, StatementData *data, Napi::Arra // set the 'statement' property if (data->sql == NULL) { rows.Set(Napi::String::New(env, STATEMENT), env.Null()); - } else { - #ifdef UNICODE - rows.Set(Napi::String::New(env, STATEMENT), Napi::String::New(env, (const char16_t*)data->sql)); - #else - rows.Set(Napi::String::New(env, STATEMENT), Napi::String::New(env, (const char*)data->sql)); - #endif + } + else { +#ifdef UNICODE + rows.Set( + Napi::String::New(env, STATEMENT), + Napi::String::New(env, (const char16_t*)data->sql) + ); +#else + rows.Set( + Napi::String::New(env, STATEMENT), + Napi::String::New(env, (const char*)data->sql) + ); +#endif } // set the 'parameters' property if (napiParameters.IsEmpty()) { rows.Set(Napi::String::New(env, PARAMETERS), env.Undefined()); - } else { + } + else { rows.Set(Napi::String::New(env, PARAMETERS), napiParameters); } // set the 'return' property - rows.Set(Napi::String::New(env, RETURN), env.Undefined()); // TODO: This doesn't exist on my DBMS of choice, need to test on MSSQL Server or similar + rows.Set( + Napi::String::New(env, RETURN), + env.Undefined() + ); // TODO: This doesn't exist on my DBMS of choice, + // need to test on MSSQL Server or similar // set the 'count' property - rows.Set(Napi::String::New(env, COUNT), Napi::Number::New(env, (double)data->rowCount)); + rows.Set( + Napi::String::New(env, COUNT), + Napi::Number::New(env, (double)data->rowCount) + ); // construct the array for the 'columns' property and then set Napi::Array napiColumns = Napi::Array::New(env); for (SQLSMALLINT h = 0; h < columnCount; h++) { Napi::Object column = Napi::Object::New(env); - #ifdef UNICODE - column.Set(Napi::String::New(env, NAME), Napi::String::New(env, (const char16_t*)columns[h]->ColumnName)); - #else - column.Set(Napi::String::New(env, NAME), Napi::String::New(env, (const char*)columns[h]->ColumnName)); - #endif - column.Set(Napi::String::New(env, DATA_TYPE), Napi::Number::New(env, columns[h]->DataType)); - column.Set(Napi::String::New(env, DATA_TYPE_NAME), Napi::String::New(env, get_odbc_type_name(columns[h]->DataType))); - column.Set(Napi::String::New(env, COLUMN_SIZE), Napi::Number::New(env, columns[h]->ColumnSize)); - column.Set(Napi::String::New(env, DECIMAL_DIGITS), Napi::Number::New(env, columns[h]->DecimalDigits)); - column.Set(Napi::String::New(env, NULLABLE), Napi::Boolean::New(env, columns[h]->Nullable)); +#ifdef UNICODE + column.Set( + Napi::String::New(env, NAME), + Napi::String::New(env, (const char16_t*)columns[h]->ColumnName) + ); +#else + column.Set( + Napi::String::New(env, NAME), + Napi::String::New(env, (const char*)columns[h]->ColumnName) + ); +#endif + column.Set( + Napi::String::New(env, DATA_TYPE), + Napi::Number::New(env, columns[h]->DataType) + ); + column.Set( + Napi::String::New(env, DATA_TYPE_NAME), + Napi::String::New(env, get_odbc_type_name(columns[h]->DataType)) + ); + column.Set( + Napi::String::New(env, COLUMN_SIZE), + Napi::Number::New(env, columns[h]->ColumnSize) + ); + column.Set( + Napi::String::New(env, DECIMAL_DIGITS), + Napi::Number::New(env, columns[h]->DecimalDigits) + ); + column.Set( + Napi::String::New(env, NULLABLE), + Napi::Boolean::New(env, columns[h]->Nullable) + ); napiColumns.Set(h, column); } rows.Set(Napi::String::New(env, COLUMNS), napiColumns); @@ -4187,11 +3994,12 @@ Napi::Array process_data_for_napi(Napi::Env env, StatementData *data, Napi::Arra if (data->fetch_array == true) { row = Napi::Array::New(env); - } else { + } + else { row = Napi::Object::New(env); } - ColumnData *storedRow = data->storedRows[i]; + ColumnData* storedRow = data->storedRows[i]; // Iterate over each column, putting the data in the row object for (SQLSMALLINT j = 0; j < columnCount; j++) { @@ -4201,97 +4009,116 @@ Napi::Array process_data_for_napi(Napi::Env env, StatementData *data, Napi::Arra // check for null data if (storedRow[j].size == SQL_NULL_DATA) { value = env.Null(); - } else { - switch(columns[j]->DataType) { - case SQL_REAL: - case SQL_DECIMAL: - case SQL_NUMERIC: - switch(columns[j]->bind_type) { - case SQL_C_DOUBLE: - value = Napi::Number::New(env, storedRow[j].double_data); - break; - default: - value = Napi::Number::New(env, atof((const char*)storedRow[j].char_data)); - break; - } + } + else { + switch (columns[j]->DataType) { + case SQL_REAL: + case SQL_DECIMAL: + case SQL_NUMERIC: + switch (columns[j]->bind_type) { + case SQL_C_DOUBLE: + value = Napi::Number::New(env, storedRow[j].double_data); break; - // Napi::Number - case SQL_FLOAT: - case SQL_DOUBLE: - switch(columns[j]->bind_type) { - case SQL_C_DOUBLE: - value = Napi::Number::New(env, storedRow[j].double_data); - break; - default: - value = Napi::Number::New(env, atof((const char*)storedRow[j].char_data)); - break; - } + default: + value = + Napi::Number::New(env, atof((const char*)storedRow[j].char_data)); break; - case SQL_TINYINT: - case SQL_SMALLINT: - case SQL_INTEGER: - switch(columns[j]->bind_type) { - case SQL_C_SHORT: - case SQL_C_USHORT: - case SQL_C_SSHORT: - value = Napi::Number::New(env, storedRow[j].smallint_data); - break; - case SQL_C_LONG: - case SQL_C_ULONG: - case SQL_C_SLONG: - value = Napi::Number::New(env, storedRow[j].integer_data); - break; - default: - value = Napi::Number::New(env, *(int*)storedRow[j].char_data); - break; - } + } + break; + // Napi::Number + case SQL_FLOAT: + case SQL_DOUBLE: + switch (columns[j]->bind_type) { + case SQL_C_DOUBLE: + value = Napi::Number::New(env, storedRow[j].double_data); break; - // Napi::BigInt - case SQL_BIGINT: - switch(columns[j]->bind_type) { - case SQL_C_SBIGINT: - value = Napi::BigInt::New(env, (int64_t)storedRow[j].bigint_data); - break; - default: - value = Napi::String::New(env, (char*)storedRow[j].char_data); - break; - } + default: + value = + Napi::Number::New(env, atof((const char*)storedRow[j].char_data)); break; - // Napi::ArrayBuffer - case SQL_BINARY : - case SQL_VARBINARY : - case SQL_LONGVARBINARY : { - SQLCHAR *binaryData = new SQLCHAR[storedRow[j].size]; // have to save the data on the heap - memcpy((SQLCHAR *) binaryData, storedRow[j].char_data, storedRow[j].size); - value = Napi::ArrayBuffer::New(env, binaryData, storedRow[j].size, [](Napi::Env env, void* finalizeData) { - delete[] (SQLCHAR*)finalizeData; - }); + } + break; + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + switch (columns[j]->bind_type) { + case SQL_C_SHORT: + case SQL_C_USHORT: + case SQL_C_SSHORT: + value = Napi::Number::New(env, storedRow[j].smallint_data); + break; + case SQL_C_LONG: + case SQL_C_ULONG: + case SQL_C_SLONG: + value = Napi::Number::New(env, storedRow[j].integer_data); + break; + default: + value = Napi::Number::New(env, *(int*)storedRow[j].char_data); break; } - // Napi::String (char16_t) - case SQL_WCHAR : - case SQL_WVARCHAR : - case SQL_WLONGVARCHAR : - value = Napi::String::New(env, (const char16_t*)storedRow[j].wchar_data, storedRow[j].size / sizeof(SQLWCHAR)); + break; + // Napi::BigInt + case SQL_BIGINT: + switch (columns[j]->bind_type) { + case SQL_C_SBIGINT: + value = Napi::BigInt::New(env, (int64_t)storedRow[j].bigint_data); break; - // Napi::String (char) - case SQL_CHAR : - case SQL_VARCHAR : - case SQL_LONGVARCHAR : default: - value = Napi::String::New(env, (const char*)storedRow[j].char_data, storedRow[j].size); + value = Napi::String::New(env, (char*)storedRow[j].char_data); break; + } + break; + // Napi::ArrayBuffer + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: { + SQLCHAR* binaryData = + new SQLCHAR[storedRow[j].size]; // have to save the data on the heap + memcpy( + (SQLCHAR*)binaryData, storedRow[j].char_data, storedRow[j].size + ); + value = Napi::ArrayBuffer::New( + env, binaryData, storedRow[j].size, + [](Napi::Env env, void* finalizeData) { + delete[] (SQLCHAR*)finalizeData; + } + ); + break; + } + // Napi::String (char16_t) + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + value = Napi::String::New( + env, (const char16_t*)storedRow[j].wchar_data, + storedRow[j].size / sizeof(SQLWCHAR) + ); + break; + // Napi::String (char) + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + default: + value = Napi::String::New( + env, (const char*)storedRow[j].char_data, storedRow[j].size + ); + break; } } // TODO: here if (data->fetch_array == true) { row.Set(j, value); - } else { - #ifdef UNICODE - row.Set(Napi::String::New(env, (const char16_t*)columns[j]->ColumnName), value); - #else - row.Set(Napi::String::New(env, (const char*)columns[j]->ColumnName), value); - #endif + } + else { +#ifdef UNICODE + row.Set( + Napi::String::New(env, (const char16_t*)columns[j]->ColumnName), value + ); +#else + row.Set( + Napi::String::New(env, (const char*)columns[j]->ColumnName), value + ); +#endif } } rows.Set(i, row); diff --git a/src/odbc_connection.h b/src/odbc_connection.h index b5688329..df0879e7 100644 --- a/src/odbc_connection.h +++ b/src/odbc_connection.h @@ -49,7 +49,6 @@ class ODBCConnection : public Napi::ObjectWrap { friend class CloseStatementAsyncWorker; public: - static Napi::FunctionReference constructor; static Napi::Object Init(Napi::Env env, Napi::Object exports); @@ -57,7 +56,6 @@ class ODBCConnection : public Napi::ObjectWrap { ~ODBCConnection(); private: - SQLRETURN Free(); // Functions exposed to the Node.js environment @@ -68,10 +66,10 @@ class ODBCConnection : public Napi::ObjectWrap { Napi::Value CallProcedure(const Napi::CallbackInfo& info); Napi::Value BeginTransaction(const Napi::CallbackInfo& info); - Napi::Value Commit(const Napi::CallbackInfo &info); - Napi::Value Rollback(const Napi::CallbackInfo &rollback); + Napi::Value Commit(const Napi::CallbackInfo& info); + Napi::Value Rollback(const Napi::CallbackInfo& rollback); - Napi::Value GetUsername(const Napi::CallbackInfo &info); + Napi::Value GetUsername(const Napi::CallbackInfo& info); Napi::Value Columns(const Napi::CallbackInfo& info); Napi::Value Tables(const Napi::CallbackInfo& info); @@ -81,7 +79,7 @@ class ODBCConnection : public Napi::ObjectWrap { Napi::Value GetConnAttr(const Napi::CallbackInfo& info); Napi::Value SetConnAttr(const Napi::CallbackInfo& info); - Napi::Value SetIsolationLevel(const Napi::CallbackInfo &info); + Napi::Value SetIsolationLevel(const Napi::CallbackInfo& info); // Property Getter/Setterss Napi::Value ConnectedGetter(const Napi::CallbackInfo& info); @@ -91,7 +89,10 @@ class ODBCConnection : public Napi::ObjectWrap { Napi::Value GetInfo(const Napi::Env env, const SQLUSMALLINT option); - void ParametersToArray(Napi::Reference *napiParameters, StatementData *data, unsigned char *overwriteParameters); + void ParametersToArray( + Napi::Reference* napiParameters, StatementData* data, + unsigned char* overwriteParameters + ); bool isConnected; bool autocommit; @@ -104,7 +105,7 @@ class ODBCConnection : public Napi::ObjectWrap { // Statement handles for in-flight operations, registered by the // AsyncWorkers so that cancel() can call SQLCancel on them from the main // thread while a worker thread is blocked on the ODBC call. - uv_mutex_t activeStatementsMutex; + uv_mutex_t activeStatementsMutex; std::set activeStatements; void RegisterActiveStatement(SQLHSTMT hstmt); @@ -112,14 +113,20 @@ class ODBCConnection : public Napi::ObjectWrap { ConnectionOptions connectionOptions; - GetInfoResults getInfoResults; + GetInfoResults getInfoResults; }; -Napi::Array process_data_for_napi(Napi::Env env, StatementData *data, Napi::Array napiParameters); -SQLRETURN bind_buffers(StatementData *data); -SQLRETURN prepare_for_fetch(StatementData *data); -SQLRETURN fetch_and_store(StatementData *data, bool set_position, bool *alloc_error); -SQLRETURN fetch_all_and_store(StatementData *data, bool set_position, bool *alloc_error); -SQLRETURN set_fetch_size(StatementData *data, SQLULEN fetch_size); -Napi::Value parse_query_options(Napi::Env env, Napi::Value options_value, QueryOptions *query_options); +Napi::Array process_data_for_napi( + Napi::Env env, StatementData* data, Napi::Array napiParameters +); +SQLRETURN bind_buffers(StatementData* data); +SQLRETURN prepare_for_fetch(StatementData* data); +SQLRETURN +fetch_and_store(StatementData* data, bool set_position, bool* alloc_error); +SQLRETURN +fetch_all_and_store(StatementData* data, bool set_position, bool* alloc_error); +SQLRETURN set_fetch_size(StatementData* data, SQLULEN fetch_size); +Napi::Value parse_query_options( + Napi::Env env, Napi::Value options_value, QueryOptions* query_options +); #endif diff --git a/src/odbc_cursor.cpp b/src/odbc_cursor.cpp index 55ad7eaa..44286401 100644 --- a/src/odbc_cursor.cpp +++ b/src/odbc_cursor.cpp @@ -18,16 +18,18 @@ Napi::FunctionReference ODBCCursor::constructor; -Napi::Object ODBCCursor::Init(Napi::Env env, Napi::Object exports) -{ +Napi::Object ODBCCursor::Init(Napi::Env env, Napi::Object exports) { Napi::HandleScope scope(env); - Napi::Function constructorFunction = DefineClass(env, "ODBCCursor", { - InstanceMethod("fetch", &ODBCCursor::Fetch), - InstanceMethod("close", &ODBCCursor::Close), + Napi::Function constructorFunction = DefineClass( + env, "ODBCCursor", + { + InstanceMethod("fetch", &ODBCCursor::Fetch), + InstanceMethod("close", &ODBCCursor::Close), - InstanceAccessor("noData", &ODBCCursor::MoreResultsGetter, nullptr), - }); + InstanceAccessor("noData", &ODBCCursor::MoreResultsGetter, nullptr), + } + ); // Attach the Database Constructor to the target object constructor = Napi::Persistent(constructorFunction); @@ -36,13 +38,14 @@ Napi::Object ODBCCursor::Init(Napi::Env env, Napi::Object exports) return exports; } - -ODBCCursor::ODBCCursor(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { +ODBCCursor::ODBCCursor(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { this->data = info[0].As>().Data(); this->odbcConnection = info[1].As>().Data(); if (info.Length() > 2 && info[2].IsArray()) { this->napiParametersReference = Napi::Persistent(info[2].As()); - } else { + } + else { this->napiParametersReference = Napi::Persistent(Napi::Array::New(Env())); } this->free_statement_on_close = info[3].As().Value(); @@ -52,20 +55,10 @@ SQLRETURN ODBCCursor::Free() { SQLRETURN return_code = SQL_SUCCESS; - if (this->free_statement_on_close && this->data) - { - if ( - this->data->hstmt && - this->data->hstmt != SQL_NULL_HANDLE - ) - { + if (this->free_statement_on_close && this->data) { + if (this->data->hstmt && this->data->hstmt != SQL_NULL_HANDLE) { uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = - SQLFreeHandle - ( - SQL_HANDLE_STMT, - this->data->hstmt - ); + return_code = SQLFreeHandle(SQL_HANDLE_STMT, this->data->hstmt); this->data->hstmt = SQL_NULL_HANDLE; uv_mutex_unlock(&ODBC::g_odbcMutex); } @@ -78,10 +71,7 @@ SQLRETURN ODBCCursor::Free() { return return_code; } -ODBCCursor::~ODBCCursor() -{ - this->Free(); -} +ODBCCursor::~ODBCCursor() { this->Free(); } //////////////////////////////////////////////////////////////////////////////// // FETCH /////////////////////////////////////////////////////////////////// @@ -89,74 +79,56 @@ ODBCCursor::~ODBCCursor() class FetchAsyncWorker : public ODBCAsyncWorker { private: - ODBCCursor *cursor; - StatementData *data; + ODBCCursor* cursor; + StatementData* data; public: - FetchAsyncWorker - ( - ODBCCursor *cursor, - Napi::Function &callback - ) - : - ODBCAsyncWorker(callback), - cursor(cursor), - data(cursor->data) - {} - - ~FetchAsyncWorker() {} - - void Execute() { - SQLRETURN return_code; - bool alloc_error = false; - - return_code = - fetch_and_store - ( - data, - true, - &alloc_error + FetchAsyncWorker(ODBCCursor* cursor, Napi::Function& callback) + : ODBCAsyncWorker(callback), cursor(cursor), data(cursor->data) {} + + ~FetchAsyncWorker() {} + + void Execute() { + SQLRETURN return_code; + bool alloc_error = false; + + return_code = fetch_and_store(data, true, &alloc_error); + + if (alloc_error == true) { + SetError( + "[odbc] Error allocating or reallocating memory when fetching " + "data. No ODBC error information available.\0" ); + return; + } - if (alloc_error == true) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); + if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) { + if (return_code == SQL_INVALID_HANDLE) { + SetError( + "[odbc] Error fetching results with SQLFetch: SQL_INVALID_HANDLE\0" + ); return; } - - if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) { - if (return_code == SQL_INVALID_HANDLE) { - SetError("[odbc] Error fetching results with SQLFetch: SQL_INVALID_HANDLE\0"); - return; - } else { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - } - SetError("[odbc] Error fetching results with SQLFetch\0"); - return; + else { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); } + SetError("[odbc] Error fetching results with SQLFetch\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - Napi::Array rows = - process_data_for_napi - ( - env, - data, - cursor->napiParametersReference.Value() - ); + Napi::Array rows = + process_data_for_napi(env, data, cursor->napiParametersReference.Value()); - std::vector callbackArguments - { - env.Null(), - rows - }; + std::vector callbackArguments { env.Null(), rows }; - Callback().Call(callbackArguments); - } + Callback().Call(callbackArguments); + } }; Napi::Value ODBCCursor::Fetch(const Napi::CallbackInfo& info) { @@ -166,7 +138,7 @@ Napi::Value ODBCCursor::Fetch(const Napi::CallbackInfo& info) { Napi::Function callback = info[0].As(); - FetchAsyncWorker *worker = new FetchAsyncWorker(this, callback); + FetchAsyncWorker* worker = new FetchAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); @@ -178,66 +150,51 @@ Napi::Value ODBCCursor::Fetch(const Napi::CallbackInfo& info) { class CursorCloseAsyncWorker : public ODBCAsyncWorker { private: - ODBCCursor *odbcCursor; - StatementData *data; + ODBCCursor* odbcCursor; + StatementData* data; public: - CursorCloseAsyncWorker - ( - ODBCCursor *cursor, - Napi::Function &callback - ) - : - ODBCAsyncWorker(callback), - odbcCursor(cursor), - data(cursor->data) - {} - - ~CursorCloseAsyncWorker() {} - - void Execute() { - - SQLRETURN return_code; - - return_code = - SQLCloseCursor - ( - data->hstmt // StatementHandle - ); + CursorCloseAsyncWorker(ODBCCursor* cursor, Napi::Function& callback) + : ODBCAsyncWorker(callback), odbcCursor(cursor), data(cursor->data) {} + + ~CursorCloseAsyncWorker() {} + + void Execute() { + + SQLRETURN return_code; + + return_code = SQLCloseCursor(data->hstmt); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error closing the cursor!\0"); + return; + } + + if (odbcCursor->free_statement_on_close) { + return_code = odbcCursor->Free(); if (!SQL_SUCCEEDED(return_code)) { this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error closing the cursor!\0"); + SetError("[odbc] Error closing the Statement\0"); return; } - - if (odbcCursor->free_statement_on_close) - { - return_code = odbcCursor->Free(); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error closing the Statement\0"); - return; - } - } - else - { - if (data != NULL) - { - data->deleteColumns(); - } + } + else { + if (data != NULL) { + data->deleteColumns(); } } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); - Callback().Call(callbackArguments); - } + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); + Callback().Call(callbackArguments); + } }; Napi::Value ODBCCursor::Close(const Napi::CallbackInfo& info) { @@ -247,7 +204,7 @@ Napi::Value ODBCCursor::Close(const Napi::CallbackInfo& info) { Napi::Function callback = info[0].As(); - CursorCloseAsyncWorker *worker = new CursorCloseAsyncWorker(this, callback); + CursorCloseAsyncWorker* worker = new CursorCloseAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); diff --git a/src/odbc_cursor.h b/src/odbc_cursor.h index 4be44f83..17723900 100644 --- a/src/odbc_cursor.h +++ b/src/odbc_cursor.h @@ -23,30 +23,29 @@ #include "odbc_connection.h" #include "odbc_statement.h" -class ODBCCursor : public Napi::ObjectWrap -{ +class ODBCCursor : public Napi::ObjectWrap { friend class FetchAsyncWorker; public: - static Napi::FunctionReference constructor; + static Napi::FunctionReference constructor; - static Napi::Object Init(Napi::Env env, Napi::Object exports); + static Napi::Object Init(Napi::Env env, Napi::Object exports); - ODBCConnection *odbcConnection; - StatementData *data; - Napi::Reference napiParametersReference; - bool free_statement_on_close; + ODBCConnection* odbcConnection; + StatementData* data; + Napi::Reference napiParametersReference; + bool free_statement_on_close; - SQLRETURN Free(); + SQLRETURN Free(); - explicit ODBCCursor(const Napi::CallbackInfo& info); - ~ODBCCursor(); + explicit ODBCCursor(const Napi::CallbackInfo& info); + ~ODBCCursor(); - Napi::Value Fetch(const Napi::CallbackInfo& info); - Napi::Value Close(const Napi::CallbackInfo& info); + Napi::Value Fetch(const Napi::CallbackInfo& info); + Napi::Value Close(const Napi::CallbackInfo& info); - // Property Getter/Setters - Napi::Value MoreResultsGetter(const Napi::CallbackInfo& info); + // Property Getter/Setters + Napi::Value MoreResultsGetter(const Napi::CallbackInfo& info); }; #endif diff --git a/src/odbc_statement.cpp b/src/odbc_statement.cpp index e7b79f9d..03ed99b4 100644 --- a/src/odbc_statement.cpp +++ b/src/odbc_statement.cpp @@ -30,13 +30,16 @@ Napi::Object ODBCStatement::Init(Napi::Env env, Napi::Object exports) { Napi::HandleScope scope(env); - Napi::Function constructorFunction = DefineClass(env, "ODBCStatement", { - InstanceMethod("prepare", &ODBCStatement::Prepare), - InstanceMethod("bind", &ODBCStatement::Bind), - InstanceMethod("execute", &ODBCStatement::Execute), - InstanceMethod("cancel", &ODBCStatement::Cancel), - InstanceMethod("close", &ODBCStatement::Close), - }); + Napi::Function constructorFunction = DefineClass( + env, "ODBCStatement", + { + InstanceMethod("prepare", &ODBCStatement::Prepare), + InstanceMethod("bind", &ODBCStatement::Bind), + InstanceMethod("execute", &ODBCStatement::Execute), + InstanceMethod("cancel", &ODBCStatement::Cancel), + InstanceMethod("close", &ODBCStatement::Close), + } + ); // Attach the Database Constructor to the target object constructor = Napi::Persistent(constructorFunction); @@ -45,38 +48,28 @@ Napi::Object ODBCStatement::Init(Napi::Env env, Napi::Object exports) { return exports; } - -ODBCStatement::ODBCStatement(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { +ODBCStatement::ODBCStatement(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { this->data = new StatementData(); this->odbcConnection = info[0].As>().Data(); this->data->hstmt = *(info[1].As>().Data()); - this->data->fetch_array = this->odbcConnection->connectionOptions.fetchArray; - this->data->maxColumnNameLength = this->odbcConnection->getInfoResults.max_column_name_length; - this->data->get_data_supports = this->odbcConnection->getInfoResults.sql_get_data_supports; + this->data->fetch_array = this->odbcConnection->connectionOptions.fetchArray; + this->data->maxColumnNameLength = + this->odbcConnection->getInfoResults.max_column_name_length; + this->data->get_data_supports = + this->odbcConnection->getInfoResults.sql_get_data_supports; } -ODBCStatement::~ODBCStatement() { - this->Free(); -} +ODBCStatement::~ODBCStatement() { this->Free(); } SQLRETURN ODBCStatement::Free() { - SQLRETURN return_code = SQL_SUCCESS; + SQLRETURN return_code = SQL_SUCCESS; - if (this->data) - { - if ( - this->data->hstmt && - this->data->hstmt != SQL_NULL_HANDLE - ) - { + if (this->data) { + if (this->data->hstmt && this->data->hstmt != SQL_NULL_HANDLE) { uv_mutex_lock(&ODBC::g_odbcMutex); - return_code = - SQLFreeHandle - ( - SQL_HANDLE_STMT, - this->data->hstmt - ); + return_code = SQLFreeHandle(SQL_HANDLE_STMT, this->data->hstmt); this->data->hstmt = SQL_NULL_HANDLE; uv_mutex_unlock(&ODBC::g_odbcMutex); } @@ -96,58 +89,52 @@ SQLRETURN ODBCStatement::Free() { class PrepareAsyncWorker : public ODBCAsyncWorker { private: - StatementData *data; + StatementData* data; public: - PrepareAsyncWorker(ODBCStatement *odbcStatement, Napi::Function& callback) : ODBCAsyncWorker(callback), - data(odbcStatement->data) {} + PrepareAsyncWorker(ODBCStatement* odbcStatement, Napi::Function& callback) + : ODBCAsyncWorker(callback), data(odbcStatement->data) {} - ~PrepareAsyncWorker() {} + ~PrepareAsyncWorker() {} - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - return_code = SQLPrepare - ( - data->hstmt, // StatementHandle - data->sql, // StatementText - SQL_NTS // TextLength - ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error preparing the statement\0"); - return; - } + return_code = SQLPrepare(data->hstmt, data->sql, SQL_NTS); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error preparing the statement\0"); + return; + } - // front-load the work of SQLNumParams here, so we can convert - // NAPI/JavaScript values to C values immediately in Bind - return_code = SQLNumParams - ( - data->hstmt, // StatementHandle - &data->parameterCount // ParameterCountPtr + // front-load the work of SQLNumParams here, so we can convert + // NAPI/JavaScript values to C values immediately in Bind + return_code = SQLNumParams(data->hstmt, &data->parameterCount); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError( + "[odbc] Error retrieving number of parameter markers to be " + "bound to the statement\0" ); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving number of parameter markers to be bound to the statement\0"); - return; - } + return; + } - data->parameters = new Parameter*[data->parameterCount]; - for (SQLSMALLINT i = 0; i < data->parameterCount; i++) { - data->parameters[i] = new Parameter(); - } + data->parameters = new Parameter*[data->parameterCount]; + for (SQLSMALLINT i = 0; i < data->parameterCount; i++) { + data->parameters[i] = new Parameter(); } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); - Callback().Call(callbackArguments); - } + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); + Callback().Call(callbackArguments); + } }; /* @@ -179,16 +166,22 @@ Napi::Value ODBCStatement::Prepare(const Napi::CallbackInfo& info) { this->napiParameters = Napi::Persistent(Napi::Array::New(env)); - if(!info[0].IsString() || !info[1].IsFunction()){ - Napi::TypeError::New(env, "Argument 0 must be a string , Argument 1 must be a function.").ThrowAsJavaScriptException(); + if (!info[0].IsString() || !info[1].IsFunction()) { + Napi::TypeError::New( + env, "Argument 0 must be a string , Argument 1 must be a function." + ) + .ThrowAsJavaScriptException(); return env.Null(); } Napi::String sql = info[0].ToString(); Napi::Function callback = info[1].As(); - if(this->data->hstmt == SQL_NULL_HANDLE) { - Napi::Error error = Napi::Error::New(env, "Statment handle is no longer valid. Cannot prepare SQL on an invalid statment handle."); + if (this->data->hstmt == SQL_NULL_HANDLE) { + Napi::Error error = Napi::Error::New( + env, "Statment handle is no longer valid. Cannot " + "prepare SQL on an invalid statment handle." + ); std::vector callbackArguments; callbackArguments.push_back(error.Value()); callback.Call(callbackArguments); @@ -197,7 +190,7 @@ Napi::Value ODBCStatement::Prepare(const Napi::CallbackInfo& info) { data->sql = ODBC::NapiStringToSQLTCHAR(sql); - PrepareAsyncWorker *worker = new PrepareAsyncWorker(this, callback); + PrepareAsyncWorker* worker = new PrepareAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); @@ -211,52 +204,58 @@ Napi::Value ODBCStatement::Prepare(const Napi::CallbackInfo& info) { class BindAsyncWorker : public ODBCAsyncWorker { public: - - BindAsyncWorker(ODBCStatement *odbcStatement, Napi::Function& callback) : ODBCAsyncWorker(callback), - data(odbcStatement->data) {} + BindAsyncWorker(ODBCStatement* odbcStatement, Napi::Function& callback) + : ODBCAsyncWorker(callback), data(odbcStatement->data) {} private: - StatementData *data; + StatementData* data; - ~BindAsyncWorker() { } + ~BindAsyncWorker() {} - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - return_code = ODBC::DescribeParameters(data->hstmt, data->parameters, data->parameterCount); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving information about the parameters in the statement\0"); - return; - } + return_code = ODBC::DescribeParameters( + data->hstmt, data->parameters, data->parameterCount + ); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError( + "[odbc] Error retrieving information about the parameters in " + "the statement\0" + ); + return; + } - return_code = ODBC::BindParameters(data->hstmt, data->parameters, data->parameterCount); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error binding parameters to the statement\0"); - return; - } + return_code = + ODBC::BindParameters(data->hstmt, data->parameters, data->parameterCount); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error binding parameters to the statement\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); - Callback().Call(callbackArguments); - } + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); + Callback().Call(callbackArguments); + } }; Napi::Value ODBCStatement::Bind(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); + Napi::Env env = info.Env(); Napi::HandleScope scope(env); - if ( !info[0].IsArray() || !info[1].IsFunction() ) { - Napi::TypeError::New(env, "Function signature is: bind(array, function)").ThrowAsJavaScriptException(); + if (!info[0].IsArray() || !info[1].IsFunction()) { + Napi::TypeError::New(env, "Function signature is: bind(array, function)") + .ThrowAsJavaScriptException(); return env.Null(); } @@ -264,8 +263,11 @@ Napi::Value ODBCStatement::Bind(const Napi::CallbackInfo& info) { this->napiParameters = Napi::Persistent(napiArray); Napi::Function callback = info[1].As(); - if(this->data->hstmt == SQL_NULL_HANDLE) { - Napi::Error error = Napi::Error::New(env, "Statment handle is no longer valid. Cannot bind SQL on an invalid statment handle."); + if (this->data->hstmt == SQL_NULL_HANDLE) { + Napi::Error error = Napi::Error::New( + env, "Statment handle is no longer valid. Cannot bind " + "SQL on an invalid statment handle." + ); std::vector callbackArguments; callbackArguments.push_back(error.Value()); callback.Call(callbackArguments); @@ -273,10 +275,23 @@ Napi::Value ODBCStatement::Bind(const Napi::CallbackInfo& info) { } // if the parameter count isnt right, end right away - if (data->parameterCount != (SQLSMALLINT)this->napiParameters.Value().Length() || data->parameters == NULL) { + if (data->parameterCount != + (SQLSMALLINT)this->napiParameters.Value().Length() || + data->parameters == NULL) { std::vector callbackArguments; - Napi::Error error = Napi::Error::New(env, Napi::String::New(env, "[node-odbc] Error in Statement::BindAsyncWorker::Bind: The number of parameters in the prepared statement (" + std::to_string(data->parameterCount) + ") doesn't match the number of parameters passed to bind (" + std::to_string((SQLSMALLINT)this->napiParameters.Value().Length()) + "}.")); + Napi::Error error = Napi::Error::New( + env, + Napi::String::New( + env, + "[node-odbc] Error in Statement::BindAsyncWorker::Bind: The number of " + "parameters in the prepared statement (" + + std::to_string(data->parameterCount) + + ") doesn't match the number of parameters passed to bind (" + + std::to_string((SQLSMALLINT)this->napiParameters.Value().Length()) + + "}." + ) + ); callbackArguments.push_back(error.Value()); callback.Call(callbackArguments); @@ -286,7 +301,7 @@ Napi::Value ODBCStatement::Bind(const Napi::CallbackInfo& info) { // converts NAPI/JavaScript values to values used by SQLBindParameter ODBC::StoreBindValues(&napiArray, this->data->parameters); - BindAsyncWorker *worker = new BindAsyncWorker(this, callback); + BindAsyncWorker* worker = new BindAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); @@ -300,193 +315,155 @@ Napi::Value ODBCStatement::Bind(const Napi::CallbackInfo& info) { class ExecuteAsyncWorker : public ODBCAsyncWorker { private: - ODBCConnection *odbcConnection; - ODBCStatement *odbcStatement; - StatementData *data; - - void Execute() { - - SQLRETURN return_code; - - // set SQL_ATTR_QUERY_TIMEOUT - if (data->query_options.timeout > 0) { - return_code = - SQLSetStmtAttr - ( - data->hstmt, - SQL_ATTR_QUERY_TIMEOUT, - (SQLPOINTER) data->query_options.timeout, + ODBCConnection* odbcConnection; + ODBCStatement* odbcStatement; + StatementData* data; + + void Execute() { + + SQLRETURN return_code; + + // set SQL_ATTR_QUERY_TIMEOUT + if (data->query_options.timeout > 0) { + return_code = SQLSetStmtAttr( + data->hstmt, SQL_ATTR_QUERY_TIMEOUT, + (SQLPOINTER)data->query_options.timeout, IGNORED_PARAMETER + ); + + // It is possible that SQLSetStmtAttr returns a warning with SQLSTATE + // 01S02, indicating that the driver changed the value specified. + // Although we never use the timeout variable again (and so we don't + // REALLY need it to be correct in the code), its just good to have + // the correct value if we need it. + if (return_code == SQL_SUCCESS_WITH_INFO) { + return_code = SQLGetStmtAttr( + data->hstmt, SQL_ATTR_QUERY_TIMEOUT, + (SQLPOINTER)&data->query_options.timeout, SQL_IS_UINTEGER, IGNORED_PARAMETER ); + } - // It is possible that SQLSetStmtAttr returns a warning with SQLSTATE - // 01S02, indicating that the driver changed the value specified. - // Although we never use the timeout variable again (and so we don't - // REALLY need it to be correct in the code), its just good to have - // the correct value if we need it. - if (return_code == SQL_SUCCESS_WITH_INFO) - { - return_code = - SQLGetStmtAttr - ( - data->hstmt, - SQL_ATTR_QUERY_TIMEOUT, - (SQLPOINTER) &data->query_options.timeout, - SQL_IS_UINTEGER, - IGNORED_PARAMETER + // Both of the SQL_ATTR_QUERY_TIMEOUT calls are combined here + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error setting the query timeout on the statement\0"); + return; + } + } + + return_code = set_fetch_size(data, data->query_options.fetch_size); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error setting the fetch size\0"); + return; + } + + return_code = SQLExecute(data->hstmt); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error executing the statement\0"); + return; + } + + if (return_code != SQL_NO_DATA) { + + if (data->query_options.use_cursor) { + if (data->query_options.cursor_name != NULL) { + return_code = SQLSetCursorName( + data->hstmt, data->query_options.cursor_name, + data->query_options.cursor_name_length ); - } - // Both of the SQL_ATTR_QUERY_TIMEOUT calls are combined here - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error setting the query timeout on the statement\0"); - return; + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error setting the cursor name on the statement\0"); + return; + } } } - return_code = - set_fetch_size - ( - data, - data->query_options.fetch_size - ); + // set_fetch_size will swallow errors in the case that the driver + // doesn't implement SQL_ATTR_ROW_ARRAY_SIZE for SQLSetStmtAttr and + // the fetch size was 1. If the fetch size was set by the user to a + // value greater than 1, throw an error. if (!SQL_SUCCEEDED(return_code)) { this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error setting the fetch size\0"); + SetError("[odbc] Error setting the fetch size on the statement\0"); return; } - return_code = - SQLExecute - ( - data->hstmt // StatementHandle - ); + return_code = prepare_for_fetch(data); if (!SQL_SUCCEEDED(return_code)) { this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error executing the statement\0"); + SetError("[odbc] Error preparing for fetch\0"); return; } - if (return_code != SQL_NO_DATA) { - - if (data->query_options.use_cursor) - { - if (data->query_options.cursor_name != NULL) - { - return_code = - SQLSetCursorName - ( - data->hstmt, - data->query_options.cursor_name, - data->query_options.cursor_name_length - ); - - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error setting the cursor name on the statement\0"); - return; - } - } - } - - // set_fetch_size will swallow errors in the case that the driver - // doesn't implement SQL_ATTR_ROW_ARRAY_SIZE for SQLSetStmtAttr and - // the fetch size was 1. If the fetch size was set by the user to a - // value greater than 1, throw an error. - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error setting the fetch size on the statement\0"); + if (!data->query_options.use_cursor) { + bool alloc_error = false; + return_code = fetch_all_and_store(data, true, &alloc_error); + if (alloc_error) { + SetError( + "[odbc] Error allocating or reallocating memory when " + "fetching data. No ODBC error information available.\0" + ); return; } - - return_code = - prepare_for_fetch - ( - data - ); if (!SQL_SUCCEEDED(return_code)) { this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error preparing for fetch\0"); - return; - } - - - if (!data->query_options.use_cursor) - { - bool alloc_error = false; - return_code = - fetch_all_and_store - ( - data, - true, - &alloc_error + SetError( + "[odbc] Error retrieving the result set from the statement\0" ); - if (alloc_error) - { - SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0"); - return; - } - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error retrieving the result set from the statement\0"); - return; - } + return; } } } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; + std::vector callbackArguments; - if (data->query_options.use_cursor) - { - // arguments for the ODBCCursor constructor - std::vector cursor_arguments = - { - Napi::External::New(env, data), - Napi::External::New(env, this->odbcConnection), - this->odbcStatement->napiParameters.Value(), - Napi::Boolean::New(env, false) - }; - - // create a new ODBCCursor object as a Napi::Value - Napi::Value cursorObject = ODBCCursor::constructor.New(cursor_arguments); - - // return cursor - std::vector callbackArguments = - { - env.Null(), - cursorObject - }; - - Callback().Call(callbackArguments); - } - else - { - Napi::Array rows = process_data_for_napi(env, data, odbcStatement->napiParameters.Value()); + if (data->query_options.use_cursor) { + // arguments for the ODBCCursor constructor + std::vector cursor_arguments = { + Napi::External::New(env, data), + Napi::External::New(env, this->odbcConnection), + this->odbcStatement->napiParameters.Value(), + Napi::Boolean::New(env, false) + }; - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); - callbackArguments.push_back(rows); + // create a new ODBCCursor object as a Napi::Value + Napi::Value cursorObject = ODBCCursor::constructor.New(cursor_arguments); - Callback().Call(callbackArguments); - } + // return cursor + std::vector callbackArguments = { env.Null(), cursorObject }; - return; + Callback().Call(callbackArguments); + } + else { + Napi::Array rows = + process_data_for_napi(env, data, odbcStatement->napiParameters.Value()); + + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); + callbackArguments.push_back(rows); + + Callback().Call(callbackArguments); } + return; + } + public: - ExecuteAsyncWorker(ODBCStatement *odbcStatement, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcConnection(odbcStatement->odbcConnection), - odbcStatement(odbcStatement), - data(odbcStatement->data) {} + ExecuteAsyncWorker(ODBCStatement* odbcStatement, Napi::Function& callback) + : ODBCAsyncWorker(callback), odbcConnection(odbcStatement->odbcConnection), + odbcStatement(odbcStatement), data(odbcStatement->data) {} - ~ExecuteAsyncWorker() {} + ~ExecuteAsyncWorker() {} }; Napi::Value ODBCStatement::Execute(const Napi::CallbackInfo& info) { @@ -498,7 +475,8 @@ Napi::Value ODBCStatement::Execute(const Napi::CallbackInfo& info) { size_t argument_count = info.Length(); // ensuring the passed parameters are correct - if ((argument_count == 1 && info[0].IsFunction()) || (argument_count == 2 && info[1].IsFunction())) { + if ((argument_count == 1 && info[0].IsFunction()) || + (argument_count == 2 && info[1].IsFunction())) { callback = info[argument_count - 1].As(); } @@ -506,42 +484,32 @@ Napi::Value ODBCStatement::Execute(const Napi::CallbackInfo& info) { // ensuring the passed parameters are correct if (argument_count >= 1 && info[0].IsObject()) { - error = - parse_query_options - ( - env, - info[0].As(), - &this->data->query_options - ); - } else { - error = - parse_query_options - ( - env, - env.Null(), - &this->data->query_options + error = parse_query_options( + env, info[0].As(), &this->data->query_options ); } + else { + error = parse_query_options(env, env.Null(), &this->data->query_options); + } - if (!error.IsNull()) - { + if (!error.IsNull()) { // Error when parsing the query options. Return the callback with the error - std::vector callback_argument = - { - error - }; + std::vector callback_argument = { error }; callback.Call(callback_argument); } - if(this->data->hstmt == SQL_NULL_HANDLE) { - Napi::Error error = Napi::Error::New(env, "Statment handle is no longer valid. Cannot execute SQL on an invalid statment handle."); + if (this->data->hstmt == SQL_NULL_HANDLE) { + Napi::Error error = Napi::Error::New( + env, "Statment handle is no longer valid. Cannot " + "execute SQL on an invalid statment handle." + ); std::vector callbackArguments; callbackArguments.push_back(error.Value()); callback.Call(callbackArguments); return env.Undefined(); } - ExecuteAsyncWorker *worker = new ExecuteAsyncWorker(this, callback); + ExecuteAsyncWorker* worker = new ExecuteAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); @@ -593,9 +561,11 @@ Napi::Value ODBCStatement::Cancel(const Napi::CallbackInfo& info) { std::vector callbackArguments; if (!SQL_SUCCEEDED(return_code)) { - Napi::Error error = Napi::Error::New(env, "[odbc] Error canceling the statement"); + Napi::Error error = + Napi::Error::New(env, "[odbc] Error canceling the statement"); callbackArguments.push_back(error.Value()); - } else { + } + else { callbackArguments.push_back(env.Null()); } callback.Call(callbackArguments); @@ -611,37 +581,39 @@ Napi::Value ODBCStatement::Cancel(const Napi::CallbackInfo& info) { class CloseStatementAsyncWorker : public ODBCAsyncWorker { private: - ODBCStatement *odbcStatement; - StatementData *data; + ODBCStatement* odbcStatement; + StatementData* data; - void Execute() { + void Execute() { - SQLRETURN return_code; + SQLRETURN return_code; - return_code = odbcStatement->Free(); - if (!SQL_SUCCEEDED(return_code)) { - this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); - SetError("[odbc] Error closing the Statement\0"); - return; - } + return_code = odbcStatement->Free(); + if (!SQL_SUCCEEDED(return_code)) { + this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt); + SetError("[odbc] Error closing the Statement\0"); + return; } + } - void OnOK() { + void OnOK() { - Napi::Env env = Env(); - Napi::HandleScope scope(env); + Napi::Env env = Env(); + Napi::HandleScope scope(env); - std::vector callbackArguments; - callbackArguments.push_back(env.Null()); - Callback().Call(callbackArguments); - } + std::vector callbackArguments; + callbackArguments.push_back(env.Null()); + Callback().Call(callbackArguments); + } public: - CloseStatementAsyncWorker(ODBCStatement *odbcStatement, Napi::Function& callback) : ODBCAsyncWorker(callback), - odbcStatement(odbcStatement), + CloseStatementAsyncWorker( + ODBCStatement* odbcStatement, Napi::Function& callback + ) + : ODBCAsyncWorker(callback), odbcStatement(odbcStatement), data(odbcStatement->data) {} - ~CloseStatementAsyncWorker() {} + ~CloseStatementAsyncWorker() {} }; Napi::Value ODBCStatement::Close(const Napi::CallbackInfo& info) { @@ -651,7 +623,8 @@ Napi::Value ODBCStatement::Close(const Napi::CallbackInfo& info) { Napi::Function callback = info[0].As(); - CloseStatementAsyncWorker *worker = new CloseStatementAsyncWorker(this, callback); + CloseStatementAsyncWorker* worker = + new CloseStatementAsyncWorker(this, callback); worker->Queue(); return env.Undefined(); diff --git a/src/odbc_statement.h b/src/odbc_statement.h index c23e30f8..76bc9326 100644 --- a/src/odbc_statement.h +++ b/src/odbc_statement.h @@ -22,23 +22,23 @@ class ODBCStatement : public Napi::ObjectWrap { public: - static Napi::FunctionReference constructor; + static Napi::FunctionReference constructor; - static Napi::Object Init(Napi::Env env, Napi::Object exports); + static Napi::Object Init(Napi::Env env, Napi::Object exports); - ODBCConnection *odbcConnection; - Napi::Reference napiParameters; - StatementData *data; + ODBCConnection* odbcConnection; + Napi::Reference napiParameters; + StatementData* data; - SQLRETURN Free(); + SQLRETURN Free(); - explicit ODBCStatement(const Napi::CallbackInfo& info); - ~ODBCStatement(); + explicit ODBCStatement(const Napi::CallbackInfo& info); + ~ODBCStatement(); - Napi::Value Prepare(const Napi::CallbackInfo& info); - Napi::Value Bind(const Napi::CallbackInfo& info); - Napi::Value Execute(const Napi::CallbackInfo& info); - Napi::Value Cancel(const Napi::CallbackInfo& info); - Napi::Value Close(const Napi::CallbackInfo& info); + Napi::Value Prepare(const Napi::CallbackInfo& info); + Napi::Value Bind(const Napi::CallbackInfo& info); + Napi::Value Execute(const Napi::CallbackInfo& info); + Napi::Value Cancel(const Napi::CallbackInfo& info); + Napi::Value Close(const Napi::CallbackInfo& info); }; #endif From 5b22f3602f5077fe1d80ee3c17088a1790836465 Mon Sep 17 00:00:00 2001 From: Korinne Adler Date: Tue, 7 Jul 2026 13:29:01 -0500 Subject: [PATCH 2/2] style: Add .git-blame-ignore-revs This can be used to ignore the these revisions with git-blame, eg. git blame --ignore-revs-file .git-blame-ignore-revs. Also GitHub will use this by default when blaming throught the UI. --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..aabdc9de --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Reformat with clang-format +a46ddbbebe8807d3a57fe334d429c13df88cf487