diff --git a/.buildpath b/.buildpath new file mode 100644 index 0000000..3675c09 --- /dev/null +++ b/.buildpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..f81e7dc --- /dev/null +++ b/.classpath @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c442d7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target/ +*.log +*~ +*.md.html +/.dbeaver diff --git a/.project b/.project new file mode 100644 index 0000000..da9bb9e --- /dev/null +++ b/.project @@ -0,0 +1,30 @@ + + + row-level-security-lua + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.dltk.core.scriptbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.ldt.nature + org.jkiss.dbeaver.DBeaverNature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..84d20e0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/lua=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/lua=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..85a3c97 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,392 @@ +eclipse.preferences.version=1 +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.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=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_record_components=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_record_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_last_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=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_statement_group_in_switch=0 +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_record_constructor=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_record_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_between_different_tags=do not 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_record_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_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=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_record_components=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=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_not_operator=do not 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_record_declaration=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_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=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_record_declaration=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_record_components=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_switch_case_expressions=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_record_constructor=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_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_record_declaration=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_record_constructor_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_declaration_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_after_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=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_record_declaration=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.text_block_indentation=0 +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..edf22a7 --- /dev/null +++ b/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,146 @@ +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=true +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.convert_to_enhanced_for_loop_if_loop_var_used=false +cleanup.correct_indentation=true +cleanup.format_source_code=true +cleanup.format_source_code_changes_only=false +cleanup.insert_inferred_type_arguments=false +cleanup.lazy_logical_operator=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.merge_conditional_blocks=false +cleanup.never_use_blocks=false +cleanup.never_use_parentheses_in_expressions=false +cleanup.number_suffix=true +cleanup.organize_imports=true +cleanup.push_down_negation=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=false +cleanup.remove_redundant_type_arguments=false +cleanup.remove_trailing_whitespaces=true +cleanup.remove_trailing_whitespaces_all=true +cleanup.remove_trailing_whitespaces_ignore_empty=false +cleanup.remove_unnecessary_array_creation=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=false +cleanup.remove_unused_private_methods=true +cleanup.remove_unused_private_types=true +cleanup.simplify_lambda_expression_and_method_ref=true +cleanup.sort_members=false +cleanup.sort_members_all=false +cleanup.use_anonymous_class_creation=false +cleanup.use_autoboxing=false +cleanup.use_blocks=true +cleanup.use_blocks_only_for_return_and_throw=false +cleanup.use_directly_map_method=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.use_unboxing=false +cleanup.use_var=false +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=19 +org.eclipse.jdt.ui.text.custom_code_templates= +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.convert_to_enhanced_for_loop_if_loop_var_used=false +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.lazy_logical_operator=true +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.merge_conditional_blocks=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=false +sp_cleanup.number_suffix=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.push_down_negation=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=true +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_array_creation=true +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.simplify_lambda_expression_and_method_ref=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_autoboxing=true +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_directly_map_method=true +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 +sp_cleanup.use_unboxing=true +sp_cleanup.use_var=false diff --git a/.settings/org.eclipse.ldt.prefs b/.settings/org.eclipse.ldt.prefs new file mode 100644 index 0000000..3b1203d --- /dev/null +++ b/.settings/org.eclipse.ldt.prefs @@ -0,0 +1,2 @@ +Grammar__default_id=lua-5.1 +eclipse.preferences.version=1 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/.settings/org.moreunit.core.prefs b/.settings/org.moreunit.core.prefs new file mode 100644 index 0000000..cc3272c --- /dev/null +++ b/.settings/org.moreunit.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.moreunit.core.anyLanguage.active=true +org.moreunit.core.anyLanguage.fileWordSeparator=_ +org.moreunit.core.anyLanguage.srcFolderPathTemplate=${srcProject}/src/main/lua/(**) +org.moreunit.core.anyLanguage.testFileNameTemplate=test_${srcFile} +org.moreunit.core.anyLanguage.testFolderPathTemplate=${srcProject}/src/test/lua/\\1 diff --git a/Findings.md b/Findings.md new file mode 100644 index 0000000..8825187 --- /dev/null +++ b/Findings.md @@ -0,0 +1,3 @@ +# Prototype findings + +* `CREATE OR REPLACE LUA ADAPTER SCRIPT` does not work. Code seems cached. Requires dropping the script and recreating it. diff --git a/README b/README deleted file mode 100644 index b40eced..0000000 --- a/README +++ /dev/null @@ -1 +0,0 @@ -# row-level-security-lua diff --git a/README.md b/README.md new file mode 100644 index 0000000..17cb13a --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# Row Level Security (Lua) + +row-level-security logo + +[![Build Status](https://api.travis-ci.com/exasol/row-level-security.svg?branch=master)](https://travis-ci.org/exasol/row-level-security-lua) + +SonarCloud results: + +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=alert_status)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) + +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=security_rating)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) +[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=sqale_index)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) + +[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=code_smells)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=coverage)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) +[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Arow-level-security&metric=ncloc)](https://sonarcloud.io/dashboard?id=com.exasol%3Arow-level-security-lua) + +Protect access to database tables on a per-row level based on roles and / or tenants. + +## Features + +Restrict access to rows (datasets) in tables to … + +* set of roles +* tenants (owners) +* group of users +* combination of group and tenant +* combination of group and role + +## Table of Contents + +### Information for Users + +* [User Guide](doc/user_guide/user_guide.md) +* [Tutorial](doc/user_guide/tutorial.md) +* [Changelog](doc/changes/changelog.md) + +### Information for Contributors + +Requirement, design documents and coverage tags are written in [OpenFastTrace](https://github.com/itsallcode/openfasttrace) format. + +* [System Requirement Specification](doc/system_requirements.md) +* [Design](doc/design.md) +* [Developer Guide](doc/developer_guide.md) + +### Run Time Dependencies + +Running the RLS Lua Virtual Schema requires a Exasol with built-in Lua 5.1 or later. + +| Dependency | Purpose | License | +|------------------------------------------|--------------------------------------------------------|-------------------------------| +| [Lua CJSON][luacjson] | JSON parsing and writing | MIT License | +| [LuaSocket][luasocket] | Socket communication | MIT License | + +Note that Lua CSON and LuaSucket both are pre-installed on an Exasol database. For local unit testing you need to install them on the test machine though. + +[luacjson]: https://www.kyne.com.au/~mark/software/lua-cjson.php +[luasocket]: http://w3.impa.br/~diego/software/luasocket/ + +### Test Dependencies + +#### Unit Test Dependencies + +Unit tests are written in Lua. + +| Dependency | Purpose | License | +|------------------------------------------|--------------------------------------------------------|-------------------------------| +| [luaunit][luaunit] | Unit testing framework | BSD License | +| [Mockagne][mockagne] | Mocking framework | MIT License | + +[luaunit]: https://github.com/bluebird75/luaunit +[mockagne]: https://github.com/vertti/mockagne + +#### Integration Test Dependencies + +The integration tests require `exasol-testcontainers` to provide an Exasol instance. The are written in Java and require version 11 or later. + +| Dependency | Purpose | License | +|----------------------------------------------------|--------------------------------------------------------|-------------------------------| +| [Exasol Testcontainers][exasol-testcontainers] | Integration test Exasol instance on Docker | MIT License | +| [Hamcrest Resultset Matcher][hamcrest-rs-matcher] | Validating JDBC resultsets | MIT License | +| [Java Hamcrest][java-hamcrest] | Checking for conditions in code via matchers | BSD License | +| [JUnit][junit5] | Unit testing framework | Eclipse Public License 1.0 | +| [Mockito][mockito] | Mocking framework | MIT License | +| [Test Database Builder][tddb-java] | Framework for writing database integration tests | MIT License | +| [Testcontainers][testcontainers] | Container-based integration tests | MIT License | +| [SLF4J][slf4j] | Logging facade | MIT License | + +[exasol-testcontainers]: https://github.com/exasol/exasol-testcontainers +[hamcrest-rs-matcher]: https://github.com/exasol/hamcrest-resultset-matcher +[java-hamcrest]: http://hamcrest.org/JavaHamcrest/ +[junit5]: https://junit.org/junit5 +[mockito]: http://site.mockito.org/ +[tddb-java]: https://github.com/exasol/test-db-builder-java +[testcontainers]: https://www.testcontainers.org/ +[slf4j]: http://www.slf4j.org/ + +### Build Dependencies + +This project has a complex build setup due to the mixture of Lua and Java. [Apache Maven][maven] serves as the main build tool. + +Lua build steps are also encapsulated by Maven. + +| Dependency | Purpose | License | +|-------------------------------------------|--------------------------------------------------------|-------------------------------| +| [Amalg][amalg] | Bundling Lua modules (and scripts) | MIT License | +| [Apache Maven][maven] | Build tool | Apache License 2.0 | +| [Build Helper Maven Plugin][build-helper] | Register non-standard source directories (here Lua) | MIT License | +| [Exec Maven Plugin][exec] | Execute external processes | Apache License 2.0 | +| [LuaRocks][luarocks] | Package management | MIT License | +| [Maven Assembly Plugin][assembly] | Building JAR archives | Apache License 2.0 | +| [Maven Compiler Plugin][compiler] | Setting required Java version | Apache License 2.0 | +| [Maven Failsafe Plugin][failsafe] | Integration testing | Apache License 2.0 | +| [Maven Jacoco Plugin][jacoco] | Code coverage metering | Eclipse Public License 2.0 | +| [Maven Source Plugin][source] | Creating a source code JAR | Apache License 2.0 | +| [Maven Surefire Plugin][surefire] | Unit testing | Apache License 2.0 | +| [OpenFastTrace Maven Plugin][oft] |Requirement Tracing | GPL V3 | +| [OSS Index Maven Plugin][oss-index] | Dependency security monitoring | Apache License 2.0 | + +[amalg]: https://github.com/siffiejoe/lua-amalg +[assembly]: https://maven.apache.org/plugins/maven-assembly-plugin/ +[build-helper]: http://www.mojohaus.org/build-helper-maven-plugin/ +[compiler]: https://maven.apache.org/plugins/maven-compiler-plugin/ +[exec]: https://www.mojohaus.org/exec-maven-plugin/ +[failsafe]: https://maven.apache.org/surefire/maven-surefire-plugin/ +[jacoco]: https://www.eclemma.org/jacoco/trunk/doc/maven.html +[luarocks]: https://luarocks.org/ +[maven]: https://maven.apache.org/ +[oft]: https://github.com/itsallcode/openfasttrace-maven-plugin +[oss-index]: https://sonatype.github.io/ossindex-maven/maven-plugin/ +[source]: https://maven.apache.org/plugins/maven-source-plugin/ +[surefire]: https://maven.apache.org/surefire/maven-surefire-plugin/ \ No newline at end of file diff --git a/doc/developer_guide/developer_guide.md b/doc/developer_guide/developer_guide.md new file mode 100644 index 0000000..f9f1e88 --- /dev/null +++ b/doc/developer_guide/developer_guide.md @@ -0,0 +1,47 @@ +# Developer Guide + +## Preparation + +Before you can build and test the application, you need to install Lua packages on the build machine. + +### Installing LuaRocks + +First install the package manager LuaRocks. + +```bash +apt install luarocks +``` + +Now update your `LUA_PATH`, so that it contains the packages (aka. "rocks"). You can auto-generate that path. + +```bash +luarocks path +``` + +Of course generating it is not enough, you also need to make sure the export is actually executed — preferably automatically each time. Here is an example that appends the path to the `.bashrc`: + +```bash +luarocks path >> ~/.bashrc +``` + +### Installing the Required Lua Packages + +You need the packages for unit testing, mocking and JSON processing. + +Execute as `root` or modify to install in your home directory: + +```bash +sudo luarocks install LuaUnit +sudo luarocks install Mockagne +sudo luarocks install lua-cjson +``` + +Most of those packages are only required for testing. While `cjson` is needed at runtime, it is prepackaged with Exasol, so no need to install it at runtime. + +### Bundling the Main Script and the Modules + +As most non-trivial pieces of software, `row-level-security-lua` is modularized. While it is possible to install individual modules as Lua scripts in Exasol, this is also a lot of work. And the more modules you install individually, the higher the chances you forget to update one of them. A safer and more convenient way is to bundle everything into one script before the installation using [lua-amalg](https://github.com/siffiejoe/lua-amalg/). + +To make this process easier, the [Maven POM file](../../pom.xml) contains an execution that automates this step. Still it is necessary to add new modules by hand in the list of modules to be bundled in the POM. + +Note that the entry point `request_dispatcher.lua` is a regular Lua script that must be added to the bundle using the `-s` switch and its relative path. The remaining bundle elements are Lua modules and must be listed in dot-notation. diff --git a/doc/user_guide/user_guide.md b/doc/user_guide/user_guide.md new file mode 100644 index 0000000..408fa69 --- /dev/null +++ b/doc/user_guide/user_guide.md @@ -0,0 +1,3 @@ +## Known Limitations + +* `SELECT *` is not yet supported due to an issue between the core database and the LUA Virtual Schemas in push-down requests (SPOT-10626) \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f235f54 --- /dev/null +++ b/pom.xml @@ -0,0 +1,266 @@ + + 4.0.0 + com.exasol + row-level-security-lua + 0.1.0 + Exasol Row Level Security (Lua) + This projects provides a plug-in to the Exasol database that adds per-row access control. + + UTF-8 + UTF-8 + 11 + 5.6.2 + 1.6.2 + 3.0.0-M4 + target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml + + ${basedir}/src/main/lua + ${basedir}/src/test/lua + /usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;./?.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua + ${lua.src}/administration + + + + maven.exasol.com + https://maven.exasol.com/artifactory/exasol-releases + + false + + + + + + com.exasol + exasol-jdbc + 6.2.5 + test + + + com.exasol + exasol-testcontainers + 3.0.0 + test + + + org.testcontainers + junit-jupiter + 1.13.0 + test + + + org.hamcrest + hamcrest + 2.2 + test + + + com.exasol + hamcrest-resultset-matcher + 1.1.0 + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.junit.platform + junit-platform-runner + ${junit.platform.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + org.slf4j + slf4j-jdk14 + 1.7.30 + test + + + com.exasol + test-db-builder-java + 0.2.0 + test + + + + + + + maven-surefire-plugin + ${surefire.and.failsafe.plugin.version} + + true + + + + default-test + none + + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${surefire.and.failsafe.plugin.version} + + + + integration-test + verify + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + + Lua Unit Tests + test + + exec + + + ${project.basedir}/tools/unittest.sh + true + + ${project.basedir} + + + + + + Lua Virtual Schema Bundle + package + + exec + + + lua + true + ${lua.src} + + ${basedir}/tools/amalg.lua + -o + ${basedir}/target/row-level-security-dist-${project.version}.lua + + -s + exasolvs/request_dispatcher.lua + + exasolvs.query_renderer + exasolrls.table_protection_status + exasolrls.adapter + exasolrls.metadata_reader + exasolrls.query_rewriter + exasollog.log + + + + + + + + org.sonatype.ossindex.maven + ossindex-maven-plugin + 3.1.0 + + + audit-dependencies + package + + audit + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.2.0 + + + generate-sources + + add-source + + + + + src/main/lua + src/test/lua + + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.codehaus.mojo + exec-maven-plugin + [1.0,) + + exec + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/lua/exasollog/log.lua b/src/main/lua/exasollog/log.lua new file mode 100644 index 0000000..c604059 --- /dev/null +++ b/src/main/lua/exasollog/log.lua @@ -0,0 +1,221 @@ +local levels = {NONE = 1, FATAL = 2, ERROR = 3, WARN = 4, INFO = 5, CONFIG = 6, DEBUG = 7, TRACE = 8} + +--- +-- This module implements a remote log client with the ability to fall back to console logging in case no connection +-- to a remote log receiver is established. +--

+-- You can optionally use a high resolution timer for performance monitoring. Since Lua's os.date() +-- function only has a resolution of seconds, that timer uses socket.gettime(). Note that the values +-- you are getting are not the milliseconds of a second, but the milliseconds counted from when the module was first +-- loaded — which is typically at the very beginning of the software using this module. +--

+-- Use the init() method to set some global parameters for this module. +--

+-- +local M = { + level = levels.INFO, + socket_client = nil, + connection_timeout = 0.1, -- seconds + log_client_name = nil, + timestamp_pattern = "%Y-%m-%d %H:%M:%S", + start_nanos = 0, + use_high_resolution_time = true, +} + +local socket = require("socket") + +--- +-- Initialize the log module +--

+-- This method allows you to set parameters that apply to all subsequent calls to logging methods. While it is possible +-- to change these settings at runtime, the recommended way is to do this once only, before you use the log for the +-- first time. +--

+--

+-- You can use a high resolution timer. Note that these are

not

the sub-second units of the timestamp! Lua +-- timestamps only offer second resolution. Rather you get a time difference in milliseconds counted from the first time +-- the log is opened. +-- +-- @param timestamp_pattern layout of timestamps displayed in the logs +-- +-- @param use_high_resolution_time switch high resolution time display on or off +-- +-- @return module loader +-- +function M.init(timestamp_pattern, use_high_resolution_time) + M.timestamp_pattern = timestamp_pattern + if use_high_resolution_time ~= nil then + M.use_high_resolution_time = use_high_resolution_time + end + return M +end + +--- +-- Set the log client name. +--

+-- This is the name presented when the log is first opened. We recommend using the name of the application or script +-- that uses the log and a version number. +--

+-- +function M.set_client_name(log_client_name) + M.log_client_name = log_client_name +end + +local function start_high_resolution_timer() + if M.use_high_resolution_time then + M.start_nanos = socket.gettime() + end +end + +local function get_level_name(level) + for k, v in pairs(levels) do + if v == level then + return k + end + end + error("E-LOG-1: Unable to determine log level name for level number " .. level .. ".") +end + +--- +-- Open a connection to a remote log receiver. +--

+-- This method allows connecting the log to an external process listening on a TCP port. The process can be on a remote +-- host. If the connection cannot be established, the logger falls back to console logging. +--

+-- +-- @param host remote host on which the logging process runs +-- +-- @param port TCP port on which the logging process listens +-- +function M.connect(host, port) + local tcp_socket = socket.tcp() + tcp_socket:settimeout(M.connection_timeout) + local ok, err = tcp_socket:connect(host, port) + local log_client_prefix = M.log_client_name and (M.log_client_name .. ": ") or "" + if ok then + M.socket_client = tcp_socket + M.info("%sConnected to log receiver listening on %s:%d with log level %s. Timezone is UTC%s.", log_client_prefix, host, port, + get_level_name(M.level), os.date("%z")) + else + print(log_client_prefix .. "W-LOG-2: Unable to open socket connection to " .. host .. ":" .. port + .. "for sending log messages. Falling back to console logging with log level " .. get_level_name(M.level) + .. ". Timezone is UTC" .. os.date("%z") .. ". Caused by: " .. err) + end +end + +--- +-- Close the connection to the remote log receiver. +-- +function M.disconnect() + if(M.socket_client) then + M.socket_client:close() + end +end + +--- +-- Set the log level. +-- +-- @param level_name name of the log level, one of: FATAL, ERROR, WARN, INFO, CONFIG, DEBUG, TRACE +-- +function M.set_level(level_name) + local level = levels[level_name] + if level == nil then + M.warning('W-LOG-1: Attempt to set illegal log level "' .. level_name + .. ' Pick one of: NONE, FATAL, ERROR, WARN, INFO, CONFIG, DEBUG, TRACE. Falling back to level INFO.') + M.level = levels.INFO + else + M.level = level + end +end + +--- +-- Write to a socket, print or discard the message. +--

+-- If a socket connection is established, this method writes to that socket. Otherwise if the global print function +-- exists (e.g. in a unit test) falls back to logging via print(). +--

+-- Exasol removed print() in it's Lua implementation, so there is no fallback on a real Exasol instance. You either use +-- remote logging or messages are discarded immediately. +--

+-- +-- @param level log level +-- +-- @param message log message; otherwise used as format string if any variadic parameters follow +-- +-- @param ... parameters to be inserted into formatted message (optional) +-- +local function write(level, message, ...) + if not M.socket_client and print then + return + else + local entry + local formatted_message = (select('#', ...) > 0) and string.format(message, ...) or message + if M.use_high_resolution_time then + local current_millis = string.format("%07.3f", (socket.gettime() - M.start_nanos) * 1000) + entry = { + os.date(M.timestamp_pattern), + " (", current_millis, "ms) [", level , "]", + string.rep(" ", 7 - string.len(level)), formatted_message + } + else + entry = { + os.date(M.timestamp_pattern), + " [", level , "]", + string.rep(" ", 7 - string.len(level)), formatted_message + } + end + if M.socket_client then + entry[#entry + 1] = "\n" + M.socket_client:send(table.concat(entry)) + else + if(print) then + print(table.concat(entry)) + end + end + end +end + +function M.fatal(...) + if M.level >= levels.FATAL then + write("FATAL", ...) + end +end + +function M.error(...) + if M.level >= levels.ERROR then + write("ERROR", ...) + end +end + +function M.warn(...) + if M.level >= levels.WARN then + write("WARN", ...) + end +end + +function M.info(...) + if M.level >= levels.INFO then + write("INFO", ...) + end +end + +function M.config(...) + if M.level >= levels.CONFIG then + write("CONFIG", ...) + end +end + +function M.debug(...) + if M.level >= levels.DEBUG then + write("DEBUG", ...) + end +end + +function M.trace(...) + if M.level >= levels.FATAL then + write("TRACE", ...) + end +end + +start_high_resolution_timer() +return M diff --git a/src/main/lua/exasolrls/adapter.lua b/src/main/lua/exasolrls/adapter.lua new file mode 100644 index 0000000..ccb4bf9 --- /dev/null +++ b/src/main/lua/exasolrls/adapter.lua @@ -0,0 +1,54 @@ +local metadata_reader = require("exasolrls.metadata_reader") +local query_rewriter = require("exasolrls.query_rewriter") + +local M = {VERSION = "0.1.0", NAME = "Row-level Security adapter (LUA)"} + +--- +-- Create a virtual schema. +-- +-- @param exa_metadata Exasol metadata +-- +-- @param request virtual schema request +-- +-- @return response containing the metadata for the virtual schema like table and column structure +-- +function M.create_virtual_schema(exa_metadata, request) + local properties = request.schemaMetadataInfo.properties + local schema_metadata = metadata_reader.read(properties.SCHEMA_NAME) + return {type = "createVirtualSchema", schemaMetadata = schema_metadata} +end + +--- +-- Drop the virtual schema +-- +-- @param exa_metadata Exasol metadata +-- +-- @param request virtual schema request +-- +-- @return response confirming the request (otherwise empty) +-- +function M.drop_virtual_schema(exa_metadata, request) + return {type = "dropVirtualSchema"} +end + +function M.refresh(exa_metadata, request) +end + +function M.set_properties(exa_metadata, request) +end + +function M.get_capabilities(exa_metadata, request) + return {type = "getCapabilities", + capabilities = {"SELECTLIST_PROJECTION", "AGGREGATE_SINGLE_GROUP", "AGGREGATE_GROUP_BY_COLUMN", + "AGGREGATE_GROUP_BY_TUPLE", "AGGREGATE_HAVING", "ORDER_BY_COLUMN", "LIMIT", + "LIMIT_WITH_OFFSET"}} +end + +function M.push_down(exa_metadata, request) + local properties = request.schemaMetadataInfo.properties + local adapter_cache = request.schemaMetadataInfo.adapterNotes + local rewritten_query = query_rewriter.rewrite(request.pushdownRequest, properties.SCHEMA_NAME, adapter_cache) + return {type = "pushdown", sql = rewritten_query} +end + +return M \ No newline at end of file diff --git a/src/main/lua/exasolrls/metadata_reader.lua b/src/main/lua/exasolrls/metadata_reader.lua new file mode 100644 index 0000000..6a3aa1a --- /dev/null +++ b/src/main/lua/exasolrls/metadata_reader.lua @@ -0,0 +1,77 @@ +local log = require("exasollog.log") +local cjson require("cjson") + +M = {} + +local CONTROL_TABLES = {"EXA_RLS_USERS", "EXA_ROLE_MAPPING", "EXA_GROUP_MAPPING"} + +local function open_schema(schema_id) + local ok, result = exa.pquery('OPEN SCHEMA "' .. schema_id .. '"') + if not ok then + error("E-MDR-1: Unable to open source schema " .. schema_id .. " for reading metadata. Caused by: " + .. result.error_message) + end +end + +local function read_columns(table_id) + local ok, result = exa.pquery('DESCRIBE "' .. table_id .. '"') + local columns = {} + local tenant_protected, role_protected, group_protected + if ok then + for i = 1, #result do + local column_id = result[i].COLUMN_NAME + if(column_id == "EXA_ROW_TENANT") then + tenant_protected = true + elseif(column_id == "EXA_ROW_ROLES") then + role_protected = true + elseif(column_id == "EXA_ROW_GROUP") then + group_protected = true + else + local column_type = result[i].SQL_TYPE + columns[i] = {name = column_id, dataType = {type = column_type}} + end + end + return columns, tenant_protected, role_protected, group_protected + else + error("E-MDR-3: Unable to read column metadata from source table " .. table_id .. ". Caused by: " + .. result.error_message) + end +end + +local function read_tables(schema_id) + local ok, result = exa.pquery('SELECT "TABLE_NAME" FROM "CAT"') + local tables = {} + local table_protection = {} + if ok then + for i = 1, #result do + local table_id = result[i].TABLE_NAME + if (table_id ~= "EXA_RLS_USERS") and (table_id ~= "EXA_ROLE_MAPPING") and (table_id ~= "EXA_GROUP_MEMBERS") + then + local columns, tenant_protected, role_protected, group_protected = read_columns(table_id) + tables[i] = {name = table_id, columns = columns} + local protection = (tenant_protected and "t" or "-") .. (role_protected and "r" or "-") + .. (group_protected and "g" or "-") + log.debug('Found table "' .. table_id .. '" (' .. #columns .. ' columns). Protection: ' .. protection) + table.insert(table_protection, table_id .. ":" .. protection) + end + end + return tables, table_protection + else + error("E-MDR-2: Unable to read table metadata from source schema. Caused by: " .. result.error_message) + end +end + +--- +-- Read the database metadata of the given schema (i.e. the internal structure of that schema) +-- +-- @param schema schema to be scanned +-- +-- @return schema metadata +-- +function M.read(schema_id) + open_schema(schema_id) + local tables, table_protection = read_tables(schema_id) + return {tables = tables, adapterNotes = table.concat(table_protection, ",")} +end + +return M diff --git a/src/main/lua/exasolrls/query_rewriter.lua b/src/main/lua/exasolrls/query_rewriter.lua new file mode 100644 index 0000000..98c0573 --- /dev/null +++ b/src/main/lua/exasolrls/query_rewriter.lua @@ -0,0 +1,53 @@ +local renderer = require("exasolvs.query_renderer") +local protection = require("exasolrls.table_protection_status") +local log = require("exasollog.log") + +local M = {} + +local function validate(query) + if not query then + error("E-RLS-QRW-1: Unable to rewrite query because it was .") + end + local push_down_type = query.type + if(push_down_type ~= "select") then + error('E-RLS-QRW-2: Unable to rewrite push-down request of type "' .. push_down_type + .. '". Only SELECT is supported.') + end +end + +--- +-- Rewrite the original query with RLS restrictions. +-- +-- @param original_query structure containing the original push-down query +-- +-- @param sourceSchema source schema RLS is put on top of +-- +-- @param adapter_cache cache taken from the adapter notes +-- +-- @return string containing the rewritten query +-- +function M.rewrite(original_query, source_schema, adapter_cache) + validate(original_query) + local query = original_query + local table = query.from.name + query.from.schema = source_schema + if protection.is_table_protected(source_schema, table, adapter_cache) then + log.debug('Table "%s" is RLS-protected. Adding row filters.', table) + local protection_filter = { + type = "predicate_equal", + left = {type = "column", tableName = table, name = "EXA_ROW_TENANT"}, + right = {type = "function_scalar", name = "CURRENT_USER"} + } + local original_filter = query.filter + if original_filter then + query.filter = {type = "predicate_and", expressions = {protection_filter, original_filter}} + else + query.filter = protection_filter + end + else + log.debug('Table "%s" is not protected. No filters added.', table) + end + return renderer.new(query).render() +end + +return M \ No newline at end of file diff --git a/src/main/lua/exasolrls/table_protection_status.lua b/src/main/lua/exasolrls/table_protection_status.lua new file mode 100644 index 0000000..2da285f --- /dev/null +++ b/src/main/lua/exasolrls/table_protection_status.lua @@ -0,0 +1,52 @@ +local log = require("exasollog.log") + +local M = {} + +local function determine_table_protection_from_cache(table_id, protection_cache) + local protection = string.match(protection_cache, table_id .. ":([-rtg]+)") + if not protection then + error("F-RLS-TPS-2: Unable to find table \"" .. table_id + .. "\" in protection status cache. Please check the table name and refresh the Virtual Schema.") + end + return protection ~= "---" +end + +local function determine_table_protection_from_metadata(schema_id, table_id) + local fully_qualified_table_id = '"' .. schema_id .. '"."'.. table_id .. '"' + log.debug("Reading protection status of table %s.", fully_qualified_table_id) + local ok, result = exa.pquery('DESCRIBE ' .. fully_qualified_table_id) + if ok then + for i = 1, #result do + local column_name = result[i].COLUMN_NAME + if column_name == "EXA_ROW_TENANT" then + return true + end + end + return false + else + error('F-RLS-TPS-1: Unable to determine protection status of table "' .. table + .. "'. Metadata could not be read. Caused by: " + result.error_msg) + end +end + +--- +-- Check whether a table is protected by RLS or not. +-- +-- @param schema_id name of the schema the table belongs to +-- +-- @param table_id name of the table for which to check the protection status +-- +-- @param adapter_cache if present, used as cached source for determining the protection status of tables +-- +-- @return true if the table is protected. +-- +function M.is_table_protected(schema_id, table_id, adapter_cache) + if not adapter_cache then + return determine_table_protection_from_metadata(schema_id, table_id) + else + return determine_table_protection_from_cache(table_id, adapter_cache) + end +end + + +return M \ No newline at end of file diff --git a/src/main/lua/exasolvs/query_renderer.lua b/src/main/lua/exasolvs/query_renderer.lua new file mode 100644 index 0000000..07fad85 --- /dev/null +++ b/src/main/lua/exasolvs/query_renderer.lua @@ -0,0 +1,177 @@ +local M = {} + +-- TODO: Move to a separate module! +-- +function string.starts_with(text, start) + return start == string.sub(text, 1, string.len(start)) +end + +--- +-- Create a new query renderer. +-- +-- @param query query to be rendered +-- +-- @return new query renderer instance +-- +function M.new (query) + local self = {original_query = query, query_elements = {}} + local OPERATORS = { + predicate_equal = "=", predicate_less = "<", predicate_greater = ">", + predicate_and = "AND", predicate_or = "OR", predicate_not = "NOT" + } + + -- forward declarations + local append_unary_predicate, append_binary_predicate, append_iterated_predicate, append_expression + + local function append(value) + self.query_elements[#self.query_elements + 1] = value + end + + local function comma(index) + if index > 1 then + self.query_elements[#self.query_elements + 1] = ", " + end + end + + local function append_column_reference(column) + append('"') + append(column.tableName) + append('"."') + append(column.name) + append('"') + end + + local function append_scalar_function(scalar_function) + local function_name = scalar_function.name + append(function_name) + if function_name ~= "CURRENT_USER" then + append("(") + local arguments = scalar_function.arguments + if(arguments) then + for i = 1, #arguments do + comma(i) + append_expression(arguments[i]) + end + end + append(")") + end + end + + local function append_select_list_elements(select_list) + for i = 1, #select_list do + local element = select_list[i] + local type = element.type + comma(i) + append_expression(element) + end + end + + local function append_select_list() + local select_list = self.original_query.selectList + if not select_list then + append("*") + else + append_select_list_elements(select_list) + end + end + + local function append_from() + if self.original_query.from then + append(' FROM "') + if self.original_query.from.schema then + append(self.original_query.from.schema) + append('"."') + end + append(self.original_query.from.name) + append('"') + end + end + + local function append_predicate(operand) + local type = string.sub(operand.type, 11) + if type == "equal" or type == "greater" or type == "less" then + append_binary_predicate(operand) + elseif type == "not" then + append_unary_predicate(operand) + elseif type == "and" or type == "or" then + append_iterated_predicate(operand) + else + error('E-VS-QR-2: Unable to render unknown SQL predicate type "' .. type .. '".') + end + end + + append_expression = function (expression) + local type = expression.type + if type == "column" then + append_column_reference(expression) + elseif(type == "literal_exactnumeric" or type == "literal_boolean") then + append(expression.value) + elseif(type == "literal_string") then + append("'") + append(expression.value) + append("'") + elseif(type == "function_scalar") then + append_scalar_function(expression) + elseif(string.starts_with(type, "predicate_")) then + append_predicate(expression) + else + error('E-VS-QR-1: Unable to render unknown SQL expression type "' .. expression.type .. '".') + end + end + + append_unary_predicate = function (predicate) + local type = predicate.type + append("(") + append(OPERATORS[predicate.type]) + append(" ") + append_expression(predicate.expression) + append(")") + end + + append_binary_predicate = function (predicate) + append("(") + append_expression(predicate.left) + append(" ") + append(OPERATORS[predicate.type]) + append(" ") + append_expression(predicate.right) + append(")") + end + + append_iterated_predicate = function (predicate) + append("(") + local expressions = predicate.expressions + for i = 1, #expressions do + if i > 1 then + append(" ") + append(OPERATORS[predicate.type]) + append(" ") + end + append_expression(expressions[i]) + end + append(")") + end + + local function append_filter() + if self.original_query.filter then + append(" WHERE ") + append_predicate(self.original_query.filter) + end + end + + --- Render the query to a string. + -- + -- @return query as string + -- + local function render() + append("SELECT ") + append_select_list() + append_from() + append_filter() + return table.concat(self.query_elements, "") + end + + return {render = render} +end + +return M diff --git a/src/main/lua/exasolvs/request_dispatcher.lua b/src/main/lua/exasolvs/request_dispatcher.lua new file mode 100644 index 0000000..1371bd8 --- /dev/null +++ b/src/main/lua/exasolvs/request_dispatcher.lua @@ -0,0 +1,65 @@ +local log = require("exasollog.log") +local cjson = require("cjson") +local adapter = require("exasolrls.adapter", "adapter") + +local function handle_request(request) + local handlers = { + pushdown = adapter.push_down, + createVirtualSchema = adapter.create_virtual_schema, + dropVirtualSchema = adapter.drop_virtual_schema, + refresh = adapter.refresh, + getCapabilities = adapter.get_capabilities, + setProperties = adapter.set_properties + } + log.info('Received "%s" request.', request.type) + local handler = handlers[request.type] + if(handler ~= nil) then + local response = cjson.encode(handler(nil, request)) + log.debug("Response:\n" .. response) + return response + else + error('F-RQD-1: Unknown Virtual Schema request type "%s" received.', request.type) + end +end + +local function log_error(message) + local error_type = string.sub(message, 1, 2) + if(error_type == "F-") then + log.fatal(message) + else + log.error(message) + end +end + +--- +-- RLS adapter entry point. +--

+-- This global function receives the request from the Exasol core database. +--

+-- +function adapter_call(request_as_json) + log.set_client_name(adapter.NAME .. " " .. adapter.VERSION) + local request = cjson.decode(request_as_json) + local properties = (request.schemaMetadataInfo or {}).properties or {} + local log_level = properties.LOG_LEVEL + if(log_level) then + log.set_level(string.upper(log_level)) + end + local debug_address = properties.DEBUG_ADDRESS + if(debug_address) then + local colon_position = string.find(debug_address,":", 1, true) + local host = string.sub(debug_address, 1, colon_position - 1) + local port = string.sub(debug_address, colon_position + 1) + log.connect(host, port) + end + log.debug("Raw request:\n%s", request_as_json) + local ok, result = pcall(function () return handle_request(request) end) + if(ok) then + log.disconnect() + return result + else + log_error(result) + log.disconnect() + error(result) + end +end diff --git a/src/test/java/com/exasol/InstallLuaAdapterScript.java b/src/test/java/com/exasol/InstallLuaAdapterScript.java new file mode 100644 index 0000000..200f858 --- /dev/null +++ b/src/test/java/com/exasol/InstallLuaAdapterScript.java @@ -0,0 +1,124 @@ +package com.exasol; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.logging.Logger; + +import com.exasol.dbbuilder.AdapterScript.Language; +import com.exasol.dbbuilder.DatabaseObjectFactory; +import com.exasol.dbbuilder.ExasolObjectFactory; +import com.exasol.dbbuilder.Schema; + +/** + * This class contains an installation helper intended to simplify declaring Lua adapter scripts. + *

+ * This script is intended for integration tests of Lua adapter scripts. It is a quick way to get the adapter script + * installed. + *

+ *

+ * IMPORTANT: This script creates a fresh schema around the adapter each time. Any other objects in that schema + * will be lost! + *

+ */ +public class InstallLuaAdapterScript { + private static final String USAGE = "Usage: java InstallLuaAdapterScript.java \n" + + " [] [] []"; + private static final Logger LOGGER = Logger.getLogger(InstallLuaAdapterScript.class.getName()); + private static final String EXASOL_LUA_MODULE_LOADER_WORKAROUND = "table.insert(package.loaders,\n" // + + " function (module_name)\n" // + + " local loader = package.preload[module_name]\n" // + + " if not loader then\n" // + + " error(\"Module \" .. module_name .. \" not found in package.preload.\")\n" // + + " else\n" // + + " return loader\n" // + + " end\n" // + + " end\n" // + + ")\n\n"; + private static final String DEFAULT_CONNECTION_STRING = "jdbc:exa:127.0.0.1:8563"; + private static final String DEFAULT_DATABASE_USER = "SYS"; + private static final String DEFAULT_DATABASE_PWD = "exasol"; + private final String adapterName; + private final String adapterSchemaName; + private Connection connection; + private final String password; + private final Path scriptPath; + private final String connectionString; + private final String user; + + /** + * Entry point for the Lua adapter script installer. + *

+ * Note that the adapter schema should only be used for this adapter. The installer will remove an existing schema + * of that name and recreate it. + *

+ * + * @param arguments array of command line arguments + *
    + *
  • name of the schema that the adapter will be installed in
  • + *
  • adapter script name
  • + *
  • path to script
  • + *
  • database connection string (optional, defaults to "jdbc:exa:127.0.0.1:8563")
  • + *
  • database user (optional, defaults to "SYS")
  • + *
  • database password (optional, defaults to "exasol")
  • + *
+ */ + public static void main(final String[] arguments) { + if (arguments.length < 3) { + System.out.println(USAGE); + System.exit(-1); + } + final String adapterSchemaName = arguments[0]; + final String adapterScriptName = arguments[1]; + final Path scriptPath = Path.of(arguments[2]); + final String connectionString = getOptionalArgument(arguments, 3, DEFAULT_CONNECTION_STRING); + final String user = getOptionalArgument(arguments, 4, DEFAULT_DATABASE_USER); + final String password = getOptionalArgument(arguments, 5, DEFAULT_DATABASE_PWD); + new InstallLuaAdapterScript(adapterSchemaName, adapterScriptName, scriptPath, connectionString, user, password) + .run(); + } + + private static String getOptionalArgument(final String arguments[], final int index, final String defaultValue) { + return (arguments.length > index) && (arguments[index] != null) ? arguments[index] : defaultValue; + } + + private InstallLuaAdapterScript(final String adapterSchemaName, final String adapterName, final Path scriptPath, + final String connectionString, final String user, final String password) { + this.adapterName = adapterName; + this.adapterSchemaName = adapterSchemaName; + this.scriptPath = scriptPath; + this.connectionString = connectionString; + this.user = user; + this.password = password; + } + + private void run() { + try { + this.connection = DriverManager.getConnection(this.connectionString, this.user, this.password); + final DatabaseObjectFactory factory = new ExasolObjectFactory(this.connection); + cleanUpOldEntries(); + final Schema scriptSchema = factory.createSchema(this.adapterSchemaName); + installAdapter(scriptSchema, this.scriptPath); + } catch (final SQLException | IOException exception) { + exception.printStackTrace(); + } + } + + private void cleanUpOldEntries() throws SQLException { + final Statement statement = this.connection.createStatement(); +// statement.execute( +// "DROP ADAPTER SCRIPT IF EXISTS \"" + this.adapterSchemaName + "\".\"" + this.adapterName + "\""); + statement.execute("DROP SCHEMA IF EXISTS \"" + this.adapterSchemaName + "\" CASCADE"); + } + + private void installAdapter(final Schema scriptSchema, final Path scriptContent) throws IOException { + LOGGER.info(() -> "Installing adpater script \"" + this.adapterName + "\" in schema \"" + scriptSchema.getName() + + "\"."); + final String content = EXASOL_LUA_MODULE_LOADER_WORKAROUND + Files.readString(scriptContent); + scriptSchema.createAdapterScript(this.adapterName, Language.LUA, content); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/RequestDispatcherIT.java b/src/test/java/com/exasol/RequestDispatcherIT.java new file mode 100644 index 0000000..5adcddc --- /dev/null +++ b/src/test/java/com/exasol/RequestDispatcherIT.java @@ -0,0 +1,102 @@ +package com.exasol; + +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.JdbcDatabaseContainer.NoDriverFoundException; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.exasol.containers.ExasolContainer; +import com.exasol.dbbuilder.AdapterScript; +import com.exasol.dbbuilder.AdapterScript.Language; +import com.exasol.dbbuilder.ExasolObjectFactory; +import com.exasol.dbbuilder.ObjectPrivilege; +import com.exasol.dbbuilder.Schema; +import com.exasol.dbbuilder.User; +import com.exasol.dbbuilder.VirtualSchema; +import com.exasol.matcher.ResultSetStructureMatcher; + +@Testcontainers +class RequestDispatcherIT { + private static final Path RLS_PACKAGE_PATH = Path.of("target/row-level-security-dist-0.1.0.lua"); + @Container + private static ExasolContainer> container = new ExasolContainer<>( + "exasol/docker-db:7.0.rc1-d1") // + .withRequiredServices() // + .withExposedPorts(8563); + private static final String EXASOL_LUA_MODULE_LOADER_WORKAROUND = "table.insert(package.loaders,\n" // + + " function (module_name)\n" // + + " local loader = package.preload[module_name]\n" // + + " if(loader == nil) then\n" // + + " error(\"Module \" .. module_name .. \" not found in package.preload.\")\n" // + + " else\n" // + + " return loader\n" // + + " end\n" // + + " end\n" // + + ")\n\n"; + private static Connection connection; + private static ExasolObjectFactory factory; + private static Schema scriptSchema; + + @BeforeAll + static void beforeAll() throws NoDriverFoundException, SQLException { + connection = container.createConnection(""); + factory = new ExasolObjectFactory(connection); + scriptSchema = factory.createSchema("L"); + } + + @Test + void testUnprotected() throws IOException, SQLException { + final String sourceSchemaName = "UNPROTECTED"; + final Schema sourceSchema = factory.createSchema(sourceSchemaName); + sourceSchema.createTable("T", "C1", "BOOLEAN") // + .insert("true") // + .insert("false"); + final VirtualSchema virtualSchema = createVirtualSchema(sourceSchema); + final User user = factory.createLoginUser("UP_USER").grant(virtualSchema, ObjectPrivilege.SELECT); + assertThat(executeRlsQueryWithUser("SELECT C1 FROM " + sourceSchemaName + "_RLS.T", user), + ResultSetStructureMatcher.table("BOOLEAN").row(true).row(false).matches()); + } + + private VirtualSchema createVirtualSchema(final Schema sourceSchema) throws IOException { + final String name = sourceSchema.getName(); + final String content = EXASOL_LUA_MODULE_LOADER_WORKAROUND + Files.readString(RLS_PACKAGE_PATH); + final AdapterScript adapterScript = scriptSchema.createAdapterScript(name + "_ADAPTER", Language.LUA, content); + return factory.createVirtualSchemaBuilder(name + "_RLS") // + .adapterScript(adapterScript) // + .sourceSchema(sourceSchema) // + .properties(Map.of("LOG_LEVEL", "TRACE", "DEBUG_ADDRESS", "172.17.0.1:3000")).build(); + } + + private ResultSet executeRlsQueryWithUser(final String query, final User user) throws SQLException { + final Statement statement = container.createConnectionForUser(user.getName(), user.getPassword()) + .createStatement(); + final ResultSet result = statement.executeQuery(query); + return result; + } + + @Test + void testTenantProtected() throws IOException, SQLException { + final String sourceSchemaName = "TENANT_PROTECTED"; + final Schema sourceSchema = factory.createSchema(sourceSchemaName); + sourceSchema.createTable("T", "C1", "BOOLEAN", "C2", "DATE", "EXA_ROW_TENANT", "VARCHAR(128)") // + .insert("false", "2020-01-01", "NON_TENANT_USER") // + .insert("true", "2020-02-02", "TENANT_USER"); + final VirtualSchema virtualSchema = createVirtualSchema(sourceSchema); + final User user = factory.createLoginUser("TENANT_USER").grant(virtualSchema, ObjectPrivilege.SELECT); + factory.createLoginUser("NON_TENANT_USER").grant(virtualSchema, ObjectPrivilege.SELECT); + assertThat(executeRlsQueryWithUser("SELECT C1 FROM " + sourceSchemaName + "_RLS.T", user), + ResultSetStructureMatcher.table("BOOLEAN").row(true).matches()); + } +} \ No newline at end of file diff --git a/src/test/lua/exasollog/test_log.lua b/src/test/lua/exasollog/test_log.lua new file mode 100644 index 0000000..163c796 --- /dev/null +++ b/src/test/lua/exasollog/test_log.lua @@ -0,0 +1,94 @@ +local luaunit = require("luaunit") +local mockagne = require("mockagne") + +local when, verify, any = mockagne.when, mockagne.verify, mockagne.any +local date_pattern = "%Y-%m-%d" + +test_log = {} + +function test_log:setUp() + self.today = os.date(date_pattern) + self.socket_mock = mockagne.getMock() + self.tcp_mock = mockagne.getMock() + self.client_mock = self.tcp_mock -- on connect(), the socket library promotes a TCP socket to a client socket + when(self.socket_mock.tcp()).thenAnswer(self.tcp_mock) + when(self.tcp_mock:connect(any(), any())).thenAnswer(1) + when(self.socket_mock.gettime()).thenAnswer(1000000) + package.preload["socket"] = function () return self.socket_mock end + self.log = require("exasollog.log").init(date_pattern, false) + self.log.set_client_name("Unit test") + self.log.connect("localhost", 3000) +end + +function test_log:tearDown() + self.log.disconnect() + package.loaded["socket"] = nil + package.loaded["exasollog.log"] = nil +end + +function test_log:assert_message(message) + verify(self.client_mock:send(message)) +end + +function test_log:assert_no_message(message) + luaunit.assertErrorMsgContains("no invocation made", function() self:assert_message(message) end) +end + +function test_log:test_startup_message() + local timezone = os.date("%z") + self:assert_message(self.today .. " [INFO] Unit test: Connected to log receiver listening on localhost:3000" + .. " with log level INFO. Timezone is UTC" .. timezone .. ".\n") +end + +function test_log:test_fatal() + self.log.fatal("Good by, cruel world!") + self:assert_message(self.today .. " [FATAL] Good by, cruel world!\n") +end + +function test_log:test_error() + self.log.error("Oops!") + self:assert_message(self.today .. " [ERROR] Oops!\n") +end + +function test_log:test_warn() + self.log.warn("This looks suspicious...") + self:assert_message(self.today .. " [WARN] This looks suspicious...\n") +end + +function test_log:test_info() + self.log.info("Good to know.") + self:assert_message(self.today .. " [INFO] Good to know.\n") +end + +function test_log:test_config() + self.log.set_level("CONFIG") + self.log.config("Life support enabled.") + self:assert_message(self.today .. " [CONFIG] Life support enabled.\n") +end + +function test_log:test_debug() + self.log.set_level("DEBUG") + self.log.debug("Look what we have here.") + self:assert_message(self.today .. " [DEBUG] Look what we have here.\n") +end + +function test_log:test_trace() + self.log.set_level("TRACE") + self.log.trace("foo(bar)") + self:assert_message(self.today .. " [TRACE] foo(bar)\n") +end + +function test_log:test_set_log_level() + self.log.set_level("WARN") + self.log.info("don't send") + self.log.warn("send") + self:assert_message(self.today .. " [WARN] send\n") + self:assert_no_message(self.today .. " [INFO] don't send\n") +end + +function test_log:test_logging_with_format_string() + self.log.info('%s says "Mount Everest is %d meters high."', "Simon", 8848) + self:assert_message(self.today .. ' [INFO] Simon says "Mount Everest is 8848 meters high."\n') +end + +os.exit(luaunit.LuaUnit.run()) diff --git a/src/test/lua/exasolrls/test_adapter.lua b/src/test/lua/exasolrls/test_adapter.lua new file mode 100644 index 0000000..42b00f2 --- /dev/null +++ b/src/test/lua/exasolrls/test_adapter.lua @@ -0,0 +1,35 @@ +local luaunit = require("luaunit") + +local mockagne = require("mockagne") +local metadata_reader_mock = mockagne.getMock() +package.preload["exasolrls.metadata_reader"] = function () return metadata_reader_mock end + +local adapter = require("exasolrls.adapter") + +test_rls_adapter = {} + +function test_rls_adapter:test_drop_virtual_schema() + luaunit.assertEquals(adapter.drop_virtual_schema(), {type="dropVirtualSchema"}) +end + +function test_rls_adapter.test_create_virtual_schema() + local schema_metadata = {tables = {{type = "table", name = "T1", columns = + {{name = "C1", dataType = { type = "BOOLEAN"}}}}}} + mockagne.when(metadata_reader_mock.read("S")).thenAnswer(schema_metadata) + local expected = {type = "createVirtualSchema", + schemaMetadata = schema_metadata} + local request = {schemaMetadataInfo = {name = "V", properties = {SCHEMA_NAME = "S"}}} + local actual = adapter.create_virtual_schema(nil, request) + luaunit.assertEquals(actual, expected) +end + +function test_rls_adapter:test_get_capabilites() + local expected = {type = "getCapabilities", + capabilities = {"SELECTLIST_PROJECTION", "AGGREGATE_SINGLE_GROUP", "AGGREGATE_GROUP_BY_COLUMN", + "AGGREGATE_GROUP_BY_TUPLE", "AGGREGATE_HAVING", "ORDER_BY_COLUMN", "LIMIT", + "LIMIT_WITH_OFFSET"}} + local actual = adapter.get_capabilities() + luaunit.assertEquals(actual , expected) +end + +os.exit(luaunit.LuaUnit.run()) diff --git a/src/test/lua/exasolrls/test_metadata_reader.lua b/src/test/lua/exasolrls/test_metadata_reader.lua new file mode 100644 index 0000000..4d4f98f --- /dev/null +++ b/src/test/lua/exasolrls/test_metadata_reader.lua @@ -0,0 +1,49 @@ +local luaunit = require("luaunit") +local mockagne = require("mockagne") +local reader = require("exasolrls.metadata_reader") + +test_metadata_reader = {} + +function test_metadata_reader.test_read() + local exa_mock = mockagne.getMock() + _G.exa = exa_mock + mockagne.when(exa_mock.pquery('OPEN SCHEMA "S"')).thenAnswer(true) + mockagne.when(exa_mock.pquery('SELECT "TABLE_NAME" FROM "CAT"')).thenAnswer(true, {{TABLE_NAME = "T1"}}) + mockagne.when(exa_mock.pquery('DESCRIBE "T1"')).thenAnswer(true, {{COLUMN_NAME = "C1", SQL_TYPE = "BOOLEAN"}}) + luaunit.assertEquals(reader.read("S"), + {tables = {{name = "T1", columns = {{name = "C1", dataType = {type = "BOOLEAN"}}}}}, + adapterNotes="T1:---" + }) +end + +function test_metadata_reader.test_hide_control_tables() + local exa_mock = mockagne.getMock() + _G.exa = exa_mock + mockagne.when(exa_mock.pquery('OPEN SCHEMA "S"')).thenAnswer(true) + mockagne.when(exa_mock.pquery('SELECT "TABLE_NAME" FROM "CAT"')) + .thenAnswer(true, {{TABLE_NAME = "T2"}, {TABLE_NAME = "EXA_RLS_USERS"}, {TABLE_NAME = "EXA_ROLE_MAPPING"}, + {TABLE_NAME = "EXA_GROUP_MEMBERS"}}) + mockagne.when(exa_mock.pquery('DESCRIBE "T2"')).thenAnswer(true, {{COLUMN_NAME = "C2", SQL_TYPE = "DATE"}}) + luaunit.assertEquals(reader.read("S"), + {tables = {{name = "T2", columns = {{name = "C2", dataType = {type = "DATE"}}}}}, adapterNotes="T2:---"}) +end + +function test_metadata_reader.test_hide_control_columns() + local exa_mock = mockagne.getMock() + _G.exa = exa_mock + mockagne.when(exa_mock.pquery('OPEN SCHEMA "S"')).thenAnswer(true) + mockagne.when(exa_mock.pquery('SELECT "TABLE_NAME" FROM "CAT"')) + .thenAnswer(true, {{TABLE_NAME = "T3"}, {TABLE_NAME = "T4"}}) + mockagne.when(exa_mock.pquery('DESCRIBE "T3"')) + .thenAnswer(true, {{COLUMN_NAME = "C3_1", SQL_TYPE = "BOOLEAN"}, {COLUMN_NAME = "EXA_ROW_TENANT"}, + {COLUMN_NAME = "EXA_ROW_ROLES"}}) + mockagne.when(exa_mock.pquery('DESCRIBE "T4"')) + .thenAnswer(true, {{COLUMN_NAME = "C4_1", SQL_TYPE = "DATE"}, {COLUMN_NAME = "EXA_ROW_GROUP"}}) + luaunit.assertEquals(reader.read("S"), + {tables = { + {name = "T3", columns = {{name = "C3_1", dataType = {type = "BOOLEAN"}}}}, + {name = "T4", columns = {{name = "C4_1", dataType = {type = "DATE"}}}} + }, adapterNotes = "T3:tr-,T4:--g" }) +end + +os.exit(luaunit.LuaUnit.run()) diff --git a/src/test/lua/exasolrls/test_query_rewriter.lua b/src/test/lua/exasolrls/test_query_rewriter.lua new file mode 100644 index 0000000..215da23 --- /dev/null +++ b/src/test/lua/exasolrls/test_query_rewriter.lua @@ -0,0 +1,33 @@ +local luaunit = require("luaunit") +local rewriter = require("exasolrls.query_rewriter") + +test_query_rewriter = {} + +function assert_rewrite(original_query, expected) + luaunit.assertEquals(rewriter.rewrite(original_query), expected) +end + +function test_query_rewriter.test_unprotected_table() + local original_query = { + type = "select", + selectList = { + {type = "column", name = "C1", tableName = "UNPROT"}, + {type = "column", name = "C2", tableName = "UNPROT"} + }, + from = { type = "table", name = "UNPROT"} + } + assert_rewrite(original_query, 'SELECT "UNPROT"."C1", "UNPROT"."C2" FROM "UNPROT"') +end + +function test_query_rewriter.test_tenant_protected_table() + local original_query = { + type = "select", + selectList = { + {type = "column", name = "C1", tableName = "PROT"}, + }, + from = { type = "table", name = "PROT"} + } + assert_rewrite(original_query, 'SELECT "PROT"."C1" FROM "PROT" WHERE ("PROT"."EXA_ROW_TENANT" = CURRENT_USER())') +end + +os.exit(luaunit.LuaUnit.run()) diff --git a/src/test/lua/exasolrls/test_table_protection_status.lua b/src/test/lua/exasolrls/test_table_protection_status.lua new file mode 100644 index 0000000..1b671ed --- /dev/null +++ b/src/test/lua/exasolrls/test_table_protection_status.lua @@ -0,0 +1,39 @@ +local luaunit = require("luaunit") + +local mockagne = require("mockagne") + +local protection = require("exasolrls.table_protection_status") + +test_table_protection_status = {} + +function test_table_protection_status.test_is_protected_calculated_from_cache_true() + local source_schema = "S" + local adapter_cache = "CITIES:---,PEOPLE:t--" + luaunit.assertEquals(protection.is_table_protected(source_schema, "PEOPLE", adapter_cache), true) + luaunit.assertEquals(protection.is_table_protected(source_schema, "CITIES", adapter_cache), false) +end + +function test_table_protection_status.test_is_protected_false() + local source_schema = "S" + local table = "MONTHS" + local exa_mock = mockagne.getMock() + _G.exa = exa_mock + mockagne.when(exa_mock.pquery('DESCRIBE "' .. source_schema .. '"."' .. table .. '"')) + .thenAnswer(true, {{COLUMN_NAME = "C1", SQL_TYPE = "BOOLEAN"}}) + luaunit.assertEquals(protection.is_table_protected(source_schema, table), false) +end + +function test_table_protection_status.test_is_protected_true() + local source_schema = "S" + local table = "PEOPLE" + local exa_mock = mockagne.getMock() + _G.exa = exa_mock + mockagne.when(exa_mock.pquery('DESCRIBE "' .. source_schema .. '"."'.. table .. '"')) + .thenAnswer(true, { + {COLUMN_NAME = "C1", SQL_TYPE = "BOOLEAN"}, + {COLUMN_NAME = "EXA_ROW_TENANT", SQL_TYPE = "VARCHAR"} + }) + luaunit.assertEquals(protection.is_table_protected(source_schema, table), true) +end + +os.exit(luaunit.LuaUnit.run()) diff --git a/src/test/lua/exasolvs/test_query_renderer.lua b/src/test/lua/exasolvs/test_query_renderer.lua new file mode 100644 index 0000000..9cff80e --- /dev/null +++ b/src/test/lua/exasolvs/test_query_renderer.lua @@ -0,0 +1,118 @@ +luaunit = require("luaunit") +renderer = require("exasolvs.query_renderer") + +test_query_renderer = {} + +local function assert_renders_to(original_query, expected) + luaunit.assertEquals(renderer.new(original_query).render(), expected); +end + +function test_query_renderer.test_render_simple_select() + local original_query = { + type = "select", + selectList = { + {type = "column", name = "C1", tableName = "T1"}, + {type = "column", name = "C2", tableName = "T1"} + }, + from = {type = "table", name = "T1"} + } + assert_renders_to(original_query, 'SELECT "T1"."C1", "T1"."C2" FROM "T1"'); +end + +function test_query_renderer.test_render_with_single_predicate_filter() + local original_query = { + type = "select", + selectList = {{type = "column", name ="NAME", tableName = "MONTHS"}}, + from = {type = "table", name = "MONTHS"}, + filter = { + type = "predicate_greater", + left = {type = "column", name="DAYS_IN_MONTH", tableName = "MONTHS"}, + right = {type = "literal_exactnumeric", value = "30"} + } + } + assert_renders_to(original_query, 'SELECT "MONTHS"."NAME" FROM "MONTHS" WHERE ("MONTHS"."DAYS_IN_MONTH" > 30)'); +end + +function test_query_renderer.test_render_nested_predicate_filter() + local original_query = { + type = "select", + selectList = {{type = "column", name ="NAME", tableName = "MONTHS"}}, + from = {type = "table", name = "MONTHS"}, + filter = { + type = "predicate_and", + expressions = {{ + type = "predicate_equal", + left = {type = "literal_string", value = "Q3"}, + right = {type = "column", name="QUARTER", tableName = "MONTHS"} + }, { + type = "predicate_greater", + left = {type = "column", name="DAYS_IN_MONTH", tableName = "MONTHS"}, + right = {type = "literal_exactnumeric", value = "30"} + } + } + } + } + assert_renders_to(original_query, 'SELECT "MONTHS"."NAME" FROM "MONTHS"' + .. ' WHERE ((\'Q3\' = "MONTHS"."QUARTER") AND ("MONTHS"."DAYS_IN_MONTH" > 30))'); +end + +function test_query_renderer.test_render_unary_not_filter() + local original_query = { + type = "select", + selectList = {{type = "column", name ="NAME", tableName = "MONTHS"}}, + from = {type = "table", name = "MONTHS"}, + filter = { + type = "predicate_not", + expression = { + type = "predicate_equal", + left = {type = "literal_string", value = "Q3"}, + right = {type = "column", name="QUARTER", tableName = "MONTHS"} + }, + } + } + assert_renders_to(original_query, 'SELECT "MONTHS"."NAME" FROM "MONTHS"' + .. ' WHERE (NOT (\'Q3\' = "MONTHS"."QUARTER"))'); +end + +function test_query_renderer.test_scalar_function_in_select_list() + local original_query = { + type = "select", + selectList = { + {type = "function_scalar", name ="UPPER", arguments = {{type = "literal_string", value = "bob"}}} + } + } + assert_renders_to(original_query, "SELECT UPPER('bob')") +end + +function test_query_renderer.test_scalar_function_in_select_list() + local original_query = { + type = "select", + selectList = { + {type = "column", name = "LASTNAME", tableName = "PEOPLE"} + }, + from = {type = "table", name = "PEOPLE"}, + filter = { + type = "predicate_equal", + left = { + type = "function_scalar", + name = "LOWER", + arguments = { + {type = "column", name = "FIRSTNAME", tableName = "PEOPLE"}, + } + }, + right = {type = "literal_string", value = "eve"} + } + } + assert_renders_to(original_query, 'SELECT "PEOPLE"."LASTNAME" FROM "PEOPLE" WHERE (LOWER("PEOPLE"."FIRSTNAME") = \'eve\')') +end + +function test_query_renderer.test_current_user() + local original_query = { + type = "select", + selectList = {{type = "function_scalar", name = "CURRENT_USER"}} + } + assert_renders_to(original_query, 'SELECT CURRENT_USER') + +end + +os.exit(luaunit.LuaUnit.run()) diff --git a/src/test/lua/exasolvs/test_request_dispatcher.lua b/src/test/lua/exasolvs/test_request_dispatcher.lua new file mode 100644 index 0000000..ff35294 --- /dev/null +++ b/src/test/lua/exasolvs/test_request_dispatcher.lua @@ -0,0 +1,38 @@ +local luaunit = require("luaunit") +local mockagne = require("mockagne") +local log_mock = mockagne.getMock() +package.preload["exasollog.log"] = function () return log_mock end +local cjson = require("cjson") +local dispatcher = require("exasolvs.request_dispatcher") + +local verify, when, any = mockagne.verify, mockagne.when, mockagne.any + +test_request_dispatcher = {} + +local function json_assert(actual, expected) + luaunit.assertEquals(cjson.decode(actual), expected) +end + +function test_request_dispatcher:test_get_capabilities() + local response = adapter_call('{"type" : "getCapabilities"}') + local expected = {type = "getCapabilities", capabilities = { + "SELECTLIST_PROJECTION", + "AGGREGATE_SINGLE_GROUP", + "AGGREGATE_GROUP_BY_COLUMN", + "AGGREGATE_GROUP_BY_TUPLE", + "AGGREGATE_HAVING", + "ORDER_BY_COLUMN", + "LIMIT", + "LIMIT_WITH_OFFSET" + }} + json_assert(response, expected) +end + +function test_request_dispatcher:test_setup_remote_logging() + adapter_call('{"type" : "getCapabilities", "schemaMetadataInfo" : ' + .. '{"properties" : {"DEBUG_ADDRESS" : "10.0.0.1:4000", "LOG_LEVEL" : "TRACE"}}}') + verify(log_mock.set_level("TRACE")) + verify(log_mock.connect("10.0.0.1", "4000")) +end + +os.exit(luaunit.LuaUnit.run()) diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties new file mode 100644 index 0000000..ad3bc9a --- /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=ALL \ No newline at end of file diff --git a/tools/amalg.lua b/tools/amalg.lua new file mode 100644 index 0000000..7a915de --- /dev/null +++ b/tools/amalg.lua @@ -0,0 +1,765 @@ +#!/usr/bin/env lua + +-- **Amalg** is a Lua tool for bundling a Lua script and dependent +-- Lua modules in a single `.lua` file for easier distribution. +-- +-- Features: +-- * Pure Lua (compatible with Lua 5.1 and up), no external +-- dependencies. (Even works for modules using the deprecated +-- `module` function.) +-- * You don't have to take care of the order in which the modules +-- are `require`d. +-- * Can embed compiled C modules. +-- * Can collect `require`d Lua (and C) modules automatically. +-- +-- What it doesn't do: +-- +-- * It does not compile to bytecode. Use `luac` for that yourself, +-- or take a look at [squish][1], or [luac.lua][4]. +-- * It doesn't do static analysis of Lua code to collect `require`d +-- modules. That won't work reliably anyway in a dynamic language! +-- You can write your own program for that (e.g. using the output +-- of `luac -p -l`), or use [squish][1], or [soar][3] instead. +-- * It will not compress, minify, obfuscate your Lua source code, +-- or any of the other things [squish][1] can do. +-- * It doesn't handle the dependencies of C modules, so it is best +-- used on C modules without dependencies (e.g. LuaSocket, LFS, +-- etc.). +-- +-- The `amalg.lua` [source code][6] is available on GitHub, and is +-- released under the [MIT license][7]. You can view [a nice HTML +-- version][8] of this file rendered by [Docco][9] on the GitHub +-- pages. +-- +-- As already mentioned, there are alternatives to this program: See +-- [squish][1], [LOOP][2], [soar][3], [luac.lua][4], and +-- [bundle.lua][5] (and probably some more). +-- +-- [1]: http://matthewwild.co.uk/projects/squish/home +-- [2]: http://loop.luaforge.net/release/preload.html +-- [3]: http://lua-users.org/lists/lua-l/2012-02/msg00609.html +-- [4]: http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/5.1/luac.lua +-- [5]: https://github.com/akavel/scissors/blob/master/tools/bundle/bundle.lua +-- [6]: http://github.com/siffiejoe/lua-amalg +-- [7]: http://opensource.org/licenses/MIT +-- [8]: http://siffiejoe.github.io/lua-amalg/ +-- [9]: http://jashkenas.github.io/docco/ +-- +-- +-- ## Getting Started +-- +-- You can bundle a collection of Lua modules in a single file by +-- calling the `amalg.lua` script and passing the module names on the +-- command line: +-- +-- ./amalg.lua module1 module2 +-- +-- The modules are collected using `package.path`, so they have to be +-- available there. The resulting merged Lua code will be written to +-- the standard output stream. You have to actually run the resulting +-- code to make the embedded Lua modules available for `require`. +-- +-- You can specify an output file to use instead of the standard +-- output stream: +-- +-- ./amalg.lua -o out.lua module1 module2 +-- +-- You can also embed the main script of your application in the +-- merged Lua code as well. Of course, the embedded Lua modules can be +-- `require`d from the embedded main script. +-- +-- ./amalg.lua -o out.lua -s main.lua module1 module2 +-- +-- If you want the original file names and line numbers to appear in +-- error messages, you have to activate debug mode. This will require +-- slightly more memory, though. +-- +-- ./amalg.lua -o out.lua -d -s main.lua module1 module2 +-- +-- To collect all Lua (and C) modules used by a program, you can load +-- the `amalg.lua` script as a module, and it will intercept calls to +-- `require` (more specifically the Lua module searchers) and save the +-- necessary Lua module names in a file `amalg.cache` in the current +-- directory: +-- +-- lua -lamalg main.lua +-- +-- Multiple calls will add to this module cache. But don't access it +-- from multiple concurrent processes (the cache isn't protected +-- against race conditions)! +-- +-- You can use the cache (in addition to all module names given on the +-- command line) using the `-c` or `-C` flag: +-- +-- ./amalg.lua -o out.lua -s main.lua -c +-- ./amalg.lua -o out.lua -s main.lua -C myamalg.cache +-- +-- However, this will only embed the Lua modules. To also embed the C +-- modules (both from the cache and from the command line), you have +-- to specify the `-x` flag: +-- +-- ./amalg.lua -o out.lua -s main.lua -c -x +-- +-- This will make the amalgamated script platform- and Lua version +-- dependent, obviously! +-- +-- In some cases you may want to ignore automatically listed modules +-- in the cache without editing the cache file. Use the `-i` option +-- for that and specify a Lua pattern: +-- +-- ./amalg.lua -o out.lua -s main.lua -c -i "^luarocks%." +-- +-- The `-i` option can be used multiple times to specify multiple +-- patterns. +-- +-- Usually, the amalgamated modules take precedence over locally +-- installed (possibly newer) versions of the same modules. If you +-- want to use local modules when available and only fall back to the +-- amalgamated code otherwise, you can specify the `-f` flag. +-- +-- ./amalg.lua -o out.lua -s main.lua -c -f +-- +-- This installs another searcher/loader function at the end of +-- `package.searchers` (or `package.loaders` on Lua 5.1) and adds +-- a new table `package.postload` that serves the same purpose as the +-- standard `package.preload` table. +-- +-- To fix a compatibility issue with Lua 5.1's vararg handling, +-- `amalg.lua` by default adds a local alias to the global `arg` table +-- to every loaded module. If for some reason you don't want that, use +-- the `-a` flag (but be aware that in Lua 5.1 with `LUA_COMPAT_VARARG` +-- defined (the default) your modules can only access the global `arg` +-- table as `_G.arg`). +-- +-- ./amalg.lua -o out.lua -a -s main.lua -c +-- +-- That's it. For further info consult the source. +-- +-- +-- ## Implementation +-- + +-- The name of the script used in warning messages and the name of the +-- cache file can be configured here by changing these local +-- variables: +local prog = "amalg.lua" +local cache = "amalg.cache" + + +-- Wrong use of the command line may cause warnings to be printed to +-- the console. This function is for printing those warnings: +local function warn( ... ) + io.stderr:write( "WARNING ", prog, ": " ) + local n = select( '#', ... ) + for i = 1, n do + local v = tostring( (select( i, ... )) ) + io.stderr:write( v, i == n and '\n' or '\t' ) + end +end + + +-- Function for parsing the command line of `amalg.lua` when invoked +-- as a script. The following flags are supported: +-- +-- * `-o `: specify output file (default is `stdout`) +-- * `-s `: specify main script to bundle +-- * `-c`: add the modules listed in the cache file `amalg.cache` +-- * `-C `: add the modules listed in the cache file +-- * `-i `: ignore modules in the cache file matching the +-- given pattern (can be given multiple times) +-- * `-d`: enable debug mode (file names and line numbers in error +-- messages will point to the original location) +-- * `-a`: do *not* apply the `arg` fix (local alias for the global +-- `arg` table) +-- * `-x`: also embed compiled C modules +-- * `--`: stop parsing command line flags (all remaining arguments +-- are considered module names) +-- +-- Other arguments are assumed to be module names. For an inconsistent +-- command line (e.g. duplicate options) a warning is printed to the +-- console. +local function parse_cmdline( ... ) + local modules, afix, ignores, tname, use_cache, cmods, dbg, script, oname, cname = + {}, true, {}, "preload" + + local function set_oname( v ) + if v then + if oname then + warn( "Resetting output file `"..oname.."'! Using `"..v.."' now!" ) + end + oname = v + else + warn( "Missing argument for -o option!" ) + end + end + + local function set_cname( v ) + if v then + if cname then + warn( "Resetting cache file `"..cname.."'! Using `"..v.."' now!" ) + end + cname = v + else + warn( "Missing argument for -C option!" ) + end + end + + local function set_script( v ) + if v then + if script then + warn( "Resetting main script `"..script.."'! Using `"..v.."' now!" ) + end + script = v + else + warn( "Missing argument for -s option!" ) + end + end + + local function add_ignore( v ) + if v then + if not pcall( string.match, "", v ) then + warn( "Invalid Lua pattern: `"..v.."'" ) + else + ignores[ #ignores+1 ] = v + end + else + warn( "Missing argument for -i option!" ) + end + end + + local i, n = 1, select( '#', ... ) + while i <= n do + local a = select( i, ... ) + if a == "--" then + for j = i+1, n do + modules[ select( j, ... ) ] = true + end + break + elseif a == "-o" then + i = i + 1 + set_oname( i <= n and select( i, ... ) ) + elseif a == "-s" then + i = i + 1 + set_script( i <= n and select( i, ... ) ) + elseif a == "-i" then + i = i + 1 + add_ignore( i <= n and select( i, ... ) ) + elseif a == "-f" then + tname = "postload" + elseif a == "-c" then + use_cache = true + elseif a == "-C" then + use_cache = true + i = i + 1 + set_cname( i <= n and select( i, ... ) ) + elseif a == "-x" then + cmods = true + elseif a == "-d" then + dbg = true + elseif a == "-a" then + afix = false + else + local prefix = a:sub( 1, 2 ) + if prefix == "-o" then + set_oname( a:sub( 3 ) ) + elseif prefix == "-s" then + set_script( a:sub( 3 ) ) + elseif prefix == "-i" then + add_ignore( a:sub( 3 ) ) + elseif a:sub( 1, 1 ) == "-" then + warn( "Unknown command line flag: "..a ) + else + modules[ a ] = true + end + end + i = i + 1 + end + return oname, script, dbg, afix, use_cache, tname, ignores, cmods, modules, cname +end + + +-- The approach for embedding precompiled Lua files is different from +-- the normal way of pasting the source code, so this function detects +-- whether a file is a binary file (Lua bytecode starts with the `ESC` +-- character): +local function is_bytecode( path ) + local f, res = io.open( path, "rb" ), false + if f then + res = f:read( 1 ) == "\027" + f:close() + end + return res +end + + +-- Read the whole contents of a file into memory without any +-- processing. +local function readfile( path, is_bin ) + local f = assert( io.open( path, is_bin and "rb" or "r" ) ) + local s = assert( f:read( "*a" ) ) + f:close() + return s +end + + +-- Lua files to be embedded into the resulting amalgamation are read +-- into memory in a single go, because under some circumstances (e.g. +-- binary chunks, shebang lines, `-d` command line flag) some +-- preprocessing/escaping is necessary. This function reads a whole +-- Lua file and returns the contents as a Lua string. +local function readluafile( path ) + local is_bin = is_bytecode( path ) + local s = readfile( path, is_bin ) + local shebang + if not is_bin then + -- Shebang lines are only supported by Lua at the very beginning + -- of a source file, so they have to be removed before the source + -- code can be embedded in the output. + shebang = s:match( "^(#![^\n]*)" ) + s = s:gsub( "^#[^\n]*", "" ) + end + return s, is_bin, shebang +end + + +-- Lua 5.1's `string.format("%q")` doesn't convert all control +-- characters to decimal escape sequences like the newer Lua versions +-- do. This might cause problems on some platforms (i.e. Windows) when +-- loading a Lua script (opened in text mode) that contains binary +-- code. +local function qformat( code ) + local s = ("%q"):format( code ) + return (s:gsub( "(%c)(%d?)", function( c, d ) + if c ~= "\n" then + return (d~="" and "\\%03d" or "\\%d"):format( c:byte() )..d + end + end )) +end + + +-- When the `-c` command line flag is given, the contents of the cache +-- file `amalg.cache` are used to specify the modules to embed. This +-- function is used to load the cache file. ist optional: +local function readcache( filename ) + local chunk = loadfile( filename or cache, "t", {} ) + if chunk then + if setfenv then setfenv( chunk, {} ) end + local result = chunk() + if type( result ) == "table" then + return result + end + end +end + + +-- When loaded as a module, `amalg.lua` collects Lua modules and C +-- modules that are `require`d and updates the cache file +-- `amalg.cache`. This function saves the updated cache contents to +-- the file: +local function writecache( c ) + local f = assert( io.open( cache, "w" ) ) + f:write( "return {\n" ) + for k,v in pairs( c ) do + if type( k ) == "string" and type( v ) == "string" then + f:write( " [ ", qformat( k ), " ] = ", qformat( v ), ",\n" ) + end + end + f:write( "}\n" ) + f:close() +end + + +-- The standard Lua function `package.searchpath` available in Lua 5.2 +-- and up is used to locate the source files for Lua modules and +-- library files for C modules. For Lua 5.1 a backport is provided. +local searchpath = package.searchpath +if not searchpath then + local delim = package.config:match( "^(.-)\n" ):gsub( "%%", "%%%%" ) + + function searchpath( name, path ) + local pname = name:gsub( "%.", delim ):gsub( "%%", "%%%%" ) + local msg = {} + for subpath in path:gmatch( "[^;]+" ) do + local fpath = subpath:gsub( "%?", pname ) + local f = io.open( fpath, "r" ) + if f then + f:close() + return fpath + end + msg[ #msg+1 ] = "\n\tno file '"..fpath.."'" + end + return nil, table.concat( msg ) + end +end + + +-- This is the main function for the use case where `amalg.lua` is run +-- as a script. It parses the command line, creates the output files, +-- collects the module and script sources, and writes the amalgamated +-- source. +local function amalgamate( ... ) + local oname, script, dbg, afix, use_cache, tname, ignores, cmods, modules, cname = + parse_cmdline( ... ) + local errors = {} + + -- When instructed to on the command line, the cache file is loaded, + -- and the modules are added to the ones listed on the command line + -- unless they are ignored via the `-i` command line option. + if use_cache then + local c = readcache( cname ) + for k,v in pairs( c or {} ) do + local addmodule = true + for _,p in ipairs( ignores ) do + if k:match( p ) then + addmodule = false + break + end + end + if addmodule then + modules[ k ] = v + end + end + end + + local out = io.stdout + if oname then + out = assert( io.open( oname, "w" ) ) + end + + -- If a main script is to be embedded, this includes the same + -- shebang line that was used in the main script, so that the + -- resulting amalgamation can be run without explicitly + -- specifying the interpreter on unixoid systems (if a shebang + -- line was specified in the first place, that is). + local script_bytes, script_binary, shebang + if script then + script_bytes, script_binary, shebang = readluafile( script ) + if shebang then + out:write( shebang, "\n\n" ) + end + out:write( "do\n\n" ) + end + + -- If fallback loading is requested, the module loaders of the + -- amalgamated module are registered in table `package.postload`, + -- and an extra searcher function is added at the end of + -- `package.searchers`. + if tname == "postload" then + out:write([=[ +do + local assert = assert + local type = assert( type ) + local searchers = package.searchers or package.loaders + local postload = {} + package.postload = postload + searchers[ #searchers+1 ] = function( mod ) + assert( type( mod ) == "string", "module name must be a string" ) + local loader = postload[ mod ] + if loader == nil then + return "\n\tno field package.postload['"..mod.."']" + else + return loader + end + end +end + +]=] ) + end + + -- Sort modules alphabetically. Modules will be embedded in + -- alphabetical order. This ensures deterministic output. + local module_names = {} + for m in pairs( modules ) do + module_names[ #module_names+1 ] = m + end + table.sort( module_names ) + + -- Every module given on the command line and/or in the cache file + -- is processed. + for _,m in ipairs( module_names ) do + local t = modules[ m ] + -- Only Lua modules are handled for now, so modules that are + -- definitely C modules are skipped and handled later. + if t ~= "C" then + local path, msg = searchpath( m, package.path ) + if not path and (t == "L" or not cmods) then + -- The module is supposed to be a Lua module, but it cannot + -- be found, so an error is raised. + error( "module `"..m.."' not found:"..msg ) + elseif not path then + -- Module possibly is a C module, so it is tried again later. + -- But the current error message is saved in case the given + -- name isn't a C module either. + modules[ m ], errors[ m ] = "C", msg + else + local bytes, is_bin = readluafile( path ) + if is_bin or dbg then + -- Precompiled Lua modules are loaded via the standard Lua + -- function `load` (or `loadstring` in Lua 5.1). Since this + -- preserves file name and line number information, this + -- approach is used for all files if the debug mode is active + -- (`-d` command line option). + out:write( "package.", tname, "[ ", qformat( m ), + " ] = assert( (loadstring or load)(\n", + qformat( bytes ), "\n, '@'..", + qformat( path ), " ) )\n\n" ) + else + -- Under normal circumstances Lua files are pasted into a + -- new anonymous vararg function, which then is put into + -- `package.preload` so that `require` can find it. Each + -- function gets its own `_ENV` upvalue (on Lua 5.2+), and + -- special care is taken that `_ENV` always is the first + -- upvalue (important for the `module` function on Lua 5.2). + -- Lua 5.1 compiled with `LUA_COMPAT_VARARG` (the default) will + -- create a local `arg` variable to emulate the vararg handling + -- of Lua 5.0. This might interfere with Lua modules that access + -- command line arguments via the `arg` global. As a workaround + -- `amalg.lua` adds a local alias to the global `arg` table + -- unless the `-a` command line flag is specified. + out:write( "do\nlocal _ENV = _ENV\n", + "package.", tname, "[ ", qformat( m ), + " ] = function( ... ) ", + afix and "local arg = _G.arg;\n" or "_ENV = _ENV;\n", + bytes, "\nend\nend\n\n" ) + end + end + end + end + + -- If the `-x` command line flag is active, C modules are embedded + -- as strings, and written out to temporary files on demand by the + -- amalgamated code. + if cmods then + local nfuncs = {} + -- To make the loading of C modules more robust, the necessary + -- global functions are saved in upvalues (because user-supplied + -- code might be run before a C module is loaded). The upvalues + -- are local to a `do ... end` block, so they aren't visible in + -- the main script code. + -- + -- On Windows the result of `os.tmpname()` is not an absolute + -- path by default. If that's the case the value of the `TMP` + -- environment variable is prepended to make it absolute. + local prefix = [=[ +local assert = assert +local newproxy = newproxy +local getmetatable = assert( getmetatable ) +local setmetatable = assert( setmetatable ) +local os_tmpname = assert( os.tmpname ) +local os_getenv = assert( os.getenv ) +local os_remove = assert( os.remove ) +local io_open = assert( io.open ) +local string_match = assert( string.match ) +local string_sub = assert( string.sub ) +local package_loadlib = assert( package.loadlib ) + +local dirsep = package.config:match( "^([^\n]+)" ) +local tmpdir +local function newdllname() + local tmpname = assert( os_tmpname() ) + if dirsep == "\\" then + if not string_match( tmpname, "[\\/][^\\/]+[\\/]" ) then + tmpdir = tmpdir or assert( os_getenv( "TMP" ) or + os_getenv( "TEMP" ), + "could not detect temp directory" ) + local first = string_sub( tmpname, 1, 1 ) + local hassep = first == "\\" or first == "/" + tmpname = tmpdir..((hassep) and "" or "\\")..tmpname + end + end + return tmpname +end +local dllnames = {} + +]=] + for _,m in ipairs( module_names ) do + local t = modules[ m ] + if t == "C" then + -- Try a search strategy similar to the standard C module + -- searcher first and then the all-in-one strategy to locate + -- the library files for the C modules to embed. + local path, msg = searchpath( m, package.cpath ) + if not path then + errors[ m ] = (errors[ m ] or "") .. msg + path, msg = searchpath( m:gsub( "%..*$", "" ), package.cpath ) + if not path then + error( "module `"..m.."' not found:"..errors[ m ]..msg ) + end + end + local qpath = qformat( path ) + -- Build the symbol(s) to look for in the dynamic library. + -- There may be multiple candidates because of optional + -- version information in the module names and the different + -- approaches of the different Lua versions in handling that. + local openf = m:gsub( "%.", "_" ) + local openf1, openf2 = openf:match( "^([^%-]*)%-(.*)$" ) + -- The amalgamation of C modules is split into two parts: + -- One part generates a temporary file name for the C library + -- and writes the binary code stored in the amalgamation to + -- that file, while the second loads the resulting dynamic + -- library using `package.loadlib`. The split is necessary + -- because multiple modules could be loaded from the same + -- library, and the amalgamated code has to simulate that. + -- Shared dynamic libraries are embedded only once. + -- + -- The temporary dynamic library files may or may not be + -- cleaned up when the amalgamated code exits (this probably + -- works on POSIX machines (all Lua versions) and on Windows + -- with Lua 5.1). The reason is that starting with version 5.2 + -- Lua ensures that libraries aren't unloaded before normal + -- user-supplied `__gc` metamethods have run to avoid a case + -- where such a metamethod would call an unloaded C function. + -- As a consequence the amalgamated code tries to remove the + -- temporary library files *before* they are actually + -- unloaded. + if not nfuncs[ path ] then + local code = readfile( path, true ) + nfuncs[ path ] = true + local qcode = qformat( code ) + out:write( prefix, "dllnames[ ", qpath, [=[ ] = function() + local dll = newdllname() + local f = assert( io_open( dll, "wb" ) ) + f:write( ]=], qcode, [=[ ) + f:close() + local sentinel = newproxy and newproxy( true ) + or setmetatable( {}, { __gc = true } ) + getmetatable( sentinel ).__gc = function() os_remove( dll ) end + dllnames[ ]=], qpath, [=[ ] = function() + local _ = sentinel + return dll + end + return dll +end + +]=] ) + prefix = "" + end -- shared libary not embedded already + -- Add a function to `package.preload` to load the temporary + -- DLL or shared object file. This function tries to mimic the + -- behavior of Lua 5.3 which is to strip version information + -- from the module name at the end first, and then at the + -- beginning if that failed. + local qm = qformat( m ) + out:write( "package.", tname, "[ ", qm, " ] = function()\n", + " local dll = dllnames[ ", qpath, " ]()\n" ) + if openf1 then + out:write( " local loader = package_loadlib( dll, ", + qformat( "luaopen_"..openf1 ), " )\n", + " if not loader then\n", + " loader = assert( package_loadlib( dll, ", + qformat( "luaopen_"..openf2 ), + " ) )\n end\n" ) + else + out:write( " local loader = assert( package_loadlib( dll, ", + qformat( "luaopen_"..openf ), " ) )\n" ) + end + out:write( " return loader( ", qm, ", dll )\nend\n\n" ) + end -- is a C module + end -- for all given module names + end -- if cmods + + -- If a main script is specified on the command line (`-s` flag), + -- embed it now that all dependent modules are available to + -- `require`. + if script then + out:write( "end\n\n" ) + if script_binary or dbg then + out:write( "assert( (loadstring or load)(\n", + qformat( script_bytes ), "\n, '@'..", + qformat( script ), " ) )( ... )\n\n" ) + else + out:write( script_bytes ) + end + end + + if oname then + out:close() + end +end + + +-- If `amalg.lua` is loaded as a module, it intercepts `require` calls +-- (more specifically calls to the searcher functions) to collect all +-- `require`d module names and store them in the cache. The cache file +-- `amalg.cache` is updated when the program terminates. +local function collect() + local searchers = package.searchers or package.loaders + -- When the searchers table has been modified, it is unknown which + -- elements in the table to replace, so `amalg.lua` bails out with + -- an error. The `luarocks.loader` module which inserts itself at + -- position 1 in the `package.searchers` table is explicitly + -- supported, though! + local off = 0 + if package.loaded[ "luarocks.loader" ] then off = 1 end + assert( #searchers == 4+off, "package.searchers has been modified" ) + local c = readcache() or {} + -- The updated cache is written to disk when the following value is + -- garbage collected, which should happen at `lua_close()`. + local sentinel = newproxy and newproxy( true ) + or setmetatable( {}, { __gc = true } ) + getmetatable( sentinel ).__gc = function() writecache( c ) end + local lua_searcher = searchers[ 2+off ] + local c_searcher = searchers[ 3+off ] + local aio_searcher = searchers[ 4+off ] -- all in one searcher + + local function rv_handler( tag, mname, ... ) + if type( (...) ) == "function" then + c[ mname ] = tag + end + return ... + end + + -- The replacement searchers just forward to the original versions, + -- but also update the cache if the search was successful. + searchers[ 2+off ] = function( ... ) + local _ = sentinel -- make sure that sentinel is an upvalue + return rv_handler( "L", ..., lua_searcher( ... ) ) + end + searchers[ 3+off ] = function( ... ) + local _ = sentinel -- make sure that sentinel is an upvalue + return rv_handler( "C", ..., c_searcher( ... ) ) + end + searchers[ 4+off ] = function( ... ) + local _ = sentinel -- make sure that sentinel is an upvalue + return rv_handler( "C", ..., aio_searcher( ... ) ) + end + + -- Since calling `os.exit` might skip the `lua_close()` call, the + -- `os.exit` function is monkey-patched to also save the updated + -- cache to the cache file on disk. + if type( os ) == "table" and type( os.exit ) == "function" then + local os_exit = os.exit + function os.exit( ... ) + writecache( c ) + return os_exit( ... ) + end + end +end + + +-- To determine whether `amalg.lua` is run as a script or loaded as a +-- module it uses the debug module to walk the call stack looking for +-- a `require` call. If such a call is found, `amalg.lua` has been +-- `require`d as a module. +local function is_script() + local i = 3 + local info = debug.getinfo( i, "f" ) + while info do + if info.func == require then + return false + end + i = i + 1 + info = debug.getinfo( i, "f" ) + end + return true +end + + +-- This checks whether `amalg.lua` has been called as a script or +-- loaded as a module and acts accordingly, by calling the +-- corresponding main function: +if is_script() then + amalgamate( ... ) +else + collect() +end \ No newline at end of file diff --git a/tools/unittest.sh b/tools/unittest.sh new file mode 100755 index 0000000..a3ddd20 --- /dev/null +++ b/tools/unittest.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# This script finds and runs Lua unit tests. + +readonly script_dir=$(dirname "$(readlink -f "$0")") +if [[ -v $1 ]] +then + readonly base_dir="$1" +else + readonly base_dir=$(readlink -f "$script_dir/..") +fi + +readonly src_module_path="$base_dir/src/main/lua" +readonly test_module_path="$base_dir/src/test/lua" +readonly target_dir="$base_dir/target" +readonly reports_dir="$target_dir/luaunit_reports" + +mkdir -p "$reports_dir" + +cd $test_module_path +readonly tests="$(find . -name '*.lua')" + +for testcase in $tests +do + testname=$(echo "$testcase" | sed -e s'/.\///' -e s'/\//./g' -e s'/.lua$//') + LUA_PATH="$src_module_path/?.lua;$(luarocks path --lr-path)" lua "$testcase" -o junit -n "$reports_dir/$testname" +done \ No newline at end of file