diff --git a/.github/workflows/dependencies_check.yml b/.github/workflows/dependencies_check.yml new file mode 100644 index 0000000..2cb0303 --- /dev/null +++ b/.github/workflows/dependencies_check.yml @@ -0,0 +1,25 @@ +name: Dependencies Check + +on: + schedule: + - cron: "0 2 * * *" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Checking dependencies for vulnerabilities + run: mvn org.sonatype.ossindex.maven:ossindex-maven-plugin:audit -f pom.xml \ No newline at end of file diff --git a/.github/workflows/github_release.yml b/.github/workflows/github_release.yml new file mode 100644 index 0000000..3afc904 --- /dev/null +++ b/.github/workflows/github_release.yml @@ -0,0 +1,36 @@ +name: GitHub Release + +on: + workflow_dispatch: + inputs: + upload_url: + description: 'Upload URL' + required: true + asset_name: + description: 'Asset file name' + required: true + asset_path: + description: 'Asset file path' + required: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build with Maven + run: mvn -B clean package --file pom.xml + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.inputs.upload_url }} + asset_path: ${{ github.event.inputs.asset_path }} + asset_name: ${{ github.event.inputs.asset_name }} + asset_content_type: application/java-archive \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb2b23d --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +/target/ + +# Eclipse and Maven +.classpath +.project +# .settings : we need Eclipse settings for code formatter and clean-up rules +target +.cache +dependency-reduced-pom.xml + +# Intellij +.idea +# Intellij recommends to share iml files, however, better don't share files which might be outdated +*.iml + +# Integration tests +src/test/resources/integration/driver/oracle/*.jar +src/test/resources/integration/driver/oracle/*.zip + +# Others +.DS_Store +*.swp +local +Scripts +.dbeaver* +**/*.log \ No newline at end of file diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..e3a8580 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,459 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.APILeak=warning +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled +org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false +org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false +org.eclipse.jdt.core.formatter.align_with_spaces=false +org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=true +org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false +org.eclipse.jdt.core.formatter.comment.indent_root_tags=false +org.eclipse.jdt.core.formatter.comment.indent_tag_description=false +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +org.eclipse.jdt.core.formatter.comment.line_length=120 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false +org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=false +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true +org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true +org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true +org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true +org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true +org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true +org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..e7e7880 --- /dev/null +++ b/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,127 @@ +cleanup.add_default_serial_version_id=true +cleanup.add_generated_serial_version_id=false +cleanup.add_missing_annotations=true +cleanup.add_missing_deprecated_annotations=true +cleanup.add_missing_methods=false +cleanup.add_missing_nls_tags=false +cleanup.add_missing_override_annotations=true +cleanup.add_missing_override_annotations_interface_methods=true +cleanup.add_serial_version_id=false +cleanup.always_use_blocks=true +cleanup.always_use_parentheses_in_expressions=false +cleanup.always_use_this_for_non_static_field_access=true +cleanup.always_use_this_for_non_static_method_access=false +cleanup.convert_functional_interfaces=true +cleanup.convert_to_enhanced_for_loop=true +cleanup.correct_indentation=true +cleanup.format_source_code=true +cleanup.format_source_code_changes_only=false +cleanup.insert_inferred_type_arguments=false +cleanup.make_local_variable_final=true +cleanup.make_parameters_final=true +cleanup.make_private_fields_final=true +cleanup.make_type_abstract_if_missing_method=false +cleanup.make_variable_declarations_final=true +cleanup.never_use_blocks=false +cleanup.never_use_parentheses_in_expressions=true +cleanup.organize_imports=false +cleanup.qualify_static_field_accesses_with_declaring_class=false +cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +cleanup.qualify_static_member_accesses_with_declaring_class=true +cleanup.qualify_static_method_accesses_with_declaring_class=false +cleanup.remove_private_constructors=true +cleanup.remove_redundant_modifiers=false +cleanup.remove_redundant_semicolons=true +cleanup.remove_redundant_type_arguments=true +cleanup.remove_trailing_whitespaces=true +cleanup.remove_trailing_whitespaces_all=true +cleanup.remove_trailing_whitespaces_ignore_empty=false +cleanup.remove_unnecessary_casts=true +cleanup.remove_unnecessary_nls_tags=true +cleanup.remove_unused_imports=true +cleanup.remove_unused_local_variables=false +cleanup.remove_unused_private_fields=true +cleanup.remove_unused_private_members=true +cleanup.remove_unused_private_methods=true +cleanup.remove_unused_private_types=true +cleanup.sort_members=false +cleanup.sort_members_all=false +cleanup.use_anonymous_class_creation=false +cleanup.use_blocks=true +cleanup.use_blocks_only_for_return_and_throw=false +cleanup.use_lambda=true +cleanup.use_parentheses_in_expressions=true +cleanup.use_this_for_non_static_field_access=true +cleanup.use_this_for_non_static_field_access_only_if_necessary=false +cleanup.use_this_for_non_static_method_access=false +cleanup.use_this_for_non_static_method_access_only_if_necessary=true +cleanup_profile=_Exasol +cleanup_settings_version=2 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_Exasol +formatter_settings_version=16 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=3 +org.eclipse.jdt.ui.staticondemandthreshold=3 +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=true +sp_cleanup.always_use_this_for_non_static_field_access=true +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=true +sp_cleanup.convert_to_enhanced_for_loop=true +sp_cleanup.correct_indentation=true +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=true +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=false +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=true +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_modifiers=false +sp_cleanup.remove_redundant_semicolons=true +sp_cleanup.remove_redundant_type_arguments=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=true +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=true +sp_cleanup.use_this_for_non_static_field_access=true +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..57aa0b0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: java + +# Setting sudo to false will cause Travis to use Containers. +# To use Docker's privileged mode, we need to enable sudo. +sudo: required + +matrix: + include: + - jdk: openjdk11 + +addons: + sonarcloud: + organization: exasol + +install: + - travis_retry mvn dependency:resolve + +script: + - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent org.jacoco:jacoco-maven-plugin:prepare-agent-integration verify sonar:sonar -Dsonar.login=${SONAR_TOKEN} + +cache: + directories: + - "$HOME/.m2" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e66941b --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# Oracle Virtual Schema + +[![Build Status](https://api.travis-ci.com/exasol/oracle-virtual-schema.svg?branch=main)](https://travis-ci.com/exasol/oracle-virtual-schema) + +SonarCloud results: + +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=alert_status)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) + +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=security_rating)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) +[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=sqale_index)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) + +[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=code_smells)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=coverage)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) +[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Aoracle-virtual-schema&metric=ncloc)](https://sonarcloud.io/dashboard?id=com.exasol%3Aoracle-virtual-schema) + +# Overview + +The **Oracle Virtual Schema** provides an abstraction layer that makes an external [Oracle database](https://www.oracle.com/database/) accessible from an Exasol database through regular SQL commands. The contents of the external Oracle database are mapped to virtual tables which look like and can be queried as any regular Exasol table. + +If you want to set up a Virtual Schema for a different database system, please head over to the [Virtual Schemas Repository][virtual-schemas]. + +## Features + +* Access an Oracle database in read only mode from an Exasol database, using a Virtual Schema. + +## Table of Contents + +### Information for Users + +* [Virtual Schemas User Guide][virtual-schemas-user-guide] +* [Oracle Dialect User Guide](doc/user_guide/oracle_user_guide.md) +* [Changelog](doc/changes/changelog.md) + +Find all the documentation in the [Virtual Schemas project][vs-doc]. + +## Information for Developers + +* [Virtual Schema API Documentation][vs-api] + +### Run Time Dependencies + +Running the Virtual Schema requires a Java Runtime version 11 or later. + +| Dependency | Purpose | License | +|--------------------------------------------------------------------|--------------------------------------------------------|-----------------------------------| +| [Exasol Virtual Schema JDBC][virtual-schema-common-jdbc] | Common JDBC functions for Virtual Schemas adapters | MIT License | +| [Oracle JDBC Driver][oracle-jdbc-driver] | JDBC driver for Oracle database | Oracle Technology Network License | +| [Exasol Error Reporting][exasol-error-reporting] | Creating unified error messages | MIT License | +| [Exasol Database Fundamentals for Java][exasol-db-funtamentals] | Base objects and ground rules for the Exasol database | MIT License | + +### Test Dependencies + +| Dependency | Purpose | License | +|--------------------------------------------------------------------|--------------------------------------------------------|-------------------------------| +| [Java Hamcrest](http://hamcrest.org/JavaHamcrest/) | Checking for conditions in code via matchers | BSD License | +| [JUnit](https://junit.org/junit5) | Unit testing framework | Eclipse Public License 1.0 | +| [Mockito](http://site.mockito.org/) | Mocking framework | MIT License | +| [Testcontainers](https://www.testcontainers.org/) | Container-based integration tests | MIT License | +| [Exasol Testcontainers][exasol-testcontainers] | Exasol extension for the Testcontainers framework | MIT License | +| [Test Database Builder][test-db-builder] | Fluent database interfaces for testing | MIT License | +| [Exasol Hamcrest Result Set Matcher][exasol-hamcrest] | Hamcrest result set matcher for testing | MIT License | +| [EqualsVerifier][jqno-equals-verifier] | Testing `hashCode` and `equals` Java contract | Apache-2.0 License | + + +### Maven Plug-ins + +| Plug-in | Purpose | License | +|--------------------------------------------------------------------|--------------------------------------------------------|-------------------------------| +| [Maven Jacoco Plugin][maven-jacoco-plugin] | Code coverage metering | Eclipse Public License 2.0 | +| [Maven Surefire Plugin][maven-surefire-plugin] | Unit testing | Apache License 2.0 | +| [Maven Compiler Plugin][maven-compiler-plugin] | Setting required Java version | Apache License 2.0 | +| [Maven Assembly Plugin][maven-assembly-plugin] | Creating JAR | Apache License 2.0 | +| [Maven Failsafe Plugin][maven-failsafe-plugin] | Integration testing | Apache License 2.0 | +| [Versions Maven Plugin][versions-maven-plugin] | Checking if dependencies updates are available | Apache License 2.0 | +| [Maven Enforcer Plugin][maven-enforcer-plugin] | Controlling environment constants | Apache License 2.0 | +| [Maven Dependency Plugin][maven-dependency-plugin] | Accessing to test dependencies | Apache License 2.0 | +| [Artifact Reference Checker Plugin][artifact-ref-checker-plugin] | Check if artifact is referenced with correct version | MIT License | +| [Project Keeper Maven Plugin][project-keeper-maven-plugin] | Checking project structure | MIT License | +| [Sonatype OSS Index Maven Plugin][sonatype-oss-index-maven-plugin] | Checking dependencies vulnerability | ASL2 | + +[virtual-schema-common-jdbc]: https://github.com/exasol/virtual-schema-common-jdbc +[oracle-jdbc-driver]: https://www.oracle.com/database/technologies/appdev/jdbc.html +[exasol-error-reporting]: https://github.com/exasol/error-reporting-java/ +[exasol-db-funtamentals]: https://github.com/exasol/db-fundamentals-java/ + +[exasol-testcontainers]: https://github.com/exasol/exasol-testcontainers +[test-db-builder]: https://github.com/exasol/test-db-builder/ +[exasol-hamcrest]: https://github.com/exasol/hamcrest-resultset-matcher +[jqno-equals-verifier]: https://github.com/jqno/equalsverifier + +[maven-jacoco-plugin]: https://www.eclemma.org/jacoco/trunk/doc/maven.html +[maven-surefire-plugin]: https://maven.apache.org/surefire/maven-surefire-plugin/ +[maven-compiler-plugin]: https://maven.apache.org/plugins/maven-compiler-plugin/ +[maven-assembly-plugin]: https://maven.apache.org/plugins/maven-assembly-plugin/ +[maven-failsafe-plugin]: https://maven.apache.org/surefire/maven-failsafe-plugin/ +[versions-maven-plugin]: https://www.mojohaus.org/versions-maven-plugin/ +[maven-enforcer-plugin]: http://maven.apache.org/enforcer/maven-enforcer-plugin/ +[artifact-ref-checker-plugin]: https://github.com/exasol/artifact-reference-checker-maven-plugin +[maven-dependency-plugin]: https://maven.apache.org/plugins/maven-dependency-plugin/ +[project-keeper-maven-plugin]: https://github.com/exasol/project-keeper-maven-plugin +[sonatype-oss-index-maven-plugin]: https://sonatype.github.io/ossindex-maven/maven-plugin/ + +[virtual-schemas-user-guide]: https://docs.exasol.com/database_concepts/virtual_schemas.htm +[virtual-schemas]: https://github.com/exasol/virtual-schemas +[vs-api]: https://github.com/exasol/virtual-schema-common-java/blob/master/doc/development/api/virtual_schema_api.md +[vs-doc]: https://github.com/exasol/virtual-schemas/tree/master/doc diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md new file mode 100644 index 0000000..a061455 --- /dev/null +++ b/doc/changes/changelog.md @@ -0,0 +1,3 @@ +# Changes + +* [1.0.0](changes_1.0.0.md) \ No newline at end of file diff --git a/doc/changes/changes_1.0.0.md b/doc/changes/changes_1.0.0.md new file mode 100644 index 0000000..986c310 --- /dev/null +++ b/doc/changes/changes_1.0.0.md @@ -0,0 +1,39 @@ +# Oracle Virtual Schemas 1.0.0, released 2021-XX-XX + +Code name: Migration from the virtual-schemas repository + +## Features / Enhancements + +* 1: Migrated from the virtual-schemas repository + +## Runtime Dependencies + +* Added `com.exasol:virtual-schema-common-jdbc:8.0.0` +* Added `com.exasol:db-fundamentals-java:0.1.1` +* Added `com.exasol:error-reporting-java:0.2.0` + +## Test Dependencies + +* Added `org.hamcrest:hamcrest:2.2` +* Added `org.junit.jupiter:junit-jupiter:5.7.0` +* Added `org.mockito:mockito-junit-jupiter:3.6.28` +* Added `nl.jqno.equalsverifier:equalsverifier:3.5` +* Added `com.exasol:exasol-testcontainers:3.3.1` +* Added `org.testcontainers:junit-jupiter:1.15.0` +* Added `org.testcontainers:oracle-xe:1.15.0` +* Added `com.oracle.ojdbc:ojdbc8:19.3.0.0` +* Added `com.exasol:test-db-builder-java:2.0.0` +* Added `com.exasol:hamcrest-resultset-matcher:1.2.2` + +## Plugin Dependencies + +* Added `org.jacoco:jacoco-maven-plugin:0.8.5` +* Added `org.apache.maven.plugins:maven-surefire-plugin:3.0.0-M3` +* Added `org.apache.maven.plugins:maven-compiler-plugin:3.8.1` +* Added `org.apache.maven.plugins:maven-assembly-plugin:3.3.0` +* Added `org.apache.maven.plugins:maven-failsafe-plugin:3.0.0-M3` +* Added `org.apache.maven.plugins:maven-enforcer-plugin:3.0.0-M3` +* Added `org.codehaus.mojo:versions-maven-plugin:2.7` +* Added `com.exasol:artifact-reference-checker-maven-plugin:0.3.1` +* Added `com.exasol:project-keeper-maven-plugin:0.4.2` +* Added `org.sonatype.ossindex.maven:ossindex-maven-plugin:3.1.0` \ No newline at end of file diff --git a/doc/user_guide/oracle_user_guide.md b/doc/user_guide/oracle_user_guide.md new file mode 100644 index 0000000..eb30e44 --- /dev/null +++ b/doc/user_guide/oracle_user_guide.md @@ -0,0 +1,216 @@ +# Oracle SQL Dialect User Guide + +[Oracle Database](https://www.oracle.com/database/) is a proprietary multi-model database management system produced and marketed by Oracle Corporation. It is a database commonly used for running online transaction processing (OLTP), data warehousing (DW) and mixed (OLTP & DW) database workloads. + +## Registering the JDBC Driver in EXAOperation + +First download the [Oracle JDBC driver](https://www.oracle.com/technetwork/database/application-development/jdbc/downloads/index.html). + +Now register the driver in EXAOperation: + +1. Click "Software" +1. Switch to tab "JDBC Drivers" +1. Click "Browse..." +1. Select JDBC driver file +1. Click "Upload" +1. Click "Add" +1. In dialog "Add EXACluster JDBC driver" configure the JDBC driver (see below) + +You need to specify the following settings when adding the JDBC driver via EXAOperation. + +| Parameter | Value | +|-----------|-----------------------------------------------------| +| Name | `ORACLE` | +| Main | `oracle.jdbc.driver.OracleDriver` | +| Prefix | `jdbc:oracle:thin:` +| Files | `ojdbc.jar` | + + +## Uploading the JDBC Driver to EXAOperation + +1. [Create a bucket in BucketFS](https://docs.exasol.com/administration/on-premise/bucketfs/create_new_bucket_in_bucketfs_service.htm) +1. Upload the driver to BucketFS + +This step is necessary since the UDF container the adapter runs in has no access to the JDBC drivers installed via EXAOperation but it can access BucketFS. + +## Installing the Adapter Script + +Upload the latest available release of [Oracle Virtual Schema](https://github.com/exasol/oracle-virtual-schema/releases) to Bucket FS. + +Then create a schema to hold the adapter script. + +```sql +CREATE SCHEMA ADAPTER; +``` + +The SQL statement below creates the adapter script, defines the Java class that serves as entry point and tells the UDF framework where to find the libraries (JAR files) for Virtual Schema and database driver. + +```sql +CREATE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS + %scriptclass com.exasol.adapter.RequestDispatcher; + %jar /buckets///virtual-schema-dist-8.0.0-bundle-5.0.0.jar; + %jar /buckets///ojdbc.jar; +/ +; +``` + +## Defining a Named Connection + +Define the connection to Oracle as shown below. + +```sql +CREATE OR REPLACE CONNECTION ORACLE_JDBC_CONNECTION + TO 'jdbc:oracle:thin:@//:/' + USER '' + IDENTIFIED BY ''; +``` + +A quick option to test the `ORACLE_JDBC_CONNECTION` connection is to run an `IMPORT FROM JDBC` query. The connection works, if `42` is returned. + +```sql +IMPORT FROM JDBC AT ORACLE_JDBC_CONNECTION + STATEMENT 'SELECT 42 FROM DUAL'; +``` + +## Creating a Virtual Schema + +Below you see how an Oracle Virtual Schema is created. + +```sql +CREATE VIRTUAL SCHEMA + USING ADAPTER.JDBC_ADAPTER + WITH + SQL_DIALECT = 'ORACLE' + CONNECTION_NAME = 'ORACLE_JDBC_CONNECTION' + SCHEMA_NAME = ''; +``` + +## Using IMPORT FROM ORA Instead of IMPORT FROM JDBC + +Exasol provides the `IMPORT FROM ORA` command for loading data from Oracle. It is possible to create a virtual schema that uses `IMPORT FROM ORA` instead of JDBC to communicate with Oracle. Both options are indented to support the same features. `IMPORT FROM ORA` almost always offers better performance since it is implemented natively. + +This behavior is toggled by the Boolean `IMPORT_FROM_ORA` variable. Note that a JDBC connection to Oracle is still required to fetch metadata. In addition, a "direct" connection to the Oracle database is needed. + +### Deploying the Oracle Instant Client + +To be able to communicate with Oracle, you first need to supply Exasol with the Oracle Instant Client, which can be obtained [directly from Oracle](http://www.oracle.com/technetwork/database/database-technologies/instant-client/overview/index.html). Open EXAoperation, visit Software -> "Upload Oracle Instant Client" and select the downloaded package. The latest version of Oracle Instant Client we tested is `instantclient-basic-linux.x64-12.1.0.2.0`. + +### Creating an Oracle Connection + +Having deployed the Oracle Instant Client, a connection to your Oracle database can be set up. + +```sql +CREATE CONNECTION ORA_CONNECTION + TO '(DESCRIPTION = + (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP) + (HOST = ) + (PORT = ))) + (CONNECT_DATA = (SERVER = DEDICATED) + (SERVICE_NAME = )))' + USER '' + IDENTIFIED BY ''; +``` + +This connection can be tested using, e.g., the following SQL expression. + +```sql +IMPORT FROM ORA at ORA_CONNECTION + STATEMENT 'SELECT 42 FROM DUAL'; +``` + +### Creating a Virtual Schema USING an ORA CONNECTION + +Assuming you already setup the JDBC connection `ORACLE_JDBC_CONNECTION` as shown in the previous section, you can continue with creating the virtual schema. + +```sql +CREATE VIRTUAL SCHEMA + USING ADAPTER.JDBC_ADAPTER + WITH + SQL_DIALECT = 'ORACLE' + CONNECTION_NAME = 'ORACLE_JDBC_CONNECTION' + SCHEMA_NAME = '' + IMPORT_FROM_ORA = 'true' + ORA_CONNECTION_NAME = 'ORA_CONNECTION'; +``` + +## Supported Capabilities + +The Oracle dialect does not support all capabilities. A complete list can be found in [OracleSqlDialect.getCapabilities()](../../src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java). + +## Type Mappings and Limitations + +| Orcale Data Type | Supported | Converted Exasol Data Type | Comments | +| -------------------------------------------------------------------------------- | --------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| BINARY\_DOUBLE | ✓ | VARCHAR(2000000) | | +| BINARY\_FLOAT | ✓ | VARCHAR(2000000) | | +| BLOB | × | | | +| BFILE | × | | | +| CHAR \[(size)\] | ✓ | CHAR | | +| CLOB | × | | | +| DATE | ✓ | TIMESTAMP | This data type is only supported for positive year values, i.e., years > 0001 | +| FLOAT \[(p)\] | ✓ | DOUBLE | | +| INTERVAL DAY \[(day\_precision)\] TO SECOND \[(fractional\_seconds\_precision)\] | ✓ | VARCHAR(2000000) | | +| INTERVAL YEAR \[(year\_precision)\] TO MONTH | ✓ | VARCHAR(2000000) | | +| LONG | ✓ | VARCHAR(2000000) | Casted to VARCHAR to prevent a loss of precision. | +| LONG RAW | × | | | +| NCLOB | × | | | +| NCHAR\[(size)\] | ✓ | CHAR | | +| NUMBER \[ (p \[, s\]) \] | ✓ | NUMBER or VARCHAR(2000000) | NUMBER with precision > 36 are casted to VARCHAR to prevent a loss of precision. [*](#Mapping-of-number-types) | +| NVARCHAR2(size) | ✓ | VARCHAR | | +| RAW(size) | × | | | +| ROWID | × | | | +| TIMESTAMP \[(fractional\_seconds\_precision)\] | ✓ | TIMESTAMP | | +| TIMESTAMP \[(fractional\_seconds\_precision)\] WITH TIME ZONE | ✓ | TIMESTAMP | | +| UROWID \[(size)\] | × | | | +| VARCHAR2(size) | ✓ | VARCHAR | | + + + +### Mapping of Number Types: + +`NUMBER`, `NUMBER with precision > 36` and `LONG` are casted to `VARCHAR` to prevent a loss of precision. + +If you want to return a DECIMAL type for these types you can set the property `ORACLE_CAST_NUMBER_TO_DECIMAL_WITH_PRECISION_AND_SCALE` to `,`. +This will cast values of such types to `DECIMAL(,)`. + +For example: + +```java +CREATE VIRTUAL SCHEMA + USING ADAPTER.JDBC_ADAPTER + WITH + SQL_DIALECT = 'ORACLE' + CONNECTION_NAME = 'ORACLE_JDBC_CONNECTION' + SCHEMA_NAME = '' + IMPORT_FROM_ORA = 'true' + ORACLE_CAST_NUMBER_TO_DECIMAL_WITH_PRECISION_AND_SCALE = '2,18' + ORA_CONNECTION_NAME = 'ORA_CONNECTION'; +``` + +Keep in mind that this will yield errors if the data in the Oracle database does not fit into the specified DECIMAL type. + +## Testing information + +In the following matrix you find combinations of JDBC driver and dialect version that we tested. + +| Virtual Schema Version | Oracle Version | Driver Name | Driver Version | +|------------------------|--------------------|---------------------------|----------------| +| 4.0.3 | Oracle XE 11g | ojdbc | 8 | +| 4.0.3 | Oracle XE 11g | instantclient-basic-linux | x64-12.1.0.2.0 | + +## Executing Disabled Integration Tests + +The integration tests are disabled by default, but it is possible to execute them locally. +The reason for the tests being disabled is we can only deliver drivers where the license allows redistribution, and it's not the case with Oracle. +Therefore we cannot include the MySQL JDBC driver, so in order to execute the integration tests you need to download them manually. + +### Starting Disabled Integration Test Locally + +1. Download a JDBC driver and the Orcal instant client from: + - Oracle [`ojdbc8.jar`](https://www.oracle.com/database/technologies/appdev/jdbc-ucp-19c-downloads.html) + - Oracle instant client [`instantclient-basic-linux.x64-12.1.0.2.0.zip`](https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html). + Please be aware that Exasol currently supports only mentioned version of the Oracle instant client. +2. Temporarily put the files into `src/test/resources/integration/driver/oracle` directory. +3. If the files' names are different (you renamed the file, or it has a different version number, for example) from the mentioned above, edit `src/test/resources/integration/driver/oracle/oracle.properties` and `settings.cfg` files. +4. Run the tests from an IDE or temporarily add the integration test name into the `maven-failsafe-plugin`'s includes a section and execute `mvn verify` command. +5. Remove the driver after the test and **do not upload it to the GitHub repository**. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4667132 --- /dev/null +++ b/pom.xml @@ -0,0 +1,312 @@ + + 4.0.0 + com.exasol + oracle-virtual-schema + 1.0.0 + Virtual Schema for Oracle + + + maven.exasol.com + https://maven.exasol.com/artifactory/exasol-releases + + + maven.exasol.com + https://maven.exasol.com/artifactory/exasol-snapshots + + + + UTF-8 + UTF-8 + 11 + 3.0.0-M3 + 8.0.0 + 1.15.0 + target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml + + + + + maven.exasol.com + https://maven.exasol.com/artifactory/exasol-releases + + false + + + + maven.exasol.com-snapshots + https://maven.exasol.com/artifactory/exasol-snapshots + + true + + + + + + com.exasol + virtual-schema-common-jdbc + ${vscjdbc.version} + + + com.exasol + db-fundamentals-java + 0.1.1 + + + com.exasol + error-reporting-java + 0.2.0 + + + + com.exasol + virtual-schema-common-jdbc + ${vscjdbc.version} + test-jar + test + + + org.hamcrest + hamcrest + 2.2 + test + + + org.junit.jupiter + junit-jupiter + 5.7.0 + test + + + org.mockito + mockito-junit-jupiter + 3.6.28 + test + + + nl.jqno.equalsverifier + equalsverifier + 3.5 + test + + + + com.exasol + exasol-testcontainers + 3.3.1 + test + + + org.testcontainers + junit-jupiter + ${org.testcontainers.version} + test + + + org.testcontainers + oracle-xe + ${org.testcontainers.version} + test + + + com.oracle.ojdbc + ojdbc8 + 19.3.0.0 + test + + + com.exasol + test-db-builder-java + 2.0.0 + test + + + com.exasol + hamcrest-resultset-matcher + 1.2.2 + test + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + prepare-agent + + prepare-agent + + + + prepare-agent-integration + + prepare-agent-integration + + + + report + test + + report + + + + report-integration + verify + + report-integration + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.and.failsafe.plugin.version} + + + -Djava.util.logging.config.file=src/test/resources/logging.properties ${argLine} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + src/assembly/all-dependencies.xml + + virtual-schema-dist-${vscjdbc.version}-oracle-${version} + false + + + + make-assembly + package + + single + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${surefire.and.failsafe.plugin.version} + + + -Djava.util.logging.config.file=src/test/resources/logging.properties ${argLine} + + OracleSqlDialectIT.java + + + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${surefire.and.failsafe.plugin.version} + + + enforce-maven + + enforce + + + + + 3.3.9 + + + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.7 + + + package + + display-plugin-updates + display-dependency-updates + + + + + file:///${project.basedir}/versionsMavenPluginRules.xml + + + + com.exasol + artifact-reference-checker-maven-plugin + 0.3.1 + + + + verify + + + + + + /doc/changes/* + + + + + com.exasol + project-keeper-maven-plugin + 0.4.2 + + + + verify + + + + + + jar_artifact + integration_tests + + + + + org.sonatype.ossindex.maven + ossindex-maven-plugin + 3.1.0 + + + package + + audit + + + + + + + \ No newline at end of file diff --git a/src/assembly/all-dependencies.xml b/src/assembly/all-dependencies.xml new file mode 100644 index 0000000..efe5abc --- /dev/null +++ b/src/assembly/all-dependencies.xml @@ -0,0 +1,22 @@ + + all-dependencies + + jar + + false + + + + metaInf-services + + + + + true + runtime + / + + + diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleColumnMetadataReader.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleColumnMetadataReader.java new file mode 100644 index 0000000..c4c0981 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleColumnMetadataReader.java @@ -0,0 +1,88 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.dialects.oracle.OracleProperties.ORACLE_CAST_NUMBER_TO_DECIMAL_PROPERTY; + +import java.sql.Connection; +import java.sql.Types; + +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.IdentifierConverter; +import com.exasol.adapter.jdbc.BaseColumnMetadataReader; +import com.exasol.adapter.jdbc.JdbcTypeDescription; +import com.exasol.adapter.metadata.DataType; + +/** + * This class implements Oracle-specific reading of column metadata. + */ +public class OracleColumnMetadataReader extends BaseColumnMetadataReader { + private static final int ORACLE_TIMESTAMP_WITH_LOCAL_TIME_ZONE = -101; + private static final int ORACLE_TIMESTAMP_WITH_TIME_ZONE = -102; + private static final int ORACLE_BINARY_FLOAT = 100; + private static final int ORACLE_BINARY_DOUBLE = 101; + private static final int INTERVAL_DAY_TO_SECOND = -104; + private static final int INTERVAL_YEAR_TO_MONTH = -103; + static final int ORACLE_MAGIC_NUMBER_SCALE = -127; + + /** + * Create a new instance of the {@link OracleColumnMetadataReader} + * + * @param connection connection to the remote data source + * @param properties user-defined adapter properties + * @param identifierConverter converter between source and Exasol identifiers + */ + public OracleColumnMetadataReader(final Connection connection, final AdapterProperties properties, + final IdentifierConverter identifierConverter) { + super(connection, properties, identifierConverter); + } + + @Override + public DataType mapJdbcType(final JdbcTypeDescription jdbcTypeDescription) { + switch (jdbcTypeDescription.getJdbcType()) { + case Types.DECIMAL: + case Types.NUMERIC: + return mapNumericType(jdbcTypeDescription); + case ORACLE_TIMESTAMP_WITH_TIME_ZONE: + case ORACLE_TIMESTAMP_WITH_LOCAL_TIME_ZONE: + return DataType.createTimestamp(false); + case INTERVAL_YEAR_TO_MONTH: + case INTERVAL_DAY_TO_SECOND: + case ORACLE_BINARY_FLOAT: + case ORACLE_BINARY_DOUBLE: + return DataType.createMaximumSizeVarChar(DataType.ExaCharset.UTF8); + default: + return super.mapJdbcType(jdbcTypeDescription); + } + } + + protected DataType mapNumericType(final JdbcTypeDescription jdbcTypeDescription) { + final int decimalScale = jdbcTypeDescription.getDecimalScale(); + if (decimalScale == ORACLE_MAGIC_NUMBER_SCALE) { + return workAroundNumberWithoutScaleAndPrecision(); + } + final int decimalPrecision = jdbcTypeDescription.getPrecisionOrSize() == 0 + ? DataType.MAX_EXASOL_DECIMAL_PRECISION + : jdbcTypeDescription.getPrecisionOrSize(); + if (decimalPrecision <= DataType.MAX_EXASOL_DECIMAL_PRECISION) { + return DataType.createDecimal(decimalPrecision, decimalScale); + } else { + return workAroundNumberWithoutScaleAndPrecision(); + } + } + + /** + * @return Oracle JDBC driver returns scale -127 if NUMBER data type was specified without scale and precision. + * Convert to VARCHAR. See http://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#i16209 and + * https://docs.oracle.com/cd/E19501-01/819-3659/gcmaz/ + */ + private DataType workAroundNumberWithoutScaleAndPrecision() { + return getOracleNumberTargetType(); + } + + private DataType getOracleNumberTargetType() { + if (this.properties.containsKey(ORACLE_CAST_NUMBER_TO_DECIMAL_PROPERTY)) { + return getNumberTypeFromProperty(ORACLE_CAST_NUMBER_TO_DECIMAL_PROPERTY); + } else { + return DataType.createMaximumSizeVarChar(DataType.ExaCharset.UTF8); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleConnectionDefinitionBuilder.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleConnectionDefinitionBuilder.java new file mode 100644 index 0000000..1aaf06d --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleConnectionDefinitionBuilder.java @@ -0,0 +1,41 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.dialects.oracle.OracleProperties.ORACLE_CONNECTION_NAME_PROPERTY; +import static com.exasol.adapter.dialects.oracle.OracleProperties.ORACLE_IMPORT_PROPERTY; + +import com.exasol.ExaConnectionInformation; +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.jdbc.BaseConnectionDefinitionBuilder; + +/** + * This class implements an Oracle-specific connection definition builder. + */ +public class OracleConnectionDefinitionBuilder extends BaseConnectionDefinitionBuilder { + @Override + public String buildConnectionDefinition(final AdapterProperties properties, + final ExaConnectionInformation exaConnectionInformation) { + if (properties.containsKey(ORACLE_IMPORT_PROPERTY)) { + return buildImportFromOraConnectionDefinition(properties); + } else { + return super.buildConnectionDefinition(properties, exaConnectionInformation); + } + } + + private String buildImportFromOraConnectionDefinition(final AdapterProperties properties) { + if (properties.containsKey(ORACLE_CONNECTION_NAME_PROPERTY)) { + return buildOracleConnectionDefinitionFromOracleConnectionOnly(properties); + } else { + throw new IllegalArgumentException("If you enable IMPORT FROM ORA with property \"" + ORACLE_IMPORT_PROPERTY + + "\" you also need to provide the name of an Oracle connection with \"" + + ORACLE_CONNECTION_NAME_PROPERTY + "\"."); + } + } + + private String getOracleConnectionName(final AdapterProperties properties) { + return properties.get(ORACLE_CONNECTION_NAME_PROPERTY); + } + + private String buildOracleConnectionDefinitionFromOracleConnectionOnly(final AdapterProperties properties) { + return "AT " + getOracleConnectionName(properties); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleIdentifier.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleIdentifier.java new file mode 100644 index 0000000..5d4a78a --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleIdentifier.java @@ -0,0 +1,64 @@ +package com.exasol.adapter.dialects.oracle; + +import java.util.Objects; + +import com.exasol.db.Identifier; + +/** + * Represents an identifier in the Oracle database. + */ +public class OracleIdentifier implements Identifier { + private final String id; + + private OracleIdentifier(final String id) { + this.id = id; + } + + /** + * Get the quoted identifier as a {@link String}. + * + * @return quoted identifier + */ + @Override + public String quote() { + return "\"" + this.id + "\""; + } + + /** + * Create a new {@link OracleIdentifier}. + * + * @param id the identifier as {@link String} + * @return new {@link OracleIdentifier} instance + */ + public static OracleIdentifier of(final String id) { + if (validate(id)) { + return new OracleIdentifier(id); + } else { + throw new AssertionError("E-ID-3: Unable to create identifier \"" + id // + + "\" because it contains illegal characters." // + + " For information about valid identifiers, please refer to" // + + " https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm"); + } + } + + private static boolean validate(final String id) { + return !id.contains("\""); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof OracleIdentifier)) { + return false; + } + final OracleIdentifier that = (OracleIdentifier) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleMetadataReader.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleMetadataReader.java new file mode 100644 index 0000000..9420ad7 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleMetadataReader.java @@ -0,0 +1,39 @@ +package com.exasol.adapter.dialects.oracle; + +import java.sql.Connection; + +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.BaseIdentifierConverter; +import com.exasol.adapter.dialects.IdentifierConverter; +import com.exasol.adapter.jdbc.*; + +/** + * This class reads Oracle-specific database metadata. + */ +public class OracleMetadataReader extends AbstractRemoteMetadataReader { + /** + * Create a new instance of the {@link OracleMetadataReader} + * + * @param connection database connection through which the reader retrieves the metadata from the remote source + * @param properties user-defined properties + */ + public OracleMetadataReader(final Connection connection, final AdapterProperties properties) { + super(connection, properties); + } + + @Override + protected TableMetadataReader createTableMetadataReader() { + return new OracleTableMetadataReader(this.connection, getColumnMetadataReader(), this.properties, + super.getIdentifierConverter()); + } + + @Override + protected ColumnMetadataReader createColumnMetadataReader() { + return new OracleColumnMetadataReader(this.connection, this.properties, getIdentifierConverter()); + } + + @Override + protected IdentifierConverter createIdentifierConverter() { + return BaseIdentifierConverter.createDefault(); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleProperties.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleProperties.java new file mode 100644 index 0000000..53a23ce --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleProperties.java @@ -0,0 +1,14 @@ +package com.exasol.adapter.dialects.oracle; + +/** + * This class contains Oracle-specific adapter properties. + */ +public final class OracleProperties { + public static final String ORACLE_CAST_NUMBER_TO_DECIMAL_PROPERTY = "ORACLE_CAST_NUMBER_TO_DECIMAL_WITH_PRECISION_AND_SCALE"; + public static final String ORACLE_IMPORT_PROPERTY = "IMPORT_FROM_ORA"; + public static final String ORACLE_CONNECTION_NAME_PROPERTY = "ORA_CONNECTION_NAME"; + + private OracleProperties() { + // prevent instantiation + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleQueryRewriter.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleQueryRewriter.java new file mode 100644 index 0000000..567c47d --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleQueryRewriter.java @@ -0,0 +1,34 @@ +package com.exasol.adapter.dialects.oracle; + +import java.sql.SQLException; + +import com.exasol.adapter.dialects.AbstractQueryRewriter; +import com.exasol.adapter.dialects.SqlDialect; +import com.exasol.adapter.jdbc.ConnectionDefinitionBuilder; +import com.exasol.adapter.jdbc.RemoteMetadataReader; + +/** + * This class implements an Oracle-specific query rewriter. + */ +public class OracleQueryRewriter extends AbstractQueryRewriter { + /** + * Create a new instance of the {@link OracleQueryRewriter}. + * + * @param dialect Oracle SQl dialect + * @param remoteMetadataReader reader for metadata from the remote data source + */ + public OracleQueryRewriter(final SqlDialect dialect, final RemoteMetadataReader remoteMetadataReader) { + super(dialect, remoteMetadataReader); + } + + @Override + protected ConnectionDefinitionBuilder createConnectionDefinitionBuilder() { + return new OracleConnectionDefinitionBuilder(); + } + + @Override + protected String generateImportStatement(final String connectionDefinition, final String pushdownQuery) + throws SQLException { + return "IMPORT FROM ORA " + connectionDefinition + " STATEMENT '" + pushdownQuery.replace("'", "''") + "'"; + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java new file mode 100644 index 0000000..d0d9c2f --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java @@ -0,0 +1,187 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.AdapterProperties.IS_LOCAL_PROPERTY; +import static com.exasol.adapter.AdapterProperties.SCHEMA_NAME_PROPERTY; +import static com.exasol.adapter.capabilities.AggregateFunctionCapability.*; +import static com.exasol.adapter.capabilities.LiteralCapability.*; +import static com.exasol.adapter.capabilities.MainCapability.*; +import static com.exasol.adapter.capabilities.PredicateCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.dialects.oracle.OracleProperties.*; + +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; + +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.capabilities.Capabilities; +import com.exasol.adapter.dialects.*; +import com.exasol.adapter.jdbc.*; +import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.sql.*; + +/** + * This class implements the Oracle SQL dialect. + */ +public class OracleSqlDialect extends AbstractSqlDialect { + static final String NAME = "ORACLE"; + private static final Capabilities CAPABILITIES = createCapabilityList(); + + private static Capabilities createCapabilityList() { + return Capabilities.builder() + .addMain(SELECTLIST_PROJECTION, SELECTLIST_EXPRESSIONS, FILTER_EXPRESSIONS, AGGREGATE_SINGLE_GROUP, + AGGREGATE_GROUP_BY_COLUMN, AGGREGATE_GROUP_BY_EXPRESSION, AGGREGATE_GROUP_BY_TUPLE, + AGGREGATE_HAVING, ORDER_BY_COLUMN, ORDER_BY_EXPRESSION, LIMIT, LIMIT_WITH_OFFSET, JOIN, + JOIN_TYPE_INNER, JOIN_TYPE_LEFT_OUTER, JOIN_TYPE_RIGHT_OUTER, JOIN_TYPE_FULL_OUTER, + JOIN_CONDITION_EQUI) + .addPredicate(AND, OR, NOT, EQUAL, NOTEQUAL, LESS, LESSEQUAL, LIKE, LIKE_ESCAPE, REGEXP_LIKE, BETWEEN, + IN_CONSTLIST, IS_NULL, IS_NOT_NULL) + .addLiteral(NULL, DATE, TIMESTAMP, TIMESTAMP_UTC, DOUBLE, EXACTNUMERIC, STRING, INTERVAL) + .addAggregateFunction(COUNT, COUNT_STAR, COUNT_DISTINCT, GROUP_CONCAT, GROUP_CONCAT_SEPARATOR, + GROUP_CONCAT_ORDER_BY) + .addAggregateFunction(SUM, SUM_DISTINCT, MIN, MAX, AVG, AVG_DISTINCT, MEDIAN, FIRST_VALUE, LAST_VALUE, + STDDEV, STDDEV_DISTINCT, STDDEV_POP, STDDEV_SAMP, VARIANCE, VARIANCE_DISTINCT, VAR_POP, + VAR_SAMP) + .addScalarFunction(CEIL, DIV, FLOOR, SIGN) + .addScalarFunction(ADD, SUB, MULT, FLOAT_DIV, NEG, ABS, ACOS, ASIN, ATAN, ATAN2, COS, COSH, COT, + DEGREES, EXP, GREATEST, LEAST, LN, LOG, MOD, POWER, RADIANS, SIN, SINH, SQRT, TAN, TANH) + .addScalarFunction(ASCII, CHR, INSTR, LENGTH, LOCATE, LOWER, LPAD, LTRIM, REGEXP_INSTR, REGEXP_REPLACE, + REGEXP_SUBSTR, REPEAT, REPLACE, REVERSE, RPAD, RTRIM, SOUNDEX, SUBSTR, TRANSLATE, TRIM, UPPER, + ADD_DAYS, ADD_HOURS, ADD_MINUTES, ADD_MONTHS, ADD_SECONDS, ADD_WEEKS, ADD_YEARS, CURRENT_DATE, + CURRENT_TIMESTAMP, DBTIMEZONE, LOCALTIMESTAMP, NUMTODSINTERVAL, NUMTOYMINTERVAL, + SESSIONTIMEZONE, SYSDATE, SYSTIMESTAMP, CAST, TO_CHAR, TO_DATE, TO_DSINTERVAL, TO_YMINTERVAL, + TO_NUMBER, TO_TIMESTAMP, BIT_AND, BIT_TO_NUM, CASE, NULLIFZERO, ZEROIFNULL) + .build(); + } + + /** + * Create a new instance of the {@link OracleSqlDialect}. + * + * @param connectionFactory factory for the JDBC connection to the remote data source + * @param properties user-defined adapter properties + */ + public OracleSqlDialect(final ConnectionFactory connectionFactory, final AdapterProperties properties) { + super(connectionFactory, properties, Set.of(SCHEMA_NAME_PROPERTY, ORACLE_IMPORT_PROPERTY, + ORACLE_CONNECTION_NAME_PROPERTY, ORACLE_CAST_NUMBER_TO_DECIMAL_PROPERTY)); + this.omitParenthesesMap.add(ScalarFunction.SYSDATE); + this.omitParenthesesMap.add(ScalarFunction.SYSTIMESTAMP); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public Capabilities getCapabilities() { + return CAPABILITIES; + } + + @Override + public Map getAggregateFunctionAliases() { + return new EnumMap<>(AggregateFunction.class); + } + + @Override + public StructureElementSupport supportsJdbcCatalogs() { + return StructureElementSupport.NONE; + } + + @Override + public StructureElementSupport supportsJdbcSchemas() { + return StructureElementSupport.MULTIPLE; + } + + DataType getOracleNumberTargetType() { + if (this.properties.containsKey(ORACLE_CAST_NUMBER_TO_DECIMAL_PROPERTY)) { + return getOracleNumberTypeFromProperty(); + } else { + return DataType.createMaximumSizeVarChar(DataType.ExaCharset.UTF8); + } + } + + private DataType getOracleNumberTypeFromProperty() { + final String oraclePrecisionAndScale = this.properties.get(ORACLE_CAST_NUMBER_TO_DECIMAL_PROPERTY); + final List precisionAndScaleList = Arrays.stream(oraclePrecisionAndScale.split(",")).map(String::trim) + .collect(Collectors.toList()); + return DataType.createDecimal(Integer.parseInt(precisionAndScaleList.get(0)), + Integer.parseInt(precisionAndScaleList.get(1))); + } + + @Override + public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext context) { + return new OracleSqlGenerationVisitor(this, context); + } + + @Override + // https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm + public String applyQuote(final String identifier) { + return OracleIdentifier.of(identifier).quote(); + } + + @Override + public boolean requiresCatalogQualifiedTableNames(final SqlGenerationContext context) { + return false; + } + + @Override + public boolean requiresSchemaQualifiedTableNames(final SqlGenerationContext context) { + return true; + } + + @Override + public NullSorting getDefaultNullSorting() { + return NullSorting.NULLS_SORTED_HIGH; + } + + @Override + // https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements003.htm + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + + /** + * Return the type of import the Oracle dialect uses. + * + * @return import type + */ + public ImportType getImportType() { + if (this.properties.isEnabled(IS_LOCAL_PROPERTY)) { + return ImportType.LOCAL; + } else if (this.properties.isEnabled(ORACLE_IMPORT_PROPERTY)) { + return ImportType.ORA; + } else { + return ImportType.JDBC; + } + } + + @Override + protected RemoteMetadataReader createRemoteMetadataReader() { + try { + return new OracleMetadataReader(this.connectionFactory.getConnection(), this.properties); + } catch (final SQLException exception) { + throw new RemoteMetadataReaderException( + "Unable to create Oracle remote metadata reader. Caused by: " + exception.getMessage(), exception); + } + } + + @Override + protected QueryRewriter createQueryRewriter() { + if (this.isImportFromOraEnabled()) { + return new OracleQueryRewriter(this, createRemoteMetadataReader()); + } + return new ImportIntoQueryRewriter(this, createRemoteMetadataReader(), this.connectionFactory); + } + + private boolean isImportFromOraEnabled() { + return this.properties.isEnabled(OracleProperties.ORACLE_IMPORT_PROPERTY); + } + + @Override + public void validateProperties() throws PropertyValidationException { + super.validateProperties(); + checkImportPropertyConsistency(ORACLE_IMPORT_PROPERTY, ORACLE_CONNECTION_NAME_PROPERTY); + validateBooleanProperty(ORACLE_IMPORT_PROPERTY); + validateCastNumberToDecimalProperty(ORACLE_CAST_NUMBER_TO_DECIMAL_PROPERTY); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectFactory.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectFactory.java new file mode 100644 index 0000000..d19b390 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectFactory.java @@ -0,0 +1,29 @@ +package com.exasol.adapter.dialects.oracle; + +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.SqlDialect; +import com.exasol.adapter.dialects.SqlDialectFactory; +import com.exasol.adapter.jdbc.ConnectionFactory; +import com.exasol.logging.VersionCollector; + +/** + * Factory for the Oracle SQL dialect. + */ +public class OracleSqlDialectFactory implements SqlDialectFactory { + @Override + public String getSqlDialectName() { + return OracleSqlDialect.NAME; + } + + @Override + public SqlDialect createSqlDialect(final ConnectionFactory connectionFactory, final AdapterProperties properties) { + return new OracleSqlDialect(connectionFactory, properties); + } + + @Override + public String getSqlDialectVersion() { + final VersionCollector versionCollector = new VersionCollector( + "META-INF/maven/com.exasol/mysql-virtual-schema/pom.properties"); + return versionCollector.getVersionNumber(); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitor.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitor.java new file mode 100644 index 0000000..eccce6e --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitor.java @@ -0,0 +1,577 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.sql.AggregateFunction.*; +import static com.exasol.adapter.sql.ScalarFunction.*; + +import java.util.*; + +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.dialects.*; +import com.exasol.adapter.metadata.*; +import com.exasol.adapter.sql.*; + +/** + * This class generates SQL queries for the {@link OracleSqlGenerationVisitor}. + */ +public class OracleSqlGenerationVisitor extends SqlGenerationVisitor { + private boolean requiresSelectListAliasesForLimit = false; + private static final String TIMESTAMP_FORMAT = "'YYYY-MM-DD HH24:MI:SS.FF3'"; + private static final List TYPE_NAMES_REQUIRING_CAST = List.of("TIMESTAMP", "INTERVAL", "BINARY_FLOAT", + "BINARY_DOUBLE"); + private final Set aggregateFunctionsCast = EnumSet.noneOf(AggregateFunction.class); + private final Set scalarFunctionsCast = EnumSet.noneOf(ScalarFunction.class); + + /** + * Create a new instance of the {@link OracleSqlGenerationVisitor}. + * + * @param dialect {@link OracleSqlDialect} SQL dialect + * @param context SQL generation context + */ + public OracleSqlGenerationVisitor(final SqlDialect dialect, final SqlGenerationContext context) { + super(dialect, context); + addAggregateFunctions(); + addScalarFunctions(); + } + + private void addScalarFunctions() { + this.scalarFunctionsCast.addAll(Arrays.asList(ADD, SUB, MULT, FLOAT_DIV, NEG, ABS, ACOS, ASIN, ATAN, ATAN2, COS, + COSH, COT, DEGREES, EXP, GREATEST, LEAST, LN, LOG, MOD, POWER, RADIANS, SIN, SINH, SQRT, TAN, TANH)); + } + + private void addAggregateFunctions() { + this.aggregateFunctionsCast.addAll(Arrays.asList(SUM, MIN, MAX, AVG, MEDIAN, FIRST_VALUE, LAST_VALUE, STDDEV, + STDDEV_POP, STDDEV_SAMP, VARIANCE, VAR_POP, VAR_SAMP)); + + } + + Set getAggregateFunctionsCast() { + return this.aggregateFunctionsCast; + } + + Set getScalarFunctionsCast() { + return this.scalarFunctionsCast; + } + + /** + * ORACLE Syntax (before 12c) for LIMIT 10:
+ * SELECT LIMIT_SUBSELECT.* FROM ( <query-with-aliases> ) LIMIT_SUBSELECT WHERE ROWNUM <= 30 + * + * ORACLE Syntax (before 12c) for LIMIT 10 OFFSET 20:
+ * SELECT c1, c2, ... FROM ( SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( <query-with-aliases> ) + * LIMIT_SUBSELECT WHERE ROWNUM <= 30 ) WHERE ROWNUM_SUB > 20 + * + * The ROWNUM filter is evaluated before ORDER BY, which is why we need sub-selects + */ + @Override + public String visit(final SqlStatementSelect select) throws AdapterException { + if (!select.hasLimit()) { + return super.visit(select); + } else { + return getSqlStatementSelectWithLimit(select); + } + } + + private String getSqlStatementSelectWithLimit(final SqlStatementSelect select) throws AdapterException { + final SqlLimit limit = select.getLimit(); + if (limit.hasOffset()) { + return getSqlStatementSelectWithOffset(select, limit); + } else { + return getSqlStatementSelectWithoutOffset(select, limit); + } + } + + private String getSqlStatementSelectWithOffset(final SqlStatementSelect select, final SqlLimit limit) + throws AdapterException { + final StringBuilder builder = new StringBuilder(); + builder.append("SELECT "); + if (select.getSelectList().isRequestAnyColumn()) { + return "1"; + } else if (select.getSelectList().isSelectStar()) { + appendSelectStar(select, builder); + } else { + final int numberOfExpressions = select.getSelectList().getExpressions().size(); + builder.append(String.join(", ", buildAliases(numberOfExpressions))); + } + builder.append(" FROM ( "); + builder.append("SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( "); + this.requiresSelectListAliasesForLimit = true; + builder.append(super.visit(select)); + builder.append(" ) LIMIT_SUBSELECT WHERE ROWNUM <= "); + builder.append(limit.getLimit() + limit.getOffset()); + builder.append(" ) WHERE ROWNUM_SUB > "); + builder.append(limit.getOffset()); + return builder.toString(); + } + + private void appendSelectStar(final SqlStatementSelect select, final StringBuilder builder) { + int numberOfColumns = 0; + final List tableMetadata = new ArrayList<>(); + SqlGenerationHelper.addMetadata(select.getFromClause(), tableMetadata); + for (final TableMetadata tableMeta : tableMetadata) { + numberOfColumns += tableMeta.getColumns().size(); + } + builder.append(String.join(", ", buildAliases(numberOfColumns))); + } + + private List buildAliases(final int numSelectListElements) { + final List aliases = new ArrayList<>(numSelectListElements); + for (int i = 0; i < numSelectListElements; i++) { + aliases.add("c" + i); + } + return aliases; + } + + private String getSqlStatementSelectWithoutOffset(final SqlStatementSelect select, final SqlLimit limit) + throws AdapterException { + final StringBuilder builder = new StringBuilder(); + builder.append("SELECT LIMIT_SUBSELECT.* FROM ( "); + builder.append(super.visit(select)); + builder.append(" ) LIMIT_SUBSELECT WHERE ROWNUM <= "); + builder.append(limit.getLimit() + limit.getOffset()); + return builder.toString(); + } + + @Override + public String visit(final SqlSelectList selectList) throws AdapterException { + if (selectList.isRequestAnyColumn()) { + return "1"; + } else { + return getSqlSelectList(selectList); + } + } + + private String getSqlSelectList(final SqlSelectList selectList) throws AdapterException { + final List selectListElements = new ArrayList<>(); + if (selectList.isSelectStar()) { + getSelectStarList(selectList, selectListElements); + } else { + for (final SqlNode node : selectList.getExpressions()) { + selectListElements.add(node.accept(this)); + } + } + if (this.requiresSelectListAliasesForLimit) { + addColumnAliases(selectListElements); + } + return String.join(", ", selectListElements); + } + + private void getSelectStarList(final SqlSelectList selectList, final List selectListElements) + throws AdapterException { + final SqlStatementSelect select = (SqlStatementSelect) selectList.getParent(); + final boolean selectListRequiresCasts = isSelectListRequiresCasts(selectList, selectListElements, select); + if (!this.requiresSelectListAliasesForLimit && !selectListRequiresCasts) { + selectListElements.clear(); + selectListElements.add("*"); + } + } + + private boolean isSelectListRequiresCasts(final SqlSelectList selectList, final List selectListElements, + final SqlStatementSelect select) throws AdapterException { + boolean selectListRequiresCasts = false; + int columnId = 0; + final List tableMetadata = new ArrayList<>(); + SqlGenerationHelper.addMetadata(select.getFromClause(), tableMetadata); + for (final TableMetadata tableMeta : tableMetadata) { + for (final ColumnMetadata columnMeta : tableMeta.getColumns()) { + final SqlColumn sqlColumn = new SqlColumn(columnId, columnMeta); + sqlColumn.setParent(selectList); + selectListRequiresCasts |= nodeRequiresCast(sqlColumn); + selectListElements.add(sqlColumn.accept(this)); + ++columnId; + } + } + return selectListRequiresCasts; + } + + private boolean nodeRequiresCast(final SqlNode node) throws AdapterException { + if (node.getType() == SqlNodeType.COLUMN) { + return checkIfColumnRequiresCast((SqlColumn) node); + } else { + return false; + } + } + + private boolean checkIfColumnRequiresCast(final SqlColumn node) throws AdapterException { + final String typeName = getTypeNameFromColumn(node); + if (typeName.equals("NUMBER")) { + if (node.getMetadata().getType().getExaDataType() == DataType.ExaDataType.VARCHAR) { + return true; + } else { + return checkIfNeedToCastNumberToDecimal(node); + } + } else { + for (final String typeRequiringCast : TYPE_NAMES_REQUIRING_CAST) { + if (typeName.startsWith(typeRequiringCast)) { + return true; + } + } + } + return false; + } + + /** + * This method determines if a NUMBER column needs to be casted to the DECIMAL type specified in the + * oracle_cast_number_to_decimal_with_precision_and_scale property. This is done by checking if the target type is + * the type specified in the property, assuming that this type was set according to the property. This method is not + * exact and will also add CASTs to columns that have the exact same type as specified in the property. + * + * @param column a NUMBER column + * @return true if a cast is necessary for the NUMBER column + */ + private boolean checkIfNeedToCastNumberToDecimal(final SqlColumn column) { + final AbstractSqlDialect dialect = (AbstractSqlDialect) getDialect(); + final DataType columnType = column.getMetadata().getType(); + final DataType castNumberToDecimalType = ((OracleSqlDialect) dialect).getOracleNumberTargetType(); + return (columnType.getPrecision() == castNumberToDecimalType.getPrecision()) + && (columnType.getScale() == castNumberToDecimalType.getScale()); + } + + private void addColumnAliases(final List selectListElements) { + for (int i = 0; i < selectListElements.size(); i++) { + selectListElements.set(i, selectListElements.get(i) + " AS c" + i); + } + } + + // Limit is realized via a {@code ROWNUM} filter in Oracle (< 12c) Oracle 12c introduced nice syntax for limit and + // offset + // functionality: "OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY" + @Override + public String visit(final SqlLimit limit) { + return ""; + } + + @Override + public String visit(final SqlPredicateLikeRegexp predicate) throws AdapterException { + return "REGEXP_LIKE(" + predicate.getLeft().accept(this) + ", " + predicate.getPattern().accept(this) + ")"; + } + + @Override + public String visit(final SqlColumn column) throws AdapterException { + return getColumnProjectionString(column, super.visit(column)); + } + + private String getColumnProjectionString(final SqlColumn column, final String projectionString) + throws AdapterException { + final boolean isDirectlyInSelectList = (column.hasParent() + && (column.getParent().getType() == SqlNodeType.SELECT_LIST)); + if (!isDirectlyInSelectList) { + return projectionString; + } else { + return getProjectionString(column, projectionString); + } + } + + private String getProjectionString(final SqlColumn column, final String projectionString) throws AdapterException { + final AbstractSqlDialect dialect = (AbstractSqlDialect) getDialect(); + final String typeName = getTypeNameFromColumn(column); + if (typeName.startsWith("INTERVAL") || typeName.equals("BINARY_FLOAT") || typeName.equals("BINARY_DOUBLE")) { + return createToChar(projectionString); + } else if (typeName.startsWith("TIMESTAMP") + && (((OracleSqlDialect) dialect).getImportType() == ImportType.JDBC)) { + return "TO_TIMESTAMP(TO_CHAR(" + projectionString + ", " + TIMESTAMP_FORMAT + "), " + TIMESTAMP_FORMAT + + ")"; + } else if (typeName.equals("NUMBER")) { + return getNumberProjectionString(column, projectionString, (OracleSqlDialect) dialect); + } else { + return projectionString; + } + } + + public String createToChar(final String operand) { + return "TO_CHAR(" + operand + ")"; + } + + private String getNumberProjectionString(final SqlColumn column, final String projectionString, + final OracleSqlDialect dialect) { + if (column.getMetadata().getType().getExaDataType() == DataType.ExaDataType.VARCHAR) { + return createToChar(projectionString); + } else { + if (checkIfNeedToCastNumberToDecimal(column)) { + final DataType castNumberToDecimalType = dialect.getOracleNumberTargetType(); + return cast(projectionString, "DECIMAL(" + castNumberToDecimalType.getPrecision() + "," + + castNumberToDecimalType.getScale() + ")"); + } else { + return projectionString; + } + } + } + + private String cast(final String value, final String as) { + return "CAST(" + value + " AS " + as + ")"; + } + + @Override + public String visit(final SqlLiteralExactnumeric literal) { + final String literalString = literal.getValue().toString(); + return getLiteralString(literalString, literal.hasParent(), literal.getParent()); + } + + private String getLiteralString(final String literalString, final boolean b, final SqlNode parent) { + final boolean isDirectlyInSelectList = (b && (parent.getType() == SqlNodeType.SELECT_LIST)); + if (isDirectlyInSelectList) { + return createToChar(literalString); + } + return literalString; + } + + @Override + public String visit(final SqlLiteralDouble literal) { + final String literalString = Double.toString(literal.getValue()); + return getLiteralString(literalString, literal.hasParent(), literal.getParent()); + } + + @Override + public String visit(final SqlFunctionAggregateGroupConcat function) throws AdapterException { + final StringBuilder builder = new StringBuilder(); + builder.append("LISTAGG"); + builder.append("("); + final String expression = function.getArgument().accept(this); + builder.append(expression); + builder.append(", "); + final String separator = function.hasSeparator() ? function.getSeparator().accept(this) : "','"; + builder.append(separator); + builder.append(") WITHIN GROUP(ORDER BY "); + if (function.hasOrderBy()) { + builder.append(getOrderByString(function)); + } else { + builder.append(expression); + } + builder.append(")"); + return builder.toString(); + } + + private String getOrderByString(final SqlFunctionAggregateGroupConcat function) throws AdapterException { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < function.getOrderBy().getExpressions().size(); i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(function.getOrderBy().getExpressions().get(i).accept(this)); + if (function.getOrderBy().isAscending().get(i).equals(false)) { + builder.append(" DESC"); + } + if (function.getOrderBy().nullsLast().get(i).equals(false)) { + builder.append(" NULLS FIRST"); + } + } + return builder.toString(); + } + + @Override + public String visit(final SqlFunctionAggregate function) throws AdapterException { + final boolean isDirectlyInSelectList = (function.hasParent() + && (function.getParent().getType() == SqlNodeType.SELECT_LIST)); + if (isDirectlyInSelectList && this.aggregateFunctionsCast.contains(function.getFunction())) { + // Cast to FLOAT because result set metadata has precision = 0, scale = 0 + return cast(super.visit(function), "FLOAT"); + } + return super.visit(function); + } + + @Override + public String visit(final SqlFunctionScalar function) throws AdapterException { + String sql = super.visit(function); + switch (function.getFunction()) { + case LOCATE: + sql = getLocate(function); + break; + case TRIM: + sql = getTrim(function); + break; + case ADD_DAYS: + case ADD_HOURS: + case ADD_MINUTES: + case ADD_SECONDS: + case ADD_WEEKS: + case ADD_YEARS: + sql = getTimeOrDate(function); + break; + case CURRENT_DATE: + sql = "CURRENT_DATE"; + break; + case CURRENT_TIMESTAMP: + sql = "CURRENT_TIMESTAMP"; + break; + case DBTIMEZONE: + sql = "DBTIMEZONE"; + break; + case LOCALTIMESTAMP: + sql = "LOCALTIMESTAMP"; + break; + case SESSIONTIMEZONE: + sql = "SESSIONTIMEZONE"; + break; + case SYSDATE: + sql = "TO_DATE(SYSDATE)"; + break; + case SYSTIMESTAMP: + sql = "SYSTIMESTAMP"; + break; + case BIT_AND: + sql = sql.replaceFirst("^BIT_AND", "BITAND"); + break; + case BIT_TO_NUM: + sql = sql.replaceFirst("^BIT_TO_NUM", "BIN_TO_NUM"); + break; + case NULLIFZERO: + sql = getSqlFunctionScalar(function, "NULLIF(", ", 0)"); + break; + case ZEROIFNULL: + sql = getSqlFunctionScalar(function, "NVL(", ", 0)"); + break; + case DIV: + sql = getDiv(function); + break; + case COT: + sql = getSqlFunctionScalar(function, "(1 / TAN(", "))"); + break; + case DEGREES: + sql = getSqlFunctionScalar(function, "((", ") * 180 / ACOS(-1))"); + break; + case RADIANS: + sql = getSqlFunctionScalar(function, "((", ") * ACOS(-1) / 180)"); + break; + case REPEAT: + sql = getRepeat(function); + break; + case REVERSE: + sql = getSqlFunctionScalar(function, "REVERSE(TO_CHAR(", "))"); + break; + default: + break; + } + final boolean isDirectlyInSelectList = (function.hasParent() + && (function.getParent().getType() == SqlNodeType.SELECT_LIST)); + if (isDirectlyInSelectList && this.scalarFunctionsCast.contains(function.getFunction())) { + // Cast to FLOAT because result set metadata has precision = 0, scale = 0 + sql = cast(sql, "FLOAT"); + } + return sql; + } + + private String getTrim(final SqlFunctionScalar function) throws AdapterException { + final List arguments = function.getArguments(); + final List argumentsSql = new ArrayList<>(arguments.size()); + for (final SqlNode node : arguments) { + argumentsSql.add(node.accept(this)); + } + final StringBuilder builder = new StringBuilder(); + builder.append("TRIM("); + if (argumentsSql.size() > 1) { + builder.append(argumentsSql.get(1)); + builder.append(" FROM "); + builder.append(argumentsSql.get(0)); + } else { + builder.append(argumentsSql.get(0)); + } + builder.append(")"); + return builder.toString(); + } + + private String getLocate(final SqlFunctionScalar function) throws AdapterException { + final List arguments = function.getArguments(); + final List argumentsSql = new ArrayList<>(arguments.size()); + for (final SqlNode node : arguments) { + argumentsSql.add(node.accept(this)); + } + final StringBuilder builder = new StringBuilder(); + builder.append("INSTR("); + builder.append(argumentsSql.get(1)); + builder.append(", "); + builder.append(argumentsSql.get(0)); + if (argumentsSql.size() > 2) { + builder.append(", "); + builder.append(argumentsSql.get(2)); + } + builder.append(")"); + return builder.toString(); + } + + private String getTimeOrDate(final SqlFunctionScalar function) throws AdapterException { + final List arguments = function.getArguments(); + final List argumentsSql = new ArrayList<>(arguments.size()); + for (final SqlNode node : arguments) { + argumentsSql.add(node.accept(this)); + } + final StringBuilder builder = new StringBuilder(); + builder.append("("); + builder.append(argumentsSql.get(0)); + builder.append(" + INTERVAL '"); + if (function.getFunction() == ScalarFunction.ADD_WEEKS) { + builder.append(7 * Integer.parseInt(argumentsSql.get(1))); + } else { + builder.append(argumentsSql.get(1)); + } + builder.append("' "); + switch (function.getFunction()) { + case ADD_DAYS: + case ADD_WEEKS: + builder.append("DAY"); + break; + case ADD_HOURS: + builder.append("HOUR"); + break; + case ADD_MINUTES: + builder.append("MINUTE"); + break; + case ADD_SECONDS: + builder.append("SECOND"); + break; + case ADD_YEARS: + builder.append("YEAR"); + break; + default: + break; + } + builder.append(")"); + return builder.toString(); + } + + private String getSqlFunctionScalar(final SqlFunctionScalar function, final String s, final String s2) + throws AdapterException { + final List arguments = function.getArguments(); + final List argumentsSql = new ArrayList<>(arguments.size()); + for (final SqlNode node : arguments) { + argumentsSql.add(node.accept(this)); + } + final StringBuilder builder = new StringBuilder(); + builder.append(s); + builder.append(argumentsSql.get(0)); + builder.append(s2); + return builder.toString(); + } + + private String getRepeat(final SqlFunctionScalar function) throws AdapterException { + final List arguments = function.getArguments(); + final List argumentsSql = new ArrayList<>(arguments.size()); + for (final SqlNode node : arguments) { + argumentsSql.add(node.accept(this)); + } + final StringBuilder builder = new StringBuilder(); + builder.append("RPAD(TO_CHAR("); + builder.append(argumentsSql.get(0)); + builder.append("), LENGTH("); + builder.append(argumentsSql.get(0)); + builder.append(") * ROUND("); + builder.append(argumentsSql.get(1)); + builder.append("), "); + builder.append(argumentsSql.get(0)); + builder.append(")"); + return builder.toString(); + } + + private String getDiv(final SqlFunctionScalar function) throws AdapterException { + final List arguments = function.getArguments(); + final List argumentsSql = new ArrayList<>(arguments.size()); + for (final SqlNode node : arguments) { + argumentsSql.add(node.accept(this)); + } + final StringBuilder builder = new StringBuilder(); + builder.append("CAST(FLOOR("); + builder.append(argumentsSql.get(0)); + builder.append(" / "); + builder.append(argumentsSql.get(1)); + builder.append(") AS NUMBER(36, 0))"); + return builder.toString(); + } +} diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleTableMetadataReader.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleTableMetadataReader.java new file mode 100644 index 0000000..de00685 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleTableMetadataReader.java @@ -0,0 +1,37 @@ +package com.exasol.adapter.dialects.oracle; + +import java.sql.Connection; + +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.IdentifierConverter; +import com.exasol.adapter.jdbc.BaseTableMetadataReader; +import com.exasol.adapter.jdbc.ColumnMetadataReader; + +/** + * This class implements a reader for Oracle database metadata. + */ +public class OracleTableMetadataReader extends BaseTableMetadataReader { + private static final String TRASH_BIN_TABLE_NAME_PREFIX = "BIN$"; + + /** + * Create a new {@link OracleTableMetadataReader} instance. + * + * @param connection connection to the remote data source + * @param columnMetadataReader reader to be used to map the metadata of the tables columns + * @param properties user-defined adapter properties + * @param identifierConverter converter between source and Exasol identifiers + */ + public OracleTableMetadataReader(final Connection connection, final ColumnMetadataReader columnMetadataReader, + final AdapterProperties properties, final IdentifierConverter identifierConverter) { + super(connection, columnMetadataReader, properties, identifierConverter); + } + + @Override + public boolean isTableIncludedByMapping(final String tableName) { + return !isTableInTrashBin(tableName); + } + + private boolean isTableInTrashBin(final String tableName) { + return tableName.startsWith(TRASH_BIN_TABLE_NAME_PREFIX); + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/services/com.exasol.adapter.dialects.SqlDialectFactory b/src/main/resources/META-INF/services/com.exasol.adapter.dialects.SqlDialectFactory new file mode 100644 index 0000000..fcdad08 --- /dev/null +++ b/src/main/resources/META-INF/services/com.exasol.adapter.dialects.SqlDialectFactory @@ -0,0 +1 @@ +com.exasol.adapter.dialects.oracle.OracleSqlDialectFactory \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/IntegrationTestConstants.java b/src/test/java/com/exasol/adapter/dialects/oracle/IntegrationTestConstants.java new file mode 100644 index 0000000..19298a0 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/IntegrationTestConstants.java @@ -0,0 +1,19 @@ +package com.exasol.adapter.dialects.oracle; + +import java.nio.file.Path; + +public final class IntegrationTestConstants { + public static final String VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION = "virtual-schema-dist-8.0.0-oracle-1.0.0.jar"; + public static final String EXASOL_DOCKER_IMAGE_REFERENCE = "exasol/docker-db:7.0.2"; + public static final Path PATH_TO_VIRTUAL_SCHEMAS_JAR = Path.of("target", VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION); + public static final String SCHEMA_EXASOL = "SCHEMA_EXASOL"; + public static final String ADAPTER_SCRIPT_EXASOL = "ADAPTER_SCRIPT_EXASOL"; + public static final String TABLE_JOIN_1 = "TABLE_JOIN_1"; + public static final String TABLE_JOIN_2 = "TABLE_JOIN_2"; + public static final String DOCKER_IP_ADDRESS = "172.17.0.1"; + public static final String JDBC_DRIVER_CONFIGURATION_FILE_NAME = "settings.cfg"; + + private IntegrationTestConstants() { + // intentionally left empty + } +} diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleColumnMetadataReaderTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleColumnMetadataReaderTest.java new file mode 100644 index 0000000..7e40fe2 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleColumnMetadataReaderTest.java @@ -0,0 +1,68 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.metadata.DataType.createMaximumSizeVarChar; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import java.sql.Types; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.BaseIdentifierConverter; +import com.exasol.adapter.jdbc.JdbcTypeDescription; +import com.exasol.adapter.metadata.DataType; + +class OracleColumnMetadataReaderTest { + private OracleColumnMetadataReader columnMetadataReader; + + @BeforeEach + void beforeEach() { + this.columnMetadataReader = createDefaultOracleColumnMetadataReader(); + } + + protected OracleColumnMetadataReader createDefaultOracleColumnMetadataReader() { + return new OracleColumnMetadataReader(null, AdapterProperties.emptyProperties(), + BaseIdentifierConverter.createDefault()); + } + + private JdbcTypeDescription createTypeDescriptionForNumeric(final int precision, final int scale) { + final int octetLength = 10; + return new JdbcTypeDescription(Types.NUMERIC, scale, precision, octetLength, "NUMERIC"); + } + + @Test + void testMapColumnTypeWithMagicScale() { + final int precision = 10; + final int scale = OracleColumnMetadataReader.ORACLE_MAGIC_NUMBER_SCALE; + final JdbcTypeDescription typeDescription = createTypeDescriptionForNumeric(precision, scale); + assertThat(this.columnMetadataReader.mapJdbcType(typeDescription), + equalTo(createMaximumSizeVarChar(DataType.ExaCharset.UTF8))); + } + + @Test + void testMapNumericColumnTypeWithMaximumDecimalPrecision() { + final int precision = DataType.MAX_EXASOL_DECIMAL_PRECISION; + final int scale = 0; + final JdbcTypeDescription typeDescription = createTypeDescriptionForNumeric(precision, scale); + assertThat(this.columnMetadataReader.mapJdbcType(typeDescription), + equalTo(DataType.createDecimal(precision, scale))); + } + + @Test + void testMapColumnTypeWithZeroPrecision() { + final int precision = 0; + final int scale = 0; + final JdbcTypeDescription typeDescription = createTypeDescriptionForNumeric(precision, scale); + assertThat(this.columnMetadataReader.mapJdbcType(typeDescription), + equalTo(DataType.createDecimal(DataType.MAX_EXASOL_DECIMAL_PRECISION, scale))); + } + + private OracleColumnMetadataReader createParameterizedColumnMetadataReader( + final Map rawProperties) { + return new OracleColumnMetadataReader(null, new AdapterProperties(rawProperties), + BaseIdentifierConverter.createDefault()); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleConnectionDefinitionBuilderTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleConnectionDefinitionBuilderTest.java new file mode 100644 index 0000000..61bd9d1 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleConnectionDefinitionBuilderTest.java @@ -0,0 +1,38 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.AdapterProperties.CONNECTION_NAME_PROPERTY; +import static com.exasol.adapter.dialects.oracle.OracleProperties.ORACLE_CONNECTION_NAME_PROPERTY; +import static com.exasol.adapter.dialects.oracle.OracleProperties.ORACLE_IMPORT_PROPERTY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.exasol.adapter.AdapterProperties; + +class OracleConnectionDefinitionBuilderTest { + private final OracleConnectionDefinitionBuilder connectionDefinitionBuilder = new OracleConnectionDefinitionBuilder(); + + @Test + void testBuildConnectionDefinition() { + final AdapterProperties properties = new AdapterProperties(Map.of( // + ORACLE_IMPORT_PROPERTY, "true", // + ORACLE_CONNECTION_NAME_PROPERTY, "ora_connection", // + CONNECTION_NAME_PROPERTY, "jdbc_connection")); + assertThat(connectionDefinitionBuilder.buildConnectionDefinition(properties, null), + containsString("AT ora_connection")); + } + + @Test + void testBuildConnectionDefinitionMissingPropertyException() { + final AdapterProperties properties = new AdapterProperties(Map.of( // + ORACLE_IMPORT_PROPERTY, "true", // + CONNECTION_NAME_PROPERTY, "ora_connection")); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> connectionDefinitionBuilder.buildConnectionDefinition(properties, null)); + assertThat(exception.getMessage(), containsString("If you enable IMPORT FROM ORA with property")); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleIdentifierTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleIdentifierTest.java new file mode 100644 index 0000000..6318d6f --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleIdentifierTest.java @@ -0,0 +1,29 @@ +package com.exasol.adapter.dialects.oracle; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import nl.jqno.equalsverifier.EqualsVerifier; + +class OracleIdentifierTest { + @ParameterizedTest + @ValueSource(strings = { "_myunderscoretable", "123columnone", "テスト", "таблица" }) + void testCreateValidIdentifier(final String identifier) { + assertDoesNotThrow(() -> OracleIdentifier.of(identifier)); + } + + @ParameterizedTest + @ValueSource(strings = { "\"testtable\"", "test\"table" }) + void testCreateInvalidIdentifier(final String identifier) { + assertThrows(AssertionError.class, () -> OracleIdentifier.of(identifier)); + } + + @Test + void testEqualsAndHashContract() { + EqualsVerifier.simple().forClass(OracleIdentifier.class).verify(); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleMetadataReaderTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleMetadataReaderTest.java new file mode 100644 index 0000000..eba2984 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleMetadataReaderTest.java @@ -0,0 +1,28 @@ +package com.exasol.adapter.dialects.oracle; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.exasol.adapter.AdapterProperties; + +class OracleMetadataReaderTest { + private OracleMetadataReader reader; + + @BeforeEach + void beforeEach() { + this.reader = new OracleMetadataReader(null, AdapterProperties.emptyProperties()); + } + + @Test + void testGetTableMetadataReader() { + assertThat(this.reader.getTableMetadataReader(), instanceOf(OracleTableMetadataReader.class)); + } + + @Test + void testGetColumnMetadataReader() { + assertThat(this.reader.getColumnMetadataReader(), instanceOf(OracleColumnMetadataReader.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleQueryRewriterTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleQueryRewriterTest.java new file mode 100644 index 0000000..e77b407 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleQueryRewriterTest.java @@ -0,0 +1,51 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.dialects.oracle.OracleProperties.ORACLE_CONNECTION_NAME_PROPERTY; +import static com.exasol.adapter.dialects.oracle.OracleProperties.ORACLE_IMPORT_PROPERTY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +import java.sql.SQLException; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.*; +import com.exasol.adapter.jdbc.ConnectionFactory; +import com.exasol.adapter.sql.TestSqlStatementFactory; + +@ExtendWith(MockitoExtension.class) +public class OracleQueryRewriterTest extends AbstractQueryRewriterTestBase { + @BeforeEach + void beforeEach() { + this.statement = TestSqlStatementFactory.createSelectOneFromDual(); + } + + @Test + void testRewriteToImportFromOraWithConnectionDetailsInProperties( + @Mock final ConnectionFactory connectionFactoryMock) throws AdapterException, SQLException { + final AdapterProperties properties = new AdapterProperties(Map.of( // + ORACLE_IMPORT_PROPERTY, "true", // + ORACLE_CONNECTION_NAME_PROPERTY, "ora_connection")); + final SqlDialectFactory dialectFactory = new OracleSqlDialectFactory(); + final SqlDialect dialect = dialectFactory.createSqlDialect(connectionFactoryMock, properties); + final QueryRewriter queryRewriter = new OracleQueryRewriter(dialect, null); + assertThat(queryRewriter.rewrite(this.statement, EXA_METADATA, properties), + equalTo("IMPORT FROM ORA AT ora_connection STATEMENT 'SELECT TO_CHAR(1) FROM \"DUAL\"'")); + } + + @Test + void testConnectionDefinitionBuilderClass() { + final SqlDialect dialect = new OracleSqlDialect(null, AdapterProperties.emptyProperties()); + final OracleQueryRewriter queryRewriter = new OracleQueryRewriter(dialect, null); + assertThat(queryRewriter.createConnectionDefinitionBuilder(), + instanceOf(OracleConnectionDefinitionBuilder.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectFactoryTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectFactoryTest.java new file mode 100644 index 0000000..506353a --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectFactoryTest.java @@ -0,0 +1,30 @@ +package com.exasol.adapter.dialects.oracle; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.exasol.adapter.AdapterProperties; + +public class OracleSqlDialectFactoryTest { + private OracleSqlDialectFactory factory; + + @BeforeEach + void beforeEach() { + this.factory = new OracleSqlDialectFactory(); + } + + @Test + void testGetName() { + assertThat(this.factory.getSqlDialectName(), equalTo("ORACLE")); + } + + @Test + void testCreateDialect() { + assertThat(this.factory.createSqlDialect(null, AdapterProperties.emptyProperties()), + instanceOf(OracleSqlDialect.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectIT.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectIT.java new file mode 100644 index 0000000..be6f91b --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectIT.java @@ -0,0 +1,953 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.dialects.oracle.IntegrationTestConstants.*; +import static com.exasol.dbbuilder.dialects.exasol.AdapterScript.Language.JAVA; +import static com.exasol.matcher.ResultSetMatcher.matchesResultSet; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.file.Path; +import java.sql.*; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.exasol.bucketfs.Bucket; +import com.exasol.bucketfs.BucketAccessException; +import com.exasol.containers.ExasolContainer; +import com.exasol.dbbuilder.dialects.exasol.AdapterScript; +import com.exasol.dbbuilder.dialects.exasol.ConnectionDefinition; +import com.exasol.dbbuilder.dialects.exasol.ExasolObjectFactory; +import com.exasol.dbbuilder.dialects.exasol.ExasolSchema; + +/** + * How to run `OracleSqlDialectIT`: See the documentation > exasolContainer = new ExasolContainer<>( + EXASOL_DOCKER_IMAGE_REFERENCE) // + .withLogConsumer(new Slf4jLogConsumer(LOGGER)); + @Container + private static final OracleContainer oracleContainer = new OracleContainer(ORACLE_CONTAINER_NAME); + private static Statement statementExasol; + + @BeforeAll + static void beforeAll() throws InterruptedException, BucketAccessException, TimeoutException, SQLException { + final String driverName = getPropertyFromFile(RESOURCES_FOLDER_DIALECT_NAME, "driver.name"); + uploadDriverToBucket(driverName, RESOURCES_FOLDER_DIALECT_NAME, exasolContainer.getDefaultBucket()); + uploadVsJarToBucket(exasolContainer.getDefaultBucket()); + uploadInstantClientToBucket(); + final Connection exasolConnection = exasolContainer.createConnectionForUser(exasolContainer.getUsername(), + exasolContainer.getPassword()); + statementExasol = exasolConnection.createStatement(); + final Statement statementOracle = oracleContainer.createConnection("").createStatement(); + createOracleUser(statementOracle); + createOracleTableAllDataTypes(statementOracle); + createOracleTableNumberHandling(statementOracle); + createOracleTableTimestamps(statementOracle); + createTestTablesForJoinTests(oracleContainer.createConnection(""), SCHEMA_ORACLE); + final Integer mappedPort = oracleContainer.getMappedPort(ORACLE_PORT); + final String oracleUsername = oracleContainer.getUsername(); + final String oraclePassword = oracleContainer.getPassword(); + final ExasolObjectFactory exasolFactory = new ExasolObjectFactory(exasolContainer.createConnection("")); + final ExasolSchema schema = exasolFactory.createSchema(SCHEMA_EXASOL); + final AdapterScript adapterScript = createAdapterScript(driverName, schema); + final String jdbcConnectionString = "jdbc:oracle:thin:@//" + DOCKER_IP_ADDRESS + ":" + mappedPort + "/xe"; + final ConnectionDefinition jdbcConnectionDefinition = exasolFactory + .createConnectionDefinition(JDBC_CONNECTION_NAME, jdbcConnectionString, oracleUsername, oraclePassword); + createOraConnection(exasolFactory, mappedPort, oracleUsername, oraclePassword); + exasolFactory.createVirtualSchemaBuilder(VIRTUAL_SCHEMA_JDBC).adapterScript(adapterScript) + .connectionDefinition(jdbcConnectionDefinition).dialectName("ORACLE") + .properties(Map.of("SCHEMA_NAME", SCHEMA_ORACLE)).build(); + exasolFactory.createVirtualSchemaBuilder(VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL).adapterScript(adapterScript) + .connectionDefinition(jdbcConnectionDefinition).dialectName("ORACLE") + .properties(Map.of("SCHEMA_NAME", SCHEMA_ORACLE)).properties(Map.of("SCHEMA_NAME", SCHEMA_ORACLE, + "oracle_cast_number_to_decimal_with_precision_and_scale", "36,1")) + .build(); + exasolFactory.createVirtualSchemaBuilder(VIRTUAL_SCHEMA_ORA).adapterScript(adapterScript) + .connectionDefinition(jdbcConnectionDefinition).dialectName("ORACLE").properties(Map.of("SCHEMA_NAME", + SCHEMA_ORACLE, "IMPORT_FROM_ORA", "true", "ORA_CONNECTION_NAME", ORA_CONNECTION_NAME)) + .build(); + exasolFactory.createVirtualSchemaBuilder(VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL).adapterScript(adapterScript) + .connectionDefinition(jdbcConnectionDefinition).dialectName("ORACLE") + .properties(Map.of("SCHEMA_NAME", SCHEMA_ORACLE, "IMPORT_FROM_ORA", "true", "ORA_CONNECTION_NAME", + ORA_CONNECTION_NAME, "oracle_cast_number_to_decimal_with_precision_and_scale", "36,1")) + .build(); + } + + private static void uploadInstantClientToBucket() + throws InterruptedException, BucketAccessException, TimeoutException { + final Bucket bucket = exasolContainer.getDefaultBucket(); + final String instantClientName = getPropertyFromFile(RESOURCES_FOLDER_DIALECT_NAME, "instant.client.name"); + final String instantClientPath = getPropertyFromFile(RESOURCES_FOLDER_DIALECT_NAME, "instant.client.path"); + bucket.uploadFile(Path.of(instantClientPath, instantClientName), "drivers/oracle/" + instantClientName); + } + + private static void createOracleUser(final Statement statementOracle) throws SQLException { + final String username = SCHEMA_ORACLE; + final String password = SCHEMA_ORACLE; + statementOracle.execute("CREATE USER " + username + " IDENTIFIED BY " + password); + statementOracle.execute("GRANT CONNECT TO " + username); + statementOracle.execute("GRANT CREATE SESSION TO " + username); + statementOracle.execute("GRANT UNLIMITED TABLESPACE TO " + username); + } + + private static void createOracleTableAllDataTypes(final Statement statementOracle) throws SQLException { + final String qualifiedTableName = SCHEMA_ORACLE + "." + TABLE_ORACLE_ALL_DATA_TYPES; + statementOracle.execute("CREATE TABLE " + qualifiedTableName + " (" // + + "c1 char(50), " // + + "c2 nchar(50), " // + + "c3 varchar2(50), " // + + "c4 nvarchar2(50), " // + + "c5 number, " // + + "c_number36 number(36), " // + + "c6 number(38), " // + + "c7 number(10,5), " // + + "c_binfloat binary_float, " // + + "c_bindouble binary_double, " // + + "c10 date, " // + + "c11 timestamp(3), " // + + "c12 timestamp, " // + + "c13 timestamp(9), " // + + "c14 timestamp with time zone, " // + + "c15 timestamp with local time zone, " // + + "c16 interval year to month, " // + + "c17 interval day to second, " // + + "c18 blob, " // + + "c19 clob, " // + + "c20 nclob, " // + + "c_float float, " // + + "c_float126 float(126), " // + + "c_long long " // + + ")"); + statementOracle.execute("INSERT INTO " + qualifiedTableName + " VALUES (" // + + "'aaaaaaaaaaaaaaaaaaaa', " // + + "'bbbbbbbbbbbbbbbbbbbb', " // + + "'cccccccccccccccccccc', " // + + "'dddddddddddddddddddd', " // + + "123456789012345678901234567890123456, " // C5 + + "123456789012345678901234567890123456, " // c_number36 + + "12345678901234567890123456789012345678, " // C6 + + "12345.12345, " // C7 + + "1234.1241723, " // C_BINFLOAT + + "1234987.120871234, " // C_BINDOUBLE + + "TO_DATE('2016-08-19', 'YYYY-MM-DD'), " // C10 + + "TO_TIMESTAMP('2013-03-11 17:30:15.123', 'YYYY-MM-DD HH24:MI:SS.FF'), " // C11 + + "TO_TIMESTAMP('2013-03-11 17:30:15.123456', 'YYYY-MM-DD HH24:MI:SS.FF'), " // C12 + + "TO_TIMESTAMP('2013-03-11 17:30:15.123456789', 'YYYY-MM-DD HH24:MI:SS.FF'), " // C13 + + "TO_TIMESTAMP_TZ('2016-08-19 11:28:05 -08:00', 'YYYY-MM-DD HH24:MI:SS TZH:TZM'), " // C14 + + "TO_TIMESTAMP_TZ('2018-04-30 10:00:05 -08:00', 'YYYY-MM-DD HH24:MI:SS TZH:TZM'), " // C15 + + "'54-2', " // C16 + + "'1 11:12:10.123', " // C17 + + "'0102030405060708090a0b0c0d0e0f', " // C18 + + "'0987asdlfkjq2222qawsf;lkja09ed8q2w;43lkrjasdf09uqaw43lkjra0-98sf[iqjw4,mfas[dpiuj[qa09w44', " // C19 + + "'0987asdlfkjq2222qawsf;lkja09ed8q2w;43lkrjasdf09uqaw43lkjra0-98sf[iqjw4,mfas[dpiuj[qa09w44', " // C20 + + "12345.01982348239, " // c_float + + "12345678.01234567901234567890123456789, " // c_float126 + + "'test long 123' " // long + + ")"); + statementOracle.execute("INSERT INTO " + qualifiedTableName + "(c3, c5, c7, c_binfloat, c17) VALUES (" // + + "'cccccccccccccccccccc', " // C3 + + "1234567890.123456789, " // C5 + + "12355.12345, " // C7 + + "123.12345687987654321, " // C_BINFLOAT + + "'2 02:03:04.123456' " // C17 + + ")"); + } + + private static void createOracleTableNumberHandling(final Statement statementOracle) throws SQLException { + final String qualifiedTableName = SCHEMA_ORACLE + "." + TABLE_ORACLE_NUMBER_HANDLING; + statementOracle.execute("CREATE TABLE " + qualifiedTableName + " (" // + + "a number, " // + + "b number(38, 10), " // + + "c number(36,2) " // + + ")"); + statementOracle.execute("INSERT INTO " + qualifiedTableName + " VALUES (" // + + "1234567890123456789012345678901234.56, " // + + "1234567890123456789012345678.9012345678, " // + + "1234567890123456789012345678901234.56 " // + + ")"); + } + + private static void createOracleTableTimestamps(final Statement statementOracle) throws SQLException { + final String qualifiedTableName = SCHEMA_ORACLE + "." + TABLE_ORACLE_TIMESTAMPS; + statementOracle.execute("CREATE TABLE " + qualifiedTableName + " (" // + + "a timestamp, " // + + "b timestamp with local time zone, " // + + "c timestamp with time zone " // + + ")"); + statementOracle.execute("INSERT INTO " + qualifiedTableName + " VALUES (" // + + "timestamp '2018-01-01 11:00:00', " // + + "timestamp '2018-01-01 11:00:00 +01:00', " // + + "timestamp '2018-01-01 11:00:00 +01:00' " // + + ")"); + } + + private static AdapterScript createAdapterScript(final String driverName, final ExasolSchema schema) { + final String content = "%scriptclass com.exasol.adapter.RequestDispatcher;\n" // + + "%jar /buckets/bfsdefault/default/" + VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION + ";\n" // + + "%jar /buckets/bfsdefault/default/drivers/jdbc/" + driverName + ";\n"; + return schema.createAdapterScript(ADAPTER_SCRIPT_EXASOL, JAVA, content); + } + + private static ConnectionDefinition createOraConnection(final ExasolObjectFactory exasolFactory, + final Integer mappedPort, final String oracleUsername, final String oraclePassword) { + final String oraConnectionString = "(DESCRIPTION =" // + + "(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)" // + + "(HOST = " + DOCKER_IP_ADDRESS + " )" // + + "(PORT = " + mappedPort + ")))" // + + "(CONNECT_DATA = (SERVER = DEDICATED)" // + + "(SERVICE_NAME = xe)))"; + return exasolFactory.createConnectionDefinition(ORA_CONNECTION_NAME, oraConnectionString, oracleUsername, + oraclePassword); + } + + private static void uploadDriverToBucket(final String driverName, final String resourcesDialectName, + final Bucket bucket) throws InterruptedException, BucketAccessException, TimeoutException { + final Path pathToSettingsFile = Path.of("src", "test", "resources", "integration", "driver", + resourcesDialectName, JDBC_DRIVER_CONFIGURATION_FILE_NAME); + bucket.uploadFile(PATH_TO_VIRTUAL_SCHEMAS_JAR, VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION); + bucket.uploadFile(pathToSettingsFile, "drivers/jdbc/" + JDBC_DRIVER_CONFIGURATION_FILE_NAME); + final String driverPath = getPropertyFromFile(resourcesDialectName, "driver.path"); + bucket.uploadFile(Path.of(driverPath, driverName), "drivers/jdbc/" + driverName); + } + + private static String getPathToPropertyFile(final String resourcesDialectName) { + return "src/test/resources/integration/driver/" + resourcesDialectName + "/" + resourcesDialectName + + ".properties"; + } + + private static void uploadVsJarToBucket(final Bucket bucket) + throws InterruptedException, BucketAccessException, TimeoutException { + bucket.uploadFile(PATH_TO_VIRTUAL_SCHEMAS_JAR, VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION); + } + + private static String getPropertyFromFile(final String resourcesDialectName, final String propertyName) { + final String pathToPropertyFile = getPathToPropertyFile(resourcesDialectName); + try (final InputStream inputStream = new FileInputStream(pathToPropertyFile)) { + final Properties properties = new Properties(); + properties.load(inputStream); + return properties.getProperty(propertyName); + } catch (final IOException e) { + throw new IllegalArgumentException( + "Cannot access the properties file or read from it. Check if the path spelling is correct" + + " and if the file exists."); + } + } + + private static void createTestTablesForJoinTests(final Connection connection, final String schemaName) + throws SQLException { + try (final Statement statement = connection.createStatement()) { + statement.execute("CREATE TABLE " + schemaName + "." + TABLE_JOIN_1 + "(x INT, y VARCHAR(100))"); + statement.execute("INSERT INTO " + schemaName + "." + TABLE_JOIN_1 + " VALUES (1,'aaa')"); + statement.execute("INSERT INTO " + schemaName + "." + TABLE_JOIN_1 + " VALUES (2,'bbb')"); + statement.execute("CREATE TABLE " + schemaName + "." + TABLE_JOIN_2 + "(x INT, y VARCHAR(100))"); + statement.execute("INSERT INTO " + schemaName + "." + TABLE_JOIN_2 + " VALUES (2,'bbb')"); + statement.execute("INSERT INTO " + schemaName + "." + TABLE_JOIN_2 + " VALUES (3,'ccc')"); + } + } + + private ResultSet getExpectedResultSet(final List expectedColumns, final List expectedRows) + throws SQLException { + final Connection connection = getExasolConnection(); + try (final Statement statement = connection.createStatement()) { + final String expectedValues = expectedRows.stream().map(row -> "(" + row + ")") + .collect(Collectors.joining(",")); + final String qualifiedExpectedTableName = SCHEMA_EXASOL + ".EXPECTED"; + statement.execute("CREATE OR REPLACE TABLE " + qualifiedExpectedTableName + "(" + + String.join(", ", expectedColumns) + ")"); + statement.execute("INSERT INTO " + qualifiedExpectedTableName + " VALUES" + expectedValues); + return statement.executeQuery("SELECT * FROM " + qualifiedExpectedTableName); + } + } + + private ResultSet getActualResultSet(final String query) throws SQLException { + final Connection connection = getExasolConnection(); + try (final Statement statement = connection.createStatement()) { + return statement.executeQuery(query); + } + } + + private Connection getExasolConnection() throws SQLException { + return exasolContainer.createConnection(""); + } + + @Test + void testCountAll() throws SQLException { + final String qualifiedTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_NUMBER_HANDLING; + final String query = "SELECT COUNT(*) FROM " + qualifiedTableName; + final ResultSet expected = getExpectedResultSet(List.of("x DECIMAL(36,0)"), // + List.of("1")); + assertThat(getActualResultSet(query), matchesResultSet(expected)); + } + + @Nested + @DisplayName("Number handling test") + class numberHandlingTest { + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL, VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL }) + void testNumberToDecimalThrowsException(final String virtualSchemaName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT c5 FROM " + qualifiedTableName; + final SQLException exception = assertThrows(SQLException.class, () -> statementExasol.execute(query)); + assertThat(exception.getMessage(), + containsString("value larger than specified precision allowed for this column")); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL, VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL }) + void testNumber36ToDecimal(final String virtualSchemaName) throws SQLException { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT c_number36 FROM " + qualifiedTableName; + assertAll( + () -> assertExpressionExecutionBigDecimalResult(query, + new BigDecimal("123456789012345678901234567890123456")), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C_NUMBER36"), + equalTo("DECIMAL(36,0)"))); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL, VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL }) + void testNumber38ToDecimalThrowsException(final String virtualSchemaName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT c6 FROM " + qualifiedTableName; + final SQLException exception = assertThrows(SQLException.class, () -> statementExasol.execute(query)); + assertThat(exception.getMessage(), + containsString("value larger than specified precision allowed for this column")); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL, VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL }) + void testNumber10S5ToDecimal(final String virtualSchemaName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C7 FROM " + qualifiedTableName; + assertAll(() -> assertExpressionExecutionBigDecimalResult(query, new BigDecimal("12345.12345")), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C7"), equalTo("DECIMAL(10,5)"))); + } + + @Test + void testSelectAllColsNumberFromJDBC() throws SQLException { + final String qualifiedTableNameActual = VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL + "." + + TABLE_ORACLE_NUMBER_HANDLING; + final ResultSet expected = getExpectedResultSet("(A DECIMAL(36,1), B DECIMAL(36,1), C DECIMAL(36,2))", + "(1234567890123456789012345678901234.6, 1234567890123456789012345678.9, 1234567890123456789012345678901234.56)"); + assertThat(statementExasol.executeQuery("SELECT * FROM " + qualifiedTableNameActual), // + matchesResultSet(expected)); + } + + @Test + void testSelectAllColsNumberFromOra() throws SQLException { + final String qualifiedTableNameActual = VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL + "." + + TABLE_ORACLE_NUMBER_HANDLING; + final ResultSet expected = getExpectedResultSet("(A VARCHAR(100), B VARCHAR(100), C VARCHAR(100))", + "('12.3456789012345678901234567890123460E32', '12.3456789012345678901234567890E26', '12.3456789012345678901234567890123456E32')"); + assertThat(statementExasol.executeQuery("SELECT * FROM " + qualifiedTableNameActual), // + matchesResultSet(expected)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL, VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL }) + void testNumberDataTypes(final String virtualSchemaName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_NUMBER_HANDLING; + assertAll(() -> assertThat(getColumnTypesOfTable(qualifiedTableName, "A"), equalTo("DECIMAL(36,1)")), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "B"), equalTo("DECIMAL(36,1)")), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C"), equalTo("DECIMAL(36,2)"))); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL, VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL }) + void testSelectOneNumberColumn(final String virtualSchemaName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_NUMBER_HANDLING; + assertAll( + () -> assertExpressionExecutionBigDecimalResult("SELECT A FROM " + qualifiedTableName, + new BigDecimal("1234567890123456789012345678901234.6")), + () -> assertExpressionExecutionBigDecimalResult("SELECT B FROM " + qualifiedTableName, + new BigDecimal("1234567890123456789012345678.9")), + () -> assertExpressionExecutionBigDecimalResult("SELECT C FROM " + qualifiedTableName, + new BigDecimal("1234567890123456789012345678901234.56"))); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC_NUMBER_TO_DECIMAL, VIRTUAL_SCHEMA_ORA_NUMBER_TO_DECIMAL }) + void testSelectAllNumberColumnsExplainVirtual(final String virtualSchemaName) throws SQLException { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_NUMBER_HANDLING; + assertExplainVirtual("SELECT * FROM " + qualifiedTableName, + "SELECT CAST(\"A\" AS DECIMAL(36,1)), CAST(\"B\" AS DECIMAL(36,1)), \"C\""); + } + } + + private String getColumnTypesOfTable(final String tableName, final String columnName) throws SQLException { + final ResultSet result = statementExasol.executeQuery("DESCRIBE " + tableName); + while (result.next()) { + if (result.getString("COLUMN_NAME").toUpperCase().equals(columnName)) { + return result.getString("SQL_TYPE").toUpperCase(); + } + } + throw new IllegalArgumentException("Type for column " + columnName + " not found"); + } + + private ResultSet getExpectedResultSet(final String expectedColumnTypes, final String expectedValues) + throws SQLException { + final String qualifiedExpectedTableName = SCHEMA_EXASOL + "." + "EXPECTED"; + statementExasol.execute("CREATE OR REPLACE TABLE " + qualifiedExpectedTableName + expectedColumnTypes); + statementExasol.execute("INSERT INTO " + qualifiedExpectedTableName + " VALUES" + expectedValues); + return statementExasol.executeQuery("SELECT * FROM " + qualifiedExpectedTableName); + } + + private void assertExpressionExecutionBigDecimalResult(final String query, final BigDecimal expectedValue) + throws SQLException { + final ResultSet result = statementExasol.executeQuery(query); + result.next(); + final BigDecimal actualResult = result.getBigDecimal(1); + assertThat(actualResult.stripTrailingZeros(), equalTo(expectedValue)); + } + + private void assertExplainVirtual(final String query, final String expected) throws SQLException { + final ResultSet explainVirtual = statementExasol.executeQuery("EXPLAIN VIRTUAL " + query); + explainVirtual.next(); + final String explainVirtualStringActual = explainVirtual.getString("PUSHDOWN_SQL"); + assertThat(explainVirtualStringActual, containsString(expected)); + } + + @Nested + @DisplayName("Join test") + class JoinTest { + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testInnerJoin(final String virtualSchema) throws SQLException { + final String query = "SELECT * FROM " + virtualSchema + "." + TABLE_JOIN_1 + " a INNER JOIN " + + virtualSchema + "." + TABLE_JOIN_2 + " b ON a.x=b.x"; + final ResultSet expected = getExpectedResultSet( + List.of("x VARCHAR(100)", "y VARCHAR(100)", "a VARCHAR(100)", "b VARCHAR(100)"), // + List.of("'2','bbb', '2','bbb'")); + assertThat(getActualResultSet(query), matchesResultSet(expected)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testInnerJoinWithProjection(final String virtualSchemaName) throws SQLException { + final String qualifiedJoinTableName1 = virtualSchemaName + "." + TABLE_JOIN_1; + final String qualifiedJoinTableName2 = virtualSchemaName + "." + TABLE_JOIN_2; + final String query = "SELECT b.y || " + qualifiedJoinTableName1 + ".y FROM " + qualifiedJoinTableName1 + + " INNER JOIN " + qualifiedJoinTableName2 + " b ON " + qualifiedJoinTableName1 + ".x=b.x"; + final ResultSet expected = getExpectedResultSet(List.of("y VARCHAR(100)"), // + List.of("'bbbbbb'")); + assertThat(getActualResultSet(query), matchesResultSet(expected)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testLeftJoin(final String virtualSchemaName) throws SQLException { + final String qualifiedJoinTableName1 = virtualSchemaName + "." + TABLE_JOIN_1; + final String qualifiedJoinTableName2 = virtualSchemaName + "." + TABLE_JOIN_2; + final String query = "SELECT * FROM " + qualifiedJoinTableName1 + " a LEFT OUTER JOIN " + + qualifiedJoinTableName2 + " b ON a.x=b.x ORDER BY a.x"; + final ResultSet expected = getExpectedResultSet( + List.of("x VARCHAR(100)", "y VARCHAR(100)", "a VARCHAR(100)", "b VARCHAR(100)"), // + List.of("'1', 'aaa', null, null", // + "'2', 'bbb', '2', 'bbb'")); + assertThat(getActualResultSet(query), matchesResultSet(expected)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testRightJoin(final String virtualSchemaName) throws SQLException { + final String qualifiedJoinTableName1 = virtualSchemaName + "." + TABLE_JOIN_1; + final String qualifiedJoinTableName2 = virtualSchemaName + "." + TABLE_JOIN_2; + final String query = "SELECT * FROM " + qualifiedJoinTableName1 + " a RIGHT OUTER JOIN " + + qualifiedJoinTableName2 + " b ON a.x=b.x ORDER BY a.x"; + final ResultSet expected = getExpectedResultSet( + List.of("x VARCHAR(100)", "y VARCHAR(100)", "a VARCHAR(100)", "b VARCHAR(100)"), // + List.of("'2', 'bbb', '2', 'bbb'", // + "null, null, '3', 'ccc'")); + assertThat(getActualResultSet(query), matchesResultSet(expected)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testFullOuterJoin(final String virtualSchemaName) throws SQLException { + final String qualifiedJoinTableName1 = virtualSchemaName + "." + TABLE_JOIN_1; + final String qualifiedJoinTableName2 = virtualSchemaName + "." + TABLE_JOIN_2; + final String query = "SELECT * FROM " + qualifiedJoinTableName1 + " a FULL OUTER JOIN " + + qualifiedJoinTableName2 + " b ON a.x=b.x ORDER BY a.x"; + final ResultSet expected = getExpectedResultSet( + List.of("x VARCHAR(100)", "y VARCHAR(100)", "a VARCHAR(100)", "b VARCHAR(100)"), // + List.of("1, 'aaa', null, null", // + "'2', 'bbb', '2', 'bbb'", // + "null, null, '3', 'ccc'")); + assertThat(getActualResultSet(query), matchesResultSet(expected)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testRightJoinWithComplexCondition(final String virtualSchemaName) throws SQLException { + final String qualifiedJoinTableName1 = virtualSchemaName + "." + TABLE_JOIN_1; + final String qualifiedJoinTableName2 = virtualSchemaName + "." + TABLE_JOIN_2; + final String query = "SELECT * FROM " + qualifiedJoinTableName1 + " a RIGHT OUTER JOIN " + + qualifiedJoinTableName2 + " b ON a.x||a.y=b.x||b.y ORDER BY a.x"; + final ResultSet expected = getExpectedResultSet( + List.of("x VARCHAR(100)", "y VARCHAR(100)", "a VARCHAR(100)", "b VARCHAR(100)"), // + List.of("'2', 'bbb', '2', 'bbb'", // + "null, null, '3', 'ccc'")); + assertThat(getActualResultSet(query), matchesResultSet(expected)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testFullOuterJoinWithComplexCondition(final String virtualSchemaName) throws SQLException { + final String qualifiedJoinTableName1 = virtualSchemaName + "." + TABLE_JOIN_1; + final String qualifiedJoinTableName2 = virtualSchemaName + "." + TABLE_JOIN_2; + final String query = "SELECT * FROM " + qualifiedJoinTableName1 + " a FULL OUTER JOIN " + + qualifiedJoinTableName2 + " b ON a.x-b.x=0 ORDER BY a.x"; + final ResultSet expected = getExpectedResultSet( + List.of("x VARCHAR(100)", "y VARCHAR(100)", "a VARCHAR(100)", "b VARCHAR(100)"), // + List.of("1, 'aaa', null, null", // + "'2', 'bbb', '2', 'bbb'", // + "null, null, '3', 'ccc'")); + assertThat(getActualResultSet(query), matchesResultSet(expected)); + } + } + + @Nested + @DisplayName("Datatype tests") + class DatatypeTest { + @ParameterizedTest + @CsvSource(value = { "VIRTUAL_SCHEMA_JDBC, 12346.12345", // + "VIRTUAL_SCHEMA_ORA, 01.2346123450E4" }) + void testSelectExpression(final String virtualSchemaName, final String expectedColumnValue) { + final String qualifiedTableNameActual = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C7 + 1 FROM " + qualifiedTableNameActual + " ORDER BY 1"; + final String expectedExplainVirtual = "SELECT CAST((\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C7\" + 1) AS FLOAT) FROM \"" + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\" ORDER BY (\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\" + 1)"; + assertAll(() -> assertExpressionExecutionStringResult(query, expectedColumnValue), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + private void assertExpressionExecutionStringResult(final String query, final String expected) + throws SQLException { + final ResultSet result = statementExasol.executeQuery(query); + result.next(); + final String actual = result.getString(1); + MatcherAssert.assertThat(actual, containsString(expected)); + } + + @ParameterizedTest + @CsvSource(value = { "VIRTUAL_SCHEMA_JDBC, 12355.12345", // + "VIRTUAL_SCHEMA_ORA, 01.2355123450E4" }) + void testFilterExpression(final String virtualSchemaName, final String expectedColumnValue) { + final String qualifiedTableNameActual = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C7 FROM " + qualifiedTableNameActual + " WHERE C7 > 12346"; + final String expectedExplainVirtual = "SELECT \"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\" FROM \"" + + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\" WHERE 12346 < \"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\""; + assertAll(() -> assertExpressionExecutionStringResult(query, expectedColumnValue), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @ParameterizedTest + @CsvSource(value = { "VIRTUAL_SCHEMA_JDBC, 12345.12345", // + "VIRTUAL_SCHEMA_ORA, 01.2345123450E4" }) + void testAggregateSingleGroup(final String virtualSchemaName, final String expectedColumnValue) { + final String qualifiedTableNameActual = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT min(C7) FROM " + qualifiedTableNameActual; + final String expectedExplainVirtual = "SELECT CAST(MIN(\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C7\") AS FLOAT) FROM \"" + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\""; + assertAll(() -> assertExpressionExecutionStringResult(query, expectedColumnValue), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @Test + void testAggregateGroupByColumnJdbc() throws SQLException { + final String qualifiedActualTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C5, min(C7) FROM " + qualifiedActualTableName + " GROUP BY C5 ORDER BY 1 DESC"; + final ResultSet expected = getExpectedResultSet("(A VARCHAR(100), B VARCHAR(100))", + "('123456789012345678901234567890123456', '12345.12345')," // + + "('1234567890.123456789', '12355.12345')"); + final ResultSet actual = statementExasol.executeQuery(query); + final String expectedExplainVirtual = "SELECT TO_CHAR(\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C5\"), CAST(MIN(\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\") AS FLOAT) FROM \"" + + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\" GROUP BY \"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C5\" ORDER BY \"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C5\" DESC"; + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @Test + void testAggregateGroupByExpressionOra() throws SQLException { + final String qualifiedActualTableName = VIRTUAL_SCHEMA_ORA + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C5 + 1, min(C7) FROM " + qualifiedActualTableName + + " GROUP BY C5 + 1 ORDER BY 1 DESC"; + final ResultSet expected = getExpectedResultSet("(A VARCHAR(100), B VARCHAR(100))", + "('12.3456789012345678901234567890123457E34', '01.2345123450E4')," // + + "('12.345678911234567890E8', '01.2355123450E4')"); + final ResultSet actual = statementExasol.executeQuery(query); + final String expectedExplainVirtual = "SELECT CAST((\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C5\" + 1) AS FLOAT), CAST(MIN(\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C7\") AS FLOAT) FROM \"" + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\" GROUP BY (\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C5\" + 1) ORDER BY (\"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C5\" + 1) DESC"; + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @Test + void testAggregateGroupByTuple() throws SQLException { + final String qualifiedActualTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C_NUMBER36, C5, min(C7) FROM " + qualifiedActualTableName + + " GROUP BY C_NUMBER36, C5 ORDER BY C5 DESC"; + final ResultSet expected = getExpectedResultSet("(A DECIMAL(36,0), B VARCHAR(100), C VARCHAR(100))", + "(123456789012345678901234567890123456, '123456789012345678901234567890123456', '12345.12345')," // + + "(null, '1234567890.123456789', '12355.12345')"); + final ResultSet actual = statementExasol.executeQuery(query); + final String expectedExplainVirtual = "SELECT \"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C_NUMBER36\", TO_CHAR(\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C5\"), CAST(MIN(\"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\") AS FLOAT) FROM \"" + SCHEMA_ORACLE + "\".\"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\" GROUP BY \"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C5\", \"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C_NUMBER36\" ORDER BY \"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C5\" DESC"; + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @Test + void testAggregateHaving() throws SQLException { + final String qualifiedActualTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C5, min(C7) FROM " + qualifiedActualTableName + + " GROUP BY C5 HAVING MIN(C7) > 12350"; + final ResultSet expected = getExpectedResultSet("(A VARCHAR(100), B VARCHAR(100))", + "('1234567890.123456789', '12355.12345')"); + final ResultSet actual = statementExasol.executeQuery(query); + final String expectedExplainVirtual = "SELECT TO_CHAR(\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C5\"), CAST(MIN(\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\") AS FLOAT) FROM \"" + + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\" GROUP BY \"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C5\" HAVING 12350 < MIN(\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C7\")"; + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testOrderByColumn(final String virtualSchemaName) { + final String qualifiedTableNameActual = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C1 FROM " + qualifiedTableNameActual + " ORDER BY C1 DESC NULLS LAST"; + final String expectedExplainVirtual = "SELECT \"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C1\" FROM \"" + + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\" ORDER BY \"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C1\" DESC NULLS LAST"; + assertAll(() -> assertExpressionExecutionStringResult(query, "aaaaaaaaaaaaaaaaaaaa"), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @Test + void testOrderByExpressionJdbc() throws SQLException { + final String qualifiedActualTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C7 FROM " + qualifiedActualTableName + " ORDER BY ABS(C7) DESC NULLS FIRST"; + final ResultSet expected = getExpectedResultSet("(A DECIMAL(36,5))", "(12355.12345), (12345.12345)"); + final ResultSet actual = statementExasol.executeQuery(query); + final String expectedExplainVirtual = "SELECT \"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\" FROM \"" + + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\" ORDER BY ABS(\"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\") DESC"; + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @Test + void testLimit() throws SQLException { + final String qualifiedActualTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C7 FROM " + qualifiedActualTableName + " ORDER BY C7 LIMIT 2"; + final ResultSet expected = getExpectedResultSet("(A DECIMAL(36,5))", "(12345.12345), (12355.12345)"); + final ResultSet actual = statementExasol.executeQuery(query); + final String expectedExplainVirtual = "SELECT LIMIT_SUBSELECT.* FROM ( SELECT \"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\" FROM \"" + SCHEMA_ORACLE + "\".\"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\" ORDER BY \"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C7\" ) LIMIT_SUBSELECT WHERE ROWNUM <= 2"; + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @Test + void testLimitOffset() throws SQLException { + final String qualifiedActualTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C7 FROM " + qualifiedActualTableName + " ORDER BY C7 LIMIT 1 OFFSET 1"; + final ResultSet expected = getExpectedResultSet("(A DECIMAL(36,5))", "(12355.12345)"); + final ResultSet actual = statementExasol.executeQuery(query); + final String expectedExplainVirtual = "SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( SELECT \"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C7\" AS c0 FROM \"" + SCHEMA_ORACLE + "\".\"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\" ORDER BY \"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C7\" ) LIMIT_SUBSELECT WHERE ROWNUM <= 2 ) WHERE ROWNUM_SUB > 1"; + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @ParameterizedTest + @CsvSource(value = { // + "VIRTUAL_SCHEMA_JDBC, C1, CHAR(50) ASCII, aaaaaaaaaaaaaaaaaaaa", // + "VIRTUAL_SCHEMA_JDBC, C2, CHAR(50) UTF8, bbbbbbbbbbbbbbbbbbbb", // + "VIRTUAL_SCHEMA_JDBC, C3, VARCHAR(50) ASCII, cccccccccccccccccccc", // + "VIRTUAL_SCHEMA_JDBC, C4, VARCHAR(50) UTF8, dddddddddddddddddddd", // + "VIRTUAL_SCHEMA_ORA, C1, CHAR(50) ASCII, aaaaaaaaaaaaaaaaaaaa", // + "VIRTUAL_SCHEMA_ORA, C2, CHAR(50) UTF8, bbbbbbbbbbbbbbbbbbbb", // + "VIRTUAL_SCHEMA_ORA, C3, VARCHAR(50) ASCII, cccccccccccccccccccc", // + "VIRTUAL_SCHEMA_ORA, C4, VARCHAR(50) UTF8, dddddddddddddddddddd" // + }) + void testCharactersColumns(final String virtualSchemaName, final String columnName, + final String expectedColumnType, final String expectedColumnValue) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT " + columnName + " FROM " + qualifiedTableName; + assertAll(() -> assertExpressionExecutionStringResult(query, expectedColumnValue), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, columnName), + equalTo(expectedColumnType))); + } + + @ParameterizedTest + @CsvSource(value = { // + "VIRTUAL_SCHEMA_JDBC, C18", // + "VIRTUAL_SCHEMA_JDBC, C19", // + "VIRTUAL_SCHEMA_JDBC, C20", // + "VIRTUAL_SCHEMA_ORA, C18", // + "VIRTUAL_SCHEMA_ORA, C19", // + "VIRTUAL_SCHEMA_ORA, C20",// + }) + void testBlobColumns(final String virtualSchemaName, final String columnName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT " + columnName + " FROM " + qualifiedTableName; + final SQLException exception = assertThrows(SQLException.class, () -> statementExasol.execute(query)); + assertThat(exception.getMessage(), startsWith("object " + columnName + " not found")); + } + + @ParameterizedTest + @CsvSource(value = { // + "VIRTUAL_SCHEMA_JDBC | C5 | VARCHAR(2000000) UTF8 | 123456789012345678901234567890123456", // + "VIRTUAL_SCHEMA_JDBC | C_NUMBER36 | DECIMAL(36,0) | 123456789012345678901234567890123456", // + "VIRTUAL_SCHEMA_JDBC | C6 | VARCHAR(2000000) UTF8 | 12345678901234567890123456789012345678", // + "VIRTUAL_SCHEMA_JDBC | C7 | DECIMAL(10,5) | 12345.12345", // + "VIRTUAL_SCHEMA_ORA | C5 | VARCHAR(2000000) UTF8 | 123456789012345678901234567890123456", // + "VIRTUAL_SCHEMA_ORA | C_NUMBER36 | DECIMAL(36,0) | 123456789012345678901234567890123456", // + "VIRTUAL_SCHEMA_ORA | C6 | VARCHAR(2000000) UTF8 | 12345678901234567890123456789012345678", // + "VIRTUAL_SCHEMA_ORA | C7 | DECIMAL(10,5) | 12345.12345" // + }, delimiter = '|') + void testNumberColumns(final String virtualSchemaName, final String columnName, final String expectedColumnType, + final String expectedValue) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT " + columnName + " FROM " + qualifiedTableName; + assertAll(() -> assertExpressionExecutionBigDecimalResult(query, new BigDecimal(expectedValue)), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, columnName), + equalTo(expectedColumnType))); + } + + @ParameterizedTest + @CsvSource(value = { // + "VIRTUAL_SCHEMA_JDBC | C_BINFLOAT | VARCHAR(2000000) UTF8 | 1234.1241723", // + "VIRTUAL_SCHEMA_JDBC | C_FLOAT | DOUBLE | 12345.01982348239", // + "VIRTUAL_SCHEMA_JDBC | C_FLOAT126 | DOUBLE | 12345678.01234567901234567890123456789", // + "VIRTUAL_SCHEMA_ORA | C_BINFLOAT | VARCHAR(2000000) UTF8 | 1234.1241723", // + "VIRTUAL_SCHEMA_ORA | C_FLOAT | DOUBLE | 12345.01982348239", // + "VIRTUAL_SCHEMA_ORA | C_FLOAT126 | DOUBLE | 12345678.01234567901234567890123456789" // + }, delimiter = '|') + void testFloatNumbers(final String virtualSchemaName, final String columnName, final String expectedColumnType, + final String expectedValue) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT " + columnName + " FROM " + qualifiedTableName; + assertAll(() -> assertExpressionExecutionFloatResult(query, Float.parseFloat(expectedValue)), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, columnName), + equalTo(expectedColumnType))); + } + + private void assertExpressionExecutionFloatResult(final String query, final float expected) + throws SQLException { + final ResultSet result = statementExasol.executeQuery(query); + result.next(); + final double actualResult = result.getFloat(1); + assertEquals(expected, actualResult, 0.000000001); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testBinaryDouble(final String virtualSchemaName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C_BINDOUBLE FROM " + qualifiedTableName; + assertAll(() -> assertExpressionExecutionDoubleResult(query, Double.parseDouble("1234987.120871234")), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C_BINDOUBLE"), + equalTo("VARCHAR(2000000) UTF8"))); + } + + private void assertExpressionExecutionDoubleResult(final String query, final double expected) + throws SQLException { + final ResultSet result = statementExasol.executeQuery(query); + result.next(); + final double actualResult = result.getDouble(1); + MatcherAssert.assertThat(actualResult, equalTo(expected)); + } + + @Test + void testLongJdbc() { + final String qualifiedTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C_LONG FROM " + qualifiedTableName; + assertAll(() -> assertExpressionExecutionStringResult(query, "test long 123"), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C_LONG"), + equalTo("VARCHAR(2000000) ASCII"))); + } + + @Test + void testLongOra() { + final String qualifiedTableName = VIRTUAL_SCHEMA_ORA + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C_LONG FROM " + qualifiedTableName; + final SQLException exception = assertThrows(SQLException.class, () -> statementExasol.execute(query)); + assertThat(exception.getMessage(), + containsString("Unknown Oracle OCI column data type (8) found for column 'C_LONG'")); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testDate(final String virtualSchemaName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C10 FROM " + qualifiedTableName; + assertAll(() -> assertExpressionExecutionDateResult(query, Date.valueOf("2016-08-19")), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C10"), equalTo("TIMESTAMP"))); + } + + private void assertExpressionExecutionDateResult(final String query, final Date expected) throws SQLException { + final ResultSet result = statementExasol.executeQuery(query); + result.next(); + final Date actualResult = result.getDate(1); + MatcherAssert.assertThat(actualResult, equalTo(expected)); + } + + @ParameterizedTest + @CsvSource(value = { // + "C11, 2013-03-11 17:30:15.123", // + "C12, 2013-03-11 17:30:15.123", // + "C13, 2013-03-11 17:30:15.123", // + "C14, 2016-08-19 11:28:05.0", // + "C15, 2018-04-30 19:00:05.0" // + }) + void testTimestampsJdbc(final String columnName, final String expectedColumnValue) { + final String qualifiedTableName = VIRTUAL_SCHEMA_JDBC + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT " + columnName + " FROM " + qualifiedTableName; + final String expectedExplainVirtual = "SELECT TO_TIMESTAMP(TO_CHAR(\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"" + columnName + "\", ''YYYY-MM-DD HH24:MI:SS.FF3''), " + + "''YYYY-MM-DD HH24:MI:SS.FF3'') FROM \"" + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\""; + assertAll(() -> assertExpressionExecutionTimestampResult(query, Timestamp.valueOf(expectedColumnValue)), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, columnName), equalTo("TIMESTAMP")), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @ParameterizedTest + @CsvSource(value = { // + "C11, 2013-03-11 17:30:15.123", // + "C12, 2013-03-11 17:30:15.123", // + "C13, 2013-03-11 17:30:15.123", // + "C14, 2016-08-19 19:28:05.0", // + "C15, 2018-04-30 18:00:05.0" // + }) + void testTimestampOra(final String columnName, final String expectedColumnValue) throws SQLException { + statementExasol.execute("ALTER SESSION SET TIME_ZONE = 'UTC'"); + final String qualifiedTableName = VIRTUAL_SCHEMA_ORA + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT " + columnName + " FROM " + qualifiedTableName; + final String expectedExplainVirtual = "SELECT \"" + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"" + columnName + + "\" FROM \"" + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\""; + assertAll(() -> assertExpressionExecutionTimestampResult(query, Timestamp.valueOf(expectedColumnValue)), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, columnName), equalTo("TIMESTAMP")), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + private void assertExpressionExecutionTimestampResult(final String query, final Timestamp expected) + throws SQLException { + final ResultSet result = statementExasol.executeQuery(query); + result.next(); + final Timestamp actual = result.getTimestamp(1); + MatcherAssert.assertThat(actual, equalTo(expected)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testIntervalYear(final String virtualSchemaName) { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C16 FROM " + qualifiedTableName; + final String expectedExplainVirtual = "SELECT TO_CHAR(\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C16\") FROM \"" + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\""; + assertAll(() -> assertExpressionExecutionStringResult(query, "+54-02"), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C16"), + equalTo("VARCHAR(2000000) UTF8")), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @ParameterizedTest + @ValueSource(strings = { VIRTUAL_SCHEMA_JDBC, VIRTUAL_SCHEMA_ORA }) + void testIntervalDay(final String virtualSchemaName) throws SQLException { + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_ALL_DATA_TYPES; + final String query = "SELECT C17 FROM " + qualifiedTableName + " ORDER BY 1"; + final ResultSet expected = getExpectedResultSet("(A VARCHAR(2000000) UTF8)", + "('+01 11:12:10.123000'), ('+02 02:03:04.123456')"); + final ResultSet actual = statementExasol.executeQuery(query); + final String expectedExplainVirtual = "SELECT TO_CHAR(\"" + TABLE_ORACLE_ALL_DATA_TYPES + + "\".\"C17\") FROM \"" + SCHEMA_ORACLE + "\".\"" + TABLE_ORACLE_ALL_DATA_TYPES + "\" ORDER BY \"" + + TABLE_ORACLE_ALL_DATA_TYPES + "\".\"C17\""; + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C17"), + equalTo("VARCHAR(2000000) UTF8")), + () -> assertExplainVirtual(query, expectedExplainVirtual)); + } + + @ParameterizedTest + @CsvSource(value = { + "VIRTUAL_SCHEMA_JDBC ! ('2018-01-01 11:00:00.0', '2018-01-01 11:00:00.0', '2018-01-01 11:00:00.000')", // + "VIRTUAL_SCHEMA_ORA ! ('2018-01-01 11:00:00.0', '2018-01-01 10:00:00.0', '2018-01-01 10:00:00.000')" }, // + delimiter = '!') + void testSelectAllTimestampColumns(final String virtualSchemaName, final String expectedColumnValue) + throws SQLException { + statementExasol.execute("ALTER SESSION SET TIME_ZONE = 'UTC'"); + final String qualifiedTableName = virtualSchemaName + "." + TABLE_ORACLE_TIMESTAMPS; + final String query = "SELECT * FROM " + qualifiedTableName; + final ResultSet expected = getExpectedResultSet("(A TIMESTAMP, B TIMESTAMP, C TIMESTAMP)", + expectedColumnValue); + final ResultSet actual = statementExasol.executeQuery(query); + assertAll(() -> assertThat(actual, matchesResultSet(expected)), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "A"), equalTo("TIMESTAMP")), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "B"), equalTo("TIMESTAMP")), + () -> assertThat(getColumnTypesOfTable(qualifiedTableName, "C"), equalTo("TIMESTAMP"))); + } + } +} diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java new file mode 100644 index 0000000..2dfeb3d --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java @@ -0,0 +1,166 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.AdapterProperties.*; +import static com.exasol.adapter.capabilities.AggregateFunctionCapability.*; +import static com.exasol.adapter.capabilities.LiteralCapability.*; +import static com.exasol.adapter.capabilities.MainCapability.*; +import static com.exasol.adapter.capabilities.PredicateCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.dialects.oracle.OracleProperties.ORACLE_IMPORT_PROPERTY; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.HashMap; +import java.util.Map; + +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.capabilities.Capabilities; +import com.exasol.adapter.dialects.*; +import com.exasol.adapter.jdbc.ConnectionFactory; + +@ExtendWith(MockitoExtension.class) +class OracleSqlDialectTest { + private OracleSqlDialect dialect; + @Mock + private ConnectionFactory connectionFactoryMock; + + @BeforeEach + void beforeEach() { + this.dialect = new OracleSqlDialect(this.connectionFactoryMock, AdapterProperties.emptyProperties()); + } + + @Test + void testGetCapabilities() { + final Capabilities capabilities = this.dialect.getCapabilities(); + assertAll( + () -> assertThat(capabilities.getMainCapabilities(), + containsInAnyOrder(SELECTLIST_PROJECTION, SELECTLIST_EXPRESSIONS, FILTER_EXPRESSIONS, + AGGREGATE_SINGLE_GROUP, AGGREGATE_GROUP_BY_COLUMN, AGGREGATE_GROUP_BY_EXPRESSION, + AGGREGATE_GROUP_BY_TUPLE, AGGREGATE_HAVING, ORDER_BY_COLUMN, ORDER_BY_EXPRESSION, LIMIT, + LIMIT_WITH_OFFSET, JOIN, JOIN_TYPE_INNER, JOIN_TYPE_LEFT_OUTER, JOIN_TYPE_RIGHT_OUTER, + JOIN_TYPE_FULL_OUTER, JOIN_CONDITION_EQUI)), // + () -> assertThat(capabilities.getLiteralCapabilities(), + containsInAnyOrder(NULL, DATE, TIMESTAMP, TIMESTAMP_UTC, DOUBLE, EXACTNUMERIC, STRING, + INTERVAL)), + () -> assertThat(capabilities.getPredicateCapabilities(), + containsInAnyOrder(AND, OR, NOT, EQUAL, NOTEQUAL, LESS, LESSEQUAL, LIKE, LIKE_ESCAPE, + REGEXP_LIKE, BETWEEN, IN_CONSTLIST, IS_NULL, IS_NOT_NULL)), + () -> assertThat(capabilities.getAggregateFunctionCapabilities(), + containsInAnyOrder(COUNT, COUNT_STAR, COUNT_DISTINCT, GROUP_CONCAT, GROUP_CONCAT_SEPARATOR, + GROUP_CONCAT_ORDER_BY, SUM, SUM_DISTINCT, MIN, MAX, AVG, AVG_DISTINCT, MEDIAN, + FIRST_VALUE, LAST_VALUE, STDDEV, STDDEV_DISTINCT, STDDEV_POP, STDDEV_SAMP, VARIANCE, + VARIANCE_DISTINCT, VAR_POP, VAR_SAMP)), // + () -> assertThat(capabilities.getScalarFunctionCapabilities(), + containsInAnyOrder(CEIL, DIV, FLOOR, SIGN, ADD, SUB, MULT, FLOAT_DIV, NEG, ABS, ACOS, ASIN, + ATAN, ATAN2, COS, COSH, COT, DEGREES, EXP, GREATEST, LEAST, LN, LOG, MOD, POWER, + RADIANS, SIN, SINH, SQRT, TAN, TANH, ASCII, CHR, INSTR, LENGTH, LOCATE, LOWER, LPAD, + LTRIM, REGEXP_INSTR, REGEXP_REPLACE, REGEXP_SUBSTR, REPEAT, REPLACE, REVERSE, RPAD, + RTRIM, SOUNDEX, SUBSTR, TRANSLATE, TRIM, UPPER, ADD_DAYS, ADD_HOURS, ADD_MINUTES, + ADD_MONTHS, ADD_SECONDS, ADD_WEEKS, ADD_YEARS, CURRENT_DATE, CURRENT_TIMESTAMP, + DBTIMEZONE, LOCALTIMESTAMP, NUMTODSINTERVAL, NUMTOYMINTERVAL, SESSIONTIMEZONE, SYSDATE, + SYSTIMESTAMP, CAST, TO_CHAR, TO_DATE, TO_DSINTERVAL, TO_YMINTERVAL, TO_NUMBER, + TO_TIMESTAMP, BIT_AND, BIT_TO_NUM, CASE, NULLIFZERO, ZEROIFNULL))); + } + + @CsvSource({ "FALSE, FALSE, JDBC", // + "TRUE, FALSE, LOCAL", // + "FALSE, TRUE, ORA" }) + @ParameterizedTest + void testGetImportTypeLocal(final String local, final String fromOracle, final String expectedImportType) { + final OracleSqlDialect dialect = new OracleSqlDialect(null, + new AdapterProperties(Map.of(IS_LOCAL_PROPERTY, local, // + ORACLE_IMPORT_PROPERTY, fromOracle))); + assertThat(dialect.getImportType().toString(), equalTo(expectedImportType)); + } + + @Test + void testCheckOracleSpecificPropertyConsistencyInvalidDialect() { + final SqlDialect sqlDialect = new OracleSqlDialect(null, + new AdapterProperties(Map.of(SQL_DIALECT_PROPERTY, "ORACLE", // + CONNECTION_NAME_PROPERTY, "MY_CONN", // + "ORACLE_CAST_NUMBER_TO_DECIMAL_WITH_PRECISION_AND_SCALE", "MY_CONN"))); + assertThrows(PropertyValidationException.class, sqlDialect::validateProperties); + } + + @Test + void testValidateCatalogProperty() { + final SqlDialect sqlDialect = new OracleSqlDialect(null, new AdapterProperties(Map.of( // + SQL_DIALECT_PROPERTY, "ORACLE", // + CONNECTION_NAME_PROPERTY, "MY_CONN", // + CATALOG_NAME_PROPERTY, "MY_CATALOG"))); + final PropertyValidationException exception = assertThrows(PropertyValidationException.class, + sqlDialect::validateProperties); + MatcherAssert.assertThat(exception.getMessage(), containsString( + "The dialect ORACLE does not support CATALOG_NAME property. Please, do not set the \"CATALOG_NAME\" property.")); + } + + @Test + void testValidateSchemaProperty() throws PropertyValidationException { + final AdapterProperties adapterProperties = new AdapterProperties(Map.of( // + SQL_DIALECT_PROPERTY, "ORACLE", // + CONNECTION_NAME_PROPERTY, "MY_CONN", // + SCHEMA_NAME_PROPERTY, "MY_SCHEMA")); + final SqlDialect sqlDialect = new OracleSqlDialect(null, adapterProperties); + sqlDialect.validateProperties(); + } + + @Test + void testQueryRewriterClassWithImportFromOra() { + this.dialect = new OracleSqlDialect(this.connectionFactoryMock, + this.getAdapaterPropertiesWithImportFromOracle()); + assertThat(this.dialect.createQueryRewriter(), instanceOf(OracleQueryRewriter.class)); + } + + private AdapterProperties getAdapaterPropertiesWithImportFromOracle() { + final Map properties = new HashMap<>(); + properties.put(OracleProperties.ORACLE_IMPORT_PROPERTY, Boolean.TRUE.toString()); + return new AdapterProperties(properties); + } + + @Test + void testQueryRewriterClassWhitImportInto() { + assertThat(this.dialect.createQueryRewriter(), instanceOf(ImportIntoQueryRewriter.class)); + } + + @CsvSource({ "tableName, \"tableName\"", // + "table 'Name, \"table 'Name\"" // + }) + @ParameterizedTest + void testApplyQuote(final String identifier, final String expected) { + assertThat(this.dialect.applyQuote(identifier), equalTo(expected)); + } + + @CsvSource({ "\"tableName\"", "table\"Name", "table name\"" }) + @ParameterizedTest + void testApplyQuoteThrowsException(final String identifier) { + assertThrows(AssertionError.class, () -> this.dialect.applyQuote(identifier)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); + } + + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitorTest.java new file mode 100644 index 0000000..3d73b79 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitorTest.java @@ -0,0 +1,471 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.dialects.VisitorAssertions.assertSqlNodeConvertedToAsterisk; +import static com.exasol.adapter.dialects.VisitorAssertions.assertSqlNodeConvertedToOne; +import static com.exasol.adapter.sql.AggregateFunction.*; +import static com.exasol.adapter.sql.ScalarFunction.*; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.adapternotes.ColumnAdapterNotes; +import com.exasol.adapter.adapternotes.ColumnAdapterNotesJsonConverter; +import com.exasol.adapter.dialects.*; +import com.exasol.adapter.metadata.*; +import com.exasol.adapter.sql.*; +import com.exasol.sql.SqlNormalizer; + +@ExtendWith(MockitoExtension.class) +class OracleSqlGenerationVisitorTest { + private OracleSqlGenerationVisitor visitor; + + @BeforeEach + void beforeEach() { + final SqlDialect dialect = new OracleSqlDialectFactory().createSqlDialect(null, + AdapterProperties.emptyProperties()); + final SqlGenerationContext context = new SqlGenerationContext("test_catalog", "test_schema", false); + this.visitor = new OracleSqlGenerationVisitor(dialect, context); + } + + @Test + void testGetAggregateFunctionsCast() { + assertThat(this.visitor.getAggregateFunctionsCast(), containsInAnyOrder(SUM, MIN, MAX, AVG, MEDIAN, FIRST_VALUE, + LAST_VALUE, STDDEV, STDDEV_POP, STDDEV_SAMP, VARIANCE, VAR_POP, VAR_SAMP)); + } + + @Test + void testGetScalarFunctionsCast() { + assertThat(this.visitor.getScalarFunctionsCast(), + containsInAnyOrder(ADD, SUB, MULT, FLOAT_DIV, NEG, ABS, ACOS, ASIN, ATAN, ATAN2, COS, COSH, COT, + DEGREES, EXP, GREATEST, LEAST, LN, LOG, MOD, POWER, RADIANS, SIN, SINH, SQRT, TAN, TANH)); + } + + @Test + void testVisitSqlStatementSelect() throws AdapterException { + final SqlSelectList selectList = SqlSelectList.createAnyValueSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table_name", tableMetadata); + final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) + .fromClause(fromClause).build(); + assertThat(this.visitor.visit(sqlStatementSelect), + equalTo("SELECT 1 FROM \"test_schema\".\"test_table_name\"")); + } + + @Test + void testVisitSqlStatementSelectWithLimitAnyValue() throws AdapterException { + final SqlSelectList selectList = SqlSelectList.createAnyValueSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlLimit limit = new SqlLimit(10, 3); + final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) + .fromClause(fromClause).limit(limit).build(); + + assertThat(this.visitor.visit(sqlStatementSelect), equalTo("1")); + } + + @Test + void testVisitSqlStatementSelectWithLimitSelectStar() throws AdapterException { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table_name", tableMetadata); + final SqlLimit limit = new SqlLimit(10, 3); + final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) + .fromClause(fromClause).limit(limit).build(); + assertThat(this.visitor.visit(sqlStatementSelect), + equalTo("SELECT FROM ( SELECT LIMIT_SUBSELECT.*, ROWNUM " + + "ROWNUM_SUB FROM ( SELECT FROM \"test_schema\".\"test_table_name\" ) LIMIT_SUBSELECT WHERE " + + "ROWNUM <= 13 ) WHERE ROWNUM_SUB > 3")); + } + + @Test + void testVisitSqlStatementSelectWithLimitRegularSelectList() throws AdapterException { + final SqlSelectList selectList = SqlSelectList + .createRegularSelectList(Arrays.asList(new SqlLiteralBool(true), new SqlLiteralString("string"))); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table_name", tableMetadata); + final SqlLimit limit = new SqlLimit(10, 3); + final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) + .fromClause(fromClause).limit(limit).build(); + assertThat(this.visitor.visit(sqlStatementSelect), + equalTo("SELECT c0, c1 FROM ( SELECT LIMIT_SUBSELECT.*, ROWNUM " + + "ROWNUM_SUB FROM ( SELECT true AS c0, 'string' AS c1 FROM \"test_schema\".\"test_table_name\"" + + " ) LIMIT_SUBSELECT WHERE ROWNUM <= 13 ) WHERE ROWNUM_SUB > 3")); + } + + @Test + void testVisitSqlStatementSelectWithLimitRegularSelectListWithoutOffset() throws AdapterException { + final SqlSelectList selectList = SqlSelectList + .createRegularSelectList(Arrays.asList(new SqlLiteralBool(true), new SqlLiteralString("string"))); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table_name", tableMetadata); + final SqlLimit limit = new SqlLimit(10); + final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) + .fromClause(fromClause).limit(limit).build(); + assertThat(this.visitor.visit(sqlStatementSelect), + equalTo("SELECT LIMIT_SUBSELECT.* FROM ( SELECT true, 'string' FROM \"test_schema\"" + + ".\"test_table_name\" ) LIMIT_SUBSELECT WHERE ROWNUM <= 10")); + } + + @Test + void testVisitSqlSelectListRequiresAnyColumn() throws AdapterException { + final SqlSelectList sqlSelectList = SqlSelectList.createAnyValueSelectList(); + assertSqlNodeConvertedToOne(sqlSelectList, this.visitor); + } + + @Test + void testVisitSqlSelectListSelectStar() throws AdapterException { + final SqlSelectList selectList = createSqlSelectStarListWithOneColumn( + "{\"jdbcDataType\":16, \"typeName\":\"BOOLEAN\"}", DataType.createBool()); + assertSqlNodeConvertedToAsterisk(selectList, this.visitor); + } + + @CsvSource({ "NUMBER", "INTERVAL", "BINARY_FLOAT", "BINARY_DOUBLE" }) + @ParameterizedTest + void testVisitSqlSelectListSelectStarCastToChar(final String dataType) throws AdapterException { + final SqlSelectList selectList = createSqlSelectStarListWithOneColumn( + "{\"jdbcDataType\":2, \"typeName\":\"" + dataType + "\"}", + DataType.createVarChar(50, DataType.ExaCharset.UTF8)); + assertThat(this.visitor.visit(selectList), equalTo("TO_CHAR(\"test_column\")")); + } + + private SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType) { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name("test_column").adapterNotes(adapterNotes).type(dataType).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause) + .build(); + selectList.setParent(sqlStatementSelect); + return selectList; + } + + @Test + void testVisitSqlSelectListSelectStarWithTimestamp() throws AdapterException { + final SqlSelectList selectList = createSqlSelectStarListWithOneColumn( + "{\"jdbcDataType\":2, \"typeName\":\"TIMESTAMP\"}", + DataType.createVarChar(50, DataType.ExaCharset.UTF8)); + assertThat(this.visitor.visit(selectList), equalTo( + "TO_TIMESTAMP(TO_CHAR(\"test_column\", 'YYYY-MM-DD HH24:MI:SS.FF3'), 'YYYY-MM-DD HH24:MI:SS.FF3')")); + } + + @Test + void testVisitSqlSelectListSelectStarNumberCastToDecimal() throws AdapterException { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name("test_column") + .adapterNotes("{\"jdbcDataType\":2, \"typeName\":\"NUMBER\"}").type(DataType.createDouble()).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode select = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause).build(); + selectList.setParent(select); + assertThat(this.visitor.visit(selectList), equalTo("CAST(\"test_column\" AS DECIMAL(0,0))")); + } + + @Test + void testVisitSqlSelectListRegularSelectList() throws AdapterException { + final SqlSelectList selectList = SqlSelectList + .createRegularSelectList(Arrays.asList(new SqlLiteralBool(true), new SqlLiteralString("string"))); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode select = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause).build(); + selectList.setParent(select); + assertThat(this.visitor.visit(selectList), equalTo("true, 'string'")); + } + + @Test + void testVisitSqlPredicateLikeRegexp() throws AdapterException { + final SqlPredicateLikeRegexp sqlSelectList = new SqlPredicateLikeRegexp(new SqlLiteralString("abcd"), + new SqlLiteralString("a_d")); + assertThat(this.visitor.visit(sqlSelectList), equalTo("REGEXP_LIKE('abcd', 'a_d')")); + } + + @Test + void testVisitSqlLiteralExactnumeric() { + final SqlLiteralExactnumeric literalExactnumeric = new SqlLiteralExactnumeric(new BigDecimal("5.9")); + assertThat(this.visitor.visit(literalExactnumeric), equalTo("5.9")); + } + + @Test + void testVisitSqlLiteralExactnumericInSelectList() { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final SqlLiteralExactnumeric literalExactnumeric = new SqlLiteralExactnumeric(new BigDecimal("5.9")); + literalExactnumeric.setParent(selectList); + assertThat(this.visitor.visit(literalExactnumeric), equalTo("TO_CHAR(5.9)")); + } + + @Test + void testVisitSqlLiteralDouble() { + final SqlLiteralDouble literalDouble = new SqlLiteralDouble(10.6); + assertThat(this.visitor.visit(literalDouble), equalTo("10.6")); + } + + @Test + void testVisitSqlLiteralDoubleInSelectList() { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final SqlLiteralDouble literalDouble = new SqlLiteralDouble(10.6); + literalDouble.setParent(selectList); + assertThat(this.visitor.visit(literalDouble), equalTo("TO_CHAR(10.6)")); + } + + @Test + void testVisitSqlFunctionAggregateGroupConcat() throws AdapterException { + final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat + .builder(new SqlLiteralDouble(10.5)).separator(new SqlLiteralString("'")).build(); + assertThat(this.visitor.visit(aggregateGroupConcat), + equalTo("LISTAGG(10.5, '''') WITHIN GROUP(ORDER BY 10.5)")); + } + + @Test + void testVisitSqlFunctionAggregateGroupConcatWithOrderBy() throws AdapterException { + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) + .build(); + final ColumnMetadata columnMetadata2 = ColumnMetadata.builder().name("test_column2") + .type(DataType.createDouble()).build(); + final List orderByArguments = List.of(new SqlColumn(1, columnMetadata), + new SqlColumn(2, columnMetadata2)); + final SqlOrderBy orderBy = new SqlOrderBy(orderByArguments, Stream.of(false, true).collect(Collectors.toList()), + Stream.of(false, true).collect(Collectors.toList())); + final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat + .builder(new SqlLiteralDouble(10.5)).separator(new SqlLiteralString("'")).orderBy(orderBy) + .distinct(true).build(); + assertThat(this.visitor.visit(aggregateGroupConcat), equalTo( + "LISTAGG(10.5, '''') WITHIN GROUP(ORDER BY \"test_column\" DESC NULLS FIRST, \"test_column2\")")); + } + + @Test + void testVisitSqlFunctionAggregate() throws AdapterException { + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) + .build(); + final List arguments = List.of(new SqlColumn(1, columnMetadata)); + final SqlFunctionAggregate sqlFunctionAggregate = new SqlFunctionAggregate(AVG, arguments, true); + assertThat(this.visitor.visit(sqlFunctionAggregate), equalTo("AVG(DISTINCT \"test_column\")")); + } + + @Test + void testVisitSqlFunctionAggregateInSelectList() throws AdapterException { + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) + .build(); + final List arguments = List.of(new SqlColumn(1, columnMetadata)); + final SqlFunctionAggregate sqlFunctionAggregate = new SqlFunctionAggregate(AVG, arguments, false); + final SqlNode selectList = SqlSelectList.createSelectStarSelectList(); + sqlFunctionAggregate.setParent(selectList); + assertThat(this.visitor.visit(sqlFunctionAggregate), equalTo("CAST(AVG(\"test_column\") AS FLOAT)")); + } + + @Test + void testVisitSqlFunctionScalarLocateThreeArguments() throws AdapterException { + final List arguments = new ArrayList<>(); + arguments.add(new SqlLiteralString("ab ")); + arguments.add(new SqlLiteralString("ab cdef")); + arguments.add(new SqlLiteralString("ab cdef rty")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(LOCATE, arguments); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("INSTR('ab cdef', 'ab ', 'ab cdef rty')")); + } + + @Test + void testVisitSqlFunctionScalarTrimOneArgument() throws AdapterException { + final List arguments = new ArrayList<>(); + arguments.add(new SqlLiteralString("test")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(TRIM, arguments); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("TRIM('test')")); + } + + @Test + void testVisitSqlFunctionScalarTrimOTwoArguments() throws AdapterException { + final List arguments = List.of(new SqlLiteralString("ab cdef"), new SqlLiteralString("ab")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(TRIM, arguments); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("TRIM('ab' FROM 'ab cdef')")); + } + + @CsvSource({ "ADD_DAYS, '10' DAY", // + "ADD_HOURS, '10' HOUR", // + "ADD_MINUTES, '10' MINUTE", // + "ADD_SECONDS, '10' SECOND", // + "ADD_YEARS, '10' YEAR", // + "ADD_WEEKS, '70' DAY" }) + @ParameterizedTest + void testVisitSqlFunctionScalarAddDateValues(final ScalarFunction scalarFunction, final String expected) + throws AdapterException { + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column") + .adapterNotes("{\"jdbcDataType\":93, " + "\"typeName\":\"TIMESTAMP\"}") + .type(DataType.createChar(20, DataType.ExaCharset.UTF8)).build(); + final List arguments = List.of(new SqlColumn(1, columnMetadata), + new SqlLiteralExactnumeric(new BigDecimal(10))); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, arguments); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("(\"test_column\" + INTERVAL " + expected + ")")); + } + + @CsvSource({ "CURRENT_DATE, CURRENT_DATE", // + "CURRENT_TIMESTAMP, CURRENT_TIMESTAMP", // + "DBTIMEZONE, DBTIMEZONE", // + "LOCALTIMESTAMP, LOCALTIMESTAMP", // + "SESSIONTIMEZONE, SESSIONTIMEZONE", // + "SYSDATE, TO_DATE(SYSDATE)", // + "SYSTIMESTAMP, SYSTIMESTAMP" }) + @ParameterizedTest + void testVisitSqlFunctionScalar1(final ScalarFunction scalarFunction, final String expected) + throws AdapterException { + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, null); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo(expected)); + } + + @CsvSource(value = { "BIT_AND : BITAND('left', 'right')", // + "BIT_TO_NUM : BIN_TO_NUM('left', 'right')", // + "NULLIFZERO : NULLIF('left', 0)", // + "ZEROIFNULL : NVL('left', 0)", // + "DIV : CAST(FLOOR('left' / 'right') AS NUMBER(36, 0))", // + "COT : (1 / TAN('left'))", // + "DEGREES : (('left') * 180 / ACOS(-1))", // + "RADIANS : (('left') * ACOS(-1) / 180)", // + "REPEAT : RPAD(TO_CHAR('left'), LENGTH('left') * ROUND('right'), 'left')", // + "REVERSE : REVERSE(TO_CHAR('left'))" // + }, delimiter = ':') + @ParameterizedTest + void testVisitSqlFunctionScalar2(final ScalarFunction scalarFunction, final String expected) + throws AdapterException { + final List arguments = List.of(new SqlLiteralString("left"), new SqlLiteralString("right")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, arguments); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo(expected)); + } + + @Test + void testVisitSqlFunctionScalarInSelectList() throws AdapterException { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List arguments = List.of(new SqlLiteralString("test"), new SqlLiteralString("")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(TANH, arguments); + sqlFunctionScalar.setParent(selectList); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("CAST(TANH('test', '') AS FLOAT)")); + } + + @Test + void testSqlGeneratorWithLimit() throws AdapterException { + final String expectedSql = "SELECT LIMIT_SUBSELECT.* FROM ( " + // + " SELECT \"USER_ID\", COUNT(\"URL\") " + // + " FROM \"test_schema\".\"CLICKS\"" + // + " WHERE 1 < \"USER_ID\"" + // + " GROUP BY \"USER_ID\"" + // + " HAVING 1 < COUNT(\"URL\")" + // + " ORDER BY \"USER_ID\" " + // + ") LIMIT_SUBSELECT WHERE ROWNUM <= 10"; // + final SqlStatementSelect testSqlNode = (SqlStatementSelect) getTestSqlNode(); + final String actualSql = this.visitor.visit(testSqlNode); + assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); + } + + @Test + void testSqlGeneratorWithLimitOffset() throws AdapterException { + final String expectedSql = "SELECT c0, c1 FROM (" + // + " SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( " + // + " SELECT \"USER_ID\" AS c0, COUNT(\"URL\") AS c1 " + // + " FROM \"test_schema\".\"CLICKS\"" + // + " WHERE 1 < \"USER_ID\"" + // + " GROUP BY \"USER_ID\"" + // + " HAVING 1 < COUNT(\"URL\")" + // + " ORDER BY \"USER_ID\"" + // + " ) LIMIT_SUBSELECT WHERE ROWNUM <= 15 " + // + ") WHERE ROWNUM_SUB > 5"; + final SqlStatementSelect testSqlNode = (SqlStatementSelect) getTestSqlNode(); + testSqlNode.getLimit().setOffset(5); + final String actualSql = this.visitor.visit(testSqlNode); + assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); + } + + @Test + void testSqlGeneratorWithSelectStarAndOffset() throws AdapterException { + SqlStatementSelect node = (SqlStatementSelect) getTestSqlNode(); + node.getLimit().setOffset(5); + node = SqlStatementSelect.builder().selectList(SqlSelectList.createSelectStarSelectList()) + .fromClause(node.getFromClause()).whereClause(node.getWhereClause()).groupBy(node.getGroupBy()) + .having(node.getHaving()).orderBy(node.getOrderBy()).limit(node.getLimit()).build(); + final String expectedSql = "SELECT c0, c1 FROM (" + // + " SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( " + // + " SELECT \"USER_ID\" AS c0, \"URL\" AS c1 " + // + " FROM \"test_schema\".\"CLICKS\"" + // + " WHERE 1 < \"USER_ID\"" + // + " GROUP BY \"USER_ID\"" + // + " HAVING 1 < COUNT(\"URL\")" + // + " ORDER BY \"USER_ID\"" + // + " ) LIMIT_SUBSELECT WHERE ROWNUM <= 15 " + // + ") WHERE ROWNUM_SUB > 5"; + final String actualSql = this.visitor.visit(node); + assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); + } + + private static SqlNode getTestSqlNode() { + return new DialectTestData().getTestSqlNode(); + } + + private static class DialectTestData { + private SqlNode getTestSqlNode() { + // SELECT USER_ID, count(URL) FROM CLICKS + // WHERE 1 < USER_ID + // GROUP BY USER_ID + // HAVING 1 < COUNT(URL) + // ORDER BY USER_ID + // LIMIT 10; + final TableMetadata clicksMeta = getClicksTableMetadata(); + final SqlTable fromClause = new SqlTable("CLICKS", clicksMeta); + final SqlSelectList selectList = SqlSelectList.createRegularSelectList( + List.of(new SqlColumn(0, clicksMeta.getColumns().get(0)), new SqlFunctionAggregate( + AggregateFunction.COUNT, List.of(new SqlColumn(1, clicksMeta.getColumns().get(1))), false))); + final SqlNode whereClause = new SqlPredicateLess(new SqlLiteralExactnumeric(BigDecimal.ONE), + new SqlColumn(0, clicksMeta.getColumns().get(0))); + final SqlExpressionList groupBy = new SqlGroupBy(List.of(new SqlColumn(0, clicksMeta.getColumns().get(0)))); + final SqlNode countUrl = new SqlFunctionAggregate(AggregateFunction.COUNT, + List.of(new SqlColumn(1, clicksMeta.getColumns().get(1))), false); + final SqlNode having = new SqlPredicateLess(new SqlLiteralExactnumeric(BigDecimal.ONE), countUrl); + final SqlOrderBy orderBy = new SqlOrderBy(List.of(new SqlColumn(0, clicksMeta.getColumns().get(0))), + List.of(true), List.of(true)); + final SqlLimit limit = new SqlLimit(10); + return SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause).whereClause(whereClause) + .groupBy(groupBy).having(having).orderBy(orderBy).limit(limit).build(); + } + + private TableMetadata getClicksTableMetadata() { + final ColumnAdapterNotesJsonConverter converter = ColumnAdapterNotesJsonConverter.getInstance(); + final List columns = new ArrayList<>(); + final ColumnAdapterNotes decimalAdapterNotes = ColumnAdapterNotes.builder() // + .jdbcDataType(3) // + .typeName("DECIMAL") // + .build(); + final ColumnAdapterNotes varcharAdapterNotes = ColumnAdapterNotes.builder() // + .jdbcDataType(12) // + .typeName("VARCHAR") // + .build(); + columns.add(ColumnMetadata.builder() // + .name("USER_ID") // + .adapterNotes(converter.convertToJson(decimalAdapterNotes)).type(DataType.createDecimal(18, 0)) // + .nullable(true) // + .identity(false) // + .defaultValue("") // + .comment("") // + .build()); + columns.add(ColumnMetadata.builder() // + .name("URL") // + .adapterNotes(converter.convertToJson(varcharAdapterNotes)) // + .type(DataType.createVarChar(10000, DataType.ExaCharset.UTF8)) // + .nullable(true) // + .identity(false) // + .defaultValue("") // + .comment("") // + .build()); + return new TableMetadata("CLICKS", "", columns, ""); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleTableMetadataReaderTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleTableMetadataReaderTest.java new file mode 100644 index 0000000..df6d799 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleTableMetadataReaderTest.java @@ -0,0 +1,61 @@ +package com.exasol.adapter.dialects.oracle; + +import static com.exasol.adapter.jdbc.TableMetadataMockUtils.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.BaseIdentifierConverter; +import com.exasol.adapter.jdbc.ColumnMetadataReader; +import com.exasol.adapter.jdbc.TableMetadataReader; +import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.metadata.TableMetadata; + +@ExtendWith(MockitoExtension.class) +class OracleTableMetadataReaderTest { + private TableMetadataReader reader; + @Mock + private ResultSet tablesMock; + @Mock + private ColumnMetadataReader columnMetadataReaderMock; + + @BeforeEach + void beforeEach() { + this.reader = new OracleTableMetadataReader(null, this.columnMetadataReaderMock, + AdapterProperties.emptyProperties(), BaseIdentifierConverter.createDefault()); + } + + @CsvSource({ "ANY_TABLE_NAME, true", "BIN$FOO, false" }) + @ParameterizedTest + void testIsTableIncludedByMapping(final String tableName, final boolean expectedIncluded) { + assertThat(this.reader.isTableIncludedByMapping(tableName), equalTo(expectedIncluded)); + } + + @Test + void testTablesInTrashBinAreNotMapped() throws SQLException { + mockTableCount(this.tablesMock, 3); + mockTableName(this.tablesMock, TABLE_A, TABLE_B, "BIN$TRASHED"); + mockTableWithColumnsOfType(this.tablesMock, this.columnMetadataReaderMock, TABLE_A, DataType.createBool()); + mockTableWithColumnsOfType(this.tablesMock, this.columnMetadataReaderMock, TABLE_B, DataType.createBool()); + final List tableNames = this.reader.mapTables(this.tablesMock, Optional.empty()) // + .stream() // + .map(TableMetadata::getName) // + .collect(Collectors.toList()); + assertThat(tableNames, containsInAnyOrder(TABLE_A, TABLE_B)); + } +} \ No newline at end of file diff --git a/src/test/resources/integration/driver/oracle/oracle.properties b/src/test/resources/integration/driver/oracle/oracle.properties new file mode 100644 index 0000000..7f6b53b --- /dev/null +++ b/src/test/resources/integration/driver/oracle/oracle.properties @@ -0,0 +1,4 @@ +driver.name=ojdbc8.jar +driver.path=src/test/resources/integration/driver/oracle +instant.client.name=instantclient-basic-linux.x64-12.1.0.2.0.zip +instant.client.path=src/test/resources/integration/driver/oracle \ No newline at end of file diff --git a/src/test/resources/integration/driver/oracle/settings.cfg b/src/test/resources/integration/driver/oracle/settings.cfg new file mode 100644 index 0000000..3af31a4 --- /dev/null +++ b/src/test/resources/integration/driver/oracle/settings.cfg @@ -0,0 +1,6 @@ +DRIVERNAME=ORACLE +JAR=ojdbc8.jar +DRIVERMAIN=oracle.jdbc.driver.OracleDriver +PREFIX=jdbc:oracle:thin: +FETCHSIZE=100000 +INSERTSIZE=-1 diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties new file mode 100644 index 0000000..8c97abe --- /dev/null +++ b/src/test/resources/logging.properties @@ -0,0 +1,6 @@ +handlers=java.util.logging.ConsoleHandler +.level=INFO +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=%1$tF %1$tT.%1$tL [%4$-7s] %5$s %n +com.exasol.level=ALL diff --git a/versionsMavenPluginRules.xml b/versionsMavenPluginRules.xml new file mode 100644 index 0000000..35bd03d --- /dev/null +++ b/versionsMavenPluginRules.xml @@ -0,0 +1,18 @@ + + + + + (?i).*Alpha(?:-?[\d.]+)? + (?i).*a(?:-?[\d.]+)? + (?i).*Beta(?:-?[\d.]+)? + (?i).*-B(?:-?[\d.]+)? + (?i).*-b(?:-?[\d.]+)? + (?i).*RC(?:-?[\d.]+)? + (?i).*CR(?:-?[\d.]+)? + (?i).*M(?:-?[\d.]+)? + + + + \ No newline at end of file